diff --git a/DEPENDS b/DEPENDS index 7d43690b3c115ea60f482031806a6dc36a6eef3b..b18c86bacb67525e328f47a25aab32e930d0ac6d 100644 --- a/DEPENDS +++ b/DEPENDS @@ -1,3 +1,3 @@ TrainMatcher, 2.3.2-2.16.2 -calngDeps, 1.0.0-2.16.2 +calngDeps, 1.0.1-2.16.2 calibrationClient, 11.0.0 diff --git a/setup.py b/setup.py index e600454731c8032d3f474a5b7bd67150e04d062f..2f94de91bc5b5eb48fbdd96f23e00ce2ce6de65b 100644 --- a/setup.py +++ b/setup.py @@ -31,10 +31,12 @@ setup(name='calng', 'Gotthard2Correction = calng.corrections.Gotthard2Correction:Gotthard2Correction', 'JungfrauCorrection = calng.corrections.JungfrauCorrection:JungfrauCorrection', 'LpdCorrection = calng.corrections.LpdCorrection:LpdCorrection', + 'LpdminiCorrection = calng.corrections.LpdminiCorrection:LpdminiCorrection', 'ShmemToZMQ = calng.ShmemToZMQ:ShmemToZMQ', 'ShmemTrainMatcher = calng.ShmemTrainMatcher:ShmemTrainMatcher', 'DetectorAssembler = calng.DetectorAssembler:DetectorAssembler', 'Gotthard2Assembler = calng.Gotthard2Assembler:Gotthard2Assembler', + 'LpdminiSplitter = calng.LpdminiSplitter:LpdminiSplitter', ], 'karabo.middlelayer_device': [ @@ -47,6 +49,7 @@ setup(name='calng', 'Dssc1MGeometry = calng.geometries:Dssc1MGeometry.Dssc1MGeometry', 'Epix100Geometry = calng.geometries:Epix100Geometry.Epix100Geometry', 'Lpd1MGeometry = calng.geometries:Lpd1MGeometry.Lpd1MGeometry', + 'LpdminiGeometry = calng.geometries:LpdminiGeometry.LpdminiGeometry', 'JungfrauGeometry = calng.geometries:JungfrauGeometry.JungfrauGeometry', 'RoiTool = calng.RoiTool:RoiTool', ], @@ -78,6 +81,12 @@ setup(name='calng', extra_compile_args=['-O3', '-march=native', '-fopenmp'], extra_link_args=['-fopenmp'], ), + Extension( + 'calng.kernels.lpd_cython', + ['src/calng/kernels/lpd_cpu.pyx'], + extra_compile_args=['-O3', '-march=native', '-fopenmp'], + extra_link_args=['-fopenmp'], + ), ], compiler_directives={ 'language_level': 3 diff --git a/src/calng/LpdminiSplitter.py b/src/calng/LpdminiSplitter.py new file mode 100644 index 0000000000000000000000000000000000000000..ed4c78a6086f1370716e9a0c2f402b1f3429a2fd --- /dev/null +++ b/src/calng/LpdminiSplitter.py @@ -0,0 +1,242 @@ +from timeit import default_timer + +import numpy as np +from karabo.bound import ( + DOUBLE_ELEMENT, + INPUT_CHANNEL, + KARABO_CLASSINFO, + NODE_ELEMENT, + OUTPUT_CHANNEL, + OVERWRITE_ELEMENT, + UINT64_ELEMENT, + VECTOR_UINT32_ELEMENT, + VECTOR_STRING_ELEMENT, + ChannelMetaData, + Hash, + MetricPrefix, + PythonDevice, + State, + Timestamp, + Unit, +) +from karabo.common.api import KARABO_SCHEMA_DISPLAY_TYPE_SCENES as DT_SCENES + +from . import scenes, schemas, shmem_utils, utils +from .base_correction import WarningLampType +from ._version import version as deviceVersion + +# plan: +# - split data into 8 modules +# - copy each into shared memory and send on distinct channel + +PROCESSING_STATE_TIMEOUT = 10 + + +@KARABO_CLASSINFO("LpdminiSplitter", deviceVersion) +class LpdminiSplitter(PythonDevice): + # up to 8 minis, using 1-indexing for naming + _output_channel_names = [f"output-{i+1}" for i in range(8)] + + @staticmethod + def expectedParameters(expected): + ( + OVERWRITE_ELEMENT(expected) + .key("state") + .setNewDefaultValue(State.INIT) + .commit(), + + INPUT_CHANNEL(expected) + .key("input") + .commit(), + + VECTOR_UINT32_ELEMENT(expected) + .key("outputDataShape") + .displayedName("Output data shape") + .readOnly() + .initialValue([512, 32, 256]) + .commit(), + + UINT64_ELEMENT(expected) + .key("trainId") + .displayedName("Train ID") + .description("ID of latest train processed by this device.") + .readOnly() + .initialValue(0) + .commit(), + + NODE_ELEMENT(expected) + .key("performance") + .commit(), + + DOUBLE_ELEMENT(expected) + .key("performance.rate") + .displayedName("Rate") + .description( + "Actual rate with which this device gets, processes, and sends trains. " + "This is a simple windowed moving average." + ) + .unit(Unit.HERTZ) + .readOnly() + .initialValue(0) + .commit(), + + DOUBLE_ELEMENT(expected) + .key("performance.processingTime") + .displayedName("Processing time") + .unit(Unit.SECOND) + .metricPrefix(MetricPrefix.MILLI) + .readOnly() + .initialValue(0) + .warnHigh(100) + .info("Processing too slow to reach 10 Hz") + .needsAcknowledging(False) + .commit(), + + VECTOR_STRING_ELEMENT(expected) + .key("availableScenes") + .setSpecialDisplayType(DT_SCENES) + .readOnly() + .initialValue(["overview"]) + .commit(), + ) + + for channel_name in LpdminiSplitter._output_channel_names: + ( + OUTPUT_CHANNEL(expected) + .key(channel_name) + .dataSchema(schemas.xtdf_output_schema()) + .commit(), + ) + + def __init__(self, config): + super().__init__(config) + self.registerInitialFunction(self._initialization) + self._output_image_shape = (512, 32, 256) # will be updated based on frames + self.KARABO_SLOT(self.requestScene) + self._latest_log_message = None + self._latest_warn_type = None + + def _initialization(self): + self.KARABO_ON_DATA("input", self.input_handler) + self._shmem_buffers = [ + shmem_utils.ShmemCircularBuffer( + 1 * 2**30, + (512, 32, 256), + np.uint16, + f"{self.getInstanceId()}:{channel_name}" + ) + for channel_name in self._output_channel_names + ] + + # performance measures and such + self._last_processing_started = 0 # used for processing time and timeout + self._buffered_status_update = Hash() + self._processing_time_tracker = utils.ExponentialMovingAverage(alpha=0.3) + self._rate_tracker = utils.WindowRateTracker() + self._input_delay_tracker = utils.ExponentialMovingAverage(alpha=0.3) + self._performance_measure_update_timer = utils.RepeatingTimer( + interval=1, + callback=self._update_performance_measures, + ) + self.updateState(State.ON) + + def input_handler(self, data_hash, metadata): + state = self.get("state") + if state is State.INIT: + return + + # TODO: handle empty DAQ hash + if not data_hash.has("image.data"): + self.log_status_info("Input hash had no image data node") + if state is State.PROCESSING: + self.updateState(State.IGNORING) + return + try: + image_data = np.asarray( + data_hash.get("image.data") + ) + except RuntimeError as err: + self.log_status_info( + f"Failed to load image data; probably empty hash from DAQ: {err}", + WarningLampType.EMPTY_HASH, + ) + if state is State.PROCESSING: + self.updateState(State.IGNORING) + return + + if state is not State.PROCESSING: + self.log_status_info("Processing data") + self.updateState(State.PROCESSING) + + self._last_processing_started = default_timer() + timestamp = Timestamp.fromHashAttributes(metadata.getAttributes("timestamp")) + self._buffered_status_update.set("trainId", timestamp.getTrainId()) + num_frames = data_hash["image.cellId"].size + image_data = image_data.reshape((num_frames, 256, 256)) + # note: reshape is equivalent to overrideInputAxisOrder in BaseCorrection + if num_frames != self._output_image_shape: + self._output_image_shape = (num_frames, 32, 256) + for shmem_buffer in self._shmem_buffers: + shmem_buffer.change_shape(self._output_image_shape) + self.set("outputDataShape", list(self._output_image_shape)) + # reshape to drop singleton axis + optionally fix DAQ shape shenanigans + receiver_source_name = metadata.get("source") + for i, (channel_name, shmem_buffer) in enumerate( + zip(self._output_channel_names, self._shmem_buffers) + ): + this_slice = image_data[:, i * 32 : (i + 1) * 32] + buffer_handle, buffer_array = shmem_buffer.next_slot() + buffer_array[:] = this_slice + data_hash.set("image.data", buffer_handle) + data_hash.set("calngShmemPaths", ["image.data"]) + # TODO: consider source name; virtual indices like in offline? + channel = self.signalSlotable.getOutputChannel(channel_name) + channel.write( + data_hash, + ChannelMetaData(f"{receiver_source_name}-{i+1}", timestamp), + # adding 1-indexed suffix index to soruce name forwarded + copyAllData=False, + ) + channel.update() + self._processing_time_tracker.update( + default_timer() - self._last_processing_started + ) + self._rate_tracker.update() + + def _update_performance_measures(self): + if self.get("state") in {State.PROCESSING, State.IGNORING}: + self._buffered_status_update.set( + "performance.rate", self._rate_tracker.get() + ) + self._buffered_status_update.set( + "performance.processingTime", self._processing_time_tracker.get() * 1000 + ) + self.set(self._buffered_status_update) + if ( + default_timer() - self._last_processing_started + > PROCESSING_STATE_TIMEOUT + ): + self.updateState(State.ON) + + def requestScene(self, params): + payload = Hash() + payload["name"] = "overview" + payload["data"] = scenes.lpdmini_splitter_overview( + device_id=self.getInstanceId(), + schema=self.getFullSchema(), + ) + payload["success"] = True + response = Hash() + response["type"] = "deviceScene" + response["origin"] = self.getInstanceId() + response["payload"] = payload + self.reply(response) + + def log_status_info(self, msg, warn_type=None): + if (msg != self._latest_log_message) and ( + warn_type is None or warn_type != self._latest_warn_type + ): + self.log.INFO(msg) + self.set("status", msg) + self._latest_log_message = msg + self._latest_warn_type = warn_type diff --git a/src/calng/base_correction.py b/src/calng/base_correction.py index 2bbc38e42319fce73f234a7aee18ad7bd3d0aae9..57a62cee7ef626e90abaa31558b41a86da8259de 100644 --- a/src/calng/base_correction.py +++ b/src/calng/base_correction.py @@ -723,16 +723,7 @@ class BaseCorrection(PythonDevice): self._update_correction_flags() self._update_frame_filter() - self._buffered_status_update = Hash( - "trainId", - 0, - "performance.rate", - 0, - "performance.processingTime", - 0, - "performance.ratioOfRecentTrainsReceived", - 0, - ) + self._buffered_status_update = Hash() self._processing_time_tracker = utils.ExponentialMovingAverage(alpha=0.3) self._rate_tracker = utils.WindowRateTracker() self._input_delay_tracker = utils.ExponentialMovingAverage(alpha=0.3) @@ -1005,6 +996,7 @@ class BaseCorrection(PythonDevice): self.output_data_dtype, shmem_buffer_name, ) + self._shmem_receiver = shmem_utils.ShmemCircularBufferReceiver() if self._cuda_pin_buffers: self.log.INFO("Trying to pin the shmem buffer memory") self._shmem_buffer.cuda_pin() @@ -1092,6 +1084,7 @@ class BaseCorrection(PythonDevice): WarningLampType.EMPTY_HASH, only_print_once=True, ) as warn: + self._shmem_receiver.dereference_shmem_handles(data_hash) try: image_data = np.asarray(data_hash.get(self._image_data_path)) cell_table = ( diff --git a/src/calng/base_geometry.py b/src/calng/base_geometry.py index e300162bed53a32e7521b0ec03ef1205ceb9c3d9..5ed201da7e05255c2106068f45059237a4a3a7ab 100644 --- a/src/calng/base_geometry.py +++ b/src/calng/base_geometry.py @@ -445,7 +445,7 @@ class ManualQuadrantsGeometryBase(ManualGeometryBase): self.quadrantCorners.offset.y = 0 -class ModuleListItem(Configurable): +class OrientableModuleListItem(Configurable): posX = Double( assignment=Assignment.OPTIONAL, defaultValue=0, @@ -458,20 +458,32 @@ class ModuleListItem(Configurable): orientY = Int32(assignment=Assignment.OPTIONAL, defaultValue=1) -def make_manual_module_list_node(defaults): - class ManualModuleListNode(BaseManualGeometryConfigNode): +class RotatableModuleListItem(Configurable): + posX = Double( + assignment=Assignment.OPTIONAL, + defaultValue=0, + ) + posY = Double( + assignment=Assignment.OPTIONAL, + defaultValue=0, + ) + rotate = Int32(assignment=Assignment.OPTIONAL, defaultValue=1) + + +def make_manual_orientable_module_list_node(defaults): + class ManualOrientableModuleListNode(BaseManualGeometryConfigNode): modules = VectorHash( displayedName="Modules", - rows=ModuleListItem, + rows=OrientableModuleListItem, defaultValue=defaults, accessMode=AccessMode.RECONFIGURABLE, assignment=Assignment.OPTIONAL, ) - return Node(ManualModuleListNode) + return Node(ManualOrientableModuleListNode) -class ManualModuleListGeometryBase(ManualGeometryBase): +class ManualOrientableModuleListGeometryBase(ManualGeometryBase): moduleList = None # subclass must define (with nice defaults) @slot @@ -499,3 +511,46 @@ class ManualModuleListGeometryBase(ManualGeometryBase): ).offset((self.moduleList.offset.x.value, self.moduleList.offset.y.value)) await self._set_geometry(geometry) self.tweakGeometry._reset() + + +def make_manual_rotatable_module_list_node(defaults): + class ManualRotatableModuleListNode(BaseManualGeometryConfigNode): + modules = VectorHash( + displayedName="Modules", + rows=RotatableModuleListItem, + defaultValue=defaults, + accessMode=AccessMode.RECONFIGURABLE, + assignment=Assignment.OPTIONAL, + ) + + return Node(ManualRotatableModuleListNode) + + +class ManualRotatableModuleListGeometryBase(ManualGeometryBase): + moduleList = None # subclass must define (with nice defaults) + + @slot + def requestScene(self, params): + name = params.get("name", default="overview") + if name == "overview": + # Assumes there are correction devices known to manager + scene_data = scenes.modules_geometry_overview( + self.deviceId, + self.getDeviceSchema(), + ) + payload = Hash("success", True, "name", name, "data", scene_data) + + return Hash("type", "deviceScene", "origin", self.deviceId, "payload", payload) + + async def _set_from_manual_config(self): + self._set_status("Updating geometry from manual configuration") + with self.push_state(State.CHANGING): + geometry = self.geometry_class.from_module_positions( + [(x, y) for (x, y, _) in self.moduleList.modules.value], + [ + rotation + for (_, _, rotation) in self.moduleList.modules.value + ], + ).offset((self.moduleList.offset.x.value, self.moduleList.offset.y.value)) + await self._set_geometry(geometry) + self.tweakGeometry._reset() diff --git a/src/calng/corrections/LpdCorrection.py b/src/calng/corrections/LpdCorrection.py index ed70abd5b7b020fadda1834687e6fa2e58bfbbb2..62a9e42f1637f1341514ce4df3aa21df157b91a3 100644 --- a/src/calng/corrections/LpdCorrection.py +++ b/src/calng/corrections/LpdCorrection.py @@ -48,7 +48,7 @@ class CorrectionFlags(enum.IntFlag): class LpdGpuRunner(base_kernel_runner.BaseKernelRunner): _gpu_based = True - _corrected_axis_order = "fxy" + _corrected_axis_order = "fyx" @property def input_shape(self): @@ -81,7 +81,7 @@ class LpdGpuRunner(base_kernel_runner.BaseKernelRunner): self.processed_data = cp.empty(self.processed_shape, dtype=output_data_dtype) self.cell_table = cp.empty(frames, dtype=np.uint16) - self.map_shape = (constant_memory_cells, pixels_x, pixels_y, 3) + self.map_shape = (constant_memory_cells, pixels_y, pixels_x, 3) self.offset_map = cp.empty(self.map_shape, dtype=np.float32) self.gain_amp_map = cp.empty(self.map_shape, dtype=np.float32) self.rel_gain_slopes_map = cp.empty(self.map_shape, dtype=np.float32) @@ -396,8 +396,8 @@ class LpdCorrection(BaseCorrection): return ( self.unsafe_get("dataFormat.frames"), 1, - self.unsafe_get("dataFormat.pixelsX"), self.unsafe_get("dataFormat.pixelsY"), + self.unsafe_get("dataFormat.pixelsX"), ) def __init__(self, config): diff --git a/src/calng/corrections/LpdminiCorrection.py b/src/calng/corrections/LpdminiCorrection.py new file mode 100644 index 0000000000000000000000000000000000000000..688b174b23474f35346014e91bd3b9074879fb4a --- /dev/null +++ b/src/calng/corrections/LpdminiCorrection.py @@ -0,0 +1,103 @@ +import numpy as np +from karabo.bound import ( + DOUBLE_ELEMENT, + KARABO_CLASSINFO, + OVERWRITE_ELEMENT, +) + +from .._version import version as deviceVersion +from ..base_correction import add_correction_step_schema +from . import LpdCorrection + + +class LpdminiGpuRunner(LpdCorrection.LpdGpuRunner): + def load_constant(self, constant_type, constant_data): + print(f"Given: {constant_type} with shape {constant_data.shape}") + # constant type → transpose order + constant_buffer_map = { + LpdCorrection.Constants.Offset: self.offset_map, + LpdCorrection.Constants.GainAmpMap: self.gain_amp_map, + LpdCorrection.Constants.FFMap: self.flatfield_map, + LpdCorrection.Constants.RelativeGain: self.rel_gain_slopes_map, + } + if constant_type in { + LpdCorrection.Constants.BadPixelsDark, + LpdCorrection.Constants.BadPixelsFF, + }: + self.bad_pixel_map |= self._xp.asarray( + constant_data, + dtype=np.uint32, + )[: self.constant_memory_cells] + else: + constant_buffer_map[constant_type].set( + constant_data.astype(np.float32)[: self.constant_memory_cells] + ) + + +class LpdminiCalcatFriend(LpdCorrection.LpdCalcatFriend): + @staticmethod + def add_schema( + schema, + managed_keys, + ): + super(LpdminiCalcatFriend, LpdminiCalcatFriend).add_schema(schema, managed_keys) + ( + OVERWRITE_ELEMENT(schema) + .key("constantParameters.biasVoltage") + .setNewDisplayedName("Bias voltage (odd)") + .setNewDescription("Bias voltage apbplied to minis 1, 3, 5, and 7.") + .commit(), + + DOUBLE_ELEMENT(schema) + .key("constantParameters.biasVoltage2") + .displayedName("Bias voltage (even)") + .description("Separate bias voltage used for minis 2, 4, 6, and 8.") + .assignmentOptional() + .defaultValue(300) + .reconfigurable() + .commit(), + + OVERWRITE_ELEMENT(schema) + .key("constantParameters.pixelsY") + .setNewDefaultValue(32) + .commit(), + ) + managed_keys.add("constantParameters.biasVoltage2") + + def basic_condition(self): + res = super().basic_condition() + if int(self.device.get("fastSources")[0][-1]) % 2 == 0: + res["Sensor Bias Voltage"] = self._get_param("biasVoltage2") + if "category" in res: + del res["category"] + return res + + +@KARABO_CLASSINFO("LpdminiCorrection", deviceVersion) +class LpdminiCorrection(LpdCorrection.LpdCorrection): + _calcat_friend_class = LpdminiCalcatFriend + _kernel_runner_class = LpdminiGpuRunner + _managed_keys = LpdCorrection.LpdCorrection._managed_keys.copy() + + @classmethod + def expectedParameters(cls, expected): + ( + OVERWRITE_ELEMENT(expected) + .key("dataFormat.pixelsY") + .setNewDefaultValue(32) + .commit(), + + OVERWRITE_ELEMENT(expected) + .key("dataFormat.frames") + .setNewDefaultValue(512) + .commit(), + ) + cls._calcat_friend_class.add_schema(expected, cls._managed_keys) + # warning: this is redundant, but needed for now to get managed keys working + add_correction_step_schema(expected, cls._managed_keys, cls._correction_steps) + ( + OVERWRITE_ELEMENT(expected) + .key("managedKeys") + .setNewDefaultValue(list(cls._managed_keys)) + .commit() + ) diff --git a/src/calng/geometries/JungfrauGeometry.py b/src/calng/geometries/JungfrauGeometry.py index c447accc7dec7857a5c858e3cfac62e001d2af29..e0c0703cb394a9b3f51fa2f163a62cc119c6a15a 100644 --- a/src/calng/geometries/JungfrauGeometry.py +++ b/src/calng/geometries/JungfrauGeometry.py @@ -2,12 +2,15 @@ import extra_geom import numpy as np from karabo.middlelayer import Hash -from ..base_geometry import ManualModuleListGeometryBase, make_manual_module_list_node +from ..base_geometry import ( + ManualOrientableModuleListGeometryBase, + make_manual_orientable_module_list_node, +) -class JungfrauGeometry(ManualModuleListGeometryBase): +class JungfrauGeometry(ManualOrientableModuleListGeometryBase): geometry_class = extra_geom.JUNGFRAUGeometry - moduleList = make_manual_module_list_node( + moduleList = make_manual_orientable_module_list_node( [ Hash("posX", x, "posY", y, "orientX", ox, "orientY", oy) for (x, y, ox, oy) in [ diff --git a/src/calng/geometries/LpdminiGeometry.py b/src/calng/geometries/LpdminiGeometry.py new file mode 100644 index 0000000000000000000000000000000000000000..31846b953ccc949564686e221b631bf0bd8c866e --- /dev/null +++ b/src/calng/geometries/LpdminiGeometry.py @@ -0,0 +1,24 @@ +import extra_geom +from karabo.middlelayer import Hash + +from ..base_geometry import ( + ManualRotatableModuleListGeometryBase, + make_manual_rotatable_module_list_node, +) + + +class LpdminiGeometry(ManualRotatableModuleListGeometryBase): + geometry_class = extra_geom.LPD_MiniGeometry + + moduleList = make_manual_rotatable_module_list_node( + [ + Hash("posX", x, "posY", y, "rotate", r) + for (x, y, r) in [ + (0, 0, 0), + # TODO: appropriate defaults + ] + ] + ) + + def _update_manual_from_current(self): + raise NotImplementedError() diff --git a/src/calng/geometries/__init__.py b/src/calng/geometries/__init__.py index 1526d0b903f59ff4810264dfa0f470258e1633bf..a13f829173ab32062f9192111e9fb87349eab331 100644 --- a/src/calng/geometries/__init__.py +++ b/src/calng/geometries/__init__.py @@ -4,5 +4,6 @@ from . import ( Dssc1MGeometry, Epix100Geometry, Lpd1MGeometry, + LpdminiGeometry, JungfrauGeometry, ) diff --git a/src/calng/kernels/lpd_cpu.pyx b/src/calng/kernels/lpd_cpu.pyx new file mode 100644 index 0000000000000000000000000000000000000000..0c3eb82d50c3e23d1414515fd967347ffe4a0876 --- /dev/null +++ b/src/calng/kernels/lpd_cpu.pyx @@ -0,0 +1,55 @@ +# cython: boundscheck=False +# cython: cdivision=True +# cython: wrapararound=False + +cdef unsigned char NONE = 0 +cdef unsigned char OFFSET = 1 +cdef unsigned char GAIN_AMP = 2 +cdef unsigned char REL_GAIN = 4 +cdef unsigned char FF_CORR = 8 +cdef unsigned char BPMASK = 16 + +from cython.parallel import prange +from libc.math cimport isinf, isnan + +def correct( + unsigned short[:, :, :, :] image_data, + unsigned short[:] cell_table, + unsigned char flags, + float[:, :, :, :] offset_map, + float[:, :, :, :] gain_amp_map, + float[:, :, :, :] rel_gain_slopes_map, + float[:, :, :, :] flatfield_map, + unsigned[:, :, :, :] bad_pixel_map, + float bad_pixel_mask_value, + # TODO: support spitting out gain map for preview purposes + float[:, :, :] output +): + cdef int frame, map_cell, ss, fs + cdef unsigned char gain_stage + cdef float res + cdef unsigned short raw_data_value + for frame in prange(image_data.shape[0], nogil=True): + map_cell = cell_table[frame] + if map_cell >= offset_map.shape[0]: + for ss in range(image_data.shape[1]): + for fs in range(image_data.shape[2]): + output[frame, ss, fs] = <float>image_data[frame, 0, ss, fs] + continue + for ss in range(image_data.shape[1]): + for fs in range(image_data.shape[3]): + raw_data_value = image_data[frame, 0, ss, fs] + gain_stage = (raw_data_value >> 12) & 0x0003 + res = <float>(raw_data_value & 0x0fff) + if gain_stage > 2 or (flags & BPMASK and bad_pixel_map[map_cell, ss, fs, gain_stage] != 0): + res = bad_pixel_mask_value + else: + if flags & OFFSET: + res = res - offset_map[map_cell, ss, fs, gain_stage] + if flags & GAIN_AMP: + res = res * gain_amp_map[map_cell, ss, fs, gain_stage] + if flags & FF_CORR: + res = res * rel_gain_slopes_map[map_cell, ss, fs, gain_stage] + if res < 1e-7 or res > 1e7 or isnan(res) or isinf(res): + res = bad_pixel_mask_value + output[frame, ss, fs] = res diff --git a/src/calng/scenes.py b/src/calng/scenes.py index 2a6c16c0751e26071b4c5e142cf51b5af8effb7c..3849d6829f59d62b0f4b0b76dbef43ec3b17807f 100644 --- a/src/calng/scenes.py +++ b/src/calng/scenes.py @@ -9,6 +9,7 @@ from karabo.common.scenemodel.api import ( DetectorGraphModel, DisplayCommandModel, DisplayLabelModel, + DisplayListModel, DisplayStateColorModel, DisplayTextLogModel, DoubleLineEditModel, @@ -46,7 +47,13 @@ def DisplayRoundedFloat(*args, decimals=2, **kwargs): return EvaluatorModel(*args, expression=f"f'{{x:.{decimals}f}}'", **kwargs) -_type_to_display_model = {"BOOL": CheckBoxModel, "FLOAT": DisplayRoundedFloat} +_type_to_display_model = { + "BOOL": CheckBoxModel, + "FLOAT": DisplayRoundedFloat, + "STRING": DisplayLabelModel, + "UINT32": DisplayLabelModel, + "VECTOR_UINT32": DisplayListModel, +} _type_to_line_editable = { "BOOL": (CheckBoxModel, {"klass": "EditableCheckBox"}), "DOUBLE": (DoubleLineEditModel, {}), @@ -301,8 +308,13 @@ class DisplayAndEditableRow(HorizontalLayout): ) if self.include_display(key_attr): + if value_type in _type_to_display_model: + model = _type_to_display_model[value_type] + else: + model = DisplayLabelModel + print(f"Scene generator would like to know more about {value_type}") self.children.append( - _type_to_display_model.get(value_type, DisplayLabelModel)( + model( keys=[f"{device_id}.{key_path}"], width=display_width * size_scale, height=height, @@ -551,7 +563,7 @@ class ManagerDeviceStatus(VerticalLayout): @titled("Device status", width=6 * NARROW_INC) @boxed -class CorrectionDeviceStatus(VerticalLayout): +class ProcessingDeviceStatus(VerticalLayout): def __init__(self, device_id, schema_hash): super().__init__(padding=0) name = DisplayLabelModel( @@ -600,28 +612,29 @@ class CorrectionDeviceStatus(VerticalLayout): ), ] ) - self.children.append( - VerticalLayout( - children=[ - HorizontalLayout( - LampModel( - keys=[f"{device_id}.{warning_lamp}"], - width=BASE_INC, - height=BASE_INC, - ), - LabelModel( - text=warning_lamp, - width=8 * BASE_INC, - height=BASE_INC, - ), - ) - for warning_lamp in schema_hash.getAttribute( - "warningLamps", "defaultValue" - ) - ], - padding=0, + if schema_hash.has("warningLamps"): + self.children.append( + VerticalLayout( + children=[ + HorizontalLayout( + LampModel( + keys=[f"{device_id}.{warning_lamp}"], + width=BASE_INC, + height=BASE_INC, + ), + LabelModel( + text=warning_lamp, + width=8 * BASE_INC, + height=BASE_INC, + ), + ) + for warning_lamp in schema_hash.getAttribute( + "warningLamps", "defaultValue" + ) + ], + padding=0, + ) ) - ) self.children.append(status_log) @@ -1241,7 +1254,7 @@ def scene_generator(fun): def correction_device_overview(device_id, schema): schema_hash = schema_to_hash(schema) main_overview = HorizontalLayout( - CorrectionDeviceStatus(device_id, schema_hash), + ProcessingDeviceStatus(device_id, schema_hash), VerticalLayout( recursive_editable( device_id, @@ -1287,6 +1300,15 @@ def correction_device_overview(device_id, schema): ) +@scene_generator +def lpdmini_splitter_overview(device_id, schema): + schema_hash = schema_to_hash(schema) + return VerticalLayout( + ProcessingDeviceStatus(device_id, schema_hash), + DisplayRow(device_id, schema_hash, "outputDataShape", 6, 8), + ) + + @scene_generator def correction_device_preview(device_id, schema, preview_channel): schema_hash = schema_to_hash(schema) @@ -1694,6 +1716,7 @@ def gotthard2_assembler_overview(device_id, schema): ), ) + @scene_generator def quadrant_geometry_overview(device_id, schema): schema_hash = schema_to_hash(schema)