From 6e7adb66cd0c0ca34258426f8b159e6635942ebd Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Wed, 28 Apr 2021 22:29:56 +0100 Subject: [PATCH 1/3] Fix handling of missing CSM matrix The code checks for the camera-stage mapper matrix by looking at its truth value. However, this is ambiguous because the matrix has many elements. That means that if it's missing, we get the expected result, but if it's present we get an error. I have avoided dict.get() and instead look it up with [] notation. This will raise a KeyError if it's missing, which I handle helpfully. If the key is present but the value is not castable to an array, we'll get an exception anyway. --- .../api/default_extensions/camera_stage_mapping.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/camera_stage_mapping.py b/openflexure_microscope/api/default_extensions/camera_stage_mapping.py index ca479e97..6630d8a3 100644 --- a/openflexure_microscope/api/default_extensions/camera_stage_mapping.py +++ b/openflexure_microscope/api/default_extensions/camera_stage_mapping.py @@ -194,10 +194,11 @@ class CSMExtension(BaseExtension): @property def image_to_stage_displacement_matrix(self) -> np.ndarray: # 2x2 integer array """A 2x2 matrix that converts displacement in image coordinates to stage coordinates.""" - displacement_matrix = self.get_settings().get("image_to_stage_displacement") - if not displacement_matrix: + settings = self.get_settings() + try: + return np.array(settings["image_to_stage_displacement"]) + except KeyError: raise ValueError("The microscope has not yet been calibrated.") - return np.array(displacement_matrix) def move_in_image_coordinates(self, displacement_in_pixels: XYCoordinateType): """Move by a given number of pixels on the camera""" From 14c4908a9827a77eba371211dd299006990f1d9d Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 4 May 2021 13:41:39 +0100 Subject: [PATCH 2/3] fix linter error --- .../api/default_extensions/camera_stage_mapping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/camera_stage_mapping.py b/openflexure_microscope/api/default_extensions/camera_stage_mapping.py index 6630d8a3..13716359 100644 --- a/openflexure_microscope/api/default_extensions/camera_stage_mapping.py +++ b/openflexure_microscope/api/default_extensions/camera_stage_mapping.py @@ -197,8 +197,8 @@ class CSMExtension(BaseExtension): settings = self.get_settings() try: return np.array(settings["image_to_stage_displacement"]) - except KeyError: - raise ValueError("The microscope has not yet been calibrated.") + except KeyError as exc: + raise ValueError("The microscope has not yet been calibrated.") from exc def move_in_image_coordinates(self, displacement_in_pixels: XYCoordinateType): """Move by a given number of pixels on the camera""" From 0c6e6caade9e5b097db89364c84d891619ca3eac Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 May 2021 10:05:47 +0100 Subject: [PATCH 3/3] Improve errors with better exception I've swapped the ValueError for a RuntimeError subclass to make it clearer what's gone wrong. --- .../camera_stage_mapping.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openflexure_microscope/api/default_extensions/camera_stage_mapping.py b/openflexure_microscope/api/default_extensions/camera_stage_mapping.py index 13716359..032660ba 100644 --- a/openflexure_microscope/api/default_extensions/camera_stage_mapping.py +++ b/openflexure_microscope/api/default_extensions/camera_stage_mapping.py @@ -83,6 +83,15 @@ class LoggingMoveWrapper: self._history: List[Tuple[float, Optional[CoordinateType]]] = [] +class CSMUncalibratedError(RuntimeError): + """A calibrated camera stage mapper is required, but this one is not calibrated. + + The camera stage mapper requires calibration information to relate image pixels + to stage coordinates. If a method attempts to retrieve this calibration before + it exists, we raise this exception. + """ + + class CSMExtension(BaseExtension): """ Use the camera as an encoder, so we can relate camera and stage coordinates @@ -133,7 +142,12 @@ class CSMExtension(BaseExtension): def get_settings(self) -> Dict[str, Any]: """Retrieve the settings for this extension""" keys: List[str] = ["extensions", self.name] - return get_by_path(self.microscope.read_settings(), keys) + try: + return get_by_path(self.microscope.read_settings(), keys) + except KeyError as exc: + raise CSMUncalibratedError( + "Camera stage mapping calibration data is missing" + ) from exc def camera_stage_functions(self) -> Tuple[Callable, Callable, Callable, Callable]: """Return functions that allow us to interface with the microscope""" @@ -198,7 +212,9 @@ class CSMExtension(BaseExtension): try: return np.array(settings["image_to_stage_displacement"]) except KeyError as exc: - raise ValueError("The microscope has not yet been calibrated.") from exc + raise CSMUncalibratedError( + "The microscope has not yet been calibrated." + ) from exc def move_in_image_coordinates(self, displacement_in_pixels: XYCoordinateType): """Move by a given number of pixels on the camera"""