From b3d7887d4d6b60a061a2fb7097a57a0988d15b26 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 12:08:16 +0200 Subject: [PATCH 1/9] fix luminance computation --- plugins/ImageReader/ImageReader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index e720ce4854..c59151f1cb 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -99,8 +99,8 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255) - height_data[y, x] = avg + luminance = (0.2126 * qRed(qrgb) + 0.7152 * qGreen(qrgb) + 0.0722 * qBlue(qrgb)) / 255 # fast computation ignoring gamma + height_data[y, x] = luminance Job.yieldThread() From 88b424d36aa43d863a3e937187337af378905908 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 12:10:18 +0200 Subject: [PATCH 2/9] feat: use logarithmic conversion for lithophanes --- plugins/ImageReader/ConfigUI.qml | 22 ++++++++++++++++++++++ plugins/ImageReader/ImageReader.py | 16 ++++++++++++---- plugins/ImageReader/ImageReaderUI.py | 6 ++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 47ba10778c..ef28c174f2 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -143,6 +143,28 @@ UM.Dialog } } + UM.TooltipArea { + Layout.fillWidth:true + height: childrenRect.height + text: catalog.i18nc("@info:tooltip","For lithophanes a logarithmic function is more appropriate for most materials. For height maps the pixel values correspond to heights linearly.") + Row { + width: parent.width + + Label { + text: "Conversion" + width: 150 * screenScaleFactor + anchors.verticalCenter: parent.verticalCenter + } + ComboBox { + id: conversion + objectName: "Conversion" + model: [ catalog.i18nc("@item:inlistbox","Logarithmic"), catalog.i18nc("@item:inlistbox","Linear") ] + width: 180 * screenScaleFactor + onCurrentIndexChanged: { manager.onConvertFunctionChanged(currentIndex) } + } + } + } + UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index c59151f1cb..bfaa6eb48c 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -3,6 +3,8 @@ import numpy +import math + from PyQt5.QtGui import QImage, qRed, qGreen, qBlue from PyQt5.QtCore import Qt @@ -46,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function): scene_node = SceneNode() mesh = MeshBuilder() @@ -124,8 +126,14 @@ class ImageReader(MeshReader): Job.yieldThread() - height_data *= scale_vector.y - height_data += base_height + if use_logarithmic_function: + min_luminance = 2.0 ** (peak_height - base_height) + for (y, x) in numpy.ndindex(height_data.shape): + mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] + height_data[y, x] = peak_height - math.log(mapped_luminance, 2) + else: + height_data *= scale_vector.y + height_data += base_height heightmap_face_count = 2 * height_minus_one * width_minus_one total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2 diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 213468a2ab..c769a8c264 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -34,6 +34,7 @@ class ImageReaderUI(QObject): self.peak_height = 2.5 self.smoothing = 1 self.lighter_is_higher = False; + self.use_logarithmic_function = False; self._ui_lock = threading.Lock() self._cancelled = False @@ -144,3 +145,8 @@ class ImageReaderUI(QObject): @pyqtSlot(int) def onImageColorInvertChanged(self, value): self.lighter_is_higher = (value == 1) + + @pyqtSlot(int) + def onConvertFunctionChanged(self, value): + self.use_logarithmic_function = (value == 0) + From b88183f4a1f218b7c87d9ccd64a2fa1d95a68f9e Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:18:07 +0200 Subject: [PATCH 3/9] fix translucency model using new permittance setting --- plugins/ImageReader/ConfigUI.qml | 23 +++++++++++++++++++++++ plugins/ImageReader/ImageReader.py | 9 +++++---- plugins/ImageReader/ImageReaderUI.py | 5 +++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index ef28c174f2..491594fa58 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -165,6 +165,29 @@ UM.Dialog } } + UM.TooltipArea { + Layout.fillWidth:true + height: childrenRect.height + text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter.") + Row { + width: parent.width + + Label { + text: catalog.i18nc("@action:label", "1mm Transmittance (%)") + width: 150 * screenScaleFactor + anchors.verticalCenter: parent.verticalCenter + } + TextField { + id: transmittance + objectName: "Transmittance" + focus: true + validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/} + width: 180 * screenScaleFactor + onTextChanged: { manager.onTransmittanceChanged(text) } + } + } + } + UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index bfaa6eb48c..ce3cab0b8f 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -48,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function, self._ui.transmittance_1mm) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function, transmittance_1mm): scene_node = SceneNode() mesh = MeshBuilder() @@ -127,10 +127,11 @@ class ImageReader(MeshReader): Job.yieldThread() if use_logarithmic_function: - min_luminance = 2.0 ** (peak_height - base_height) + p = 1.0 / math.log(transmittance_1mm / 100.0, 2) + min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = peak_height - math.log(mapped_luminance, 2) + height_data[y, x] = peak_height - p * math.log(mapped_luminance, 2) else: height_data *= scale_vector.y height_data += base_height diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index c769a8c264..67d6444538 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,6 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_logarithmic_function = False; + self.transmittance_1mm = 40.0; self._ui_lock = threading.Lock() self._cancelled = False @@ -76,6 +77,7 @@ class ImageReaderUI(QObject): self._ui_view.findChild(QObject, "Base_Height").setProperty("text", str(self.base_height)) self._ui_view.findChild(QObject, "Peak_Height").setProperty("text", str(self.peak_height)) + self._ui_view.findChild(QObject, "Transmittance").setProperty("text", str(self.transmittance_1mm)) self._ui_view.findChild(QObject, "Smoothing").setProperty("value", self.smoothing) def _createConfigUI(self): @@ -150,3 +152,6 @@ class ImageReaderUI(QObject): def onConvertFunctionChanged(self, value): self.use_logarithmic_function = (value == 0) + @pyqtSlot(int) + def onTransmittanceChanged(self, value): + self.transmittance_1mm = value From 2b55b85a1234791babf7700a362c940d4ca42473 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:21:05 +0200 Subject: [PATCH 4/9] fix luminance computation --- plugins/ImageReader/ImageReader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index ce3cab0b8f..50825f2464 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -101,8 +101,10 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - luminance = (0.2126 * qRed(qrgb) + 0.7152 * qGreen(qrgb) + 0.0722 * qBlue(qrgb)) / 255 # fast computation ignoring gamma - height_data[y, x] = luminance + if use_logarithmic_function: + height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2)) + else: + height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma Job.yieldThread() From a8b3d7e49ddcbc33df41dd6c4ec71b619511b966 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:27:51 +0200 Subject: [PATCH 5/9] make naming of Logarithmic Conversion Function intelligible --- plugins/ImageReader/ConfigUI.qml | 12 ++++++------ plugins/ImageReader/ImageReader.py | 8 ++++---- plugins/ImageReader/ImageReaderUI.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 491594fa58..8a4ac67b3e 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -146,21 +146,21 @@ UM.Dialog UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height - text: catalog.i18nc("@info:tooltip","For lithophanes a logarithmic function is more appropriate for most materials. For height maps the pixel values correspond to heights linearly.") + text: catalog.i18nc("@info:tooltip","For lithophanes a simple logarithmic model for translucency is available. For height maps the pixel values correspond to heights linearly.") Row { width: parent.width Label { - text: "Conversion" + text: "Color Model" width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } ComboBox { - id: conversion - objectName: "Conversion" - model: [ catalog.i18nc("@item:inlistbox","Logarithmic"), catalog.i18nc("@item:inlistbox","Linear") ] + id: color_model + objectName: "ColorModel" + model: [ catalog.i18nc("@item:inlistbox","Translucency"), catalog.i18nc("@item:inlistbox","Linear") ] width: 180 * screenScaleFactor - onCurrentIndexChanged: { manager.onConvertFunctionChanged(currentIndex) } + onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) } } } } diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 50825f2464..0086736f6d 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -48,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function, self._ui.transmittance_1mm) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_transparency_model, self._ui.transmittance_1mm) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function, transmittance_1mm): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_transparency_model, transmittance_1mm): scene_node = SceneNode() mesh = MeshBuilder() @@ -101,7 +101,7 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - if use_logarithmic_function: + if use_transparency_model: height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2)) else: height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma @@ -128,7 +128,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if use_logarithmic_function: + if use_transparency_model: p = 1.0 / math.log(transmittance_1mm / 100.0, 2) min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 67d6444538..41d8741b38 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -34,7 +34,7 @@ class ImageReaderUI(QObject): self.peak_height = 2.5 self.smoothing = 1 self.lighter_is_higher = False; - self.use_logarithmic_function = False; + self.use_transparency_model = True; self.transmittance_1mm = 40.0; self._ui_lock = threading.Lock() @@ -149,8 +149,8 @@ class ImageReaderUI(QObject): self.lighter_is_higher = (value == 1) @pyqtSlot(int) - def onConvertFunctionChanged(self, value): - self.use_logarithmic_function = (value == 0) + def onColorModelChanged(self, value): + self.use_transparency_model = (value == 0) @pyqtSlot(int) def onTransmittanceChanged(self, value): From 364483f6533905b710b5c2d8f2ff3d77b5d73598 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:37:14 +0200 Subject: [PATCH 6/9] explain litho transmittance better --- plugins/ImageReader/ConfigUI.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 8a4ac67b3e..842ca612d9 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -168,7 +168,7 @@ UM.Dialog UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height - text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter.") + text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.") Row { width: parent.width From 5915947a7a54502b48367a9ba5e47e5888d5351b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:55:10 +0200 Subject: [PATCH 7/9] fix litho thickness computation --- plugins/ImageReader/ImageReader.py | 4 ++-- plugins/ImageReader/ImageReaderUI.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 0086736f6d..2084844548 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -108,7 +108,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if not lighter_is_higher: + if lighter_is_higher is use_transparency_model: height_data = 1 - height_data for _ in range(0, blur_iterations): @@ -133,7 +133,7 @@ class ImageReader(MeshReader): min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = peak_height - p * math.log(mapped_luminance, 2) + height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) else: height_data *= scale_vector.y height_data += base_height diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 41d8741b38..0fb9ea78de 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,7 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_transparency_model = True; - self.transmittance_1mm = 40.0; + self.transmittance_1mm = 20.0; # based on pearl PLA self._ui_lock = threading.Lock() self._cancelled = False From 449ad198225bb3df6c68a699b71e02660ad233ba Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 20:19:29 +0200 Subject: [PATCH 8/9] based transmittance on measurements on print ratio of luminance of 1.4mm thickness to luminance at 0.4mm --- plugins/ImageReader/ImageReaderUI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 0fb9ea78de..a61fabb742 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,7 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_transparency_model = True; - self.transmittance_1mm = 20.0; # based on pearl PLA + self.transmittance_1mm = 50.0; # based on pearl PLA self._ui_lock = threading.Lock() self._cancelled = False From 03f7fab1247cd50ea409a4129feb0b92174ca402 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 24 Oct 2019 17:26:20 +0200 Subject: [PATCH 9/9] lil fix --- plugins/ImageReader/ImageReader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 2084844548..a77cc9b0ed 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -108,7 +108,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if lighter_is_higher is use_transparency_model: + if lighter_is_higher == use_transparency_model: height_data = 1 - height_data for _ in range(0, blur_iterations): @@ -129,11 +129,11 @@ class ImageReader(MeshReader): Job.yieldThread() if use_transparency_model: - p = 1.0 / math.log(transmittance_1mm / 100.0, 2) + p = 1.0 / math.log(transmittance_1mm / 100.0, 2) # base doesn't matter here. use base 2 for fast computation min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) + height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) # use same base as a couple lines above this else: height_data *= scale_vector.y height_data += base_height