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