Updated docstrings and fixed ruff errors
This commit is contained in:
parent
1c7527c2ef
commit
d494761cbb
1 changed files with 30 additions and 46 deletions
|
|
@ -1,15 +1,24 @@
|
||||||
|
"""File contains all the functions used to measure the range of motion of the OpenFlexure Microscope translation stage.
|
||||||
|
|
||||||
|
The range of motion is measured by first taking 5 'medium' sized steps which gives enough positions to predict future z positions. Next, one 'big' step is taken followed by 3
|
||||||
|
'small' steps to test that the stage is still moving as expected and has not reached the edge. Once the edge has been found and too account for the possibility that a 'big'
|
||||||
|
step was take just before the edge was reached, the stage is moved in a sequence of increasing pixel sizes in the opposite direction until motion is detected. This is
|
||||||
|
position is taken as the true final position.
|
||||||
|
|
||||||
|
Throughout the test, parasitic motion(motion in the axis not being measured) is tracked and an error is raised if it exceeds a reasonable amount.
|
||||||
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import cv2
|
import cv2
|
||||||
import json
|
import json
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import time
|
import time
|
||||||
from typing import Dict, Optional
|
|
||||||
from scipy.optimize import curve_fit
|
from scipy.optimize import curve_fit
|
||||||
from camera_stage_mapping import fft_image_tracking
|
from camera_stage_mapping import fft_image_tracking
|
||||||
from labthings_fastapi.thing import Thing
|
from labthings_fastapi.thing import Thing
|
||||||
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
||||||
from labthings_fastapi.dependencies.invocation import CancelHook, InvocationLogger
|
from labthings_fastapi.dependencies.invocation import CancelHook, InvocationLogger
|
||||||
from labthings_fastapi.decorators import thing_action, thing_property
|
from labthings_fastapi.decorators import thing_action
|
||||||
from labthings_sangaboard import SangaboardThing
|
from labthings_sangaboard import SangaboardThing
|
||||||
from labthings_picamera2.thing import StreamingPiCamera2
|
from labthings_picamera2.thing import StreamingPiCamera2
|
||||||
from labthings_fastapi.types.numpy import DenumpifyingDict
|
from labthings_fastapi.types.numpy import DenumpifyingDict
|
||||||
|
|
@ -21,17 +30,6 @@ CamDep = direct_thing_client_dependency(StreamingPiCamera2, "/camera/")
|
||||||
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
||||||
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
||||||
|
|
||||||
'''
|
|
||||||
This file contains all the functions used to measure the range of motion of the OpenFlexure Microscope translation stage.
|
|
||||||
|
|
||||||
The range of motion is measured by first taking 5 'medium' sized steps which gives enough positions to predict future z positions. Next, one 'big' step is taken followed by 3
|
|
||||||
'small' steps to test that the stage is still moving as expected and has not reached the edge. Once the edge has been found and too account for the possibility that a 'big'
|
|
||||||
step was take just before the edge was reached, the stage is moved in a sequence of increasing pixel sizes in the opposite direction until motion is detected. This is
|
|
||||||
position is taken as the true final position.
|
|
||||||
|
|
||||||
Throughout the test, parasitic motion(motion in the axis not being measured) is tracked and an error is raised if it exceeds a reasonable amount.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def quadratic(x, a, b, c):
|
def quadratic(x, a, b, c):
|
||||||
"""Quadratic function. Used for predicting z.
|
"""Quadratic function. Used for predicting z.
|
||||||
|
|
||||||
|
|
@ -43,8 +41,8 @@ def quadratic(x, a, b, c):
|
||||||
"""
|
"""
|
||||||
return a * x**2 + b * x + c
|
return a * x**2 + b * x + c
|
||||||
|
|
||||||
def dict_generate(fov_perc: int, stream_resolution: list[int], direction: int, factor: float = 1) -> dict[str, float]:
|
def generate_move_dicts(fov_perc: int, stream_resolution: list[int], direction: int, factor: float = 1) -> dict[str, float]:
|
||||||
"""Creates a single dictionary of x and y moves for moving in image coordinates.
|
"""Create a single dictionary of x and y moves for moving in image coordinates.
|
||||||
|
|
||||||
:param fov_perc: The percentage of field of view the stage should move by.
|
:param fov_perc: The percentage of field of view the stage should move by.
|
||||||
:param stream_resolution: The resolution of the stream from the camera.
|
:param stream_resolution: The resolution of the stream from the camera.
|
||||||
|
|
@ -52,15 +50,14 @@ def dict_generate(fov_perc: int, stream_resolution: list[int], direction: int, f
|
||||||
:param factor: Multiplicative factor which changes the final result.
|
:param factor: Multiplicative factor which changes the final result.
|
||||||
:return: A dictionary with keys 'x' and 'y' with a pixel distance move the stage can make.
|
:return: A dictionary with keys 'x' and 'y' with a pixel distance move the stage can make.
|
||||||
"""
|
"""
|
||||||
xy_dict = {
|
return {
|
||||||
"x": (fov_perc / 100) * stream_resolution[0] * factor * direction,
|
"x": (fov_perc / 100) * stream_resolution[0] * factor * direction,
|
||||||
"y": (fov_perc / 100) * stream_resolution[1] * factor * direction,
|
"y": (fov_perc / 100) * stream_resolution[1] * factor * direction,
|
||||||
}
|
}
|
||||||
return xy_dict
|
|
||||||
|
|
||||||
def predict_z(positions: list, axis: str, relative_move: float, stage: StageDep, csm: CSMDep) -> float:
|
def predict_z(positions: list, axis: str, relative_move: float, stage: StageDep, csm: CSMDep) -> float:
|
||||||
"""Predicts the next z position for a 'big' move using an array of previous positions. This avoids long autofocuses and reduces the possibility of colliding with
|
"""Predict the next z position for a move using previous positions.
|
||||||
the sample.
|
|
||||||
|
|
||||||
:params positions: The list of positions used for predicting z. This will usually be a list of all previuos positions.
|
:params positions: The list of positions used for predicting z. This will usually be a list of all previuos positions.
|
||||||
:params axis: The axis in which the stage is moving. This must be 'x' or 'y'.
|
:params axis: The axis in which the stage is moving. This must be 'x' or 'y'.
|
||||||
|
|
@ -76,9 +73,8 @@ def predict_z(positions: list, axis: str, relative_move: float, stage: StageDep,
|
||||||
z_positions = [i['z'] for i in positions]
|
z_positions = [i['z'] for i in positions]
|
||||||
parameters, _ = curve_fit(quadratic, lateral_positions, z_positions)
|
parameters, _ = curve_fit(quadratic, lateral_positions, z_positions)
|
||||||
z_dest = quadratic(stage.position[axis] + (relative_move/pixel_step[axis]), *parameters)
|
z_dest = quadratic(stage.position[axis] + (relative_move/pixel_step[axis]), *parameters)
|
||||||
z_diff = z_dest - stage.position['z']
|
|
||||||
|
|
||||||
return z_diff
|
return z_dest - stage.position["z"]
|
||||||
|
|
||||||
def move_and_measure(
|
def move_and_measure(
|
||||||
step_size: dict[str, float],
|
step_size: dict[str, float],
|
||||||
|
|
@ -88,7 +84,7 @@ def move_and_measure(
|
||||||
csm: CSMDep,
|
csm: CSMDep,
|
||||||
autofocus: AutofocusDep,
|
autofocus: AutofocusDep,
|
||||||
cam:CamDep) -> tuple:
|
cam:CamDep) -> tuple:
|
||||||
"""Moves the stage and measures the offset between the two positions.
|
"""Move the stage and measure the offset between the two positions.
|
||||||
|
|
||||||
:params step_size: A dictionary with keys 'x' and 'y' with pixel distances.
|
:params step_size: A dictionary with keys 'x' and 'y' with pixel distances.
|
||||||
:params axis: The axis in which the stage is moving. This must be 'x' or 'y'.
|
:params axis: The axis in which the stage is moving. This must be 'x' or 'y'.
|
||||||
|
|
@ -127,7 +123,7 @@ def medium_moves(
|
||||||
autofocus: AutofocusDep,
|
autofocus: AutofocusDep,
|
||||||
logger: InvocationLogger
|
logger: InvocationLogger
|
||||||
) -> tuple:
|
) -> tuple:
|
||||||
"""Carries out the medium sized steps section of the range of motion test to get 5 points to make z position predictions with
|
"""Carries out the medium sized steps section of the range of motion test to get 5 points to make z position predictions with.
|
||||||
|
|
||||||
:params stream_resolution: The resolution of the stream from the camera.
|
:params stream_resolution: The resolution of the stream from the camera.
|
||||||
:param direction: The direction the stage moves.
|
:param direction: The direction the stage moves.
|
||||||
|
|
@ -138,9 +134,8 @@ def medium_moves(
|
||||||
:params cor_lat_steps: A list of all correlation values.
|
:params cor_lat_steps: A list of all correlation values.
|
||||||
:return: Stage_coords and cor_lat_steps are lists of data tracked throughout the test. Delta is updated and tracked after each move.
|
:return: Stage_coords and cor_lat_steps are lists of data tracked throughout the test. Delta is updated and tracked after each move.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
medium_step = 50
|
medium_step = 50
|
||||||
wrong_axis_max_medium = dict_generate(
|
wrong_axis_max_medium = generate_move_dicts(
|
||||||
medium_step, stream_resolution, direction, factor=0.1
|
medium_step, stream_resolution, direction, factor=0.1
|
||||||
)
|
)
|
||||||
for loop in range(5):
|
for loop in range(5):
|
||||||
|
|
@ -148,7 +143,7 @@ def medium_moves(
|
||||||
np.array(Image.open(cam.grab_jpeg().open())), dsize=(0, 0), fx=1, fy=1
|
np.array(Image.open(cam.grab_jpeg().open())), dsize=(0, 0), fx=1, fy=1
|
||||||
)
|
)
|
||||||
delta, offset, focus_data, wrong_axis = move_and_measure(
|
delta, offset, focus_data, wrong_axis = move_and_measure(
|
||||||
step_size=dict_generate(medium_step, stream_resolution, direction),
|
step_size=generate_move_dicts(medium_step, stream_resolution, direction),
|
||||||
axis=axis,
|
axis=axis,
|
||||||
delta=delta,
|
delta=delta,
|
||||||
image1=image1,
|
image1=image1,
|
||||||
|
|
@ -181,7 +176,7 @@ def small_moves(
|
||||||
autofocus: AutofocusDep,
|
autofocus: AutofocusDep,
|
||||||
logger: InvocationLogger,
|
logger: InvocationLogger,
|
||||||
) -> tuple:
|
) -> tuple:
|
||||||
"""Carries out the medium sized steps section of the range of motion test to get 5 points to make z position predictions with
|
"""Carries out 3 small moves in a given direction and axis.
|
||||||
|
|
||||||
:params small_step: The integer value used to generate the small step sizes.
|
:params small_step: The integer value used to generate the small step sizes.
|
||||||
:params stream_resolution: The resolution of the stream from the camera.
|
:params stream_resolution: The resolution of the stream from the camera.
|
||||||
|
|
@ -195,14 +190,14 @@ def small_moves(
|
||||||
:return: Stage_coords and cor_lat_steps are lists of data tracked throughout the test. Delta is updated and tracked after each move.
|
:return: Stage_coords and cor_lat_steps are lists of data tracked throughout the test. Delta is updated and tracked after each move.
|
||||||
"""
|
"""
|
||||||
failure_count = 0
|
failure_count = 0
|
||||||
wrong_axis_max_small = dict_generate(small_step, stream_resolution, direction, factor=0.1)
|
wrong_axis_max_small = generate_move_dicts(small_step, stream_resolution, direction, factor=0.1)
|
||||||
|
|
||||||
for loop in range(3):
|
for loop in range(3):
|
||||||
image1 = cv2.resize(
|
image1 = cv2.resize(
|
||||||
np.array(Image.open(cam.grab_jpeg().open())), dsize=(0, 0), fx=1, fy=1
|
np.array(Image.open(cam.grab_jpeg().open())), dsize=(0, 0), fx=1, fy=1
|
||||||
)
|
)
|
||||||
delta, offset, focus_data, wrong_axis = move_and_measure(
|
delta, offset, focus_data, wrong_axis = move_and_measure(
|
||||||
step_size=dict_generate(small_step, stream_resolution, direction),
|
step_size=generate_move_dicts(small_step, stream_resolution, direction),
|
||||||
axis=axis,
|
axis=axis,
|
||||||
delta=delta,
|
delta=delta,
|
||||||
image1=image1,
|
image1=image1,
|
||||||
|
|
@ -253,8 +248,7 @@ def small_moves(
|
||||||
return stage_coords, cor_lat_steps, delta
|
return stage_coords, cor_lat_steps, delta
|
||||||
|
|
||||||
def motion_detection(axis: str, direction: int, csm: CSMDep, stage: StageDep, cam: CamDep, logger: InvocationLogger) -> dict:
|
def motion_detection(axis: str, direction: int, csm: CSMDep, stage: StageDep, cam: CamDep, logger: InvocationLogger) -> dict:
|
||||||
"""Moves the stage until motion is detected. This happens at the end of each axis and direction and where the stage is moved in the opposite direction until
|
"""Move the stage until motion is detected along a specified axis and direction.
|
||||||
motion is detected.
|
|
||||||
|
|
||||||
:params axis: The axis in which the stage is moving. This must be 'x' or 'y'.
|
:params axis: The axis in which the stage is moving. This must be 'x' or 'y'.
|
||||||
:params direction: The direction in which the stage was moving previous to motion detection being used.
|
:params direction: The direction in which the stage was moving previous to motion detection being used.
|
||||||
|
|
@ -288,18 +282,17 @@ def motion_detection(axis: str, direction: int, csm: CSMDep, stage: StageDep, ca
|
||||||
logger.info("Motion detected.")
|
logger.info("Motion detected.")
|
||||||
break
|
break
|
||||||
|
|
||||||
true_final = stage.position
|
return stage.position
|
||||||
|
|
||||||
return true_final
|
|
||||||
|
|
||||||
class RangeofMotionThing(Thing):
|
class RangeofMotionThing(Thing):
|
||||||
|
"""A class used to measure the range of motion of the stage in X and Y."""
|
||||||
|
|
||||||
def rom_axis(
|
def rom_axis(
|
||||||
self,
|
self,
|
||||||
autofocus: AutofocusDep,
|
autofocus: AutofocusDep,
|
||||||
stage: StageDep,
|
stage: StageDep,
|
||||||
cam: CamDep,
|
cam: CamDep,
|
||||||
csm: CSMDep,
|
csm: CSMDep,
|
||||||
cancel: CancelHook,
|
|
||||||
logger: InvocationLogger,
|
logger: InvocationLogger,
|
||||||
axis: str,
|
axis: str,
|
||||||
direction: int
|
direction: int
|
||||||
|
|
@ -310,7 +303,6 @@ class RangeofMotionThing(Thing):
|
||||||
:params direction: The direction which is being measured. This must be 1 or -1.
|
:params direction: The direction which is being measured. This must be 1 or -1.
|
||||||
:return: Results dictionary containing stage positions, correlations and the final position.
|
:return: Results dictionary containing stage positions, correlations and the final position.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
focus_data = autofocus.looping_autofocus(dz = 1000)
|
focus_data = autofocus.looping_autofocus(dz = 1000)
|
||||||
|
|
||||||
starting_position = list(stage.position.values())
|
starting_position = list(stage.position.values())
|
||||||
|
|
@ -332,7 +324,7 @@ class RangeofMotionThing(Thing):
|
||||||
|
|
||||||
big_step = 200
|
big_step = 200
|
||||||
small_step = 20
|
small_step = 20
|
||||||
step_sizes_big = dict_generate(big_step, stream_resolution, direction)
|
step_sizes_big = generate_move_dicts(big_step, stream_resolution, direction)
|
||||||
|
|
||||||
delta = {
|
delta = {
|
||||||
'x':0,
|
'x':0,
|
||||||
|
|
@ -360,7 +352,7 @@ class RangeofMotionThing(Thing):
|
||||||
|
|
||||||
# 1 big step followed by 3 small steps
|
# 1 big step followed by 3 small steps
|
||||||
|
|
||||||
minimum_offset_small = dict_generate(
|
minimum_offset_small = generate_move_dicts(
|
||||||
small_step, stream_resolution, direction, factor=0.65
|
small_step, stream_resolution, direction, factor=0.65
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -470,11 +462,3 @@ class RangeofMotionThing(Thing):
|
||||||
|
|
||||||
return rom_results
|
return rom_results
|
||||||
|
|
||||||
@thing_property
|
|
||||||
def rom_data(self) -> Optional[Dict]:
|
|
||||||
"""The results of the last range of motion calibration that was run."""
|
|
||||||
filename = '/var/openflexure/settings/range_of_motion/settings.json'
|
|
||||||
with open(filename) as f:
|
|
||||||
rom_object = json.load(f)
|
|
||||||
|
|
||||||
return rom_object
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue