From 319a0a116d95bf9fe1430458e007b78ff5e7de78 Mon Sep 17 00:00:00 2001 From: David Hammer <dhammer@mailbox.org> Date: Mon, 11 Apr 2022 13:46:06 +0200 Subject: [PATCH] Enable overriding constant using specific file --- src/calng/base_calcat.py | 78 +++++++++++++++++------- src/calng/base_correction.py | 29 +++++++-- src/calng/scenes.py | 112 ++++++++++++++++++++++++++++++++++- 3 files changed, 190 insertions(+), 29 deletions(-) diff --git a/src/calng/base_calcat.py b/src/calng/base_calcat.py index 5d126cec..0061c92c 100644 --- a/src/calng/base_calcat.py +++ b/src/calng/base_calcat.py @@ -160,22 +160,30 @@ def add_status_schema_from_enum(schema, prefix, enum_class): STRING_ELEMENT(schema) .key(f"{constant_node}.dataFilePath") - .displayedName("[Debug] Data file path") + .displayedName("Data file path") .description( - "Where is the actual file for the currently loaded constant located?" + "Where is the actual file for the constant located?" ) - .readOnly() - .initialValue("") + .assignmentOptional() + .defaultValue("") + .reconfigurable() .commit(), STRING_ELEMENT(schema) .key(f"{constant_node}.dataSetName") - .displayedName("[Debug] Data set name") + .displayedName("Data set name") .description( "Within the actual data file, where is the constant data located?" ) - .readOnly() - .initialValue("") + .assignmentOptional() + .defaultValue("") + .reconfigurable() + .commit(), + + SLOT_ELEMENT(schema) + .key(f"{constant_node}.overrideConstantFromFile") + .displayedName("Override from file") + .description("Constant from manually specified .h5 file and data set name") .commit(), ) @@ -559,7 +567,20 @@ class BaseCalcatFriend: return constant_data - def get_specific_constant_version(self, constant): + def get_constant_version_and_call_me_back(self, constant, callback): + """Runs get_constant_version in thread, will call callback on completion""" + # TODO: do we want to use asyncio / "modern" async? + # TODO: consider moving out of this class, closer to correction device + def aux(): + with self.api_lock: + data = self.get_constant_version(constant) + callback(constant, data) + + thread = threading.Thread(target=aux) + thread.start() + return thread + + def get_overridden_constant_version(self, constant): # TODO: warn if PDU or constant type does not match # TODO: warn if result is list (happens for empty version ID) constant_version_id = self.device.get( @@ -577,34 +598,45 @@ class BaseCalcatFriend: self.cached_constants[constant] = constant_data self._set_status(constant, "beginValidityAt", resp["data"]["begin_at"]) self._set_status(constant, "calibrationId", "manual override") - self._set_status(constant, "conditionId", "manual override") - self._set_status(constant, "constantId", "manual override") + self._set_status(constant, "usedConditionId", "manual override") + self._set_status(constant, "usedConstantId", "manual override") self._set_status(constant, "constantVersionId", constant_version_id) self._set_status(constant, "found", True) return constant_data - def get_constant_version_and_call_me_back(self, constant, callback): - """Runs get_constant_version in thread, will call callback on completion""" - # TODO: do we want to use asyncio / "modern" async? - # TODO: consider moving out of this class, closer to correction device + def get_overridden_constant_version_and_call_me_back(self, constant, callback): + """Blindly load whatever CalCat points to for CCV - user must be confident that + this CCV corresponds to correct kind of constant.""" + + # TODO: warn user about all the things that go wrong def aux(): with self.api_lock: - data = self.get_constant_version(constant) + data = self.get_overriden_constant_version(constant) callback(constant, data) thread = threading.Thread(target=aux) thread.start() return thread - def get_specific_constant_version_and_call_me_back(self, constant, callback): - """Blindly load whatever CalCat points to for CCV - user must be confident that - this CCV corresponds to correct kind of constant.""" - - # TODO: warn user about all the things that go wrong + def get_overridden_constant_from_file_and_call_me_back(self, constant, callback): def aux(): - with self.api_lock: - data = self.get_specific_constant_version(constant) - callback(constant, data) + file_path = self.device.get( + f"{self.status_prefix}.{constant.name}.dataFilePath" + ) + data_set_name = self.device.get( + f"{self.status_prefix}.{constant.name}.dataSetName" + ) + with h5py.File(file_path, "r") as fd: + constant_data = np.array(fd[data_set_name]["data"]) + with self.cached_constants_lock: + self.cached_constants[constant] = constant_data + self._set_status(constant, "beginValidityAt", "manual override") + self._set_status(constant, "calibrationId", "manual override") + self._set_status(constant, "usedConditionId", "manual override") + self._set_status(constant, "usedConstantId", "manual override") + self._set_status(constant, "constantVersionId", "manual override") + self._set_status(constant, "found", True) + callback(constant, constant_data) thread = threading.Thread(target=aux) thread.start() diff --git a/src/calng/base_correction.py b/src/calng/base_correction.py index e498d34a..ddba4484 100644 --- a/src/calng/base_correction.py +++ b/src/calng/base_correction.py @@ -330,7 +330,7 @@ class BaseCorrection(PythonDevice): .key("availableScenes") .setSpecialDisplayType(DT_SCENES) .readOnly() - .initialValue(["overview"]) + .initialValue(["overview", "constant_overrides"]) .commit(), ) @@ -614,9 +614,17 @@ class BaseCorrection(PythonDevice): # register slots # TODO: the CalCatFriend could add these for us # note: overly complicated for closure to work - def make_wrapper_capturing_constant(constant): + def make_wrapper_1(constant): def aux(): - self.calcat_friend.get_specific_constant_version_and_call_me_back( + self.calcat_friend.get_overridden_constant_version_and_call_me_back( + constant, self._load_constant_to_runner + ) + + return aux + + def make_wrapper_2(constant): + def aux(): + self.calcat_friend.get_overridden_constant_from_file_and_call_me_back( constant, self._load_constant_to_runner ) @@ -626,7 +634,13 @@ class BaseCorrection(PythonDevice): slot_name = f"foundConstants.{constant.name}.overrideConstantVersion" meth_name = slot_name.replace(".", "_") self.KARABO_SLOT( - make_wrapper_capturing_constant(constant), + make_wrapper_1(constant), + slotName=meth_name, + ) + slot_name = f"foundConstants.{constant.name}.overrideConstantFromFile" + meth_name = slot_name.replace(".", "_") + self.KARABO_SLOT( + make_wrapper_2(constant), slotName=meth_name, ) @@ -725,7 +739,7 @@ class BaseCorrection(PythonDevice): self._update_correction_flags() def _lock_and_update_in_background(self, method): - # TODO: securely handle errors (postReconfigure may succeed and device state not) + # TODO: securely handle errors (postReconfigure may succeed, device state not) def runner(): with self._buffer_lock, utils.StateContext(self, State.CHANGING): method() @@ -769,6 +783,11 @@ class BaseCorrection(PythonDevice): device_id=self.getInstanceId(), schema=self.getFullSchema(), ) + elif name == "constant_overrides": + payload["data"] = scenes.correction_device_constant_overrides( + device_id=self.getInstanceId(), + schema=self.getFullSchema(), + ) elif name.startswith("browse_schema"): if ":" in name: prefix = name[len("browse_schema:"):] diff --git a/src/calng/scenes.py b/src/calng/scenes.py index 58168b48..ed9dd89e 100644 --- a/src/calng/scenes.py +++ b/src/calng/scenes.py @@ -331,6 +331,16 @@ class FoundConstantsColumn(VerticalLayout): padding=0, ) self.children.append(constant_row) + self.children.append( + DeviceSceneLinkModel( + text="Overrides", + keys=[f"{device_id}.availableScenes"], + target="constant_overrides", + target_window=SceneTargetWindow.Dialog, + width=8 * BASE_INC, + height=BASE_INC, + ) + ) class ConstantLoadedAmpeln(HorizontalLayout): @@ -340,8 +350,8 @@ class ConstantLoadedAmpeln(HorizontalLayout): [ ColorBoolModel( keys=[f"{device_id}.{prefix}.{key}.found"], - height=BASE_INC, width=BASE_INC, + height=BASE_INC, ) for key in schema_hash.get(prefix).getKeys() ] @@ -866,6 +876,106 @@ def correction_device_overview(device_id, schema): ) +@scene_generator +def correction_device_constant_overrides(device_id, schema, prefix="foundConstants"): + schema_hash = schema_to_hash(schema) + + return HorizontalLayout( + children=[ + VerticalLayout( + VerticalLayout( + LabelModel(text="constant", width=6 * BASE_INC, height=BASE_INC), + LabelModel(text="timestamp", width=6 * BASE_INC, height=BASE_INC), + padding=0, + ), + VerticalLayout( + LabelModel( + text="constant version ID", + width=6 * BASE_INC, + height=BASE_INC, + ), + Space(width=6 * BASE_INC, height=BASE_INC), + padding=0, + ), + VerticalLayout( + LabelModel( + text="data file name", width=6 * BASE_INC, height=BASE_INC + ), + LabelModel( + text="dataset name", width=6 * BASE_INC, height=BASE_INC + ), + Space(width=6 * BASE_INC, height=BASE_INC), + padding=0, + ), + ) + ] + + [ + VerticalLayout( + VerticalLayout( + HorizontalLayout( + ColorBoolModel( + keys=[f"{device_id}.{prefix}.{constant}.found"], + width=BASE_INC, + height=BASE_INC, + ), + LabelModel( + text=constant, + width=7 * BASE_INC, + height=BASE_INC, + ), + ), + DisplayLabelModel( + keys=[f"{device_id}.{prefix}.{constant}.beginValidityAt"], + width=8 * BASE_INC, + height=BASE_INC, + font_size=9, + ), + padding=0, + ), + VerticalLayout( + LineEditModel( + klass="EditableLineEdit", + keys=[f"{device_id}.{prefix}.{constant}.constantVersionId"], + width=8 * BASE_INC, + height=BASE_INC, + ), + DisplayCommandModel( + keys=[ + f"{device_id}.{prefix}.{constant}.overrideConstantVersion" + ], + width=8 * BASE_INC, + height=BASE_INC, + ), + padding=0, + ), + VerticalLayout( + LineEditModel( + klass="EditableLineEdit", + keys=[f"{device_id}.{prefix}.{constant}.dataFilePath"], + width=8 * BASE_INC, + height=BASE_INC, + ), + LineEditModel( + klass="EditableLineEdit", + keys=[f"{device_id}.{prefix}.{constant}.dataSetName"], + width=8 * BASE_INC, + height=BASE_INC, + ), + DisplayCommandModel( + keys=[ + f"{device_id}.{prefix}.{constant}.overrideConstantFromFile" + ], + width=8 * BASE_INC, + height=BASE_INC, + ), + padding=0, + ), + ) + for constant in schema_hash.get(prefix).getKeys() + ] + ) + + @scene_generator def manager_device_overview( manager_device_id, -- GitLab