Merge branch 'temporary-scan-captures' into 'master'

Temporary scan captures

See merge request openflexure/openflexure-microscope-server!10
This commit is contained in:
Joel Collins 2019-04-14 09:00:21 +00:00
commit 941aadebfe
5 changed files with 34 additions and 92 deletions

View file

@ -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)

View file

@ -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(

View file

@ -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()

View file

@ -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

View file

@ -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,