Merge branch 'temporary-scan-captures' into 'master'
Temporary scan captures See merge request openflexure/openflexure-microscope-server!10
This commit is contained in:
commit
941aadebfe
5 changed files with 34 additions and 92 deletions
|
|
@ -101,7 +101,7 @@ class ListAPI(MicroscopeView):
|
||||||
payload = JsonPayload(request)
|
payload = JsonPayload(request)
|
||||||
|
|
||||||
filename = payload.param('filename')
|
filename = payload.param('filename')
|
||||||
temporary = payload.param('temporary', default=True, convert=bool)
|
temporary = payload.param('temporary', default=False, convert=bool)
|
||||||
use_video_port = payload.param('use_video_port', default=False, convert=bool)
|
use_video_port = payload.param('use_video_port', default=False, convert=bool)
|
||||||
bayer = payload.param('bayer', default=True, convert=bool)
|
bayer = payload.param('bayer', default=True, convert=bool)
|
||||||
metadata = payload.param('metadata', default={}, convert=dict)
|
metadata = payload.param('metadata', default={}, convert=dict)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ except ImportError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from _thread import get_ident
|
from _thread import get_ident
|
||||||
|
|
||||||
from .capture import CaptureObject, capture_from_dict, BASE_CAPTURE_PATH
|
from .capture import CaptureObject, capture_from_dict, BASE_CAPTURE_PATH, TEMP_CAPTURE_PATH
|
||||||
from openflexure_microscope.config import USER_CONFIG_DIR
|
from openflexure_microscope.config import USER_CONFIG_DIR
|
||||||
from openflexure_microscope.utilities import entry_by_id
|
from openflexure_microscope.utilities import entry_by_id
|
||||||
from openflexure_microscope.lock import StrictLock
|
from openflexure_microscope.lock import StrictLock
|
||||||
|
|
@ -111,6 +111,8 @@ class BaseCamera(object):
|
||||||
self.paths = {
|
self.paths = {
|
||||||
'image': BASE_CAPTURE_PATH,
|
'image': BASE_CAPTURE_PATH,
|
||||||
'video': BASE_CAPTURE_PATH,
|
'video': BASE_CAPTURE_PATH,
|
||||||
|
'image_tmp': TEMP_CAPTURE_PATH,
|
||||||
|
'video_tpm': TEMP_CAPTURE_PATH
|
||||||
} #: dict: Dictionary of capture paths
|
} #: dict: Dictionary of capture paths
|
||||||
|
|
||||||
# Capture data
|
# Capture data
|
||||||
|
|
@ -292,7 +294,7 @@ class BaseCamera(object):
|
||||||
write_to_file: bool = False,
|
write_to_file: bool = False,
|
||||||
temporary: bool = True,
|
temporary: bool = True,
|
||||||
filename: str = None,
|
filename: str = None,
|
||||||
folder: str = None,
|
folder: str = "",
|
||||||
fmt: str = 'jpeg'):
|
fmt: str = 'jpeg'):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -311,10 +313,8 @@ class BaseCamera(object):
|
||||||
logging.debug(filename)
|
logging.debug(filename)
|
||||||
|
|
||||||
# Generate folder
|
# Generate folder
|
||||||
if folder:
|
base_folder = self.paths['image_tmp'] if temporary else self.paths['image']
|
||||||
folder = os.path.join(self.paths['image'], folder)
|
folder = os.path.join(base_folder, folder)
|
||||||
else:
|
|
||||||
folder = self.paths['image']
|
|
||||||
|
|
||||||
# Create capture object
|
# Create capture object
|
||||||
output = CaptureObject(
|
output = CaptureObject(
|
||||||
|
|
@ -357,10 +357,8 @@ class BaseCamera(object):
|
||||||
logging.debug(filename)
|
logging.debug(filename)
|
||||||
|
|
||||||
# Generate folder
|
# Generate folder
|
||||||
if folder:
|
base_folder = self.paths['video_tmp'] if temporary else self.paths['video']
|
||||||
folder = os.path.join(self.paths['video'], folder)
|
folder = os.path.join(base_folder, folder)
|
||||||
else:
|
|
||||||
folder = self.paths['video']
|
|
||||||
|
|
||||||
# Create capture object
|
# Create capture object
|
||||||
output = CaptureObject(
|
output = CaptureObject(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import glob
|
import glob
|
||||||
import datetime
|
import datetime
|
||||||
import yaml
|
import yaml
|
||||||
|
|
@ -25,10 +26,7 @@ def clear_tmp():
|
||||||
global TEMP_CAPTURE_PATH
|
global TEMP_CAPTURE_PATH
|
||||||
|
|
||||||
logging.info("Clearing {}...".format(TEMP_CAPTURE_PATH))
|
logging.info("Clearing {}...".format(TEMP_CAPTURE_PATH))
|
||||||
files = glob.glob('{}/*'.format(TEMP_CAPTURE_PATH))
|
shutil.rmtree(TEMP_CAPTURE_PATH)
|
||||||
for f in files:
|
|
||||||
os.remove(f)
|
|
||||||
logging.debug("Removed {}".format(f))
|
|
||||||
logging.debug("Cleared {}.".format(TEMP_CAPTURE_PATH))
|
logging.debug("Cleared {}.".format(TEMP_CAPTURE_PATH))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -81,8 +79,8 @@ def capture_from_dict(capture_dict):
|
||||||
|
|
||||||
# Get inherent capture information from database
|
# Get inherent capture information from database
|
||||||
capture.file = capture_dict['path']
|
capture.file = capture_dict['path']
|
||||||
capture.split_file_path(capture.file)
|
|
||||||
capture.temporary = capture_dict['temporary']
|
capture.temporary = capture_dict['temporary']
|
||||||
|
capture.split_file_path(capture.file)
|
||||||
|
|
||||||
if capture.format.upper() in EXIF_FORMATS:
|
if capture.format.upper() in EXIF_FORMATS:
|
||||||
md_exif = pull_usercomment_dict(capture.file)
|
md_exif = pull_usercomment_dict(capture.file)
|
||||||
|
|
@ -137,9 +135,7 @@ class CaptureObject(object):
|
||||||
filename = self.id
|
filename = self.id
|
||||||
self.filename = "{}.{}".format(filename, fmt)
|
self.filename = "{}.{}".format(filename, fmt)
|
||||||
|
|
||||||
# Create folder path. Default to BASE_CAPTURE_PATH
|
# Create folder path
|
||||||
if not folder:
|
|
||||||
folder = BASE_CAPTURE_PATH
|
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
|
|
||||||
# Dictionary for storing custom metadata
|
# Dictionary for storing custom metadata
|
||||||
|
|
@ -185,7 +181,7 @@ class CaptureObject(object):
|
||||||
file on disk.
|
file on disk.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.build_file_path(self.filename, self.folder)
|
self.build_file_path()
|
||||||
|
|
||||||
# Byte bytestream properties
|
# Byte bytestream properties
|
||||||
self.bytestream = io.BytesIO() # Byte bytestream that data will be written to
|
self.bytestream = io.BytesIO() # Byte bytestream that data will be written to
|
||||||
|
|
@ -201,43 +197,30 @@ class CaptureObject(object):
|
||||||
# Save initial metadata file
|
# Save initial metadata file
|
||||||
self.save_metadata()
|
self.save_metadata()
|
||||||
|
|
||||||
def build_file_path(
|
def build_file_path(self):
|
||||||
self,
|
|
||||||
filename: str,
|
|
||||||
folder: str):
|
|
||||||
"""
|
"""
|
||||||
Construct a full file path, based on filename, folder, and file format.
|
Construct a full file path, based on filename, folder, and file format.
|
||||||
Defaults to UUID.
|
Defaults to UUID.
|
||||||
|
|
||||||
Args:
|
|
||||||
filename (str): Filename of capture
|
|
||||||
folder (str): Directory on disk to store capture file
|
|
||||||
"""
|
"""
|
||||||
global TEMP_CAPTURE_PATH
|
global TEMP_CAPTURE_PATH
|
||||||
|
# TODO: Combine this and split_file_path, and tidy. Let the device (camera) handle folders. This should be more basic.
|
||||||
|
# TODO: Even let the base camera manage moving captures to temp folder
|
||||||
|
# This module will clear out the temp folder, but won't MOVE anything there.
|
||||||
|
# In Base cameras new_image method, the full folder is constructed. Temp folder should be inserted there.
|
||||||
|
|
||||||
self.file = os.path.join(folder, filename) # Full file name by joining given folder to given name
|
self.file = os.path.join(self.folder, self.filename) # Full file name by joining given folder to given name
|
||||||
|
|
||||||
self.split_file_path(self.file) # Split file path into folder, filename, and basename
|
self.split_file_path(self.file) # Split file path into folder, filename, and basename
|
||||||
|
|
||||||
# Check directory is a subdirectory of BASE_CAPTURE_PATH
|
# Check directory is a subdirectory of BASE_CAPTURE_PATH
|
||||||
|
# TODO: Do we need this?
|
||||||
if not os.path.commonprefix([self.file, BASE_CAPTURE_PATH]) == BASE_CAPTURE_PATH:
|
if not os.path.commonprefix([self.file, BASE_CAPTURE_PATH]) == BASE_CAPTURE_PATH:
|
||||||
raise Exception("Captures cannot be stored in a lower-level directory than {}.".format(BASE_CAPTURE_PATH))
|
raise Exception("Captures cannot be stored in a lower-level directory than {}.".format(BASE_CAPTURE_PATH))
|
||||||
|
|
||||||
# Move to tmp directory if not being kept
|
|
||||||
if self.temporary:
|
|
||||||
self.file_notmp = self.file # Store originally defined folder
|
|
||||||
self.filefolder = TEMP_CAPTURE_PATH
|
|
||||||
self.file = os.path.join(self.filefolder, self.filename)
|
|
||||||
|
|
||||||
# Create folder and file
|
# Create folder and file
|
||||||
if not os.path.exists(self.filefolder):
|
if not os.path.exists(self.filefolder):
|
||||||
os.makedirs(self.filefolder)
|
os.makedirs(self.filefolder)
|
||||||
|
|
||||||
logging.debug(self.file)
|
|
||||||
logging.debug(self.filename)
|
|
||||||
logging.debug(self.basename)
|
|
||||||
logging.debug(self.filefolder)
|
|
||||||
|
|
||||||
def split_file_path(self, filepath):
|
def split_file_path(self, filepath):
|
||||||
"""
|
"""
|
||||||
Take a full file path, and split it into separated class properties.
|
Take a full file path, and split it into separated class properties.
|
||||||
|
|
@ -453,13 +436,6 @@ class CaptureObject(object):
|
||||||
|
|
||||||
def save_file(self) -> bool:
|
def save_file(self) -> bool:
|
||||||
"""Write the StreamObjects bytestream to a file."""
|
"""Write the StreamObjects bytestream to a file."""
|
||||||
if self.temporary: # If capture is currently temporary
|
|
||||||
self.load_file() # Load data from tmp file into bytestream, if tmp file exists
|
|
||||||
self.temporary = False # Flag as kept on disk
|
|
||||||
self.file = self.file_notmp # Reset file path to non-temporary path
|
|
||||||
self.split_file_path(self.file) # Set split properties based on new path
|
|
||||||
logging.info("Moved temporary file out to {}".format(self.file))
|
|
||||||
|
|
||||||
if self.stream_exists: # If there's a bytestream to save
|
if self.stream_exists: # If there's a bytestream to save
|
||||||
with open(self.file, 'ab') as f: # Load file as bytes
|
with open(self.file, 'ab') as f: # Load file as bytes
|
||||||
logging.debug("Writing bytestream to file {}".format(self.file))
|
logging.debug("Writing bytestream to file {}".format(self.file))
|
||||||
|
|
@ -509,6 +485,7 @@ class CaptureObject(object):
|
||||||
"""Both clear the bytestream, and delete any associated on-disk data."""
|
"""Both clear the bytestream, and delete any associated on-disk data."""
|
||||||
logging.info("Closing {}".format(self.id))
|
logging.info("Closing {}".format(self.id))
|
||||||
self.delete_stream()
|
self.delete_stream()
|
||||||
|
# Delete the file from disk if temporary
|
||||||
if self.temporary:
|
if self.temporary:
|
||||||
self.delete()
|
self.delete()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class TileScanAPI(MicroscopeViewPlugin):
|
||||||
|
|
||||||
# Get params
|
# Get params
|
||||||
filename = payload.param('filename')
|
filename = payload.param('filename')
|
||||||
|
temporary = payload.param('temporary', default=False, convert=bool)
|
||||||
|
|
||||||
step_size = payload.param('step_size', default=[2000, 1500, 100], convert=list)
|
step_size = payload.param('step_size', default=[2000, 1500, 100], convert=list)
|
||||||
step_size = [int(i) for i in step_size]
|
step_size = [int(i) for i in step_size]
|
||||||
|
|
@ -37,6 +38,7 @@ class TileScanAPI(MicroscopeViewPlugin):
|
||||||
task = self.microscope.task.start(
|
task = self.microscope.task.start(
|
||||||
self.plugin.tile,
|
self.plugin.tile,
|
||||||
basename=filename,
|
basename=filename,
|
||||||
|
temporary=temporary,
|
||||||
step_size=step_size,
|
step_size=step_size,
|
||||||
grid=grid,
|
grid=grid,
|
||||||
style=style,
|
style=style,
|
||||||
|
|
@ -51,43 +53,3 @@ class TileScanAPI(MicroscopeViewPlugin):
|
||||||
|
|
||||||
# return a handle on the autofocus task
|
# return a handle on the autofocus task
|
||||||
return jsonify(task.state), 202
|
return jsonify(task.state), 202
|
||||||
|
|
||||||
|
|
||||||
class ZStackAPI(MicroscopeViewPlugin):
|
|
||||||
def post(self):
|
|
||||||
payload = JsonPayload(request)
|
|
||||||
|
|
||||||
# Get params
|
|
||||||
name = payload.param('name')
|
|
||||||
step_size = payload.param('step_size', default=100, convert=int)
|
|
||||||
steps = payload.param('steps', default=5, convert=int)
|
|
||||||
center = payload.param('center', default=True, convert=bool)
|
|
||||||
|
|
||||||
use_video_port = payload.param('use_video_port', default=False, convert=bool)
|
|
||||||
resize = payload.param('size', default=None)
|
|
||||||
if resize:
|
|
||||||
if ('width' in resize) and ('height' in resize):
|
|
||||||
resize = (int(resize['width']), int(resize['height'])) # Convert dict to tuple
|
|
||||||
else:
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
bayer = payload.param('bayer', default=True, convert=bool)
|
|
||||||
metadata = payload.param('metadata', default={}, convert=dict)
|
|
||||||
tags = payload.param('tags', default=[], convert=list)
|
|
||||||
|
|
||||||
print("Running tile scan...")
|
|
||||||
task = self.microscope.task.start(
|
|
||||||
self.plugin.stack,
|
|
||||||
basename=name,
|
|
||||||
step_size=step_size,
|
|
||||||
steps=steps,
|
|
||||||
center=center,
|
|
||||||
use_video_port=use_video_port,
|
|
||||||
resize=resize,
|
|
||||||
bayer=bayer,
|
|
||||||
metadata=metadata,
|
|
||||||
tags=tags
|
|
||||||
)
|
|
||||||
|
|
||||||
# return a handle on the autofocus task
|
|
||||||
return jsonify(task.state), 202
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import logging
|
||||||
from openflexure_microscope.camera.base import generate_basename
|
from openflexure_microscope.camera.base import generate_basename
|
||||||
from openflexure_microscope.plugins import MicroscopePlugin
|
from openflexure_microscope.plugins import MicroscopePlugin
|
||||||
|
|
||||||
from .api import TileScanAPI, ZStackAPI
|
from .api import TileScanAPI
|
||||||
|
|
||||||
def construct_grid(initial, step_sizes, n_steps, style='raster'):
|
def construct_grid(initial, step_sizes, n_steps, style='raster'):
|
||||||
"""
|
"""
|
||||||
|
|
@ -48,12 +48,12 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
|
|
||||||
api_views = {
|
api_views = {
|
||||||
'/tile': TileScanAPI,
|
'/tile': TileScanAPI,
|
||||||
'/stack': ZStackAPI,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def capture(self,
|
def capture(self,
|
||||||
basename,
|
basename,
|
||||||
scan_id,
|
scan_id,
|
||||||
|
temporary: bool = False,
|
||||||
use_video_port: bool = False,
|
use_video_port: bool = False,
|
||||||
resize: Tuple[int, int] = None,
|
resize: Tuple[int, int] = None,
|
||||||
bayer: bool = False,
|
bayer: bool = False,
|
||||||
|
|
@ -62,14 +62,14 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
|
|
||||||
# Construct a tile filename
|
# Construct a tile filename
|
||||||
filename = "{}_{}_{}_{}".format(basename, *self.microscope.stage.position)
|
filename = "{}_{}_{}_{}".format(basename, *self.microscope.stage.position)
|
||||||
foldername = "SCAN_{}".format(basename)
|
folder = "SCAN_{}".format(basename)
|
||||||
|
|
||||||
# Create output object
|
# Create output object
|
||||||
output = self.microscope.camera.new_image(
|
output = self.microscope.camera.new_image(
|
||||||
write_to_file=True,
|
write_to_file=True,
|
||||||
temporary=False,
|
temporary=temporary,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
folder=foldername)
|
folder=folder)
|
||||||
|
|
||||||
# Capture
|
# Capture
|
||||||
self.microscope.camera.capture(
|
self.microscope.camera.capture(
|
||||||
|
|
@ -94,6 +94,7 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
def tile(
|
def tile(
|
||||||
self,
|
self,
|
||||||
basename: str = None,
|
basename: str = None,
|
||||||
|
temporary: bool = False,
|
||||||
step_size: int = [2000, 1500, 100],
|
step_size: int = [2000, 1500, 100],
|
||||||
grid: list = [3, 3, 5],
|
grid: list = [3, 3, 5],
|
||||||
style='raster',
|
style='raster',
|
||||||
|
|
@ -178,6 +179,7 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
self.capture(
|
self.capture(
|
||||||
basename,
|
basename,
|
||||||
scan_id,
|
scan_id,
|
||||||
|
temporary=temporary,
|
||||||
use_video_port=use_video_port,
|
use_video_port=use_video_port,
|
||||||
resize=resize,
|
resize=resize,
|
||||||
bayer=bayer,
|
bayer=bayer,
|
||||||
|
|
@ -188,6 +190,7 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
logging.debug("Entering z-stack")
|
logging.debug("Entering z-stack")
|
||||||
self.stack(
|
self.stack(
|
||||||
basename=basename,
|
basename=basename,
|
||||||
|
temporary=temporary,
|
||||||
scan_id=scan_id,
|
scan_id=scan_id,
|
||||||
step_size=step_size[2],
|
step_size=step_size[2],
|
||||||
steps=grid[2],
|
steps=grid[2],
|
||||||
|
|
@ -212,6 +215,7 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
def stack(
|
def stack(
|
||||||
self,
|
self,
|
||||||
basename: str = None,
|
basename: str = None,
|
||||||
|
temporary: bool = False,
|
||||||
scan_id: str = None,
|
scan_id: str = None,
|
||||||
step_size: int = 100,
|
step_size: int = 100,
|
||||||
steps: int = 5,
|
steps: int = 5,
|
||||||
|
|
@ -251,6 +255,7 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
self.capture(
|
self.capture(
|
||||||
basename,
|
basename,
|
||||||
scan_id,
|
scan_id,
|
||||||
|
temporary=temporary,
|
||||||
use_video_port=use_video_port,
|
use_video_port=use_video_port,
|
||||||
resize=resize,
|
resize=resize,
|
||||||
bayer=bayer,
|
bayer=bayer,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue