Merge branch 'calibration-wizard-refactor' into 'v3'
Refactor Calibration Wizard Closes #578 See merge request openflexure/openflexure-microscope-server!415
This commit is contained in:
commit
418a6b5364
25 changed files with 883 additions and 371 deletions
|
|
@ -75,6 +75,8 @@ def test_get_tuning_algo(picamera_thing):
|
||||||
|
|
||||||
def test_calibration(picamera_thing, client):
|
def test_calibration(picamera_thing, client):
|
||||||
"""Check that full auto calibrate completes and set the expected values."""
|
"""Check that full auto calibrate completes and set the expected values."""
|
||||||
|
# Check the calibration_required property used by the calibration wizard
|
||||||
|
assert picamera_thing.calibration_required
|
||||||
# Save copy of default tuning file for end of test
|
# Save copy of default tuning file for end of test
|
||||||
original_default = deepcopy(picamera_thing.default_tuning)
|
original_default = deepcopy(picamera_thing.default_tuning)
|
||||||
# Tuning should start the same as the server is loading with no settings.
|
# Tuning should start the same as the server is loading with no settings.
|
||||||
|
|
@ -88,7 +90,9 @@ def test_calibration(picamera_thing, client):
|
||||||
# Run full auto calibrate
|
# Run full auto calibrate
|
||||||
client.full_auto_calibrate()
|
client.full_auto_calibrate()
|
||||||
|
|
||||||
# After calibration the tuning files should be different
|
# After calibration it should report that calibration is no longer required
|
||||||
|
assert not picamera_thing.calibration_required
|
||||||
|
# The tuning files should be different
|
||||||
assert picamera_thing.default_tuning != picamera_thing.tuning
|
assert picamera_thing.default_tuning != picamera_thing.tuning
|
||||||
# The default should be unchanged
|
# The default should be unchanged
|
||||||
assert picamera_thing.default_tuning == original_default
|
assert picamera_thing.default_tuning == original_default
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -37,15 +37,11 @@ For a step-by-step demonstration, watch the [full video walkthrough on running t
|
||||||
|
|
||||||
## Running a simulated scan
|
## Running a simulated scan
|
||||||
|
|
||||||
To run a simulated scan, you first need to set a background image:
|
To run a simulated scan, you must first complete the calibration wizard.
|
||||||
|
|
||||||
1. Click on the settings tab on the left
|
1. Go to the **Slide Scan** tab
|
||||||
2. Navigate to the **Camera** settings under **Microscope Settings**
|
2. Click the **Start Smart Scan** Button.
|
||||||
3. Click `Remove Sample`. This removes the simulated sample from the camera stream
|
3. This will scan until the whole sample has been imaged. You can cancel the scan at any time, with the **Cancel** button.
|
||||||
4. Click on the **Background Detect** tab on the left and click `Set Background`
|
|
||||||
5. Repeat the steps 1 and 2 to return to the camera settings and click `Add Sample`
|
|
||||||
|
|
||||||
You are now ready to start a simulated scan in the Slide Scan tab.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -53,27 +49,31 @@ You are now ready to start a simulated scan in the Slide Scan tab.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Autocalibration
|
### Auto-calibration
|
||||||
|
|
||||||
* Your simulated microscope should prompt you to complete the auto-calibration wizard upon your first use of the simulated server.
|
* Your simulated microscope should prompt you to complete the auto-calibration wizard upon your first use of the simulated server.
|
||||||
* If the stage moves in the wrong direction in the **View** or **Navigate** tabs, you can re-run **Auto-calibration** from the UI to correct it.
|
* You can press the **Escape** key at any point to exit the wizard, but this will leave your simulated microscope in an uncalibrated state.
|
||||||
* To auto-calibrate your microscope, go to: **Settings → Camera to Stage Mapping → Auto-Calibrate Using Camera**.
|
* If your microscope is not calibrated, the wizard will launch each time you load the UI, prompting you to complete remaining calibrations.
|
||||||
|
* You can always launch the full calibration wizard by going to: **Settings → Launch Calibration Wizard**.
|
||||||
|
|
||||||
|
The calibration steps are:
|
||||||
|
|
||||||
|
1. The Camera Calibration will set the background image needed for background detect.
|
||||||
|
* You can rerun this calibration by going to: **Settings → Camera → Full Auto-Calibrate**.
|
||||||
|
2. The Camera-Stage Mapping calibration is used to find the relationship between stage and image coordinates.
|
||||||
|
* If the stage moves in the wrong direction when double clicking on the camera feed in the **View** or **Navigate** tabs, you may need to re-run Camera-Stage Mapping.
|
||||||
|
* To re-run Camera-Stage Mapping, go to: **Settings → Camera to Stage Mapping → Auto-Calibrate Using Camera**.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Sample Coverage
|
### Background Detect
|
||||||
|
|
||||||
* The default **sample coverage** of 25% is often too high for simulations. A figure closer to 10% performs better.
|
|
||||||
* You can adjust this in the **Background Detect tab** under:
|
|
||||||
**Configure → Sample Coverage**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Background and Samples
|
|
||||||
|
|
||||||
* You can **load a sample** to simulate imaging conditions, using the **Load Sample** options in the **Settings tab**.
|
* You can **load a sample** to simulate imaging conditions, using the **Load Sample** options in the **Settings tab**.
|
||||||
* To remove a sample and reset the view, use the **Remove Sample** option in the **Settings tab**.
|
* To remove a sample and reset the view, use the **Remove Sample** option in the **Settings tab**.
|
||||||
* You can load and remove simulated samples under: **Settings → Camera → Load/Remove Sample**
|
* You can load and remove simulated samples under: **Settings → Camera → Load/Remove Sample**.
|
||||||
|
* The Full Auto-Calibrate option in camera settings can be used to **Remove the sample → Run background detection → Re-load the sample**.
|
||||||
|
* You can adjust the sample coverage needed for the image not to be detected as background in the **Background Detect tab** under: **Configure → Sample Coverage**. The default value is 25%.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,15 @@ class BaseCamera(lt.Thing):
|
||||||
"""Close hardware connection when the Thing context manager is closed."""
|
"""Close hardware connection when the Thing context manager is closed."""
|
||||||
raise NotImplementedError("CameraThings must define their own __exit__ method")
|
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
|
@lt.thing_action
|
||||||
def start_streaming(
|
def start_streaming(
|
||||||
self, main_resolution: tuple[int, int], buffer_count: int
|
self, main_resolution: tuple[int, int], buffer_count: int
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,11 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
finally:
|
finally:
|
||||||
self._setting_save_in_progress = False
|
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
|
## Persistent controls! These are settings
|
||||||
|
|
||||||
_analogue_gain: float = 1.0
|
_analogue_gain: float = 1.0
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,11 @@ class SimulatedCamera(BaseCamera):
|
||||||
self.generate_blobs()
|
self.generate_blobs()
|
||||||
self.generate_canvas()
|
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:
|
def validate_inputs(self) -> None:
|
||||||
"""Validate the inputs passed to the simulation, and raises an error if invalid.
|
"""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=}")
|
LOGGER.warning(f"Simulation camera camera doesn't respect {stream_name=}")
|
||||||
return Image.fromarray(self.generate_frame())
|
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
|
@lt.thing_action
|
||||||
def remove_sample(self) -> None:
|
def remove_sample(self) -> None:
|
||||||
"""Show the simulated background with no sample."""
|
"""Show the simulated background with no sample."""
|
||||||
|
|
@ -381,6 +402,15 @@ class SimulatedCamera(BaseCamera):
|
||||||
raise RuntimeError("Sample is already in place.")
|
raise RuntimeError("Sample is already in place.")
|
||||||
self._show_sample = True
|
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
|
@lt.thing_property
|
||||||
def secondary_calibration_actions(self) -> list[ActionButton]:
|
def secondary_calibration_actions(self) -> list[ActionButton]:
|
||||||
"""The calibration actions that appear only in settings panel."""
|
"""The calibration actions that appear only in settings panel."""
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,11 @@ class CameraStageMapper(lt.Thing):
|
||||||
return None
|
return None
|
||||||
return self.last_calibration["image_resolution"]
|
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:
|
def assert_calibrated(self) -> None:
|
||||||
"""Raise an exception if the image_to_stage_displacement matrix is not set."""
|
"""Raise an exception if the image_to_stage_displacement matrix is not set."""
|
||||||
if self.image_to_stage_displacement_matrix is None:
|
if self.image_to_stage_displacement_matrix is None:
|
||||||
|
|
|
||||||
|
|
@ -68,3 +68,14 @@ def test_handle_broken_frame():
|
||||||
for _i in range(15):
|
for _i in range(15):
|
||||||
array = camera.grab_as_array(portal)
|
array = camera.grab_as_array(portal)
|
||||||
assert isinstance(array, np.ndarray)
|
assert isinstance(array, np.ndarray)
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulation_cam_calibration():
|
||||||
|
"""Test that the simulated camera can be calibrated and reports calibration correctly."""
|
||||||
|
camera = SimulatedCamera()
|
||||||
|
with camera_server(camera):
|
||||||
|
portal = lt.get_blocking_portal(camera)
|
||||||
|
assert camera.calibration_required
|
||||||
|
camera.full_auto_calibrate(portal)
|
||||||
|
assert not camera.calibration_required
|
||||||
|
assert camera.background_detector_status.ready
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
uk-grid
|
uk-grid
|
||||||
>
|
>
|
||||||
<!-- Initialisation modals -->
|
<!-- Initialisation modals -->
|
||||||
<calibrationModal
|
<calibrationWizard
|
||||||
ref="calibrationModal"
|
ref="calibrationWizard"
|
||||||
@onClose="enterApp()"
|
@onClose="enterApp()"
|
||||||
></calibrationModal>
|
></calibrationWizard>
|
||||||
<!-- Vertical tab bar -->
|
<!-- Vertical tab bar -->
|
||||||
<div id="switcher-left-container">
|
<div id="switcher-left-container">
|
||||||
<div
|
<div
|
||||||
|
|
@ -112,7 +112,7 @@ import loggingContent from "./tabContentComponents/loggingContent.vue";
|
||||||
import powerContent from "./tabContentComponents/powerContent.vue";
|
import powerContent from "./tabContentComponents/powerContent.vue";
|
||||||
|
|
||||||
// Import modal components for device initialisation
|
// Import modal components for device initialisation
|
||||||
import calibrationModal from "./modalComponents/calibrationModal.vue";
|
import calibrationWizard from "./modalComponents/calibrationWizard.vue";
|
||||||
import TabIcon from "./genericComponents/tabIcon.vue";
|
import TabIcon from "./genericComponents/tabIcon.vue";
|
||||||
import ScanListContent from "./tabContentComponents/scanListContent.vue";
|
import ScanListContent from "./tabContentComponents/scanListContent.vue";
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ export default {
|
||||||
slideScanContent,
|
slideScanContent,
|
||||||
viewContent,
|
viewContent,
|
||||||
settingsContent,
|
settingsContent,
|
||||||
calibrationModal,
|
calibrationWizard,
|
||||||
aboutContent,
|
aboutContent,
|
||||||
loggingContent,
|
loggingContent,
|
||||||
TabIcon,
|
TabIcon,
|
||||||
|
|
@ -246,7 +246,7 @@ export default {
|
||||||
this.currentTab = newId;
|
this.currentTab = newId;
|
||||||
},
|
},
|
||||||
startModals: function() {
|
startModals: function() {
|
||||||
this.$refs.calibrationModal.show();
|
this.$refs.calibrationWizard.show_if_needed();
|
||||||
},
|
},
|
||||||
enterApp: function() {
|
enterApp: function() {
|
||||||
// Stuff to do once connected and all init modals are finished
|
// Stuff to do once connected and all init modals are finished
|
||||||
|
|
|
||||||
|
|
@ -1,336 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id="modal-example" ref="calibrationModalEl" uk-modal="bg-close: false;">
|
|
||||||
<div v-if="ready" class="uk-modal-dialog uk-modal-body">
|
|
||||||
<h2 class="uk-modal-title">Microscope Calibration</h2>
|
|
||||||
<div v-show="stepValue == 0">
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>Some important microscope calibration data is currently missing.</b
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Your microscope will still function, however some functionality will
|
|
||||||
be limited, and image quality will likely suffer.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b>Click Next to begin microscope calibration.</b>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="stepValue == 1">
|
|
||||||
<h3>Lens-shading</h3>
|
|
||||||
<div v-if="isLSTCalibrated">
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>Your lens-shading table has already been calibrated. Click Next
|
|
||||||
to move on.</b
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>Follow the important steps below before starting lens-shading
|
|
||||||
calibration!</b
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<ul class="uk-list uk-list-bullet">
|
|
||||||
<li>Remove any samples from your microscope</li>
|
|
||||||
<li>Ensure your illumination is on and properly fixed in place</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<miniStreamDisplay
|
|
||||||
v-if="stepValue == 1"
|
|
||||||
class="mini-preview"
|
|
||||||
></miniStreamDisplay>
|
|
||||||
|
|
||||||
<p>Once you're ready, click auto-calibrate.</p>
|
|
||||||
|
|
||||||
<cameraCalibrationSettings
|
|
||||||
:show-extra-settings="false"
|
|
||||||
:camera-uri="cameraUri"
|
|
||||||
></cameraCalibrationSettings>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="stepValue == 2">
|
|
||||||
<h3>Adjust z height</h3>
|
|
||||||
<p>
|
|
||||||
Insert a sample and adjust the z position of
|
|
||||||
the stage using the buttons below, until the
|
|
||||||
sample is in focus.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You may also adjust the z position with <b>page up</b> and <b>page down</b>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<miniStreamDisplay
|
|
||||||
v-if="stepValue == 2"
|
|
||||||
class="mini-preview"
|
|
||||||
></miniStreamDisplay>
|
|
||||||
<div class="action-button-container">
|
|
||||||
<action-button
|
|
||||||
class="moveZ"
|
|
||||||
thing="stage"
|
|
||||||
action="move_relative"
|
|
||||||
:button-primary="false"
|
|
||||||
:submit-data="{ x: 0, y: 0, z: -100}"
|
|
||||||
:submit-label="' - '"
|
|
||||||
:hideOnRun="false"
|
|
||||||
:can-terminate="false"
|
|
||||||
/>
|
|
||||||
<action-button
|
|
||||||
class="moveZ"
|
|
||||||
thing="stage"
|
|
||||||
action="move_relative"
|
|
||||||
:button-primary="false"
|
|
||||||
:submit-data="{ x: 0, y: 0, z: 100}"
|
|
||||||
:submit-label="'+'"
|
|
||||||
:can-terminate="false"
|
|
||||||
:hideOnRun="false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="stepValue == 3">
|
|
||||||
<h3>Camera-stage mapping</h3>
|
|
||||||
<div v-if="isCSMCalibrated">
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>Your camera-stage mapping has already been calibrated. Click Next
|
|
||||||
to move on.</b
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="!canCSMCalibrated">
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>No stage connected. Please skip this step, or connect a valid
|
|
||||||
stage, then reboot your microscope.</b
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>Follow the important steps below before starting camera-stage
|
|
||||||
mapping calibration!</b
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<ul class="uk-list uk-list-bullet">
|
|
||||||
<li>Insert a clearly visible sample to the microscope</li>
|
|
||||||
<li>
|
|
||||||
Ensure the sample is reasonably well centered on the microscope
|
|
||||||
camera
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<miniStreamDisplay
|
|
||||||
v-if="stepValue == 3"
|
|
||||||
class="mini-preview"
|
|
||||||
></miniStreamDisplay>
|
|
||||||
|
|
||||||
<p>Once you're ready, click auto-calibrate.</p>
|
|
||||||
|
|
||||||
<CSMCalibrationSettings
|
|
||||||
:show-extra-settings="false"
|
|
||||||
></CSMCalibrationSettings>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="stepValue == 4">
|
|
||||||
<p>
|
|
||||||
<b>Calibration complete</b>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Click Finish to return to your microscope, or Restart to re-run the
|
|
||||||
calibration routine
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="uk-text-right">
|
|
||||||
<button
|
|
||||||
v-show="stepValue == 0"
|
|
||||||
class="uk-button uk-button-default uk-modal-close"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-show="stepValue == 4"
|
|
||||||
class="uk-button uk-button-default"
|
|
||||||
type="button"
|
|
||||||
@click="restart()"
|
|
||||||
>
|
|
||||||
Restart
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-show="stepValue < 4"
|
|
||||||
class="uk-button uk-button-primary uk-margin-left"
|
|
||||||
type="button"
|
|
||||||
@click="increment()"
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-show="stepValue == 4"
|
|
||||||
class="uk-button uk-button-primary uk-margin-left"
|
|
||||||
type="button"
|
|
||||||
@click="hide()"
|
|
||||||
>
|
|
||||||
Finish
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import cameraCalibrationSettings from "../tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue";
|
|
||||||
import CSMCalibrationSettings from "../tabContentComponents/settingsComponents/CSMSettingsComponents/CSMCalibrationSettings.vue";
|
|
||||||
import miniStreamDisplay from "../genericComponents/miniStreamDisplay.vue";
|
|
||||||
import ActionButton from "../labThingsComponents/actionButton.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "CalibrationModal",
|
|
||||||
|
|
||||||
components: {
|
|
||||||
cameraCalibrationSettings,
|
|
||||||
CSMCalibrationSettings,
|
|
||||||
miniStreamDisplay,
|
|
||||||
ActionButton
|
|
||||||
},
|
|
||||||
|
|
||||||
data: function() {
|
|
||||||
return {
|
|
||||||
ready: false,
|
|
||||||
stepValue: 0,
|
|
||||||
isCSMCalibrated: undefined,
|
|
||||||
isLSTCalibrated: undefined
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
canCSMCalibrated: function() {
|
|
||||||
// Assert CSM extension is enabled
|
|
||||||
return this.thingAvailable("camera_stage_mapping");
|
|
||||||
},
|
|
||||||
canLSTCalibrated: function() {
|
|
||||||
// Assert LST extension is enabled
|
|
||||||
return (
|
|
||||||
"calibrate_lens_shading" in this.thingDescription("camera").actions
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isUseful: function() {
|
|
||||||
var CSMUseful = this.canCSMCalibrated && !this.isCSMCalibrated;
|
|
||||||
var LSTUseful = this.canLSTCalibrated && !this.isLSTCalibrated;
|
|
||||||
return CSMUseful || LSTUseful;
|
|
||||||
},
|
|
||||||
cameraUri: function() {
|
|
||||||
return `${this.$store.getters.baseUri}/camera/`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.$refs["calibrationModalEl"].addEventListener("hidden", this.onHide);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
show: async function() {
|
|
||||||
// Check if the camera and stage are calibrated, if they can be
|
|
||||||
if (this.canCSMCalibrated) {
|
|
||||||
let csm = await this.readThingProperty(
|
|
||||||
"camera_stage_mapping",
|
|
||||||
"image_to_stage_displacement_matrix",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
this.isCSMCalibrated = Boolean(csm);
|
|
||||||
}
|
|
||||||
if (this.canLSTCalibrated) {
|
|
||||||
this.isLSTCalibrated = await this.readThingProperty(
|
|
||||||
"camera",
|
|
||||||
"lens_shading_is_static"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Check if this calibration wizard can actually do anything useful
|
|
||||||
if (this.isUseful) {
|
|
||||||
this.ready = true;
|
|
||||||
this.stepValue = 0;
|
|
||||||
// Show the modal
|
|
||||||
var el = this.$refs["calibrationModalEl"];
|
|
||||||
this.showModalElement(el); // Calls the mixin
|
|
||||||
} else {
|
|
||||||
// If not useful, we just return the onClose event immediately
|
|
||||||
this.onHide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Forces modal to show on button press
|
|
||||||
force_show: function() {
|
|
||||||
this.ready = true;
|
|
||||||
this.stepValue = 1;
|
|
||||||
this.force = true
|
|
||||||
// Show the modal
|
|
||||||
var el = this.$refs["calibrationModalEl"];
|
|
||||||
this.showModalElement(el); // Calls the mixin
|
|
||||||
},
|
|
||||||
|
|
||||||
restart: function() {
|
|
||||||
if (this.force == true){
|
|
||||||
this.stepValue = 1
|
|
||||||
} else {
|
|
||||||
this.stepValue = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hide: function() {
|
|
||||||
// Show the modal
|
|
||||||
var el = this.$refs["calibrationModalEl"];
|
|
||||||
this.hideModalElement(el); // Calls the mixin
|
|
||||||
this.ready = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
onHide: function() {
|
|
||||||
this.$emit("onClose");
|
|
||||||
},
|
|
||||||
|
|
||||||
decrement: function() {
|
|
||||||
if (this.stepValue > 0) {
|
|
||||||
this.stepValue = this.stepValue - 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
increment: function() {
|
|
||||||
// Upper bound on section number
|
|
||||||
if (this.stepValue < 4) {
|
|
||||||
this.stepValue = this.stepValue + 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.mini-preview {
|
|
||||||
width: 75%;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
.action-button-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row; /* Stack vertically */
|
|
||||||
justify-content: center; /* Left align */
|
|
||||||
gap: 8px; /* Small space between buttons */
|
|
||||||
margin-top: 4px; /* Gap from image */
|
|
||||||
}
|
|
||||||
>>> .moveZ .uk-button.uk-width-1-1 {
|
|
||||||
line-height: 50px;
|
|
||||||
font-size: 50px !important;
|
|
||||||
height: 60px;
|
|
||||||
padding-bottom: 46px;
|
|
||||||
margin: 0; /* Remove default margin */
|
|
||||||
width: 120px;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
198
webapp/src/components/modalComponents/calibrationWizard.vue
Normal file
198
webapp/src/components/modalComponents/calibrationWizard.vue
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
<template>
|
||||||
|
<div id="modal-example" ref="calibrationModalEl" uk-modal="bg-close: false;">
|
||||||
|
<div class="uk-modal-dialog uk-modal-body">
|
||||||
|
<h2 class="uk-modal-title">Microscope Calibration</h2>
|
||||||
|
|
||||||
|
<component
|
||||||
|
v-if="currentTask"
|
||||||
|
:is="currentTask.component"
|
||||||
|
:key="taskIndex"
|
||||||
|
v-bind="currentTask.props"
|
||||||
|
:first="isFirstTask"
|
||||||
|
:final="isFinalTask"
|
||||||
|
:startOnLast="movingBackward"
|
||||||
|
@next="nextTask"
|
||||||
|
@back="previousTask"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import singleStepTask from "./calibrationWizardComponents/singleStepTask.vue";
|
||||||
|
import welcomeStep from "./calibrationWizardComponents/welcomeStep.vue";
|
||||||
|
import cameraCalibrationTask from "./calibrationWizardComponents/cameraCalibrationTask.vue";
|
||||||
|
import cameraStageMappingTask from "./calibrationWizardComponents/cameraStageMappingTask.vue";
|
||||||
|
import finalStep from "./calibrationWizardComponents/finalStep.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "calibrationWizard",
|
||||||
|
|
||||||
|
components: {},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
isNeeded: undefined,
|
||||||
|
availableCalibrationTasks: {},
|
||||||
|
tasks: [],
|
||||||
|
taskIndex: 0,
|
||||||
|
movingBackward: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
currentTask() {
|
||||||
|
return this.tasks[this.taskIndex] || null;
|
||||||
|
},
|
||||||
|
isFirstTask() {
|
||||||
|
return this.taskIndex === 0;
|
||||||
|
},
|
||||||
|
isFinalTask() {
|
||||||
|
return this.taskIndex === this.tasks.length - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$refs["calibrationModalEl"].addEventListener("hidden", this.onHide);
|
||||||
|
// Check which Things are available on mount.
|
||||||
|
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 which require calibration.
|
||||||
|
*
|
||||||
|
* Iterates over `this.availableCalibrationTasks` (set during mounted()) and reads the
|
||||||
|
* `calibration_required` property.
|
||||||
|
*
|
||||||
|
* Returns a list of thing names that report calibration is required.
|
||||||
|
*/
|
||||||
|
async check_things_needing_calibration() {
|
||||||
|
const needsCalibration = [];
|
||||||
|
const calibrateableThings = Object.keys(this.availableCalibrationTasks);
|
||||||
|
|
||||||
|
for (const name of calibrateableThings) {
|
||||||
|
try {
|
||||||
|
const thingNeedsCal = await this.readThingProperty(
|
||||||
|
name,
|
||||||
|
"calibration_required"
|
||||||
|
);
|
||||||
|
if (thingNeedsCal) {
|
||||||
|
needsCalibration.push(name);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`${name}: missing calibration_required property`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return needsCalibration;
|
||||||
|
},
|
||||||
|
|
||||||
|
resetData: function() {
|
||||||
|
this.movingBackward = false;
|
||||||
|
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 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
|
||||||
|
this.onHide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
// Show the modal element
|
||||||
|
var el = this.$refs["calibrationModalEl"];
|
||||||
|
this.showModalElement(el); // Calls the mixin
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
// Show the modal
|
||||||
|
var el = this.$refs["calibrationModalEl"];
|
||||||
|
this.hideModalElement(el); // Calls the mixin
|
||||||
|
},
|
||||||
|
|
||||||
|
onHide: function() {
|
||||||
|
this.$emit("onClose");
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move to the previous task.
|
||||||
|
*/
|
||||||
|
previousTask: function() {
|
||||||
|
this.movingBackward = true;
|
||||||
|
if (this.taskIndex > 0) {
|
||||||
|
this.taskIndex = this.taskIndex - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move to the next task or close the modal if this is the final task.
|
||||||
|
*/
|
||||||
|
nextTask: function() {
|
||||||
|
this.movingBackward = false;
|
||||||
|
if (this.taskIndex < this.tasks.length - 1) {
|
||||||
|
this.taskIndex = this.taskIndex + 1;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
# The Calibration Wizard Components
|
||||||
|
|
||||||
|
## Tasks vs Steps
|
||||||
|
|
||||||
|
The components for the wizard uses the terms "Task" and "Step" to divide up the wizard's behaviour:
|
||||||
|
|
||||||
|
* **Task**: A task is what we want to achieve. Such as "Calibrate the camera" or "Run camera stage mapping", there are multiple things to do for these complex tasks. A task can also be as simple as "welcome the user to their microscope."
|
||||||
|
* **Step**: A single page/pane of information shown to the user. The components for these can be as simple as a single `<div>` with no javascript logic.
|
||||||
|
|
||||||
|
This is done to allow complex tasks like camera stage mapping to have multiple steps:
|
||||||
|
|
||||||
|
1. An explanation and telling the user what sample to get
|
||||||
|
2. An interface for focusing
|
||||||
|
3. Actually running camera stage mapping
|
||||||
|
|
||||||
|
None of these individual steps makes sense in the wizard on their own, hence the need for the "Task" grouping. By grouping the steps we can:
|
||||||
|
|
||||||
|
- Easily include/exclude tasks based on state
|
||||||
|
- Have a consistent subtitle during a task
|
||||||
|
- Skip entire task rather than make the user click next through each pane of the task (not yet implemented)
|
||||||
|
|
||||||
|
Some simple tasks such as the welcome screen will have only one step.
|
||||||
|
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### The main wizard component
|
||||||
|
|
||||||
|
The calibration wizard is a modal. The top level component in `calibrationWizard.vue` handles:
|
||||||
|
|
||||||
|
* Creating a modal
|
||||||
|
* Checking which Things that have calibration tasks are on the server
|
||||||
|
* Checking which of these Things need calibrating
|
||||||
|
* Starting modal on startup if not all Things are calibrated (with a welcome screen and just those tasks)
|
||||||
|
* Starting the modal when launched from settings, with all tasks but no welcome screen.
|
||||||
|
* Before starting the modal, a list of task components is dynamically generated.
|
||||||
|
|
||||||
|
### The main task component
|
||||||
|
|
||||||
|
The main task component `calibrationWizardTask.vue` is used to create multi-step tasks. Rather than using it directly it should be wrapped by a new component that:
|
||||||
|
|
||||||
|
* Forwards all the props
|
||||||
|
* Forwards the events back to the wizard
|
||||||
|
* Creates a list of steps.
|
||||||
|
|
||||||
|
Examples of this pattern are in `cameraCalibrationTask.vue` and `cameraStageMappingTask.vue`.
|
||||||
|
|
||||||
|
### The single step task component
|
||||||
|
|
||||||
|
For simple tasks with only one step there is no need to make both a specific task component. Instead `singleStepTask.vue` can be used, the step component can be supplied as a prop.
|
||||||
|
|
||||||
|
This is used in the main wizard to create the welcome screen and the final page.
|
||||||
|
|
||||||
|
## stepTemplateWithStream
|
||||||
|
|
||||||
|
Step components are simple enough that generally there is no need for a template. However, a number of steps have mini-stream views. To keep these consistent `stepTemplateWithStream.vue` can be used.
|
||||||
|
|
||||||
|
The template puts component content above the stream view, by default. But extra information can be added to the area below the stream by using the `<template #below-stream>` tag.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<stepTemplateWithStream>
|
||||||
|
<p>
|
||||||
|
<b>This is above the stream.</b>
|
||||||
|
</p>
|
||||||
|
<template #below-stream>
|
||||||
|
<div>
|
||||||
|
This text is below the stream
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</stepTemplateWithStream>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Vue seems to get angry if the HTML within the `<template #below-stream>` tag is not all contained in a single `<div>`
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
<!-- The base component for a wizard task.
|
||||||
|
|
||||||
|
A task should be used for higher level groupings of individual steps in the wizard,
|
||||||
|
such as calibrating a Thing. Tasks can be divided into multiple steps.
|
||||||
|
|
||||||
|
## How to use this component:
|
||||||
|
|
||||||
|
*To make a task with lots of steps:*
|
||||||
|
Create a task component for a `Thing`, that wraps just this component and passes in
|
||||||
|
a list of steps. See `cameraCalibrationTask.vue` and `cameraStageMappingTask.vue`
|
||||||
|
as examples.
|
||||||
|
|
||||||
|
*To make a simple task with just one step:*
|
||||||
|
See `singleStepTask.vue`. This is what is used to display the welcome task.
|
||||||
|
|
||||||
|
## How not to use this component:
|
||||||
|
|
||||||
|
Do not import this directly into CalibrationWizard as the list of tasks will then
|
||||||
|
also need props to be passed with the list of step components and step props, and it
|
||||||
|
gets very confusing.
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3 v-if="title">{{ title }}</h3>
|
||||||
|
<component
|
||||||
|
v-if="currentStep"
|
||||||
|
:is="currentStep.component"
|
||||||
|
:key="stepIndex"
|
||||||
|
v-bind="currentStep.props"
|
||||||
|
/>
|
||||||
|
<p class="uk-text-right">
|
||||||
|
<button
|
||||||
|
v-if="showBackButton"
|
||||||
|
class="uk-button uk-button-default"
|
||||||
|
type="button"
|
||||||
|
@click="previousStep"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-primary uk-margin-left"
|
||||||
|
type="button"
|
||||||
|
@click="nextStep"
|
||||||
|
>
|
||||||
|
{{ nextButtonText }}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "calibrationWizardTask",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
first: Boolean,
|
||||||
|
final: Boolean,
|
||||||
|
startOnLast: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
steps: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stepIndex: this.startOnLast ? this.steps.length - 1 : 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
currentStep() {
|
||||||
|
return this.steps[this.stepIndex] || null;
|
||||||
|
},
|
||||||
|
showBackButton() {
|
||||||
|
return !this.first || this.stepIndex > 0;
|
||||||
|
},
|
||||||
|
nextButtonText() {
|
||||||
|
return this.final && this.stepIndex === this.steps.length - 1
|
||||||
|
? "Finish"
|
||||||
|
: "Next";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/*
|
||||||
|
* Move to the previous step in this task, or the previous task if first step.
|
||||||
|
*/
|
||||||
|
previousStep: function() {
|
||||||
|
if (this.stepIndex > 0) {
|
||||||
|
this.stepIndex = this.stepIndex - 1;
|
||||||
|
} else {
|
||||||
|
this.$emit("back");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move to the next step in this task, or the next task if final step.
|
||||||
|
*/
|
||||||
|
nextStep: function() {
|
||||||
|
if (this.stepIndex < this.steps.length - 1) {
|
||||||
|
this.stepIndex = this.stepIndex + 1;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.$emit("next");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Before starting camera calibration:
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
<ul class="uk-list uk-list-bullet">
|
||||||
|
<li>Remove any samples from your microscope</li>
|
||||||
|
<li>Ensure your illumination is on, well focused, and centred.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "camCalibrationExplanation"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<stepTemplateWithStream>
|
||||||
|
<p>
|
||||||
|
<b>Once your field of view is empty and well illuminated, click Full Auto-Calibrate.</b>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<template #below-stream>
|
||||||
|
<div class="action-button-container">
|
||||||
|
<cameraCalibrationSettings
|
||||||
|
:show-extra-settings="false"
|
||||||
|
:camera-uri="cameraUri"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</stepTemplateWithStream>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import stepTemplateWithStream from "../stepTemplateWithStream.vue";
|
||||||
|
import cameraCalibrationSettings from "../../../tabContentComponents/settingsComponents/cameraSettingsComponents/cameraCalibrationSettings.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cameraMainCalibrationStep",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
stepTemplateWithStream,
|
||||||
|
cameraCalibrationSettings
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
cameraUri: function() {
|
||||||
|
return `${this.$store.getters.baseUri}/camera/`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.action-button-container {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<calibrationWizardTask
|
||||||
|
title="Camera Calibration"
|
||||||
|
:first="first"
|
||||||
|
:final="final"
|
||||||
|
:startOnLast="startOnLast"
|
||||||
|
:steps="steps"
|
||||||
|
@next="$emit('next')"
|
||||||
|
@back="$emit('back')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import calibrationWizardTask from "./calibrationWizardTask.vue";
|
||||||
|
import camCalibrationExplanation from "./cameraCalibrationSteps/camCalibrationExplanation.vue";
|
||||||
|
import cameraMainCalibrationStep from "./cameraCalibrationSteps/cameraMainCalibrationStep.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cameraCalibrationTask",
|
||||||
|
components: { calibrationWizardTask },
|
||||||
|
props: {
|
||||||
|
// Standard calibrationWizardTask props below:
|
||||||
|
first: Boolean,
|
||||||
|
final: Boolean,
|
||||||
|
startOnLast: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
steps: [
|
||||||
|
{ component: camCalibrationExplanation },
|
||||||
|
{ component: cameraMainCalibrationStep }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<calibrationWizardTask
|
||||||
|
title="Camera-Stage Mapping"
|
||||||
|
:first="first"
|
||||||
|
:final="final"
|
||||||
|
:startOnLast="startOnLast"
|
||||||
|
:steps="steps"
|
||||||
|
@next="$emit('next')"
|
||||||
|
@back="$emit('back')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import calibrationWizardTask from "./calibrationWizardTask.vue";
|
||||||
|
import csmExplanation from "./csmSteps/csmExplanation.vue";
|
||||||
|
import focusStep from "./csmSteps/focusStep.vue";
|
||||||
|
import runCsmStep from "./csmSteps/runCsmStep.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cameraCalibrationTask",
|
||||||
|
components: { calibrationWizardTask },
|
||||||
|
props: {
|
||||||
|
// Standard calibrationWizardTask props below:
|
||||||
|
first: Boolean,
|
||||||
|
final: Boolean,
|
||||||
|
startOnLast: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
steps: [
|
||||||
|
{ component: csmExplanation },
|
||||||
|
{ component: focusStep },
|
||||||
|
{ component: runCsmStep }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Camera-stage mapping will calibrate the stage movement using the camera.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Before starting camera-stage mapping:
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
<ul class="uk-list uk-list-bullet">
|
||||||
|
<li>Add a sample with dense features to the microscope.</li>
|
||||||
|
<li>Ensure the sample is reasonably well centered on the microscope.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "csmExplanation"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<stepTemplateWithStream>
|
||||||
|
<p>
|
||||||
|
Use the buttons below to bring the sample into focus.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You may also adjust the z position with <b>page up</b> and
|
||||||
|
<b>page down</b>.
|
||||||
|
</p>
|
||||||
|
<template #below-stream>
|
||||||
|
<div class="action-button-container">
|
||||||
|
<action-button
|
||||||
|
class="moveZ"
|
||||||
|
thing="stage"
|
||||||
|
action="move_relative"
|
||||||
|
:button-primary="false"
|
||||||
|
:submit-data="{ x: 0, y: 0, z: -100 }"
|
||||||
|
:submit-label="' - '"
|
||||||
|
:hideOnRun="false"
|
||||||
|
:can-terminate="false"
|
||||||
|
/>
|
||||||
|
<action-button
|
||||||
|
class="moveZ"
|
||||||
|
thing="stage"
|
||||||
|
action="move_relative"
|
||||||
|
:button-primary="false"
|
||||||
|
:submit-data="{ x: 0, y: 0, z: 100 }"
|
||||||
|
:submit-label="'+'"
|
||||||
|
:can-terminate="false"
|
||||||
|
:hideOnRun="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</stepTemplateWithStream>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import stepTemplateWithStream from "../stepTemplateWithStream.vue";
|
||||||
|
import ActionButton from "../../../labThingsComponents/actionButton.vue";
|
||||||
|
export default {
|
||||||
|
name: "cameraMainCalibrationStep",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
stepTemplateWithStream,
|
||||||
|
ActionButton
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.action-button-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row; /* Stack vertically */
|
||||||
|
justify-content: center; /* Left align */
|
||||||
|
gap: 8px; /* Small space between buttons */
|
||||||
|
margin-top: 4px; /* Gap from image */
|
||||||
|
}
|
||||||
|
.moveZ >>> .uk-button.uk-width-1-1 {
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 50px !important;
|
||||||
|
height: 60px;
|
||||||
|
padding-bottom: 46px;
|
||||||
|
margin: 0; /* Remove default margin */
|
||||||
|
width: 120px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<stepTemplateWithStream>
|
||||||
|
<p>
|
||||||
|
<b>If the sample is in focus, click Auto-Calibrate Using Camera.</b>
|
||||||
|
</p>
|
||||||
|
<p>If it is not in focus, click back and re-focus.</p>
|
||||||
|
<template #below-stream>
|
||||||
|
<div class="action-button-container">
|
||||||
|
<CSMCalibrationSettings :show-extra-settings="false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</stepTemplateWithStream>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import stepTemplateWithStream from "../stepTemplateWithStream.vue";
|
||||||
|
import CSMCalibrationSettings from "../../../tabContentComponents/settingsComponents/CSMSettingsComponents/CSMCalibrationSettings.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cameraMainCalibrationStep",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
stepTemplateWithStream,
|
||||||
|
CSMCalibrationSettings
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.action-button-container {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<b>Calibration complete</b>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You'll need to repeat these steps from the Settings tab if you swap your objective.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Click Finish to return to your microscope.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "finalStep"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<!-- A component to make a task with just one step
|
||||||
|
|
||||||
|
The calibration wizard makes a distinction between tasks (what you are trying to
|
||||||
|
achieve, such as calibrating the camera) and steps (individual pages/panes shown to the
|
||||||
|
user). The down side of this is there is some complexity in handling tasks that is
|
||||||
|
unhelpful when trying to add a single pane.
|
||||||
|
|
||||||
|
This component takes one step Component (which can be as simple as a <div> with some
|
||||||
|
text) as the :stepComponent prop, and optionally any props to be sent to the component
|
||||||
|
can be passed as an object to :stepProps. All other prop are automatically
|
||||||
|
supplied by the wizard.
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<calibrationWizardTask
|
||||||
|
:title="title"
|
||||||
|
:first="first"
|
||||||
|
:final="final"
|
||||||
|
:startOnLast="startOnLast"
|
||||||
|
:steps="steps"
|
||||||
|
@next="$emit('next')"
|
||||||
|
@back="$emit('back')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import calibrationWizardTask from "./calibrationWizardTask.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "singleStepTask",
|
||||||
|
components: { calibrationWizardTask },
|
||||||
|
props: {
|
||||||
|
// This must be sent
|
||||||
|
stepComponent: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// This is optional.
|
||||||
|
stepProps: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
// Standard calibrationWizardTask props below:
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
first: Boolean,
|
||||||
|
final: Boolean,
|
||||||
|
startOnLast: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
steps: [{ component: this.stepComponent, props: this.stepProps }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
<miniStreamDisplay class="mini-preview" />
|
||||||
|
<slot name="below-stream"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "stepTemplateWithStream",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
miniStreamDisplay
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mini-preview {
|
||||||
|
width: 75%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Some important microscope calibration data is currently missing.
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Your microscope will still function, however some functionality will be
|
||||||
|
limited, and image quality will likely suffer.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Click Next to begin microscope calibration.</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "welcomeStep"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- Grid managing tab content -->
|
<!-- Grid managing tab content -->
|
||||||
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
<div uk-grid class="uk-height-1-1 uk-margin-remove uk-padding-remove">
|
||||||
<calibrationModal
|
<calibrationWizard
|
||||||
ref="calibrationModal"
|
ref="calibrationWizard"
|
||||||
@onClose="enterApp()"
|
></calibrationWizard>
|
||||||
></calibrationModal>
|
|
||||||
<div class="settings-nav">
|
<div class="settings-nav">
|
||||||
<ul class="uk-nav uk-nav-default">
|
<ul class="uk-nav uk-nav-default">
|
||||||
<li class="uk-nav-header">Application Settings</li>
|
<li class="uk-nav-header">Application Settings</li>
|
||||||
|
|
@ -116,7 +115,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import streamSettings from "./settingsComponents/streamSettings.vue";
|
import streamSettings from "./settingsComponents/streamSettings.vue";
|
||||||
import calibrationModal from "../modalComponents/calibrationModal.vue";
|
import calibrationWizard from "../modalComponents/calibrationWizard.vue";
|
||||||
import cameraSettings from "./settingsComponents/cameraSettings.vue";
|
import cameraSettings from "./settingsComponents/cameraSettings.vue";
|
||||||
import appSettings from "./settingsComponents/appSettings.vue";
|
import appSettings from "./settingsComponents/appSettings.vue";
|
||||||
import CSMSettings from "./settingsComponents/CSMSettings.vue";
|
import CSMSettings from "./settingsComponents/CSMSettings.vue";
|
||||||
|
|
@ -137,7 +136,7 @@ export default {
|
||||||
appSettings,
|
appSettings,
|
||||||
tabIcon,
|
tabIcon,
|
||||||
tabContent,
|
tabContent,
|
||||||
calibrationModal
|
calibrationWizard
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
|
|
@ -154,7 +153,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
startModals: function() {
|
startModals: function() {
|
||||||
this.$refs.calibrationModal.force_show();
|
this.$refs.calibrationWizard.force_show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue