Completes all necessary movements along a single axis
This commit is contained in:
parent
3d3ca9ebcf
commit
eb01aaf017
1 changed files with 188 additions and 0 deletions
188
src/openflexure_microscope_server/things/stage_measure.py
Normal file
188
src/openflexure_microscope_server/things/stage_measure.py
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
import numpy as np
|
||||||
|
import logging
|
||||||
|
import cv2
|
||||||
|
import json
|
||||||
|
from PIL import Image
|
||||||
|
import time
|
||||||
|
from typing import Annotated, Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Sequence, Tuple
|
||||||
|
from scipy.optimize import curve_fit
|
||||||
|
from camera_stage_mapping import camera_stage_tracker
|
||||||
|
from camera_stage_mapping import fft_image_tracking
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from labthings_fastapi.thing import Thing
|
||||||
|
from labthings_fastapi.dependencies.thing import direct_thing_client_dependency
|
||||||
|
from labthings_fastapi.dependencies.invocation import CancelHook, InvocationLogger, InvocationCancelledError
|
||||||
|
from labthings_fastapi.decorators import thing_action, thing_property
|
||||||
|
from .stage import StageDependency as StageDep
|
||||||
|
from labthings_sangaboard import SangaboardThing
|
||||||
|
from labthings_picamera2.thing import StreamingPiCamera2
|
||||||
|
from labthings_fastapi.types.numpy import NDArray, denumpify, DenumpifyingDict
|
||||||
|
from openflexure_microscope_server.things.autofocus import AutofocusThing
|
||||||
|
from openflexure_microscope_server.things.camera_stage_mapping import CameraStageMapper
|
||||||
|
|
||||||
|
StageDep = direct_thing_client_dependency(SangaboardThing, "/stage/")
|
||||||
|
CamDep = direct_thing_client_dependency(StreamingPiCamera2, "/camera/")
|
||||||
|
CSMDep = direct_thing_client_dependency(CameraStageMapper, "/camera_stage_mapping/")
|
||||||
|
AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
||||||
|
|
||||||
|
def dict_generate(dict_steps, stream_resolution, dir, factor = 1):
|
||||||
|
'''
|
||||||
|
Creates a single dictionary of step sizes
|
||||||
|
'''
|
||||||
|
dict = {
|
||||||
|
'x':(dict_steps/100) * stream_resolution[0] * factor * dir,
|
||||||
|
'y':(dict_steps/100) * stream_resolution[1] * factor * dir
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
|
||||||
|
def steps_generate(small_step, z_perc, big_step, dir, stream_resolution):
|
||||||
|
'''
|
||||||
|
Creates all required dictionaries of all necessary step sizes.
|
||||||
|
'''
|
||||||
|
step_sizes_big = dict_generate(big_step, stream_resolution, dir)
|
||||||
|
step_sizes_small = dict_generate(small_step, stream_resolution, dir)
|
||||||
|
minimum_offset_small = dict_generate(small_step, stream_resolution, dir, factor = 0.8)
|
||||||
|
z_steps = dict_generate(z_perc, stream_resolution, dir)
|
||||||
|
minimum_offset_z = dict_generate(z_perc, stream_resolution, dir, factor = 0.8)
|
||||||
|
|
||||||
|
return step_sizes_small, minimum_offset_small, z_steps, minimum_offset_z, step_sizes_big
|
||||||
|
|
||||||
|
class RangeofMotionThing(Thing):
|
||||||
|
def rom_axis(
|
||||||
|
self,
|
||||||
|
autofocus: AutofocusDep,
|
||||||
|
stage: StageDep,
|
||||||
|
cam: CamDep,
|
||||||
|
csm: CSMDep,
|
||||||
|
cancel: CancelHook,
|
||||||
|
logger: InvocationLogger,
|
||||||
|
axis: str,
|
||||||
|
direction: int
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Measure the range of motion in a single axis and direction.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Generate required dictionaries for step sizes and minimum offsets
|
||||||
|
stream_resolution = cam.stream_resolution
|
||||||
|
|
||||||
|
step_sizes_small, minimum_offset_small, z_steps, minimum_offset_z, step_sizes_big = steps_generate(20, 50, 200, direction, stream_resolution=stream_resolution)
|
||||||
|
|
||||||
|
delta = {
|
||||||
|
'x':0,
|
||||||
|
'y':0
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"Using the follwing steps: {step_sizes_small, minimum_offset_small, z_steps, minimum_offset_z, step_sizes_big}")
|
||||||
|
|
||||||
|
focus_data = autofocus.looping_autofocus(dz = 1000)
|
||||||
|
|
||||||
|
starting_position = list(stage.position.values())
|
||||||
|
|
||||||
|
logger.info("Moving the stage in 4 medium sized steps.")
|
||||||
|
|
||||||
|
# Medium sized steps
|
||||||
|
for loop in range(4):
|
||||||
|
image1 = cv2.resize(np.array(Image.open(cam.grab_jpeg().open())), dsize=(0,0), fx= 1, fy= 1)
|
||||||
|
if axis == 'x':
|
||||||
|
csm.move_in_image_coordinates(x = z_steps['x'], y = 0)
|
||||||
|
else:
|
||||||
|
csm.move_in_image_coordinates(x = 0, y = z_steps['y'])
|
||||||
|
focus_data = autofocus.looping_autofocus(dz = 800)
|
||||||
|
image2 = cv2.resize(np.array(Image.open(cam.grab_jpeg().open())), dsize=(0,0), fx= 1, fy= 1)
|
||||||
|
offset = [x * 1 for x in fft_image_tracking.displacement_between_images(image_0 = image1, image_1 = image2, sigma=10, fractional_threshold=0.1, pad=True)] # Units is pixels
|
||||||
|
delta['x'] = int(offset[1])
|
||||||
|
delta['y'] = int(offset[0])
|
||||||
|
logger.info(f"Offset measured as {delta[axis]}")
|
||||||
|
|
||||||
|
# 1 big step followed by 3 small steps
|
||||||
|
while np.abs(delta[axis]) > minimum_offset_small[axis]:
|
||||||
|
|
||||||
|
if axis == 'x':
|
||||||
|
csm.move_in_image_coordinates(x = step_sizes_big['x'], y = 0)
|
||||||
|
else:
|
||||||
|
csm.move_in_image_coordinates(x = 0, y = step_sizes_big['y'])
|
||||||
|
|
||||||
|
for loop in range(3):
|
||||||
|
image1 = cv2.resize(np.array(Image.open(cam.grab_jpeg().open())), dsize=(0,0), fx= 1, fy= 1)
|
||||||
|
if axis == 'x':
|
||||||
|
csm.move_in_image_coordinates(x = step_sizes_small['x'], y = 0)
|
||||||
|
else:
|
||||||
|
csm.move_in_image_coordinates(x = 0, y = step_sizes_small['y'])
|
||||||
|
focus_data = autofocus.looping_autofocus(dz = 800)
|
||||||
|
image2 = cv2.resize(np.array(Image.open(cam.grab_jpeg().open())), dsize=(0,0), fx= 1, fy= 1)
|
||||||
|
offset = [x * 1 for x in fft_image_tracking.displacement_between_images(image_0 = image1, image_1 = image2, sigma=10, fractional_threshold=0.1, pad=True)] # Units is pixels
|
||||||
|
delta['x'] = int(offset[1])
|
||||||
|
delta['y'] = int(offset[0])
|
||||||
|
logger.info(f"Offset measured as {delta[axis]}")
|
||||||
|
|
||||||
|
if np.abs(delta[axis]) < minimum_offset_small[axis]: # this means the edge has been found
|
||||||
|
logger.info(f"Edge has been found.")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Motion detection
|
||||||
|
|
||||||
|
logger.info(f"Running motion detection")
|
||||||
|
|
||||||
|
displacements = np.array([1,2,4,8,16,32,64,128,256,512,1024]) # Array of increasing step sizes
|
||||||
|
motion_minimum = 20 # minimum nuber of pixels for motion to be detected
|
||||||
|
|
||||||
|
this_motion_step = {
|
||||||
|
'x':np.zeros(np.shape(displacements)[0]),
|
||||||
|
'y':np.zeros(np.shape(displacements)[0]),
|
||||||
|
'z':0
|
||||||
|
}
|
||||||
|
|
||||||
|
this_motion_step[axis] = displacements * direction * -1
|
||||||
|
|
||||||
|
for loop in range(np.shape(displacements)[0]):
|
||||||
|
logger.info(f"Testing with step size {this_motion_step[axis][loop]}")
|
||||||
|
image1 = cv2.resize(np.array(Image.open(cam.grab_jpeg().open())), dsize=(0,0), fx= 1, fy= 1)
|
||||||
|
stage.move_relative(x = this_motion_step['x'][loop], y = this_motion_step['y'][loop], z = this_motion_step['z'])
|
||||||
|
image2 = cv2.resize(np.array(Image.open(cam.grab_jpeg().open())), dsize=(0,0), fx= 1, fy= 1)
|
||||||
|
offset = [x * 1 for x in fft_image_tracking.displacement_between_images(
|
||||||
|
image_0 = image1, image_1 = image2, sigma=10, fractional_threshold=0.1, pad=True)] # Units is pixels
|
||||||
|
delta['x'] = int(offset[1])
|
||||||
|
delta['y'] = int(offset[0])
|
||||||
|
logger.info(f"Offset measured as {np.abs(delta[axis])}")
|
||||||
|
if np.abs(delta[axis]) > motion_minimum:
|
||||||
|
logger.info("Motion detected.")
|
||||||
|
break
|
||||||
|
|
||||||
|
stage.move_absolute(x = starting_position[0], y = starting_position[1], z = starting_position[2], block_cancellation=True)
|
||||||
|
|
||||||
|
except:
|
||||||
|
logger.error("Stopping measurement because it was cancelled by the user")
|
||||||
|
stage.move_absolute(x = starting_position[0], y = starting_position[1], z = starting_position[2], block_cancellation=True)
|
||||||
|
raise Exception
|
||||||
|
return focus_data
|
||||||
|
|
||||||
|
@thing_action
|
||||||
|
def rom_main(
|
||||||
|
self,
|
||||||
|
autofocus: AutofocusDep,
|
||||||
|
stage: StageDep,
|
||||||
|
cam: CamDep,
|
||||||
|
csm: CSMDep,
|
||||||
|
cancel: CancelHook,
|
||||||
|
logger: InvocationLogger
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Measures the range of motion of the stage across the x and y axes.
|
||||||
|
"""
|
||||||
|
logger.info("Using the stage to measure the Range of Motion. Please ensure you are using a big enough sample.")
|
||||||
|
x_pos_results = self.rom_axis(
|
||||||
|
autofocus,
|
||||||
|
stage,
|
||||||
|
cam,
|
||||||
|
csm,
|
||||||
|
cancel,
|
||||||
|
logger,
|
||||||
|
axis = 'x',
|
||||||
|
direction = 1
|
||||||
|
)
|
||||||
|
return x_pos_results
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue