diff --git a/setup.py b/setup.py index 31a693e453590a0797e9e86020bd14e063034c53..389c53da2b3ba212b4a1203c9821d541c94bf83b 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 bde13b4b1f05176902909465ba86d7ceaf7e1633..20b91305f7fe6118799d2fecd317f995c5d50479 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 847265f6a2b40b718afc3132f224824549ffeec5..75a11b87b3e2b27bbc2569bcb87ffbf685909a47 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 d550ce916b187c6ea2877ed40f5131a82c8e2ebe..9edf8c4087d7a53b24c4a61ac8a4fe1ce9061f7a 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,