Format and linting of recentre and smart scan

This commit is contained in:
Richard Bowman 2023-12-14 16:06:05 +00:00
parent a54c972891
commit 1b6e2686be
2 changed files with 246 additions and 202 deletions

View file

@ -1,8 +1,5 @@
import numpy as np import numpy as np
import logging import logging
import matplotlib.pyplot as plt
import json
import numpy as np
import time import time
from labthings_fastapi.thing import Thing from labthings_fastapi.thing import Thing
@ -18,176 +15,175 @@ 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/")
def _gui_description():
return {
"icon": "center_focus_weak",
"forms": [
{
"name": "Run recentre",
"route": "/recentre",
"submitLabel": "Run recentre",
"isTask": True,
"schema": [
{
"fieldType": "htmlBlock",
"name": "status_display",
"label": "current_status",
"content": (
"Find the centre of the range of motion of the microscope, based on the position of the highest point."
"<br />"
"<br />"
"Requires a flat sample with fairly dense features to focus on."
)
},
]
}
]
}
def turningpoints(lst): def turningpoints(lst):
dx = np.diff(lst) dx = np.diff(lst)
return (dx[1:] * dx[:-1] < 0) return dx[1:] * dx[:-1] < 0
def unpack_autofocus(scan_data): def unpack_autofocus(scan_data):
"""Extract z, sharpness data from a move_and_measure call """Extract z, sharpness data from a move_and_measure call"""
"""
scan_data = dict(scan_data) scan_data = dict(scan_data)
jpeg_times = scan_data['jpeg_times'] jpeg_times = scan_data["jpeg_times"]
jpeg_sizes = scan_data['jpeg_sizes'] jpeg_sizes = scan_data["jpeg_sizes"]
jpeg_sizes_MB = [x / 10**3 for x in jpeg_sizes] jpeg_sizes_MB = [x / 10**3 for x in jpeg_sizes]
stage_times = scan_data['stage_times'] stage_times = scan_data["stage_times"]
stage_positions = scan_data['stage_positions'] stage_positions = scan_data["stage_positions"]
stage_height = [pos['z'] for pos in stage_positions] stage_height = [pos["z"] for pos in stage_positions]
jpeg_heights = np.interp(jpeg_times,stage_times,stage_height) jpeg_heights = np.interp(jpeg_times, stage_times, stage_height)
turning = np.where(turningpoints(jpeg_heights))[0]+1 turning = np.where(turningpoints(jpeg_heights))[0] + 1
return jpeg_heights[turning[0]:turning[1]], jpeg_sizes_MB[turning[0]:turning[1]] return jpeg_heights[turning[0] : turning[1]], jpeg_sizes_MB[turning[0] : turning[1]]
class RecentringThing(Thing): class RecentringThing(Thing):
@thing_action @thing_action
def looping_autofocus(self, autofocus: AutofocusDep, stage: StageDep, dz = 2000): def looping_autofocus(self, autofocus: AutofocusDep, stage: StageDep, dz=2000):
repeat = True repeat = True
attempts = 0 attempts = 0
while repeat and attempts < 10: while repeat and attempts < 10:
height_min = stage.position['z'] - dz / 2 height_min = stage.position["z"] - dz / 2
height_max = stage.position['z'] + dz / 2 height_max = stage.position["z"] + dz / 2
data = autofocus.fast_autofocus(dz = dz) data = autofocus.fast_autofocus(dz=dz)
heights, _ = unpack_autofocus(data) heights, _ = unpack_autofocus(data)
print(min(heights), max(heights), stage.position['z']) print(min(heights), max(heights), stage.position["z"])
time.sleep(0.3) time.sleep(0.3)
# TODO: max heights seems badly wrong! Something about turning? # TODO: max heights seems badly wrong! Something about turning?
if stage.position['z'] - height_min < dz / 5 or height_max - stage.position['z'] < dz / 5: if (
stage.position["z"] - height_min < dz / 5
or height_max - stage.position["z"] < dz / 5
):
print(heights) print(heights)
attempts += 1 attempts += 1
else: else:
repeat = False repeat = False
@thing_action @thing_action
def recentre(self, autofocus: AutofocusDep, stage: StageDep, max_steps = 15, lateral_distance = 5000): def recentre(
""" Recentre the OpenFlexure stage, based on the self,
location of the xy turning point autofocus: AutofocusDep,
stage: StageDep,
max_steps=15,
lateral_distance=5000,
):
"""Recentre the OpenFlexure stage, based on the
location of the xy turning point
Autofocuses at multiple points around the sample to Autofocuses at multiple points around the sample to
find the overall maximum (or minimum) height, which find the overall maximum (or minimum) height, which
corresponds to the centre of the stage. corresponds to the centre of the stage.
max_steps: The maximum number of moves in x or y before max_steps: The maximum number of moves in x or y before
aborting due to a poorly positioned stage or hard to focus aborting due to a poorly positioned stage or hard to focus
sample sample
lateral_distance: The xy distance between areas to check. lateral_distance: The xy distance between areas to check.
Below 3000 becomes less reliable, as focus shouldn't shift Below 3000 becomes less reliable, as focus shouldn't shift
much between these sites, making the procedure more sensitive much between these sites, making the procedure more sensitive
to noise or a failed autofocus. to noise or a failed autofocus.
""" """
max_steps = 20 max_steps = 20
dx = lateral_distance dx = lateral_distance
centre = list(stage.position.values()) centre = list(stage.position.values())
# A list of all the positions we've focused # A list of all the positions we've focused
focused_pos = [[],[]] focused_pos = [[], []]
self.looping_autofocus(autofocus, stage) self.looping_autofocus(autofocus, stage)
for direction in [0, 1]: for direction in [0, 1]:
# Start off with the current position, and moving in the positive direction # Start off with the current position, and moving in the positive direction
focused_pos[direction] = [list(stage.position.values())] focused_pos[direction] = [list(stage.position.values())]
moves = +1 moves = +1
print(centre) print(centre)
stage.move_absolute(x = centre[0], y = centre[1], z = centre[2]) stage.move_absolute(x=centre[0], y=centre[1], z=centre[2])
steps = 0 steps = 0
all_heights = [] all_heights = []
# We'll run this for x, then y # We'll run this for x, then y
while True: while True:
# If we're moving in the positive direction, we want the highest point # If we're moving in the positive direction, we want the highest point
# Otherwise, we want the lowest # Otherwise, we want the lowest
if moves > 0: if moves > 0:
starting_point = np.max(np.array(focused_pos[direction])[:,direction]) starting_point = np.max(
else: np.array(focused_pos[direction])[:, direction]
starting_point = np.min(np.array(focused_pos[direction])[:,direction]) )
else:
starting_point = np.min(
np.array(focused_pos[direction])[:, direction]
)
# Next location is an extra move in the direction we want # Next location is an extra move in the direction we want
destination = centre destination = centre
destination[direction] = starting_point + moves * dx destination[direction] = starting_point + moves * dx
stage.move_absolute(x = int(destination[0]), y = int(destination[1]), z = destination[2]) stage.move_absolute(
self.looping_autofocus(autofocus, stage) x=int(destination[0]), y=int(destination[1]), z=destination[2]
position = list(stage.position.values()) )
focused_pos[direction].append(position) self.looping_autofocus(autofocus, stage)
position = list(stage.position.values())
focused_pos[direction].append(position)
logging.info(focused_pos) logging.info(focused_pos)
steps += 1 steps += 1
if steps > max_steps: if steps > max_steps:
logging.warning("Couldn't find a suitable position. Roughly centre the stage and check your sample is suitable for autofocus") logging.warning(
"Couldn't find a suitable position. Roughly centre the stage and check your sample is suitable for autofocus"
)
break
if len(focused_pos[direction]) > 4:
all_heights = [x[2] for x in focused_pos[direction]]
direction_index = [x[direction] for x in focused_pos[direction]]
sorted_all_heights = [
x for y, x in sorted(zip(direction_index, all_heights))
]
sorted_lateral = sorted(direction_index)
quad_fit = np.polyfit(sorted_lateral, sorted_all_heights, 2)
quad_fit_func = np.poly1d(quad_fit)
turning = quad_fit_func.deriv()
turning_loc = -turning[0] / (turning[1])
logging.warning(sorted_all_heights)
if (
np.argmax(sorted_all_heights) != 0
and np.argmax(sorted_all_heights) != len(all_heights) - 1
):
logging.info(
f"Breaking because the highest point is at {np.argmax(sorted_all_heights)} in the list"
)
# plt.plot(sorted_lateral, sorted_all_heights,'.')
# plt.plot(sorted_lateral, quad_fit_func(sorted_lateral))
# plt.show()
break break
else:
if len(focused_pos[direction]) > 4: if turning_loc < np.min(sorted_lateral):
moves = -1
all_heights = [x[2] for x in focused_pos[direction]] elif turning_loc > np.max(sorted_lateral):
direction_index = [x[direction] for x in focused_pos[direction]] moves = 1
else:
sorted_all_heights = [x for y, x in sorted(zip(direction_index, all_heights))]
sorted_lateral = sorted(direction_index)
quad_fit = np.polyfit(sorted_lateral, sorted_all_heights, 2)
quad_fit_func = np.poly1d(quad_fit)
turning = quad_fit_func.deriv()
turning_loc = -turning[0]/(turning[1])
logging.warning(sorted_all_heights)
if np.argmax(sorted_all_heights) != 0 and np.argmax(sorted_all_heights) != len(all_heights) - 1:
logging.info(f'Breaking because the highest point is at {np.argmax(sorted_all_heights)} in the list')
# plt.plot(sorted_lateral, sorted_all_heights,'.') # plt.plot(sorted_lateral, sorted_all_heights,'.')
# plt.plot(sorted_lateral, quad_fit_func(sorted_lateral)) # plt.plot(sorted_lateral, quad_fit_func(sorted_lateral))
# plt.show() # plt.show()
break pass
else:
if turning_loc < np.min(sorted_lateral):
moves = -1
elif turning_loc > np.max(sorted_lateral):
moves = 1
else:
# plt.plot(sorted_lateral, sorted_all_heights,'.')
# plt.plot(sorted_lateral, quad_fit_func(sorted_lateral))
# plt.show()
pass
# Centre value is replaced by the maximum value recorded in that axis
centre[direction] = focused_pos[direction][np.argmax(all_heights)][
direction
]
stage.move_absolute(x=centre[0], y=centre[1], z=centre[2])
self.looping_autofocus(autofocus, stage)
# Centre value is replaced by the maximum value recorded in that axis logging.info(f"Centre of ROM is at {centre, stage.position['z']} \n")
centre[direction] = focused_pos[direction][np.argmax(all_heights)][direction]
stage.move_absolute(x = centre[0], y = centre[1], z = centre[2])
self.looping_autofocus(autofocus, stage)
logging.info(f"Centre of ROM is at {centre, stage.position['z']} \n") return focused_pos
return focused_pos

View file

@ -4,7 +4,6 @@ import os
import time import time
from PIL import Image from PIL import Image
from scipy.stats import norm from scipy.stats import norm
import pprint
import logging import logging
from copy import deepcopy from copy import deepcopy
@ -25,24 +24,30 @@ AutofocusDep = direct_thing_client_dependency(AutofocusThing, "/autofocus/")
RecentreStage = direct_thing_client_dependency(RecentringThing, "/auto_recentre_stage/") RecentreStage = direct_thing_client_dependency(RecentringThing, "/auto_recentre_stage/")
SettingsDep = direct_thing_client_dependency(SettingsManager, "/settings/") SettingsDep = direct_thing_client_dependency(SettingsManager, "/settings/")
def check_dist(image, stats_list, multiple): def check_dist(image, stats_list, multiple):
'''tests whether each pixel in image is within multiple stdevs (stats_list[1]) of the mean (stats_list[0]) for all three channels """tests whether each pixel in image is within multiple stdevs (stats_list[1]) of the mean (stats_list[0]) for all three channels
returns a mask with True for pixels within that range (similar to stats list) and False otherwise returns a mask with True for pixels within that range (similar to stats list) and False otherwise
''' """
return( return np.all(
np.all(np.abs(image - stats_list[np.newaxis, np.newaxis, 0, :]) < stats_list[np.newaxis, np.newaxis, 1, :] * multiple, axis = 2) np.abs(image - stats_list[np.newaxis, np.newaxis, 0, :])
< stats_list[np.newaxis, np.newaxis, 1, :] * multiple,
axis=2,
) )
def closest(current, focused_path): def closest(current, focused_path):
''' Finds the index of the closest x-y position in a list from the current position, """Finds the index of the closest x-y position in a list from the current position,
with ties split by the later element in the list (most recently taken) with ties split by the later element in the list (most recently taken)
must be float64 to deal with the huge numbers involved!''' must be float64 to deal with the huge numbers involved!"""
current_pos = np.array(current[:2], dtype='float64') current_pos = np.array(current[:2], dtype="float64")
path_pos = np.asarray(focused_path, dtype='float64').T[:2].T path_pos = np.asarray(focused_path, dtype="float64").T[:2].T
dist_2 = np.sqrt(np.sum((path_pos - current_pos)**2, axis=1, dtype='float64'), dtype = 'float64') dist_2 = np.sqrt(
np.sum((path_pos - current_pos) ** 2, axis=1, dtype="float64"), dtype="float64"
)
min_dist = np.argmin(dist_2) min_dist = np.argmin(dist_2)
mask = np.where(dist_2 == dist_2[min_dist], 1, 0) mask = np.where(dist_2 == dist_2[min_dist], 1, 0)
try: try:
@ -51,58 +56,64 @@ def closest(current, focused_path):
closest = 0 closest = 0
return closest return closest
def unpack_autofocus(scan_data): def unpack_autofocus(scan_data):
"""Extract z, sharpness data from a move_and_measure call """Extract z, sharpness data from a move_and_measure call
Data will start at `start_index`, i.e. `start_index` points are dropped Data will start at `start_index`, i.e. `start_index` points are dropped
from the beginning of the array. from the beginning of the array.
""" """
jpeg_times = scan_data['jpeg_times'] jpeg_times = scan_data["jpeg_times"]
jpeg_sizes = scan_data['jpeg_sizes'] jpeg_sizes = scan_data["jpeg_sizes"]
jpeg_sizes_MB = [x / 10**3 for x in jpeg_sizes] jpeg_sizes_MB = [x / 10**3 for x in jpeg_sizes]
stage_times = scan_data['stage_times'] stage_times = scan_data["stage_times"]
stage_positions = scan_data['stage_positions'] stage_positions = scan_data["stage_positions"]
stage_height = [pos[2] for pos in stage_positions] stage_height = [pos[2] for pos in stage_positions]
jpeg_heights = np.interp(jpeg_times,stage_times,stage_height) jpeg_heights = np.interp(jpeg_times, stage_times, stage_height)
def turningpoints(lst): def turningpoints(lst):
dx = np.diff(lst) dx = np.diff(lst)
return dx[1:] * dx[:-1] < 0 return dx[1:] * dx[:-1] < 0
turning = np.where(turningpoints(jpeg_heights))[0]+1 turning = np.where(turningpoints(jpeg_heights))[0] + 1
return jpeg_heights[turning[0] : turning[1]], jpeg_sizes_MB[turning[0] : turning[1]]
return jpeg_heights[turning[0]:turning[1]], jpeg_sizes_MB[turning[0]:turning[1]]
def limit_focus_change(prev_pos, prev_z, new_pos, new_z, limit): def limit_focus_change(prev_pos, prev_z, new_pos, new_z, limit):
# limit is the largest ratio of change in z to change in xy that's allowed # limit is the largest ratio of change in z to change in xy that's allowed
prev_xy = np.asarray(prev_pos, dtype = 'float64') prev_xy = np.asarray(prev_pos, dtype="float64")
new_xy = np.asarray(new_pos, dtype = 'float64') new_xy = np.asarray(new_pos, dtype="float64")
dist = np.sqrt(np.sum(((new_xy - prev_xy)/10**4)**2, dtype = 'float64')) dist = np.sqrt(np.sum(((new_xy - prev_xy) / 10**4) ** 2, dtype="float64"))
focus_change = abs(new_z - prev_z)/10**4 focus_change = abs(new_z - prev_z) / 10**4
if dist == 0: if dist == 0:
print(f'Not moved between {prev_pos} and {new_pos}') print(f"Not moved between {prev_pos} and {new_pos}")
movement_ratio = 0 movement_ratio = 0
else: else:
movement_ratio = np.divide(focus_change, dist, dtype='float64') movement_ratio = np.divide(focus_change, dist, dtype="float64")
# print('Movement ratio is {0} in z per lateral step. The limit is {1}'.format(round(movement_ratio, 4), round(limit,4))) # print('Movement ratio is {0} in z per lateral step. The limit is {1}'.format(round(movement_ratio, 4), round(limit,4)))
# print(f'This is the distance between {prev_pos}, {prev_z} and {new_pos}, {new_z}') # print(f'This is the distance between {prev_pos}, {prev_z} and {new_pos}, {new_z}')
if movement_ratio > limit: if movement_ratio > limit:
return 'reject' return "reject"
else: else:
return 'accept' return "accept"
def distance_to_site(current, next): def distance_to_site(current, next):
current = np.array(current, dtype='float64') current = np.array(current, dtype="float64")
next = np.array(next, dtype='float64') next = np.array(next, dtype="float64")
if (next[1] - current[1])**2 + (next[0] - current[0])**2 < 0: if (next[1] - current[1]) ** 2 + (next[0] - current[0]) ** 2 < 0:
print(f'Negative distance between {next} and {current}') print(f"Negative distance between {next} and {current}")
return np.sqrt((next[1] - current[1])**2 + (next[0] - current[0])**2, dtype='float64') return np.sqrt(
(next[1] - current[1]) ** 2 + (next[0] - current[0]) ** 2, dtype="float64"
)
# def set_template(microscope, pos): # def set_template(microscope, pos):
# microscope.move(pos) # microscope.move(pos)
@ -121,10 +132,12 @@ def distance_to_site(current, next):
# stats_list = np.vstack([mu, std]) # stats_list = np.vstack([mu, std])
# return stats_list # return stats_list
def distance_to_site(current, next): def distance_to_site(current, next):
next = np.array(next, dtype='float64') next = np.array(next, dtype="float64")
current = np.array(current, dtype='float64') current = np.array(current, dtype="float64")
return np.sqrt( (next[1] - current[1])**2 + (next[0] - current[0])**2 ) return np.sqrt((next[1] - current[1]) ** 2 + (next[0] - current[0]) ** 2)
class SmartScanThing(Thing): class SmartScanThing(Thing):
@thing_action @thing_action
@ -139,14 +152,16 @@ class SmartScanThing(Thing):
ch2 = (background_LUV.T[1]).flatten() ch2 = (background_LUV.T[1]).flatten()
ch3 = (background_LUV.T[2]).flatten() ch3 = (background_LUV.T[2]).flatten()
points = np.array([np.asarray(ch1),np.asarray(ch2),np.asarray(ch3)]).T points = np.array([np.asarray(ch1), np.asarray(ch2), np.asarray(ch3)]).T
# we get the mean and standard deviation of values in each channel # we get the mean and standard deviation of values in each channel
mu, std = np.apply_along_axis(norm.fit, 0, points) mu, std = np.apply_along_axis(norm.fit, 0, points)
stats_list = np.vstack([mu, std]) stats_list = np.vstack([mu, std])
settings.update_external_metadata(data = {"background_stats": stats_list.tolist()}) settings.update_external_metadata(
data={"background_stats": stats_list.tolist()}
)
current_keys = settings.external_metadata_in_state current_keys = settings.external_metadata_in_state
logging.info(type(current_keys)) logging.info(type(current_keys))
@ -159,9 +174,17 @@ class SmartScanThing(Thing):
logging.info(settings.external_metadata) logging.info(settings.external_metadata)
@thing_action @thing_action
def sample_scan(self, autofocus: AutofocusDep, stage: StageDep, cam: CamDep, csm: CSMDep, settings: SettingsDep, recentre: RecentreStage): def sample_scan(
self,
autofocus: AutofocusDep,
stage: StageDep,
cam: CamDep,
csm: CSMDep,
settings: SettingsDep,
recentre: RecentreStage,
):
# try: # try:
stats_list = np.array(settings.external_metadata['background_stats']) stats_list = np.array(settings.external_metadata["background_stats"])
# print(stats_list) # print(stats_list)
# except: # except:
# logging.warning("No template set") # logging.warning("No template set")
@ -183,13 +206,17 @@ class SmartScanThing(Thing):
camera_stage_mapping_matrix = csm.image_to_stage_displacement_matrix camera_stage_mapping_matrix = csm.image_to_stage_displacement_matrix
#TODO: CHECK HOW TO GET CALIBRATION IMAGE SIZE # TODO: CHECK HOW TO GET CALIBRATION IMAGE SIZE
dx = dx * arr.shape[1] / 100 dx = dx * arr.shape[1] / 100
dy = dy * arr.shape[0] / 100 dy = dy * arr.shape[0] / 100
dx = np.array(np.dot(np.array([0,dx]), camera_stage_mapping_matrix), dtype=int)[0] dx = np.array(
dy = np.array(np.dot(np.array([dy,0]), camera_stage_mapping_matrix), dtype=int)[1] np.dot(np.array([0, dx]), camera_stage_mapping_matrix), dtype=int
)[0]
dy = np.array(
np.dot(np.array([dy, 0]), camera_stage_mapping_matrix), dtype=int
)[1]
dz = 3000 dz = 3000
@ -198,7 +225,7 @@ class SmartScanThing(Thing):
print(dx, dy) print(dx, dy)
# construct a 2D scan path # construct a 2D scan path
path = [[stage.position['x'], stage.position['y']]] path = [[stage.position["x"], stage.position["y"]]]
# a list of the sites images have been taken at, and sites with a successful autofocus # a list of the sites images have been taken at, and sites with a successful autofocus
focused_path = [] focused_path = []
@ -210,7 +237,7 @@ class SmartScanThing(Thing):
start_time = time.strftime("%H_%M_%S-%d_%m_%Y") start_time = time.strftime("%H_%M_%S-%d_%m_%Y")
folder_path = r'scans' folder_path = r"scans"
if not os.path.exists(folder_path): if not os.path.exists(folder_path):
os.makedirs(folder_path) os.makedirs(folder_path)
@ -222,17 +249,19 @@ class SmartScanThing(Thing):
# move to each x-y position. in z, move to the height of the closest x-y position that successfully focused # move to each x-y position. in z, move to the height of the closest x-y position that successfully focused
while len(path) > 0: while len(path) > 0:
loc = [path[0][0], path[0][1], stage.position['z']] loc = [path[0][0], path[0][1], stage.position["z"]]
path.remove(loc[:2]) path.remove(loc[:2])
stage.move_absolute(x = int(loc[0]), y = int(loc[1]), z = int(loc[2])) stage.move_absolute(x=int(loc[0]), y=int(loc[1]), z=int(loc[2]))
if len(focused_path) > 1: if len(focused_path) > 1:
z_index = closest(loc, focused_path) z_index = closest(loc, focused_path)
# print('Moving to {0}'.format([coords[0], coords[1], focused_path[z_index][2]])) # print('Moving to {0}'.format([coords[0], coords[1], focused_path[z_index][2]]))
# print(focused_path) # print(focused_path)
stage.move_absolute(x = int(loc[0]), y = int(loc[1]), z = int(focused_path[z_index][2])) stage.move_absolute(
x=int(loc[0]), y=int(loc[1]), z=int(focused_path[z_index][2])
)
# capture an image, convert it to LUV, then make lists of the 3 channels' values # capture an image, convert it to LUV, then make lists of the 3 channels' values
img = cam.grab_jpeg() img = cam.grab_jpeg()
@ -246,43 +275,58 @@ class SmartScanThing(Thing):
# mask the image to only include pixels outside 5 stds of the mean of all three channels. # mask the image to only include pixels outside 5 stds of the mean of all three channels.
# get the percent of pixels that are in the mask (assumed sample) # get the percent of pixels that are in the mask (assumed sample)
img_mask = check_dist(img2, stats_list, 3) img_mask = check_dist(img2, stats_list, 3)
background_coverage = round(100*np.count_nonzero(img_mask)/img_mask.size, 1) background_coverage = round(
100 * np.count_nonzero(img_mask) / img_mask.size, 1
)
# if more than 92% of the image is background, treat it as background and continue # if more than 92% of the image is background, treat it as background and continue
if 100 - background_coverage < sample_coverage: if 100 - background_coverage < sample_coverage:
category = 'background' category = "background"
# fig.set_facecolor("gray") # fig.set_facecolor("gray")
else: else:
# if not, it's sample. run an autofocus and use the updated height # if not, it's sample. run an autofocus and use the updated height
new_pos = [[stage.position['x'] - dx, stage.position['y']], [stage.position['x'] + dx, stage.position['y']], [stage.position['x'], stage.position['y'] - dy], [stage.position['x'], stage.position['y'] + dy]] new_pos = [
[stage.position["x"] - dx, stage.position["y"]],
[stage.position["x"] + dx, stage.position["y"]],
[stage.position["x"], stage.position["y"] - dy],
[stage.position["x"], stage.position["y"] + dy],
]
for pos in new_pos: for pos in new_pos:
if pos not in [sublist[:2] for sublist in true_path] and pos not in path: if (
pos not in [sublist[:2] for sublist in true_path]
and pos not in path
):
path.append(pos) path.append(pos)
category = 'sample' category = "sample"
# fig.set_facecolor("pink") # fig.set_facecolor("pink")
attempts = 0 attempts = 0
while True: while True:
recentre.looping_autofocus(dz=3000) recentre.looping_autofocus(dz=3000)
current_height = stage.position['z'] current_height = stage.position["z"]
# if there have been successful autofocuses in this scan, find the closest one in x-y # if there have been successful autofocuses in this scan, find the closest one in x-y
# test if the change in z between them exceeds a ratio (indicating a failed autofocus) # test if the change in z between them exceeds a ratio (indicating a failed autofocus)
if len(focused_path) > 0: if len(focused_path) > 0:
nearest_focused_site = focused_path[closest(loc, focused_path)] nearest_focused_site = focused_path[closest(loc, focused_path)]
result = limit_focus_change(nearest_focused_site[0:2], nearest_focused_site[-1], loc[0:2], current_height, 0.3) result = limit_focus_change(
nearest_focused_site[0:2],
nearest_focused_site[-1],
loc[0:2],
current_height,
0.3,
)
# if there haven't been any previous autofocuses, we have to assume this one worked # if there haven't been any previous autofocuses, we have to assume this one worked
else: else:
result = 'accept' result = "accept"
# if the autofocus worked, take a new, more focused image. add the current position to the list of successful locations # if the autofocus worked, take a new, more focused image. add the current position to the list of successful locations
if result == 'accept': if result == "accept":
loc = list(stage.position.values()) loc = list(stage.position.values())
# img = microscope.grab_image_array() # img = microscope.grab_image_array()
focused_path.append(loc) focused_path.append(loc)
@ -291,14 +335,18 @@ class SmartScanThing(Thing):
if attempts >= 5: if attempts >= 5:
break break
else: else:
# if the autofocus was rejected, we return to the height of the closest successful autofocus. not perfect, but better than wandering out of focus # if the autofocus was rejected, we return to the height of the closest successful autofocus. not perfect, but better than wandering out of focus
print('Resetting from a previous focus position. Options are {0}, we chose {1}'.format(focused_path, nearest_focused_site)) print(
stage.move_absolute(z = int(focused_path[z_index][2])) "Resetting from a previous focus position. Options are {0}, we chose {1}".format(
focused_path, nearest_focused_site
)
)
stage.move_absolute(z=int(focused_path[z_index][2]))
attempts += 1 attempts += 1
img = cam.capture_jpeg() img = cam.capture_jpeg()
img = np.array(Image.open(img.open())) img = np.array(Image.open(img.open()))
img = cv2.resize(img, (0,0), fx = 0.5, fy = 0.5) img = cv2.resize(img, (0, 0), fx=0.5, fy=0.5)
img = Image.fromarray(img) img = Image.fromarray(img)
img.save(os.path.join(folder_path, f"rabbit_{loc[0]}_{loc[1]}.jpg")) img.save(os.path.join(folder_path, f"rabbit_{loc[0]}_{loc[1]}.jpg"))
@ -307,9 +355,9 @@ class SmartScanThing(Thing):
# add the current position to the list of all positions visited # add the current position to the list of all positions visited
true_path.append(loc) true_path.append(loc)
path = sorted(path, key = lambda x: distance_to_site(loc[:2], x)) path = sorted(path, key=lambda x: distance_to_site(loc[:2], x))
if len(true_path) > 750: if len(true_path) > 750:
break break
stage.move_absolute(x = starting_loc[0], y = starting_loc[1], z = starting_loc[2]) stage.move_absolute(x=starting_loc[0], y=starting_loc[1], z=starting_loc[2])