Skip to content

Commit b89c767

Browse files
committed
[cytation5] speed up focus by starting/stopping acquisition just once
1 parent fb9bce4 commit b89c767

File tree

1 file changed

+100
-75
lines changed

1 file changed

+100
-75
lines changed

pylabrobot/plate_reading/biotek_backend.py

Lines changed: 100 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,31 +1137,33 @@ async def _acquire_image(
11371137

11381138
assert self.imaging_config is not None, "Need to set imaging_config first"
11391139

1140-
self.cam.BeginAcquisition()
1141-
try:
1142-
num_tries = 0
1143-
while num_tries < self.imaging_config.max_image_read_attempts:
1144-
node_softwaretrigger_cmd = PySpin.CCommandPtr(nodemap.GetNode("TriggerSoftware"))
1145-
if not PySpin.IsWritable(node_softwaretrigger_cmd):
1146-
raise RuntimeError("unable to execute software trigger")
1147-
node_softwaretrigger_cmd.Execute()
1140+
num_tries = 0
1141+
while num_tries < self.imaging_config.max_image_read_attempts:
1142+
node_softwaretrigger_cmd = PySpin.CCommandPtr(nodemap.GetNode("TriggerSoftware"))
1143+
if not PySpin.IsWritable(node_softwaretrigger_cmd):
1144+
raise RuntimeError("unable to execute software trigger")
1145+
node_softwaretrigger_cmd.Execute()
11481146

1149-
try:
1150-
image_result = self.cam.GetNextImage(1000)
1151-
if not image_result.IsIncomplete():
1152-
processor = PySpin.ImageProcessor()
1153-
processor.SetColorProcessing(color_processing_algorithm)
1154-
image_converted = processor.Convert(image_result, pixel_format)
1155-
image_result.Release()
1156-
return image_converted.GetNDArray().tolist() # type: ignore
1157-
except SpinnakerException as e:
1158-
# the image is not ready yet, try again
1159-
logger.debug("Failed to get image: %s", e)
1160-
num_tries += 1
1161-
await asyncio.sleep(0.3)
1162-
raise TimeoutError("max_image_read_attempts reached")
1163-
finally:
1164-
self.cam.EndAcquisition()
1147+
try:
1148+
t0 = time.time()
1149+
image_result = self.cam.GetNextImage(1000)
1150+
t1 = time.time()
1151+
logger.debug("[cytation5] GetNextImage took %.2f seconds", t1 - t0)
1152+
if not image_result.IsIncomplete():
1153+
t0 = time.time()
1154+
processor = PySpin.ImageProcessor()
1155+
processor.SetColorProcessing(color_processing_algorithm)
1156+
image_converted = processor.Convert(image_result, pixel_format)
1157+
image_result.Release()
1158+
logger.debug("[cytation5] acquired image in %d tries", num_tries + 1)
1159+
logger.debug("[cytation5] Convert took %.2f seconds for BS", time.time() - t0)
1160+
return image_converted.GetNDArray().tolist() # type: ignore
1161+
except SpinnakerException as e:
1162+
# the image is not ready yet, try again
1163+
logger.debug("Failed to get image: %s", e)
1164+
num_tries += 1
1165+
await asyncio.sleep(0.3)
1166+
raise TimeoutError("max_image_read_attempts reached")
11651167

11661168
async def capture(
11671169
self,
@@ -1202,58 +1204,81 @@ async def capture(
12021204
if self.cam is None:
12031205
raise ValueError("Camera not initialized. Run setup(use_cam=True) first.")
12041206

1205-
await self.set_objective(objective)
1206-
await self.set_imaging_mode(mode, led_intensity=led_intensity)
1207-
await self.select(row, column)
1208-
await self.set_exposure(exposure_time)
1209-
await self.set_gain(gain)
1210-
await self.set_focus(focal_height)
1211-
1212-
def image_size(magnification: float) -> Tuple[float, float]:
1213-
# "wide fov" is an option in gen5.exe, but in reality it takes the same pictures. So we just
1214-
# simply take the wide fov option.
1215-
# um to mm (plr unit)
1216-
if magnification == 4:
1217-
return (3474 / 1000, 3474 / 1000)
1218-
if magnification == 20:
1219-
return (694 / 1000, 694 / 1000)
1220-
if magnification == 40:
1221-
return (347 / 1000, 347 / 1000)
1222-
raise ValueError(f"Don't know image size for magnification {magnification}")
1223-
1224-
if self._objective is None:
1225-
raise RuntimeError("Objective not set. Run set_objective() first.")
1226-
magnification = self._objective.magnification
1227-
img_width, img_height = image_size(magnification)
1228-
1229-
first_well = plate.get_item(0)
1230-
well_size_x, well_size_y = (first_well.get_size_x(), first_well.get_size_y())
1231-
if coverage == "full":
1232-
coverage = (
1233-
math.ceil(well_size_x / image_size(magnification)[0]),
1234-
math.ceil(well_size_y / image_size(magnification)[1]),
1235-
)
1236-
rows, cols = coverage
1237-
1238-
# Get positions, centered around enter_position
1239-
if center_position is None:
1240-
center_position = (0, 0)
1241-
# Going in a snake pattern is not faster (strangely)
1242-
positions = [
1243-
(x * img_width + center_position[0], -y * img_height + center_position[1])
1244-
for y in [i - (rows - 1) / 2 for i in range(rows)]
1245-
for x in [i - (cols - 1) / 2 for i in range(cols)]
1246-
]
1247-
1248-
images: List[Image] = []
1249-
for x_pos, y_pos in positions:
1250-
await self.set_position(x=x_pos, y=y_pos)
1251-
await asyncio.sleep(0.1)
1252-
images.append(
1253-
await self._acquire_image(
1254-
color_processing_algorithm=color_processing_algorithm, pixel_format=pixel_format
1207+
self.cam.BeginAcquisition()
1208+
try:
1209+
t0 = time.time()
1210+
await self.set_objective(objective)
1211+
t_objective = time.time()
1212+
logger.debug("[cytation5] set objective in %.2f seconds", t_objective - t0)
1213+
await self.set_imaging_mode(mode, led_intensity=led_intensity)
1214+
t_imaging_mode = time.time()
1215+
logger.debug("[cytation5] set imaging mode in %.2f seconds", t_imaging_mode - t_objective)
1216+
await self.select(row, column)
1217+
t_select = time.time()
1218+
logger.debug("[cytation5] selected well in %.2f seconds", t_select - t_imaging_mode)
1219+
await self.set_exposure(exposure_time)
1220+
t_exposure = time.time()
1221+
logger.debug("[cytation5] set exposure in %.2f seconds", t_exposure - t_select)
1222+
await self.set_gain(gain)
1223+
t_gain = time.time()
1224+
logger.debug("[cytation5] set gain in %.2f seconds", t_gain - t_exposure)
1225+
await self.set_focus(focal_height)
1226+
t_focus = time.time()
1227+
logger.debug("[cytation5] set focus in %.2f seconds", t_focus - t_gain)
1228+
1229+
def image_size(magnification: float) -> Tuple[float, float]:
1230+
# "wide fov" is an option in gen5.exe, but in reality it takes the same pictures. So we just
1231+
# simply take the wide fov option.
1232+
# um to mm (plr unit)
1233+
if magnification == 4:
1234+
return (3474 / 1000, 3474 / 1000)
1235+
if magnification == 20:
1236+
return (694 / 1000, 694 / 1000)
1237+
if magnification == 40:
1238+
return (347 / 1000, 347 / 1000)
1239+
raise ValueError(f"Don't know image size for magnification {magnification}")
1240+
1241+
if self._objective is None:
1242+
raise RuntimeError("Objective not set. Run set_objective() first.")
1243+
magnification = self._objective.magnification
1244+
img_width, img_height = image_size(magnification)
1245+
1246+
first_well = plate.get_item(0)
1247+
well_size_x, well_size_y = (first_well.get_size_x(), first_well.get_size_y())
1248+
if coverage == "full":
1249+
coverage = (
1250+
math.ceil(well_size_x / image_size(magnification)[0]),
1251+
math.ceil(well_size_y / image_size(magnification)[1]),
12551252
)
1256-
)
1253+
rows, cols = coverage
1254+
1255+
# Get positions, centered around enter_position
1256+
if center_position is None:
1257+
center_position = (0, 0)
1258+
# Going in a snake pattern is not faster (strangely)
1259+
positions = [
1260+
(x * img_width + center_position[0], -y * img_height + center_position[1])
1261+
for y in [i - (rows - 1) / 2 for i in range(rows)]
1262+
for x in [i - (cols - 1) / 2 for i in range(cols)]
1263+
]
1264+
1265+
images: List[Image] = []
1266+
for x_pos, y_pos in positions:
1267+
await self.set_position(x=x_pos, y=y_pos)
1268+
await asyncio.sleep(0.1)
1269+
t0 = time.time()
1270+
images.append(
1271+
await self._acquire_image(
1272+
color_processing_algorithm=color_processing_algorithm, pixel_format=pixel_format
1273+
)
1274+
)
1275+
t1 = time.time()
1276+
logger.debug(
1277+
"[cytation5] acquired image in %.2f seconds at position",
1278+
t1 - t0,
1279+
)
1280+
finally:
1281+
self.cam.EndAcquisition()
12571282

12581283
exposure_ms = float(self.cam.ExposureTime.GetValue()) / 1000
12591284
assert self._focal_height is not None, "Focal height not set. Run set_focus() first."

0 commit comments

Comments
 (0)