Parallelise capture of images and the next move
This commit is contained in:
parent
6e4e79f2d2
commit
4a18ef729a
1 changed files with 59 additions and 31 deletions
|
|
@ -14,6 +14,7 @@ from scipy.stats import norm
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from subprocess import CompletedProcess, Popen, PIPE, SubprocessError, run
|
from subprocess import CompletedProcess, Popen, PIPE, SubprocessError, run
|
||||||
|
from threading import Event, Thread
|
||||||
import glob
|
import glob
|
||||||
import zipfile
|
import zipfile
|
||||||
import json
|
import json
|
||||||
|
|
@ -514,11 +515,21 @@ class SmartScanThing(Thing):
|
||||||
with open(os.path.join(images_folder, 'scan_inputs.json'), 'w', encoding='utf-8') as f:
|
with open(os.path.join(images_folder, 'scan_inputs.json'), 'w', encoding='utf-8') as f:
|
||||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
# Make the initial move (which probably does nothing)
|
# At the start of the loop, we simultaneously capture an image and move to the next scan point.
|
||||||
# NB we will move to the next scan point at the **end** of the loop, so that it can
|
# We skip capturing on the first run, because we've not focused yet - and also we skip capturing if
|
||||||
# happen concurrently with saving the images.
|
# it looks like background.
|
||||||
loc = self.move_to_next_point(stage, logger, path=path, focused_path=focused_path)
|
capture_thread = None
|
||||||
while len(path) > 0:
|
while len(path) > 0:
|
||||||
|
loc = self.move_to_next_point(stage, logger, path=path, focused_path=focused_path)
|
||||||
|
if capture_thread: # wait for the previous capture to be saved
|
||||||
|
capture_thread.join()
|
||||||
|
|
||||||
|
if not self.preview_stitch_running():
|
||||||
|
self.preview_stitch_start(images_folder)
|
||||||
|
if self.stitch_automatically:
|
||||||
|
if not self.correlate_running():
|
||||||
|
self.correlate_start(images_folder, overlap=overlap)
|
||||||
|
|
||||||
ensure_free_disk_space(scan_folder)
|
ensure_free_disk_space(scan_folder)
|
||||||
|
|
||||||
# Check if the image is background
|
# Check if the image is background
|
||||||
|
|
@ -530,7 +541,9 @@ class SmartScanThing(Thing):
|
||||||
# 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 not image_is_sample:
|
if not image_is_sample:
|
||||||
logger.info(f"Skipping {stage.position} as it is {round(background_detect.background_fraction(),0)}% background.")
|
logger.info(f"Skipping {stage.position} as it is {round(background_detect.background_fraction(),0)}% background.")
|
||||||
|
capture_image = False
|
||||||
else:
|
else:
|
||||||
|
capture_image = True
|
||||||
# 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 = [
|
new_pos = [
|
||||||
[stage.position["x"] - dx, stage.position["y"]],
|
[stage.position["x"] - dx, stage.position["y"]],
|
||||||
|
|
@ -581,40 +594,55 @@ class SmartScanThing(Thing):
|
||||||
)
|
)
|
||||||
stage.move_absolute(z=int(loc[2]))
|
stage.move_absolute(z=int(loc[2]))
|
||||||
attempts += 1
|
attempts += 1
|
||||||
|
|
||||||
|
|
||||||
|
# Acquire the image in a thread, and continue once it's acquired (i.e. leave saving in the background)
|
||||||
|
acquired = Event()
|
||||||
name = f"image_{loc[0]}_{loc[1]}.jpg"
|
name = f"image_{loc[0]}_{loc[1]}.jpg"
|
||||||
# img = Image.open(cam.capture_jpeg(resolution="full").open())
|
def capture_and_save(): #cam: CamDep, logger: InvocationLogger, acquired: Event, name: str, images_folder: str, raw_images_folder: str) -> None:
|
||||||
jpegblob = cam.capture_jpeg(resolution="full")
|
"""Capture an image and save it to disk
|
||||||
jpegblob.save(os.path.join(raw_images_folder, name))
|
|
||||||
img = Image.open(jpegblob.open())
|
This will set the event `acquired` once the image has been acquired, so
|
||||||
exif = img.info['exif']
|
that the stage may be moved while it's saved.
|
||||||
width, height = img.size
|
"""
|
||||||
img = img.resize((int(width*0.5), int(height*0.5)))
|
capture_start = time.time()
|
||||||
|
jpegblob = cam.capture_jpeg(resolution="full")
|
||||||
img_width, _ = img.size
|
acquired.set()
|
||||||
|
acquisition_time = time.time() - capture_start
|
||||||
logger.info(f"Saving {name}")
|
jpegblob.save(os.path.join(raw_images_folder, name))
|
||||||
img.save(
|
img = Image.open(jpegblob.open())
|
||||||
os.path.join(images_folder, name),
|
exif = img.info['exif']
|
||||||
exif=exif,
|
width, height = img.size
|
||||||
quality=95,
|
img = img.resize((int(width*0.5), int(height*0.5)))
|
||||||
subsampling=0
|
logger.info(f"Saving {name}")
|
||||||
|
img.save(
|
||||||
|
os.path.join(images_folder, name),
|
||||||
|
exif=exif,
|
||||||
|
quality=95,
|
||||||
|
subsampling=0
|
||||||
|
)
|
||||||
|
save_time = time.time() - acquisition_time - capture_start
|
||||||
|
logger.info(f"Acquired {name} in {acquisition_time}s then {save_time}s saving to disk")
|
||||||
|
capture_thread = Thread(
|
||||||
|
target=capture_and_save,
|
||||||
|
#kwargs={
|
||||||
|
# "cam": cam,
|
||||||
|
# "logger": logger,
|
||||||
|
# "acquired": acquired,
|
||||||
|
# "name": name,
|
||||||
|
# "images_folder": images_folder,
|
||||||
|
# "raw_images_folder": raw_images_folder,
|
||||||
|
#}
|
||||||
)
|
)
|
||||||
|
capture_thread.start()
|
||||||
|
acquired.wait() # wait until the image is acquired
|
||||||
positions.append(loc[:2])
|
positions.append(loc[:2])
|
||||||
names.append(name)
|
names.append(name)
|
||||||
|
|
||||||
if not self.preview_stitch_running():
|
|
||||||
self.preview_stitch_start(images_folder)
|
|
||||||
if self.stitch_automatically:
|
|
||||||
if not self.correlate_running():
|
|
||||||
self.correlate_start(images_folder, overlap=overlap)
|
|
||||||
|
|
||||||
# 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)
|
||||||
|
|
||||||
if len(names) > 1:
|
#if len(names) > 1:
|
||||||
generate_config(images_folder, positions, names, CSM, csm_calibration_width, img_width, logger)
|
# generate_config(images_folder, positions, names, CSM, csm_calibration_width, img_width, logger)
|
||||||
|
|
||||||
temp_path = []
|
temp_path = []
|
||||||
|
|
||||||
|
|
@ -623,9 +651,7 @@ class SmartScanThing(Thing):
|
||||||
temp_path.append(i)
|
temp_path.append(i)
|
||||||
else:
|
else:
|
||||||
logger.info(f'Rejected moving to {i} as it is out of range')
|
logger.info(f'Rejected moving to {i} as it is out of range')
|
||||||
|
|
||||||
path = temp_path.copy()
|
path = temp_path.copy()
|
||||||
|
|
||||||
path = sorted(path, key=lambda x: (steps_from_centre(x, true_path[0][:2], dx, dy), distance_to_site(loc[:2], x)))
|
path = sorted(path, key=lambda x: (steps_from_centre(x, true_path[0][:2], dx, dy), distance_to_site(loc[:2], x)))
|
||||||
|
|
||||||
except InvocationCancelledError:
|
except InvocationCancelledError:
|
||||||
|
|
@ -645,6 +671,8 @@ class SmartScanThing(Thing):
|
||||||
)
|
)
|
||||||
raise e
|
raise e
|
||||||
finally:
|
finally:
|
||||||
|
if capture_thread:
|
||||||
|
capture_thread.join()
|
||||||
try:
|
try:
|
||||||
logger.info("Returning to starting position.")
|
logger.info("Returning to starting position.")
|
||||||
if starting_position is not None:
|
if starting_position is not None:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue