From 800075f3a86048e0a4902f33e3703c7fb09b8096 Mon Sep 17 00:00:00 2001
From: David Hammer <dhammer@mailbox.org>
Date: Mon, 24 Jan 2022 10:20:17 +0100
Subject: [PATCH] Overhaul geometry device classes, initial Jungfrau support

---
 setup.py                            |   4 +
 src/calng/ManualAgipdGeometry.py    |   4 +-
 src/calng/ManualDsscGeometry.py     |   4 +-
 src/calng/ManualJungfrauGeometry.py |  28 ++++++
 src/calng/SimpleAssembler.py        |  31 ++++--
 src/calng/manual_geometry_base.py   | 150 ++++++++++++++++++++--------
 6 files changed, 166 insertions(+), 55 deletions(-)
 create mode 100644 src/calng/ManualJungfrauGeometry.py

diff --git a/setup.py b/setup.py
index 94b98d0d..74bd68d3 100644
--- a/setup.py
+++ b/setup.py
@@ -27,7 +27,11 @@ setup(name='calng',
               'AgipdCorrection = calng.AgipdCorrection:AgipdCorrection',
               'DsscCorrection = calng.DsscCorrection:DsscCorrection',
               'ModuleStacker = calng.ModuleStacker:ModuleStacker',
+              'ManualAgipdGeometry = calng.ManualAgipdGeometry:ManualAgipdGeometry',
+              'ManualDsscGeometry = calng.ManualDsscGeometry:ManualDsscGeometry',
+              'ManualJungfrauGeometry = calng.ManualJungfrauGeometry:ManualJungfrauGeometry',
               'ShmemToZMQ = calng.ShmemToZMQ:ShmemToZMQ',
+              'SimpleAssembler = calng.SimpleAssembler:SimpleAssembler',
           ],
 
           'karabo.middlelayer_device': [
diff --git a/src/calng/ManualAgipdGeometry.py b/src/calng/ManualAgipdGeometry.py
index b01e96bc..20428d6b 100644
--- a/src/calng/ManualAgipdGeometry.py
+++ b/src/calng/ManualAgipdGeometry.py
@@ -2,11 +2,11 @@ import extra_geom
 from karabo.bound import KARABO_CLASSINFO
 
 from ._version import version as deviceVersion
-from .manual_geometry_base import ManualGeometryBase
+from .manual_geometry_base import ManualQuadrantsGeometryBase
 
 
 @KARABO_CLASSINFO("ManualAgipdGeometry", deviceVersion)
-class ManualAgipdGeometry(ManualGeometryBase):
+class ManualAgipdGeometry(ManualQuadrantsGeometryBase):
     geometry_class = extra_geom.AGIPD_1MGeometry
 
     @staticmethod
diff --git a/src/calng/ManualDsscGeometry.py b/src/calng/ManualDsscGeometry.py
index 27f951d1..5b3d9b2e 100644
--- a/src/calng/ManualDsscGeometry.py
+++ b/src/calng/ManualDsscGeometry.py
@@ -2,11 +2,11 @@ import extra_geom
 from karabo.bound import KARABO_CLASSINFO
 
 from ._version import version as deviceVersion
-from .manual_geometry_base import ManualGeometryBase
+from .manual_geometry_base import ManualQuadrantsGeometryBase
 
 
 @KARABO_CLASSINFO("ManualDsscGeometry", deviceVersion)
-class ManualDsscGeometry(ManualGeometryBase):
+class ManualDsscGeometry(ManualQuadrantsGeometryBase):
     geometry_class = extra_geom.DSSC_1MGeometry
 
     @staticmethod
diff --git a/src/calng/ManualJungfrauGeometry.py b/src/calng/ManualJungfrauGeometry.py
new file mode 100644
index 00000000..57f7da91
--- /dev/null
+++ b/src/calng/ManualJungfrauGeometry.py
@@ -0,0 +1,28 @@
+import extra_geom
+from karabo.bound import KARABO_CLASSINFO, OVERWRITE_ELEMENT, Hash
+
+from ._version import version as deviceVersion
+from .manual_geometry_base import ManualModulesGeometryBase
+
+
+@KARABO_CLASSINFO("ManualJungfrauGeometry", deviceVersion)
+class ManualJungfrauGeometry(ManualModulesGeometryBase):
+    geometry_class = extra_geom.JUNGFRAUGeometry
+
+    @staticmethod
+    def expectedParameters(expected):
+        # TODO: come up with some sweet defaults (this is two modules from docs 4M)
+        (
+            OVERWRITE_ELEMENT(expected)
+            .key("modules")
+            .setNewDefaultValue(
+                [
+                    Hash(
+                        "posX", 95, "posY", 564, "orientationX", -1, "orientationY", -1
+                    ),
+                    Hash(
+                        "posX", 95, "posY", 17, "orientationX", -1, "orientationY", -1
+                    ),
+                ]
+            )
+        )
diff --git a/src/calng/SimpleAssembler.py b/src/calng/SimpleAssembler.py
index 9f7d7523..dbd191fb 100644
--- a/src/calng/SimpleAssembler.py
+++ b/src/calng/SimpleAssembler.py
@@ -3,6 +3,7 @@ import pickle
 import re
 
 import numpy as np
+from calng import utils
 from karabo.bound import (
     FLOAT_ELEMENT,
     IMAGEDATA_ELEMENT,
@@ -25,20 +26,21 @@ from karabo.bound import (
     Unit,
 )
 from karabo.common.api import KARABO_SCHEMA_DISPLAY_TYPE_SCENES as DT_SCENES
-from TrainMatcher import TrainMatcher, scenes as trainmatcher_scenes
+from TrainMatcher import TrainMatcher
+from TrainMatcher import scenes as trainmatcher_scenes
 
 from . import scenes
 from ._version import version as deviceVersion
-from calng import utils
-
 
 preview_schema = Schema()
 (
     IMAGEDATA_ELEMENT(preview_schema).key("image").commit(),
+
     UINT64_ELEMENT(preview_schema).key("trainId").readOnly().commit(),
 )
 
 xtdf_source_re = re.compile(r".*\/DET\/(\d+)CH0:xtdf")
+daq_source_re = re.compile(r".*\/DET\/.*?(\d+):daqOutput")
 
 # TODO: merge scene with TrainMatcher's nice overview
 @KARABO_CLASSINFO("SimpleAssembler", deviceVersion)
@@ -48,9 +50,9 @@ class SimpleAssembler(TrainMatcher.TrainMatcher):
         (
             OVERWRITE_ELEMENT(expected)
             .key("availableScenes")
-            # TODO: add assemblerOverview scene
             .setNewDefaultValue(["scene", "assemblerOverview"])
             .commit(),
+
             FLOAT_ELEMENT(expected)
             .key("processingTime")
             .unit(Unit.SECOND)
@@ -61,6 +63,7 @@ class SimpleAssembler(TrainMatcher.TrainMatcher):
             .info("Cannot keep up with GUI limit")
             .needsAcknowledging(False)
             .commit(),
+
             FLOAT_ELEMENT(expected)
             .key("timeOfFlight")
             .unit(Unit.SECOND)
@@ -71,15 +74,18 @@ class SimpleAssembler(TrainMatcher.TrainMatcher):
             .info("Time of flight exceeding 1 s")
             .needsAcknowledging(False)
             .commit(),
+
             STRING_ELEMENT(expected)
             .key("pathToStack")
             .assignmentOptional()
             .defaultValue("image.data")
             .commit(),
+
             INPUT_CHANNEL(expected)
             .key("geometryInput")
             .displayedName("Geometry input")
             .commit(),
+
             OUTPUT_CHANNEL(expected)  # can OVERWRITE_ELEMENT even do this?
             .key("output")
             .dataSchema(preview_schema)
@@ -145,7 +151,7 @@ class SimpleAssembler(TrainMatcher.TrainMatcher):
             module_indices_unfilled.discard(module_index)
 
         for unfilled_module in module_indices_unfilled:
-            self.input_buffer[module_index].fill(0)
+            self.input_buffer[unfilled_module].fill(0)
             # TODO: configurable treatment of missing modules
 
         # TODO: reusable output buffer to save on allocation
@@ -179,8 +185,15 @@ class SimpleAssembler(TrainMatcher.TrainMatcher):
     @functools.lru_cache
     def _source_to_index(self, source):
         # note: cache means warning only shows up once (also not performance-critical)
+        # TODO: allow user to inspect, modify the mapping
+
         match = xtdf_source_re.match(source)
-        if match is None:
-            self.log.WARN(f"Couldn't figure out index for source {source}")
-            return 0
-        return int(match.group(1))
+        if match is not None:
+            return int(match.group(1))
+
+        match = daq_source_re.match(source)
+        if match is not None:
+            return int(match.group(1)) - 1
+
+        self.log.WARN(f"Couldn't figure out index for source {source}")
+        return 0
diff --git a/src/calng/manual_geometry_base.py b/src/calng/manual_geometry_base.py
index e7629f3a..2d2827b8 100644
--- a/src/calng/manual_geometry_base.py
+++ b/src/calng/manual_geometry_base.py
@@ -1,12 +1,16 @@
 import pickle
 
+import matplotlib.pyplot as plt
+import numpy as np
 from karabo.bound import (
     DOUBLE_ELEMENT,
-    KARABO_CLASSINFO,
     IMAGEDATA_ELEMENT,
+    INT32_ELEMENT,
+    KARABO_CLASSINFO,
     NODE_ELEMENT,
     OUTPUT_CHANNEL,
     SLOT_ELEMENT,
+    TABLE_ELEMENT,
     VECTOR_CHAR_ELEMENT,
     VECTOR_STRING_ELEMENT,
     Encoding,
@@ -17,14 +21,11 @@ from karabo.bound import (
     State,
 )
 from karabo.common.api import KARABO_SCHEMA_DISPLAY_TYPE_SCENES as DT_SCENES
-import numpy as np
 from matplotlib.backends.backend_agg import FigureCanvasAgg
-import matplotlib.pyplot as plt
 
 from . import scenes
 from ._version import version as deviceVersion
 
-
 geometry_schema = Schema()
 (
     VECTOR_CHAR_ELEMENT(geometry_schema)
@@ -38,6 +39,37 @@ geometry_schema = Schema()
 preview_schema = Schema()
 (IMAGEDATA_ELEMENT(preview_schema).key("overview").commit())
 
+ModuleColumn = Schema()
+(
+    DOUBLE_ELEMENT(ModuleColumn)
+    .key("posX")
+    .assignmentOptional()
+    .defaultValue(95)
+    .reconfigurable()
+    .commit(),
+
+    DOUBLE_ELEMENT(ModuleColumn)
+    .key("posY")
+    .assignmentOptional()
+    .defaultValue(564)
+    .reconfigurable()
+    .commit(),
+
+    INT32_ELEMENT(ModuleColumn)
+    .key("orientationX")
+    .assignmentOptional()
+    .defaultValue(-1)
+    .reconfigurable()
+    .commit(),
+
+    INT32_ELEMENT(ModuleColumn)
+    .key("orientationY")
+    .assignmentOptional()
+    .defaultValue(-1)
+    .reconfigurable()
+    .commit(),
+)
+
 
 @KARABO_CLASSINFO("ManualGeometryBase", deviceVersion)
 class ManualGeometryBase(PythonDevice):
@@ -49,37 +81,20 @@ class ManualGeometryBase(PythonDevice):
             .key("geometryOutput")
             .dataSchema(geometry_schema)
             .commit(),
+
             SLOT_ELEMENT(expected).key("pleaseSendYourGeometry").commit(),
+
             SLOT_ELEMENT(expected).key("reloadScenes").commit(),
+
             OUTPUT_CHANNEL(expected)
             .key("previewOutput")
             .dataSchema(preview_schema)
             .commit(),
+
             IMAGEDATA_ELEMENT(expected).key("layoutPreview").commit(),
         )
 
-        # configuring this one manually
-        # subclasses should set better defaults
-        (NODE_ELEMENT(expected).key("quadrantCorners").commit(),)
-        for q in range(1, 5):
-            (
-                NODE_ELEMENT(expected).key(f"quadrantCorners.Q{q}").commit(),
-                DOUBLE_ELEMENT(expected)
-                .key(f"quadrantCorners.Q{q}.x")
-                .assignmentOptional()
-                .defaultValue(0)
-                .reconfigurable()
-                .commit(),
-                DOUBLE_ELEMENT(expected)
-                .key(f"quadrantCorners.Q{q}.y")
-                .assignmentOptional()
-                .defaultValue(0)
-                .reconfigurable()
-                .commit(),
-            )
-
         # scenes are fun
-        # TODO: add the scene
         (
             VECTOR_STRING_ELEMENT(expected)
             .key("availableScenes")
@@ -89,6 +104,9 @@ class ManualGeometryBase(PythonDevice):
             .commit(),
         )
 
+    def update_geom(self):
+        raise NotImplementedError()
+
     def __init__(self, config):
         super().__init__(config)
 
@@ -116,22 +134,6 @@ class ManualGeometryBase(PythonDevice):
         response["payload"] = payload
         self.reply(response)
 
-    def update_geom(self):
-        self.quadrant_corners = tuple(
-            (self.get(f"quadrantCorners.Q{q}.x"), self.get(f"quadrantCorners.Q{q}.y"))
-            for q in range(1, 5)
-        )
-        self.geom = self.geometry_class.from_quad_positions(self.quadrant_corners)
-        self.pickled = pickle.dumps(self.geom)
-        # TODO: send to anyone who asks? make slot for that?
-        self.writeChannel("geometryOutput", Hash("pickledGeometry", self.pickled))
-
-    def reloadScenes(self):
-        global scenes
-        import importlib
-
-        scenes = importlib.reload(scenes)
-
     def pleaseSendYourGeometry(self):
         self.writeChannel("geometryOutput", Hash("pickledGeometry", self.pickled))
         axis = self.geom.inspect()
@@ -148,7 +150,6 @@ class ManualGeometryBase(PythonDevice):
             "layoutPreview",
             ImageData(image_buffer, encoding=Encoding.RGBA, bitsPerPixel=3 * 8),
         )
-        # self.writeChannel("previewOutput", Hash("overview", ImageData()))
 
     def preReconfigure(self, config):
         self._prereconfigure_update_hash = config
@@ -159,3 +160,68 @@ class ManualGeometryBase(PythonDevice):
             for path in self._prereconfigure_update_hash.getPaths()
         ):
             self.update_geom()
+        del self._prereconfigure_update_hash
+
+
+@KARABO_CLASSINFO("ManualQuadrantsGeometryBase", deviceVersion)
+class ManualQuadrantsGeometryBase(ManualGeometryBase):
+    @staticmethod
+    def expectedParameters(expected):
+        # note: subclasses should set better defaults
+        (NODE_ELEMENT(expected).key("quadrantCorners").commit(),)
+        for q in range(1, 5):
+            (
+                NODE_ELEMENT(expected).key(f"quadrantCorners.Q{q}").commit(),
+                DOUBLE_ELEMENT(expected)
+                .key(f"quadrantCorners.Q{q}.x")
+                .assignmentOptional()
+                .defaultValue(0)
+                .reconfigurable()
+                .commit(),
+
+                DOUBLE_ELEMENT(expected)
+                .key(f"quadrantCorners.Q{q}.y")
+                .assignmentOptional()
+                .defaultValue(0)
+                .reconfigurable()
+                .commit(),
+            )
+
+    def update_geom(self):
+        self.quadrant_corners = tuple(
+            (self.get(f"quadrantCorners.Q{q}.x"), self.get(f"quadrantCorners.Q{q}.y"))
+            for q in range(1, 5)
+        )
+        self.geom = self.geometry_class.from_quad_positions(self.quadrant_corners)
+        self.pickled = pickle.dumps(self.geom)
+        # TODO: send to anyone who asks? make slot for that? send on connect?
+        self.writeChannel("geometryOutput", Hash("pickledGeometry", self.pickled))
+
+
+@KARABO_CLASSINFO("ManualModulesGeometryBase", deviceVersion)
+class ManualModulesGeometryBase(ManualGeometryBase):
+    @staticmethod
+    def expectedParameters(expected):
+        (
+            TABLE_ELEMENT(expected)
+            .key("modules")
+            .setColumns(ModuleColumn)
+            .assignmentOptional()
+            .defaultValue([])
+            .reconfigurable()
+            .commit(),
+        )
+
+    def update_geom(self):
+        modules = self.get("modules")
+        module_pos = [(module.get("posX"), module.get("posY")) for module in modules]
+        orientations = [
+            (module.get("orientationX"), module.get("orientationY"))
+            for module in modules
+        ]
+        self.geom = self.geometry_class.from_module_positions(
+            module_pos, orientations=orientations
+        )
+        self.pickled = pickle.dumps(self.geom)
+        # TODO: send to anyone who asks? make slot for that?
+        self.writeChannel("geometryOutput", Hash("pickledGeometry", self.pickled))
-- 
GitLab