From c0a8d1562899a835472222805afd370aee4e3893 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 22 Oct 2025 14:12:42 +0100 Subject: [PATCH 01/16] Add calibration_required property to Camera and CSM things. --- .../things/camera/__init__.py | 9 +++++++++ .../things/camera/picamera.py | 5 +++++ .../things/camera_stage_mapping.py | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index bca3c63f..fc11ca1f 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -198,6 +198,15 @@ class BaseCamera(lt.Thing): """Close hardware connection when the Thing context manager is closed.""" raise NotImplementedError("CameraThings must define their own __exit__ method") + @lt.thing_property + def calibration_required(self) -> bool: + """Whether the camera needs calibrating. + + This always returns False in BaseCamera. It should be reimplemented by child + classes if calibration is required. + """ + return False + @lt.thing_action def start_streaming( self, main_resolution: tuple[int, int], buffer_count: int diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index 2afc1d7b..68f5c281 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -213,6 +213,11 @@ class StreamingPiCamera2(BaseCamera): finally: self._setting_save_in_progress = False + @lt.thing_property + def calibration_required(self) -> bool: + """Whether the camera needs calibrating.""" + return not self.lens_shading_is_static + ## Persistent controls! These are settings _analogue_gain: float = 1.0 diff --git a/src/openflexure_microscope_server/things/camera_stage_mapping.py b/src/openflexure_microscope_server/things/camera_stage_mapping.py index a05a3385..57c9a596 100644 --- a/src/openflexure_microscope_server/things/camera_stage_mapping.py +++ b/src/openflexure_microscope_server/things/camera_stage_mapping.py @@ -242,6 +242,11 @@ class CameraStageMapper(lt.Thing): return None return self.last_calibration["image_resolution"] + @lt.thing_property + def calibration_required(self) -> bool: + """Whether the camera stage mapper needs calibrating.""" + return self.image_to_stage_displacement_matrix is None + def assert_calibrated(self) -> None: """Raise an exception if the image_to_stage_displacement matrix is not set.""" if self.image_to_stage_displacement_matrix is None: From 160c49749442de1de3b7565dd09112d30c34646c Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 22 Oct 2025 14:47:14 +0100 Subject: [PATCH 02/16] Rename calibrationModal to calibrationWizard to avoid confusion with the internal modal element --- webapp/src/components/appContent.vue | 12 ++++++------ ...brationModal.vue => calibrationWizard.vue} | 19 +++++++++++-------- .../tabContentComponents/settingsContent.vue | 12 ++++++------ 3 files changed, 23 insertions(+), 20 deletions(-) rename webapp/src/components/modalComponents/{calibrationModal.vue => calibrationWizard.vue} (96%) diff --git a/webapp/src/components/appContent.vue b/webapp/src/components/appContent.vue index a466ab61..31d7b50d 100644 --- a/webapp/src/components/appContent.vue +++ b/webapp/src/components/appContent.vue @@ -5,10 +5,10 @@ uk-grid > - + >
\ No newline at end of file From 00f0fe664c931f3b5c7bc78b74e9bb3cd7677a8e Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 22 Oct 2025 18:18:19 +0100 Subject: [PATCH 04/16] Refactored Calibration Wizard: Add way to move forward and backward through tasks and sub-steps in tasks --- .../modalComponents/calibrationWizard.vue | 139 ++++++++---------- .../calibrationWizardContents.vue | 25 +++- .../calibrationWizardTask.vue | 82 +++++++++++ 3 files changed, 164 insertions(+), 82 deletions(-) create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardTask.vue diff --git a/webapp/src/components/modalComponents/calibrationWizard.vue b/webapp/src/components/modalComponents/calibrationWizard.vue index afd5a0db..f57edccc 100644 --- a/webapp/src/components/modalComponents/calibrationWizard.vue +++ b/webapp/src/components/modalComponents/calibrationWizard.vue @@ -2,46 +2,25 @@ - - diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue index c7650ec5..bc6f28d3 100644 --- a/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue @@ -164,4 +164,27 @@ export default { ActionButton }, } - \ No newline at end of file + + diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardTask.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardTask.vue new file mode 100644 index 00000000..c31ae71b --- /dev/null +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardTask.vue @@ -0,0 +1,82 @@ + + + From 8f1253ec02abaf9804109e894e986516581293ae Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 22 Oct 2025 21:49:12 +0100 Subject: [PATCH 05/16] Reinstate welcome pane in refactored wizard. --- .../modalComponents/calibrationWizard.vue | 11 +++-- .../calibrationWizardTask.vue | 37 ++++++++++++--- .../singleStepTask.vue | 47 +++++++++++++++++++ .../welcomeStep.vue | 23 +++++++++ .../tabContentComponents/settingsContent.vue | 1 - 5 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/singleStepTask.vue create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/welcomeStep.vue diff --git a/webapp/src/components/modalComponents/calibrationWizard.vue b/webapp/src/components/modalComponents/calibrationWizard.vue index f57edccc..92f121f8 100644 --- a/webapp/src/components/modalComponents/calibrationWizard.vue +++ b/webapp/src/components/modalComponents/calibrationWizard.vue @@ -5,9 +5,9 @@ diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/welcomeStep.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/welcomeStep.vue new file mode 100644 index 00000000..9308be80 --- /dev/null +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/welcomeStep.vue @@ -0,0 +1,23 @@ + + + diff --git a/webapp/src/components/tabContentComponents/settingsContent.vue b/webapp/src/components/tabContentComponents/settingsContent.vue index d6654ff0..2a6311c7 100644 --- a/webapp/src/components/tabContentComponents/settingsContent.vue +++ b/webapp/src/components/tabContentComponents/settingsContent.vue @@ -3,7 +3,6 @@
    From 0ebc507eff20ac05f5fd61bf768b0b5cf451ffd7 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 22 Oct 2025 22:45:19 +0100 Subject: [PATCH 06/16] Add camera calibration steps into refactored calibration wizard. --- .../things/camera/simulation.py | 30 ++++++++++++++ .../modalComponents/calibrationWizard.vue | 7 +++- .../calibrationWizardContents.vue | 24 +---------- .../calibrationWizardTask.vue | 9 ++-- .../camCalibrationExplanation.vue | 20 +++++++++ .../cameraMainCalibrationStep.vue | 34 +++++++++++++++ .../cameraCalibrationTask.vue | 41 +++++++++++++++++++ .../calibrationWizardComponents/finalStep.vue | 17 ++++++++ .../singleStepTask.vue | 9 ++-- .../stepTemplateWithStream.vue | 27 ++++++++++++ 10 files changed, 186 insertions(+), 32 deletions(-) create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/camCalibrationExplanation.vue create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationTask.vue create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/finalStep.vue create mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/stepTemplateWithStream.vue diff --git a/src/openflexure_microscope_server/things/camera/simulation.py b/src/openflexure_microscope_server/things/camera/simulation.py index 7c32aa08..b93b9c78 100644 --- a/src/openflexure_microscope_server/things/camera/simulation.py +++ b/src/openflexure_microscope_server/things/camera/simulation.py @@ -86,6 +86,11 @@ class SimulatedCamera(BaseCamera): self.generate_blobs() self.generate_canvas() + @lt.thing_property + def calibration_required(self) -> bool: + """Whether the camera needs calibrating.""" + return not self.background_detector_status.ready + def validate_inputs(self) -> None: """Validate the inputs passed to the simulation, and raises an error if invalid. @@ -367,6 +372,22 @@ class SimulatedCamera(BaseCamera): LOGGER.warning(f"Simulation camera camera doesn't respect {stream_name=}") return Image.fromarray(self.generate_frame()) + @lt.thing_action + def full_auto_calibrate(self, portal: lt.deps.BlockingPortal) -> None: + """Perform a full auto-calibration. + + For the simulation microscope the process is: + + * ``remove_sample`` + * ``set_background`` + * ``load_sample`` + """ + self.remove_sample() + time.sleep(0.2) + self.set_background(portal) + time.sleep(0.2) + self.load_sample() + @lt.thing_action def remove_sample(self) -> None: """Show the simulated background with no sample.""" @@ -381,6 +402,15 @@ class SimulatedCamera(BaseCamera): raise RuntimeError("Sample is already in place.") self._show_sample = True + @lt.thing_property + def primary_calibration_actions(self) -> list[ActionButton]: + """The calibration actions for both calibration wizard and settings panel.""" + return [ + action_button_for( + self.full_auto_calibrate, submit_label="Full Auto Calibrate" + ), + ] + @lt.thing_property def secondary_calibration_actions(self) -> list[ActionButton]: """The calibration actions that appear only in settings panel.""" diff --git a/webapp/src/components/modalComponents/calibrationWizard.vue b/webapp/src/components/modalComponents/calibrationWizard.vue index 92f121f8..446e3b03 100644 --- a/webapp/src/components/modalComponents/calibrationWizard.vue +++ b/webapp/src/components/modalComponents/calibrationWizard.vue @@ -22,6 +22,8 @@ diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue new file mode 100644 index 00000000..7089adeb --- /dev/null +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue @@ -0,0 +1,34 @@ + + + diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationTask.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationTask.vue new file mode 100644 index 00000000..be5d4e83 --- /dev/null +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationTask.vue @@ -0,0 +1,41 @@ + + + diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/finalStep.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/finalStep.vue new file mode 100644 index 00000000..edf59bc4 --- /dev/null +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/finalStep.vue @@ -0,0 +1,17 @@ + + + diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/singleStepTask.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/singleStepTask.vue index 908772c7..c7da2345 100644 --- a/webapp/src/components/modalComponents/calibrationWizardComponents/singleStepTask.vue +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/singleStepTask.vue @@ -1,5 +1,6 @@ From 958b28aa9471440052f49e6a47e42035fb511837 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 23 Oct 2025 00:06:01 +0100 Subject: [PATCH 08/16] Dynamically create lists of tasks for Calibration Wizard --- .../modalComponents/calibrationWizard.vue | 78 +++++--- .../calibrationWizardContents.vue | 166 ------------------ .../csmSteps/csmExplanation.vue | 1 - 3 files changed, 51 insertions(+), 194 deletions(-) delete mode 100644 webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue diff --git a/webapp/src/components/modalComponents/calibrationWizard.vue b/webapp/src/components/modalComponents/calibrationWizard.vue index 4625c4fa..6a4763fc 100644 --- a/webapp/src/components/modalComponents/calibrationWizard.vue +++ b/webapp/src/components/modalComponents/calibrationWizard.vue @@ -34,7 +34,7 @@ export default { data: function() { return { isNeeded: undefined, - calibrateableThings: [], + availableCalibrationTasks: {}, tasks: [], taskIndex: 0, movingBackward: false @@ -56,46 +56,42 @@ export default { mounted() { this.$refs["calibrationModalEl"].addEventListener("hidden", this.onHide); // Check which Things are available on mount. - const thingsToCheck = [ - "camera", - "camera_stage_mapping" - ]; - this.calibrateableThings = thingsToCheck.filter(name => this.thingAvailable(name)); - this.tasks = [ - {component: singleStepTask, props: {stepComponent: welcomeStep}}, - {component: cameraCalibrationTask}, - {component: cameraStageMappingTask}, - {component: singleStepTask, props: {stepComponent: finalStep}}, - ] + const allCalibrationTasks = { + camera: cameraCalibrationTask, + camera_stage_mapping: cameraStageMappingTask + }; + this.availableCalibrationTasks = Object.fromEntries( + Object.entries(allCalibrationTasks) + .filter(([thing]) => this.thingAvailable(thing)) + ); + }, methods: { /** - * Check all calibratable Things to see if any require calibration. + * Check all calibratable Things to see which require calibration. * - * Iterates over `this.calibrateableThings` (set during mounted()) and reads the + * Iterates over `this.availableCalibrationTasks` (set during mounted()) and reads the * `calibration_required` property. * - * Returns `true` if any Thing reports that calibration is required. - * + * Returns a list of thing names that report calibration is required. */ - async check_if_needed() { - let wizardNeeded = false; - let thingNeedsCal = false; + async check_things_needing_calibration() { + const needsCalibration = []; + const calibrateableThings = Object.keys(this.availableCalibrationTasks); - for (const name of this.calibrateableThings) { + for (const name of calibrateableThings) { try { - thingNeedsCal = await this.readThingProperty(name, "calibration_required"); + const thingNeedsCal = await this.readThingProperty(name, "calibration_required"); + if (thingNeedsCal) { + needsCalibration.push(name); + } } catch (e) { console.error(`${name}: missing calibration_required property`, e); - thingNeedsCal = false; } - - // OR it into the function-level flag - wizardNeeded = wizardNeeded || thingNeedsCal; } - return wizardNeeded; + return needsCalibration; }, resetData: function() { @@ -103,13 +99,38 @@ export default { this.taskIndex = 0; }, + /** + * Create the calibration wizard task list dynamically. + */ + create_task_list: function(thingsToCal, includeWelcome=true) { + const tasks = []; + + // Optionally include the welcome screen + if (includeWelcome) { + tasks.push({ component: singleStepTask, props: { stepComponent: welcomeStep } }); + } + + // Add calibration task for each thing + for (const thing of thingsToCal) { + const taskComponent = this.availableCalibrationTasks[thing]; + tasks.push({ component: taskComponent }); + } + + // Always include the final step + tasks.push({ component: singleStepTask, props: { stepComponent: finalStep } }); + + this.tasks = tasks; + }, + show_if_needed: async function() { // Check if the calibration modal is needed, and only show it if it is. - let needed = await this.check_if_needed() + let thingsToCal = await this. check_things_needing_calibration() + const needed = thingsToCal.length > 0; // Check if this calibration wizard can actually do anything useful if (needed) { this.resetData(); + this.create_task_list(thingsToCal); this.show(); } else { // If not needed, we just return the onClose event immediately @@ -119,7 +140,10 @@ export default { // Forces modal to show on button press force_show: function() { + const allThings = Object.keys(this.availableCalibrationTasks); + this.resetData(); + this.create_task_list(allThings, false); this.show(); }, diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue deleted file mode 100644 index 81c43230..00000000 --- a/webapp/src/components/modalComponents/calibrationWizardComponents/calibrationWizardContents.vue +++ /dev/null @@ -1,166 +0,0 @@ - -
    - -
    - -
    -

    Lens-shading

    -
    -

    - Your lens-shading table has already been calibrated. Click Next - to move on. -

    -
    -
    - - - - -

    Once you're ready, click auto-calibrate.

    - - -
    -
    - -
    -

    Adjust z height

    -

    - Insert a sample and adjust the z position of - the stage using the buttons below, until the - sample is in focus. -

    -

    - You may also adjust the z position with page up and page down. -

    - - -
    - - -
    -
    - -
    -

    Camera-stage mapping

    -
    -

    - Your camera-stage mapping has already been calibrated. Click Next - to move on. -

    -
    -
    -

    - No stage connected. Please skip this step, or connect a valid - stage, then reboot your microscope. -

    -
    -
    -

    - Follow the important steps below before starting camera-stage - mapping calibration! -

    -
      -
    • Insert a clearly visible sample to the microscope
    • -
    • - Ensure the sample is reasonably well centered on the microscope - camera -
    • -
    - - - -

    Once you're ready, click auto-calibrate.

    - - -
    -
    - -
    -

    - Calibration complete -

    -

    - Click Finish to return to your microscope, or Restart to re-run the - calibration routine -

    -
    - - - diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/csmExplanation.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/csmExplanation.vue index b2e62cde..0ed4bfcc 100644 --- a/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/csmExplanation.vue +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/csmExplanation.vue @@ -16,7 +16,6 @@ diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/camCalibrationExplanation.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/camCalibrationExplanation.vue index 493bd281..8cadb948 100644 --- a/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/camCalibrationExplanation.vue +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/camCalibrationExplanation.vue @@ -13,8 +13,7 @@ diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue index de748977..07e1ffb4 100644 --- a/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/cameraCalibrationSteps/cameraMainCalibrationStep.vue @@ -16,7 +16,7 @@ \ No newline at end of file + diff --git a/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/runCsmStep.vue b/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/runCsmStep.vue index 4c37dfa8..d2aa0adf 100644 --- a/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/runCsmStep.vue +++ b/webapp/src/components/modalComponents/calibrationWizardComponents/csmSteps/runCsmStep.vue @@ -12,7 +12,7 @@