From f96733102869aa6a27eb2949f53b7177be5f363f Mon Sep 17 00:00:00 2001
From: David Hammer <dhammer@mailbox.org>
Date: Tue, 12 Apr 2022 10:07:10 +0200
Subject: [PATCH] Preview as float32 without nan, improved scenes

---
 src/calng/DetectorAssembler.py |  28 +++++--
 src/calng/scenes.py            | 135 ++++++++++++++++++++++++---------
 2 files changed, 123 insertions(+), 40 deletions(-)

diff --git a/src/calng/DetectorAssembler.py b/src/calng/DetectorAssembler.py
index 56178d94..dbfd2222 100644
--- a/src/calng/DetectorAssembler.py
+++ b/src/calng/DetectorAssembler.py
@@ -5,6 +5,7 @@ import re
 import numpy as np
 from karabo.bound import (
     DOUBLE_ELEMENT,
+    FLOAT_ELEMENT,
     IMAGEDATA_ELEMENT,
     NDARRAY_ELEMENT,
     NODE_ELEMENT,
@@ -131,6 +132,20 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
             .reconfigurable()
             .commit(),
 
+            FLOAT_ELEMENT(expected)
+            .key("preview.replaceNanWith")
+            .description(
+                "Displaying images in KaraboGUI seems to not go well when there are "
+                "NaN values in data. And there will be with bad pixel masking or just "
+                "geometry space between modules. NaN values get replaced with this "
+                "value to get around this; choose a value which clearly stands out "
+                "from the image data you want to see."
+            )
+            .assignmentOptional()
+            .defaultValue(-1000)
+            .reconfigurable()
+            .commit(),
+
             DOUBLE_ELEMENT(expected)
             .key("preview.maxRate")
             .displayedName("Max rate")
@@ -241,7 +256,9 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
             return
         self._geometry = geom_utils.deserialize_geometry(serialized_geometry)
         # TODO: allow multiple memory cells (extra geom notion of extra dimensions)
-        self._stack_input_buffer = np.zeros(self._geometry.expected_data_shape)
+        self._stack_input_buffer = np.zeros(
+            self._geometry.expected_data_shape, dtype=np.float32
+        )
 
     def on_matched_data(self, train_id, sources):
         if self._geometry is None:
@@ -265,9 +282,9 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
             # prepare for assembly
             # TODO: handle failure to "parse" source, get data out
             module_index = self._source_to_index(source)
-            self._stack_input_buffer[module_index] = np.squeeze(
-                data.get(self._path_to_stack)
-            )
+            self._stack_input_buffer[module_index] = data.get(
+                self._path_to_stack
+            ).astype(np.float32, copy=False)  # TODO: set dtype based on input?
             module_indices_unfilled.discard(module_index)
             earliest_source_timestamp = min(
                 earliest_source_timestamp, source_timestamp.toTimestamp()
@@ -308,11 +325,12 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
                         np, self.unsafe_get("preview.downsamplingFunction")
                     ),
                 )
+            assembled[np.isnan(assembled)] = self.unsafe_get("preview.replaceNanWith")
             output_hash = Hash(
                 "image.data",
                 ImageData(
                     # TODO: get around this being mirrored...
-                    assembled.astype(np.int32)[::-1, ::-1],
+                    assembled[::-1, ::-1],
                     Dims(*assembled.shape),
                     Encoding.GRAY,
                     bitsPerPixel=32,
diff --git a/src/calng/scenes.py b/src/calng/scenes.py
index ed9dd89e..68bfa38a 100644
--- a/src/calng/scenes.py
+++ b/src/calng/scenes.py
@@ -17,6 +17,7 @@ from karabo.common.scenemodel.api import (
     IntLineEditModel,
     LabelModel,
     LineEditModel,
+    LineModel,
     RectangleModel,
     SceneModel,
     SceneTargetWindow,
@@ -138,6 +139,23 @@ class Space:
         return []
 
 
+class Hline:
+    def __init__(self, width):
+        self.width = width
+        self.height = 0
+
+    def render(self, x, y):
+        return [
+            LineModel(
+                stroke="#000000",
+                x1=x,
+                x2=x+self.width,
+                y1=y,
+                y2=y,
+            )
+        ]
+
+
 def dummy_wrap(model_class):
     class Wrapper:
         def __init__(self, *args, **kwargs):
@@ -557,6 +575,16 @@ class AssemblerDeviceStatus(VerticalLayout):
             width=7 * BASE_INC,
             height=BASE_INC,
         )
+        rate = DisplayRoundedFloat(
+            keys=[f"{device_id}.outProcessing"],
+            width=7 * BASE_INC,
+            height=BASE_INC,
+        )
+        tof = DisplayRoundedFloat(
+            keys=[f"{device_id}.timeOfFlight"],
+            width=7 * BASE_INC,
+            height=BASE_INC,
+        )
         self.children.extend(
             [
                 name,
@@ -565,37 +593,9 @@ class AssemblerDeviceStatus(VerticalLayout):
                     train_id,
                     padding=0,
                 ),
-                LabelModel(
-                    text="Image downsampling",
-                    width=14 * BASE_INC,
-                    height=BASE_INC,
-                ),
-                HorizontalLayout(
-                    LabelModel(
-                        text="Factor",
-                        width=7 * BASE_INC,
-                        height=BASE_INC,
-                    ),
-                    ComboBoxModel(
-                        keys=[f"{device_id}.preview.downsamplingFactor"],
-                        width=7 * BASE_INC,
-                        height=BASE_INC,
-                        klass="EditableComboBox",
-                    ),
-                    padding=0,
-                ),
                 HorizontalLayout(
-                    LabelModel(
-                        text="Function",
-                        width=7 * BASE_INC,
-                        height=BASE_INC,
-                    ),
-                    ComboBoxModel(
-                        keys=[f"{device_id}.preview.downsamplingFunction"],
-                        width=7 * BASE_INC,
-                        height=BASE_INC,
-                        klass="EditableComboBox",
-                    ),
+                    rate,
+                    tof,
                     padding=0,
                 ),
                 DeviceSceneLinkModel(
@@ -606,6 +606,16 @@ class AssemblerDeviceStatus(VerticalLayout):
                     width=14 * BASE_INC,
                     height=BASE_INC,
                 ),
+                LabelModel(
+                    text="My geometry device:",
+                    width=14 * BASE_INC,
+                    height=BASE_INC,
+                ),
+                DisplayLabelModel(
+                    keys=[f"{device_id}.geometryDevice"],
+                    width=14 * BASE_INC,
+                    height=BASE_INC,
+                ),  # TODO: some day, get dynamic link to this friend
             ]
         )
 
@@ -1131,12 +1141,67 @@ def detector_assembler_overview(device_id):
     return VerticalLayout(
         HorizontalLayout(
             AssemblerDeviceStatus(device_id),
-            titled("My geometry device")(boxed(VerticalLayout))(
-                DisplayLabelModel(
-                    keys=[f"{device_id}.geometryDevice"],
-                    width=6 * BASE_INC,
+            titled("Preview settings")(boxed(VerticalLayout))(
+                HorizontalLayout(
+                    LabelModel(
+                        text="Display NaN values as:",
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                    ),
+                    DoubleLineEditModel(
+                        keys=[f"{device_id}.preview.replaceNanWith"],
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                    ),
+                    padding=0,
+                ),
+                HorizontalLayout(
+                    LabelModel(
+                        text="Max preview rate",
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                    ),
+                    DoubleLineEditModel(
+                        keys=[f"{device_id}.preview.maxRate"],
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                    ),
+                    padding=0,
+                ),
+                Hline(width=14 * BASE_INC),
+                LabelModel(
+                    text="Image downsampling",
+                    width=14 * BASE_INC,
                     height=BASE_INC,
-                )
+                ),
+                HorizontalLayout(
+                    LabelModel(
+                        text="Factor",
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                    ),
+                    ComboBoxModel(
+                        keys=[f"{device_id}.preview.downsamplingFactor"],
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                        klass="EditableComboBox",
+                    ),
+                    padding=0,
+                ),
+                HorizontalLayout(
+                    LabelModel(
+                        text="Function",
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                    ),
+                    ComboBoxModel(
+                        keys=[f"{device_id}.preview.downsamplingFunction"],
+                        width=7 * BASE_INC,
+                        height=BASE_INC,
+                        klass="EditableComboBox",
+                    ),
+                    padding=0,
+                ),
             ),
         ),
         titled("Preview image")(boxed(dummy_wrap(DetectorGraphModel)))(
-- 
GitLab