From ec2c4d835b93de6ae4f5a7d52a574f94ca5304ed Mon Sep 17 00:00:00 2001 From: David Hammer <dhammer@mailbox.org> Date: Wed, 9 Feb 2022 11:45:02 +0100 Subject: [PATCH] Rename SimpleAssember, split output in regular and preview --- setup.py | 2 +- src/calng/CalibrationManager.py | 2 +- ...impleAssembler.py => DetectorAssembler.py} | 115 +++++++++++++----- src/calng/scenes.py | 8 +- 4 files changed, 93 insertions(+), 34 deletions(-) rename src/calng/{SimpleAssembler.py => DetectorAssembler.py} (71%) diff --git a/setup.py b/setup.py index 31a693e4..389c53da 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ setup(name='calng', 'ManualDsscGeometry = calng.ManualDsscGeometry:ManualDsscGeometry', 'ManualJungfrauGeometry = calng.ManualJungfrauGeometry:ManualJungfrauGeometry', 'ShmemToZMQ = calng.ShmemToZMQ:ShmemToZMQ', - 'SimpleAssembler = calng.SimpleAssembler:SimpleAssembler', + 'DetectorAssembler = calng.DetectorAssembler:DetectorAssembler', ], 'karabo.middlelayer_device': [ diff --git a/src/calng/CalibrationManager.py b/src/calng/CalibrationManager.py index bde13b4b..20b91305 100644 --- a/src/calng/CalibrationManager.py +++ b/src/calng/CalibrationManager.py @@ -86,7 +86,7 @@ class ClassIdsNode(Configurable): displayedName='Assembler class', description='Device class to use for assembling the matched output of ' 'a preview layer.', - defaultValue='FemDataAssembler', + defaultValue='DetectorAssembler', accessMode=AccessMode.INITONLY, assignment=Assignment.MANDATORY) diff --git a/src/calng/SimpleAssembler.py b/src/calng/DetectorAssembler.py similarity index 71% rename from src/calng/SimpleAssembler.py rename to src/calng/DetectorAssembler.py index 847265f6..75a11b87 100644 --- a/src/calng/SimpleAssembler.py +++ b/src/calng/DetectorAssembler.py @@ -9,6 +9,8 @@ from karabo.bound import ( FLOAT_ELEMENT, IMAGEDATA_ELEMENT, INPUT_CHANNEL, + NDARRAY_ELEMENT, + NODE_ELEMENT, KARABO_CLASSINFO, OUTPUT_CHANNEL, OVERWRITE_ELEMENT, @@ -27,16 +29,25 @@ from karabo.bound import ( Trainstamp, Unit, ) -from karabo.common.api import KARABO_SCHEMA_DISPLAY_TYPE_SCENES as DT_SCENES from TrainMatcher import TrainMatcher -from TrainMatcher import scenes as trainmatcher_scenes from . import scenes from ._version import version as deviceVersion +output_schema = Schema() +( + NODE_ELEMENT(output_schema).key("image").commit(), + + NDARRAY_ELEMENT(output_schema).key("image.data").commit(), + + UINT64_ELEMENT(output_schema).key("trainId").readOnly().commit(), +) + preview_schema = Schema() ( - IMAGEDATA_ELEMENT(preview_schema).key("image").commit(), + NODE_ELEMENT(preview_schema).key("image").commit(), + + IMAGEDATA_ELEMENT(preview_schema).key("image.data").commit(), UINT64_ELEMENT(preview_schema).key("trainId").readOnly().commit(), ) @@ -46,8 +57,8 @@ daq_source_re = re.compile(r".*\/DET\/.*?(\d+):daqOutput") # TODO: merge scene with TrainMatcher's nice overview -@KARABO_CLASSINFO("SimpleAssembler", deviceVersion) -class SimpleAssembler(TrainMatcher.TrainMatcher): +@KARABO_CLASSINFO("DetectorAssembler", deviceVersion) +class DetectorAssembler(TrainMatcher.TrainMatcher): @staticmethod def expectedParameters(expected): ( @@ -84,8 +95,18 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): .defaultValue("image.data") .commit(), + NODE_ELEMENT(expected) + .key("preview") + .description( + "The preview output is intended for Karabo GUI previews. It differs " + "from the main output in that it is lower rate (controlled by train " + "stride), can be downsampled, and is given the ImageData type for use " + "within Karabo." + ) + .commit(), + UINT32_ELEMENT(expected) - .key("downsamplingFactor") + .key("preview.downsamplingFactor") .description( "If greater than 1, the assembled image will be downsampled by this " "factor in x and y dimensions before sending. This is only to save " @@ -98,7 +119,7 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): .commit(), STRING_ELEMENT(expected) - .key("downsamplingFunction") + .key("preview.downsamplingFunction") .description("Reduction function used during downsampling.") .assignmentOptional() .defaultValue("nanmax") @@ -106,6 +127,19 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): .reconfigurable() .commit(), + OUTPUT_CHANNEL(expected) + .key("preview.output") + .dataSchema(preview_schema) + .commit(), + + UINT32_ELEMENT(expected) + .key("preview.trainStride") + .displayedName("Train stride") + .description("Only trains which are a multiple of this are sent to preview") + .assignmentOptional() + .defaultValue(10) + .commit(), + INPUT_CHANNEL(expected) .key("geometryInput") .displayedName("Geometry input") @@ -113,7 +147,7 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): OUTPUT_CHANNEL(expected) # can OVERWRITE_ELEMENT even do this? .key("output") - .dataSchema(preview_schema) + .dataSchema(output_schema) .commit(), ) @@ -130,6 +164,7 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): self.KARABO_SLOT(self.requestScene) self.ask_for_geometry() + self.preview_output = self.signalSlotable.getOutputChannel("preview.output") self.start() def requestScene(self, params): @@ -137,7 +172,7 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): scene_name = params.get("name", default="") if scene_name == "overview": payload = Hash("name", scene_name, "success", True) - payload["data"] = scenes.simple_assembler_overview( + payload["data"] = scenes.detector_assembler_overview( device_id=self.getInstanceId(), geometry_device_id=self.get("geometryInput.connectedOutputChannels")[ 0 @@ -195,7 +230,7 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): def _send(self, train_id, sources): # TODO: adapt to appropriate hook for new TrainMatcher (no _send) if self.geometry is None: - self.log.WARN("Have not received a geometry yet") + self.log.WARN("Have not received a geometry yet, will not send anything") return timestamp = Timestamp(Epochstamp(), Trainstamp(train_id)) @@ -207,36 +242,51 @@ class SimpleAssembler(TrainMatcher.TrainMatcher): self.input_buffer[module_index] = np.squeeze(data.get(self._path_to_stack)) module_indices_unfilled.discard(module_index) - for unfilled_module in module_indices_unfilled: - self.input_buffer[unfilled_module].fill(0) + for module_index in module_indices_unfilled: + self.input_buffer[module_index].fill(0) # TODO: configurable treatment of missing modules # TODO: reusable output buffer to save on allocation assembled, _ = self.geometry.position_modules_fast(self.input_buffer) - downsampling_factor = self.get("downsamplingFactor") - if downsampling_factor > 1: - assembled = downsample_2d( - assembled, - downsampling_factor, - reduction_fun=getattr(np, self.get("downsamplingFunction")) - ) - # TODO: optionally include control data out_hash = Hash( - "image", - ImageData( - # TODO: get around this being mirrored... - (assembled[::-1, ::-1]).astype(np.int32), - Dims(*assembled.shape), - Encoding.GRAY, - ), + "image.data", + assembled, "trainId", train_id, ) + # TODO: just get channel once, reuse (should not need to reinject) channel = self.signalSlotable.getOutputChannel("output") - channel.write(out_hash, ChannelMetaData(self.getInstanceId(), timestamp)) + output_metadata = ChannelMetaData(self.getInstanceId(), timestamp) + channel.write(out_hash, output_metadata) channel.update() + + if train_id % self.unsafe_get("preview.trainStride") == 0: + downsampling_factor = self.unsafe_get("preview.downsamplingFactor") + if downsampling_factor > 1: + assembled = downsample_2d( + assembled, + downsampling_factor, + reduction_fun=getattr( + np, self.unsafe_get("preview.downsamplingFunction") + ), + ) + out_hash = Hash( + "image.data", + ImageData( + # TODO: get around this being mirrored... + assembled.astype(np.int32)[::-1, ::-1], + Dims(*assembled.shape), + Encoding.GRAY, + ), + "trainId", + train_id, + ) + channel = self.signalSlotable.getOutputChannel("preview.output") + channel.write(out_hash, output_metadata) + channel.update() + self.rate_out.update() @functools.lru_cache() @@ -277,3 +327,12 @@ def downsample_2d(arr, factor, reduction_fun=np.nanmax): ), axis=0 ) return arr + + +# forward-compatible unsafe_get proposed by @haufs +if not hasattr(DetectorAssembler, "unsafe_get"): + def unsafe_get(self, key): + """See base_correction.py""" + return self._parameters.get(key) + + setattr(DetectorAssembler, "unsafe_get", unsafe_get) diff --git a/src/calng/scenes.py b/src/calng/scenes.py index d550ce91..9edf8c40 100644 --- a/src/calng/scenes.py +++ b/src/calng/scenes.py @@ -556,7 +556,7 @@ class AssemblerDeviceStatus(VerticalLayout): height=BASE_INC, ), ComboBoxModel( - keys=[f"{device_id}.downsamplingFactor"], + keys=[f"{device_id}.preview.downsamplingFactor"], width=7 * BASE_INC, height=BASE_INC, klass="EditableComboBox", @@ -570,7 +570,7 @@ class AssemblerDeviceStatus(VerticalLayout): height=BASE_INC, ), ComboBoxModel( - keys=[f"{device_id}.downsamplingFunction"], + keys=[f"{device_id}.preview.downsamplingFunction"], width=7 * BASE_INC, height=BASE_INC, klass="EditableComboBox", @@ -795,7 +795,7 @@ def correction_constant_dashboard( @scene_generator -def simple_assembler_overview(device_id, geometry_device_id): +def detector_assembler_overview(device_id, geometry_device_id): return VerticalLayout( HorizontalLayout( AssemblerDeviceStatus(device_id), @@ -818,7 +818,7 @@ def simple_assembler_overview(device_id, geometry_device_id): ), ), titled("Preview image")(boxed(dummy_wrap(DetectorGraphModel)))( - keys=[f"{device_id}.preview.output.schema.image"], + keys=[f"{device_id}.preview.output.schema.image.data"], colormap="viridis", width=30 * BASE_INC, height=30 * BASE_INC, -- GitLab