Microscope capture objects now just handle on-disk data
This commit is contained in:
parent
a832dfb2f1
commit
dc794d4df8
10 changed files with 125 additions and 311 deletions
|
|
@ -43,7 +43,7 @@ DEFAULT_LOGFILE = os.path.join(USER_CONFIG_DIR, "openflexure_microscope.log")
|
|||
if (__name__ == "__main__") or (not is_gunicorn):
|
||||
# If imported, but not by gunicorn
|
||||
print("Letting sys handle logs")
|
||||
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
else:
|
||||
# Direct standard Python logging to file and console
|
||||
root = logging.getLogger()
|
||||
|
|
@ -66,10 +66,33 @@ else:
|
|||
# Create a dummy microscope object, with no hardware attachments
|
||||
api_microscope = Microscope()
|
||||
|
||||
# Rebuild the capture list
|
||||
# TODO: Offload to a thread?
|
||||
stored_image_list = build_captures_from_exif()
|
||||
# Initialise camera
|
||||
logging.debug("Creating camera object...")
|
||||
try:
|
||||
api_camera = PiCameraStreamer()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.warning("No valid camera hardware found. Falling back to mock camera!")
|
||||
api_camera = MockStreamer()
|
||||
|
||||
# Initialise stage
|
||||
logging.debug("Creating stage object...")
|
||||
try:
|
||||
api_stage = SangaStage()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.warning("No valid stage hardware found. Falling back to mock stage!")
|
||||
api_stage = MockStage()
|
||||
|
||||
# Attach devices to microscope
|
||||
logging.debug("Attaching devices to microscope...")
|
||||
api_microscope.attach(api_camera, api_stage)
|
||||
|
||||
# Restore loaded capture array to camera object
|
||||
logging.debug("Restoring captures...")
|
||||
api_microscope.camera.images = build_captures_from_exif(api_microscope.camera.paths["default"])
|
||||
|
||||
logging.debug("Microscope successfully attached!")
|
||||
|
||||
# Generate API URI based on version from filename
|
||||
def uri(suffix, api_version, base=None):
|
||||
|
|
@ -89,48 +112,8 @@ CORS(app, resources=r"/api/*")
|
|||
# Make errors more API friendly
|
||||
handler = JSONExceptionHandler(app)
|
||||
|
||||
|
||||
# After app starts, but before first request, attach hardware to global microscope
|
||||
@app.before_first_request
|
||||
def attach_microscope():
|
||||
# Create the microscope object globally (common to all spawned server threads)
|
||||
global api_microscope, stored_image_list
|
||||
logging.debug("First request made. Populating microscope with hardware...")
|
||||
|
||||
# Initialise camera
|
||||
logging.debug("Creating camera object...")
|
||||
try:
|
||||
api_camera = PiCameraStreamer()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.warning("No valid camera hardware found. Falling back to mock camera!")
|
||||
api_camera = MockStreamer()
|
||||
|
||||
# Initialise stage
|
||||
logging.debug("Creating stage object...")
|
||||
try:
|
||||
api_stage = SangaStage()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.warning("No valid stage hardware found. Falling back to mock stage!")
|
||||
api_stage = MockStage()
|
||||
|
||||
# Attach devices to microscope
|
||||
logging.debug("Attaching devices to microscope...")
|
||||
api_microscope.attach(api_camera, api_stage)
|
||||
|
||||
# Restore loaded capture array to camera object
|
||||
logging.debug("Restoring captures...")
|
||||
if stored_image_list:
|
||||
api_microscope.camera.images = stored_image_list
|
||||
|
||||
logging.debug("Microscope successfully attached!")
|
||||
|
||||
|
||||
# WEBAPP ROUTES
|
||||
|
||||
# API ROUTES
|
||||
|
||||
# Base routes
|
||||
base_blueprint = blueprints.base.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(base_blueprint, url_prefix=uri("", "v1"))
|
||||
|
|
|
|||
|
|
@ -123,11 +123,11 @@ class ListAPI(MicroscopeView):
|
|||
# Explicitally acquire lock (prevents empty files being created if lock is unavailable)
|
||||
with self.microscope.camera.lock:
|
||||
output = self.microscope.camera.new_image(
|
||||
write_to_file=True, temporary=temporary, filename=filename
|
||||
temporary=temporary, filename=filename
|
||||
)
|
||||
|
||||
self.microscope.camera.capture(
|
||||
output, use_video_port=use_video_port, resize=resize, bayer=bayer
|
||||
output.file, use_video_port=use_video_port, resize=resize, bayer=bayer
|
||||
)
|
||||
|
||||
metadata.update(
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from .capture import CaptureObject, BASE_CAPTURE_PATH, TEMP_CAPTURE_PATH
|
||||
from .capture import CaptureObject
|
||||
from openflexure_microscope.utilities import entry_by_id
|
||||
from openflexure_microscope.lock import StrictLock
|
||||
|
||||
|
||||
BASE_CAPTURE_PATH = os.path.join(os.path.expanduser("~"), "micrographs")
|
||||
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, "tmp")
|
||||
|
||||
|
||||
def last_entry(object_list: list):
|
||||
"""Return the last entry of a list, if the list contains items."""
|
||||
if object_list: # If any images have been captured
|
||||
|
|
@ -121,11 +126,11 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
self.stream_timeout_enabled = False
|
||||
|
||||
self.state = {}
|
||||
|
||||
# TODO: Load/save these to config
|
||||
self.paths = {
|
||||
"image": BASE_CAPTURE_PATH,
|
||||
"video": BASE_CAPTURE_PATH,
|
||||
"image_tmp": TEMP_CAPTURE_PATH,
|
||||
"video_tpm": TEMP_CAPTURE_PATH,
|
||||
"default": BASE_CAPTURE_PATH,
|
||||
"temp": TEMP_CAPTURE_PATH
|
||||
}
|
||||
|
||||
# Capture data
|
||||
|
|
@ -157,10 +162,22 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
for capture_list in [self.images, self.videos]:
|
||||
for stream_object in capture_list:
|
||||
stream_object.close()
|
||||
# Empty temp directory
|
||||
self.clear_tmp()
|
||||
# Stop worker thread
|
||||
self.stop_worker()
|
||||
logging.info("Closed {}".format(self))
|
||||
|
||||
def clear_tmp(self):
|
||||
"""
|
||||
Removes all files in the temporary capture directories
|
||||
"""
|
||||
|
||||
if os.path.isdir(self.paths["temp"]):
|
||||
logging.info("Clearing {}...".format(self.paths["temp"]))
|
||||
shutil.rmtree(self.paths["temp"])
|
||||
logging.debug("Cleared {}.".format(self.paths["temp"]))
|
||||
|
||||
# START AND STOP WORKER THREAD
|
||||
|
||||
def start_worker(self, timeout: int = 5) -> bool:
|
||||
|
|
@ -244,7 +261,6 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
|
||||
def new_image(
|
||||
self,
|
||||
write_to_file: bool = True,
|
||||
temporary: bool = True,
|
||||
filename: str = None,
|
||||
folder: str = "",
|
||||
|
|
@ -252,10 +268,9 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
):
|
||||
|
||||
"""
|
||||
Create a new image capture object. Adds to the image list, and shunt all others.
|
||||
Create a new image capture object.
|
||||
|
||||
Args:
|
||||
write_to_file (bool): Should the StreamObject write to a file, or an in-memory byte stream.
|
||||
temporary (bool): Should the data be deleted after session ends.
|
||||
Creating the capture with a content manager sets this to true.
|
||||
filename (str): Name of the stored file. Defaults to timestamp.
|
||||
|
|
@ -270,16 +285,14 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
filename = "{}.{}".format(filename, fmt)
|
||||
|
||||
# Generate folder
|
||||
base_folder = self.paths["image_tmp"] if temporary else self.paths["image"]
|
||||
base_folder = self.paths["temp"] if temporary else self.paths["default"]
|
||||
folder = os.path.join(base_folder, folder)
|
||||
|
||||
# Generate file path
|
||||
filepath = os.path.join(folder, filename)
|
||||
|
||||
# Create capture object
|
||||
output = CaptureObject(
|
||||
write_to_file=write_to_file, temporary=temporary, filepath=filepath
|
||||
)
|
||||
output = CaptureObject(filepath=filepath)
|
||||
|
||||
# Update capture list
|
||||
shunt_captures(self.images)
|
||||
|
|
@ -289,7 +302,6 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
|
||||
def new_video(
|
||||
self,
|
||||
write_to_file: bool = True,
|
||||
temporary: bool = False,
|
||||
filename: str = None,
|
||||
folder: str = "",
|
||||
|
|
@ -297,10 +309,9 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
):
|
||||
|
||||
"""
|
||||
Create a new video capture object. Adds to the image list, and shunt all others.
|
||||
Create a new video capture object.
|
||||
|
||||
Args:
|
||||
write_to_file (bool): Should the StreamObject write to a file, or an in-memory byte stream.
|
||||
temporary (bool): Should the data be deleted after session ends.
|
||||
Creating the capture with a content manager sets this to true.
|
||||
filename (str): Name of the stored file. Defaults to timestamp.
|
||||
|
|
@ -315,16 +326,14 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
filename = "{}.{}".format(filename, fmt)
|
||||
|
||||
# Generate folder
|
||||
base_folder = self.paths["video_tmp"] if temporary else self.paths["video"]
|
||||
base_folder = self.paths["temp"] if temporary else self.paths["default"]
|
||||
folder = os.path.join(base_folder, folder)
|
||||
|
||||
# Generate file path
|
||||
filepath = os.path.join(folder, filename)
|
||||
|
||||
# Create capture object
|
||||
output = CaptureObject(
|
||||
write_to_file=write_to_file, temporary=temporary, filepath=filepath
|
||||
)
|
||||
output = CaptureObject(filepath=filepath)
|
||||
|
||||
# Update capture list
|
||||
shunt_captures(self.videos)
|
||||
|
|
|
|||
|
|
@ -12,32 +12,11 @@ import atexit
|
|||
from openflexure_microscope.camera import piexif
|
||||
from openflexure_microscope.camera.piexif._exceptions import InvalidImageDataError
|
||||
|
||||
"""
|
||||
Attributes:
|
||||
BASE_CAPTURE_PATH (str): Base path to store all captures
|
||||
TEMP_CAPTURE_PATH (str): Base path to store all temporary captures (automatically emptied)
|
||||
"""
|
||||
|
||||
PIL_FORMATS = ["JPG", "JPEG", "PNG", "TIF", "TIFF"]
|
||||
EXIF_FORMATS = ["JPG", "JPEG", "TIF", "TIFF"]
|
||||
THUMBNAIL_SIZE = (200, 150)
|
||||
|
||||
BASE_CAPTURE_PATH = os.path.join(os.path.expanduser("~"), "micrographs")
|
||||
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, "tmp")
|
||||
|
||||
|
||||
# TODO: Move these methods to a camera utilities module?
|
||||
def clear_tmp():
|
||||
"""
|
||||
Removes all files in the ``TEMP_CAPTURE_PATH`` directory
|
||||
"""
|
||||
global TEMP_CAPTURE_PATH
|
||||
|
||||
if os.path.isdir(TEMP_CAPTURE_PATH):
|
||||
logging.info("Clearing {}...".format(TEMP_CAPTURE_PATH))
|
||||
shutil.rmtree(TEMP_CAPTURE_PATH)
|
||||
logging.debug("Cleared {}.".format(TEMP_CAPTURE_PATH))
|
||||
|
||||
|
||||
def pull_usercomment_dict(filepath):
|
||||
"""
|
||||
|
|
@ -68,11 +47,11 @@ def make_file_list(directory, formats):
|
|||
return files
|
||||
|
||||
|
||||
def build_captures_from_exif():
|
||||
global BASE_CAPTURE_PATH, EXIF_FORMATS
|
||||
def build_captures_from_exif(capture_path):
|
||||
global EXIF_FORMATS
|
||||
|
||||
logging.debug("Reloading captures from {}...".format(BASE_CAPTURE_PATH))
|
||||
files = make_file_list(BASE_CAPTURE_PATH, EXIF_FORMATS)
|
||||
logging.debug("Reloading captures from {}...".format(capture_path))
|
||||
files = make_file_list(capture_path, EXIF_FORMATS)
|
||||
captures = []
|
||||
|
||||
for f in files:
|
||||
|
|
@ -119,27 +98,20 @@ def capture_from_exif(path, exif_dict):
|
|||
|
||||
class CaptureObject(object):
|
||||
"""
|
||||
StreamObject used to store and process capture data, and metadata.
|
||||
StreamObject used to store and process on-disk capture data, and metadata.
|
||||
|
||||
Attributes:
|
||||
timestring (str): Timestring of capture creation time
|
||||
temporary (bool): Mark the capture as temporary, to be deleted as the server closes,
|
||||
or as resources are required
|
||||
_metadata (dict): Dictionary of custom metadata to be included in metadata file
|
||||
tags (list): List of tags. Essentially just as extra custom metadata field, but useful for quick organisation
|
||||
bytestream (:py:class:`io.BytesIO`): Byte bytestream that data will be written to
|
||||
filefolder (str): Folder in which the capture file will be stored
|
||||
filename (str): Full name of the capture file
|
||||
basename (str): Filename of the capture, without a file extension
|
||||
format (str): Format of the capture data
|
||||
|
||||
Notes:
|
||||
Captures cannot be stored in a lower-level directory than BASE_CAPTURE_PATH.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, write_to_file: bool = False, temporary: bool = False, filepath: str = ""
|
||||
) -> None:
|
||||
def __init__(self, filepath) -> None:
|
||||
"""Create a new StreamObject, to manage capture data."""
|
||||
|
||||
# Store a nice ID
|
||||
|
|
@ -147,14 +119,8 @@ class CaptureObject(object):
|
|||
logging.debug("Created StreamObject {}".format(self.id))
|
||||
self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
# Keep on disk after close by default
|
||||
self.temporary = temporary
|
||||
|
||||
# Create file name. Default to UUID
|
||||
if not filepath:
|
||||
self.file = self.build_file_path()
|
||||
else:
|
||||
self.file = filepath
|
||||
self.file = filepath
|
||||
self.split_file_path(self.file)
|
||||
|
||||
# Dictionary for storing custom metadata
|
||||
|
|
@ -163,54 +129,11 @@ class CaptureObject(object):
|
|||
# List for storing tags
|
||||
self.tags = []
|
||||
|
||||
# Byte bytestream properties
|
||||
self.bytestream = io.BytesIO()
|
||||
|
||||
# Set default write target
|
||||
if not write_to_file:
|
||||
self.stream = self.bytestream
|
||||
else:
|
||||
self.stream = self.file
|
||||
|
||||
# Log if created by context manager
|
||||
self.context_manager = False
|
||||
|
||||
# Thumbnail (populated only for PIL captures)
|
||||
self.thumb_bytes = None
|
||||
|
||||
def __enter__(self):
|
||||
"""Create StreamObject in context, to auto-clean disk data."""
|
||||
logging.debug(
|
||||
"Entering context for {}. Stored files will be cleaned up automatically regardless of location.".format(
|
||||
self.id
|
||||
)
|
||||
)
|
||||
self.temporary = True # Flag file to be removed on close.
|
||||
self.context_manager = True # Used in metadata
|
||||
|
||||
logging.info("Rebuilding as a temporary capture...")
|
||||
self.build_file_path()
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
"""Exit StreamObject, and auto-clean disk data."""
|
||||
logging.info("Cleaning up {}".format(self.id))
|
||||
self.close()
|
||||
|
||||
def build_file_path(self):
|
||||
"""
|
||||
Construct a full file path, based on filename, folder, and file format.
|
||||
Defaults to UUID.
|
||||
"""
|
||||
global TEMP_CAPTURE_PATH, BASE_CAPTURE_PATH
|
||||
if self.temporary:
|
||||
base_dir = TEMP_CAPTURE_PATH
|
||||
else:
|
||||
base_dir = BASE_CAPTURE_PATH
|
||||
return os.path.join(
|
||||
base_dir, self.filename
|
||||
) # Full file name by joining given folder to given name
|
||||
def open(self, mode):
|
||||
return open(self.file, mode)
|
||||
|
||||
def split_file_path(self, filepath):
|
||||
"""
|
||||
|
|
@ -230,19 +153,7 @@ class CaptureObject(object):
|
|||
os.makedirs(self.filefolder)
|
||||
|
||||
@property
|
||||
def stream_exists(self, auto_rewind=True) -> bool:
|
||||
"""Check if capture data BytesIO bytestream exists in memory."""
|
||||
if auto_rewind:
|
||||
self.bytestream.seek(0) # Rewind the data bytes for reading
|
||||
if self.bytestream.getvalue(): # If data bytestream contains data
|
||||
self.bytestream.seek(0) # Rewind the data bytes for reading
|
||||
return True
|
||||
else:
|
||||
self.bytestream.seek(0) # Rewind the data bytes for reading
|
||||
return False
|
||||
|
||||
@property
|
||||
def file_exists(self) -> bool:
|
||||
def exists(self) -> bool:
|
||||
"""Check if capture data file exists on disk."""
|
||||
if os.path.isfile(self.file):
|
||||
return True
|
||||
|
|
@ -293,7 +204,7 @@ class CaptureObject(object):
|
|||
"""
|
||||
global EXIF_FORMATS
|
||||
|
||||
if self.format.upper() in EXIF_FORMATS and self.file_exists:
|
||||
if self.format.upper() in EXIF_FORMATS and self.exists:
|
||||
logging.debug("Writing exif data to capture file")
|
||||
# Extract current Exif data
|
||||
exif_dict = piexif.load(self.file)
|
||||
|
|
@ -331,15 +242,6 @@ class CaptureObject(object):
|
|||
"""
|
||||
return yaml.dump(self.metadata, default_flow_style=False)
|
||||
|
||||
@property
|
||||
def exists(self) -> bool:
|
||||
"""
|
||||
Check if either an in-memory byte stream or on-disk file of capture data exists.
|
||||
|
||||
If False, the capture data cannot be obtained.
|
||||
"""
|
||||
return self.stream_exists or self.file_exists
|
||||
|
||||
@property
|
||||
def state(self) -> dict:
|
||||
"""
|
||||
|
|
@ -347,13 +249,7 @@ class CaptureObject(object):
|
|||
"""
|
||||
|
||||
# Create basic state dictionary
|
||||
d = {"path": self.file, "temporary": self.temporary, "metadata": self.metadata}
|
||||
|
||||
# Check bytestream
|
||||
if self.stream_exists:
|
||||
d["bytestream"] = True
|
||||
else:
|
||||
d["bytestream"] = False
|
||||
d = {"path": self.file, "metadata": self.metadata}
|
||||
|
||||
# Combined availability of data
|
||||
if self.exists:
|
||||
|
|
@ -367,27 +263,17 @@ class CaptureObject(object):
|
|||
def data(self) -> io.BytesIO:
|
||||
"""
|
||||
Return a byte string of the capture data.
|
||||
|
||||
If the capture data exists in-memory, this will be loaded. If the data only exists
|
||||
on disk, this will automatically fall back to loading from disk.
|
||||
"""
|
||||
self.bytestream.seek(0) # Rewind the data bytes for reading
|
||||
|
||||
if self.stream_exists: # If data bytestream contains data
|
||||
if self.exists: # If data file exists
|
||||
logging.info("Opening from file {}".format(self.file))
|
||||
with open(self.file, "rb") as f:
|
||||
d = io.BytesIO(f.read()) # Load bytes from file
|
||||
d.seek(0) # Rewind loaded bytestream
|
||||
# Create a copy of the bytestream bytes
|
||||
logging.debug("STREAM EXISTS")
|
||||
data = io.BytesIO(self.bytestream.getbuffer())
|
||||
|
||||
else: # If data bytestream is empty
|
||||
if self.file_exists: # If data file exists
|
||||
logging.info("Opening from file {}".format(self.file))
|
||||
with open(self.file, "rb") as f:
|
||||
d = io.BytesIO(f.read()) # Load bytes from file
|
||||
d.seek(0) # Rewind loaded bytestream
|
||||
# Create a copy of the bytestream bytes
|
||||
data = io.BytesIO(d.getbuffer())
|
||||
else:
|
||||
data = None
|
||||
data = io.BytesIO(d.getbuffer())
|
||||
else:
|
||||
data = None
|
||||
|
||||
return data # Read and return bytes data
|
||||
|
||||
|
|
@ -418,41 +304,13 @@ class CaptureObject(object):
|
|||
data = io.BytesIO(self.thumb_bytes.getbuffer())
|
||||
return data
|
||||
|
||||
def load_file(self) -> bool:
|
||||
"""Load data stored on disk to the in-memory bytestream."""
|
||||
if self.file_exists: # If data file exists
|
||||
with open(self.file, "rb") as f:
|
||||
self.bytestream = io.BytesIO(f.read()) # Load bytes from file
|
||||
self.bytestream.seek(0) # Rewind data bytes again
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def save_file(self) -> bool:
|
||||
"""Write the StreamObjects bytestream to a file."""
|
||||
if self.stream_exists: # If there's a bytestream to save
|
||||
with open(self.file, "ab") as f: # Load file as bytes
|
||||
logging.debug("Writing bytestream to file {}".format(self.file))
|
||||
f.seek(0, 0) # Seek to the start of the file
|
||||
f.write(self.binary) # Write data bytes to file
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def save(self) -> None:
|
||||
"""Write stream to file, and save/update metadata file"""
|
||||
# Try to save the file (only succeeds if an unsaved stream exists)
|
||||
self.save_file()
|
||||
|
||||
# If a stream OR file exists, save the metadata file
|
||||
if self.exists:
|
||||
self.save_metadata()
|
||||
|
||||
def delete_stream(self):
|
||||
"""Clear the BytesIO bytestream of the StreamObject."""
|
||||
self.bytestream = io.BytesIO()
|
||||
|
||||
def delete_file(self) -> bool:
|
||||
def delete(self) -> bool:
|
||||
"""If the StreamObject has been saved, delete the file."""
|
||||
|
||||
if os.path.isfile(self.file):
|
||||
|
|
@ -463,25 +321,10 @@ class CaptureObject(object):
|
|||
else:
|
||||
return False
|
||||
|
||||
def delete(self):
|
||||
"""Entirely delete all capture data."""
|
||||
logging.info("Deleting {}".format(self.id))
|
||||
self.delete_stream()
|
||||
self.delete_file()
|
||||
|
||||
def shunt(self):
|
||||
"""Demote the StreamObject from being stored in memory."""
|
||||
if not self.file_exists: # If file doesn't already exist
|
||||
self.save() # Save bytestream to disk, if it exists
|
||||
self.delete_stream() # Delete the bytestream from memory
|
||||
# TODO: Remove
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""Both clear the bytestream, and delete any associated on-disk data."""
|
||||
logging.info("Closing {}".format(self.id))
|
||||
self.delete_stream()
|
||||
# Delete the file from disk if temporary
|
||||
if self.temporary:
|
||||
self.delete()
|
||||
|
||||
|
||||
atexit.register(clear_tmp)
|
||||
# TODO: Remove
|
||||
pass
|
||||
|
|
@ -90,24 +90,18 @@ class MockStreamer(BaseCamera):
|
|||
Args:
|
||||
config (dict): Dictionary of config parameters.
|
||||
"""
|
||||
paused_stream = False
|
||||
logging.debug("PiCameraStreamer: Applying config:")
|
||||
logging.debug("MockStreamer: Applying config:")
|
||||
logging.debug(config)
|
||||
|
||||
with self.lock:
|
||||
|
||||
# Apply valid config params to Picamera object
|
||||
# Apply valid config params to camera object
|
||||
if not self.state["record_active"]: # If not recording a video
|
||||
|
||||
# PiCameraStreamer parameters
|
||||
for key, value in config.items(): # For each provided setting
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
# If stream was paused to update config, unpause
|
||||
if paused_stream:
|
||||
logging.info("Resuming stream.")
|
||||
|
||||
else:
|
||||
raise Exception(
|
||||
"Cannot update camera config while recording is active."
|
||||
|
|
@ -135,7 +129,7 @@ class MockStreamer(BaseCamera):
|
|||
Start a new video recording, writing to a output object.
|
||||
|
||||
Args:
|
||||
output (CaptureObject/str): Output object to write data bytes to.
|
||||
output: String or file-like object to write capture data to
|
||||
fmt (str): Format of the capture.
|
||||
quality (int): Video recording quality.
|
||||
|
||||
|
|
@ -167,7 +161,7 @@ class MockStreamer(BaseCamera):
|
|||
Target object can be overridden for development purposes.
|
||||
|
||||
Args:
|
||||
output (CaptureObject/str): Output object to write data bytes to.
|
||||
output: String or file-like object to write capture data to
|
||||
use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster.
|
||||
fmt (str): Format of the capture.
|
||||
resize ((int, int)): Resize the captured image.
|
||||
|
|
@ -175,7 +169,12 @@ class MockStreamer(BaseCamera):
|
|||
"""
|
||||
|
||||
with self.lock:
|
||||
logging.warning("Capture not implemented in mock camera")
|
||||
if isinstance(output, str):
|
||||
output = open(output, 'wb')
|
||||
|
||||
output.write(self.stream.getvalue())
|
||||
|
||||
output.close()
|
||||
|
||||
# HANDLE STREAM FRAMES
|
||||
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
Start a new video recording, writing to a output object.
|
||||
|
||||
Args:
|
||||
output (CaptureObject/str): Output object to write data bytes to.
|
||||
output: String or file-like object to write capture data to
|
||||
fmt (str): Format of the capture.
|
||||
quality (int): Video recording quality.
|
||||
|
||||
|
|
@ -308,18 +308,11 @@ class PiCameraStreamer(BaseCamera):
|
|||
# Start recording method only if a current recording is not running
|
||||
if not self.state["record_active"]:
|
||||
|
||||
# If output is a StreamObject
|
||||
if isinstance(output, CaptureObject):
|
||||
# Set target to capture stream
|
||||
output_stream = output.stream
|
||||
else:
|
||||
output_stream = output
|
||||
|
||||
# Start the camera video recording on port 2
|
||||
logging.info("Recording to {}".format(output))
|
||||
|
||||
self.camera.start_recording(
|
||||
output_stream,
|
||||
output,
|
||||
format=fmt,
|
||||
splitter_port=2,
|
||||
resize=self.stream_resolution,
|
||||
|
|
@ -453,7 +446,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
Target object can be overridden for development purposes.
|
||||
|
||||
Args:
|
||||
output (CaptureObject/str): Output object to write data bytes to.
|
||||
output: String or file-like object to write capture data to
|
||||
use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster.
|
||||
fmt (str): Format of the capture.
|
||||
resize ((int, int)): Resize the captured image.
|
||||
|
|
@ -461,13 +454,6 @@ class PiCameraStreamer(BaseCamera):
|
|||
"""
|
||||
|
||||
with self.lock:
|
||||
# If output is a StreamObject
|
||||
if isinstance(output, CaptureObject):
|
||||
# Set target to capture stream
|
||||
output_stream = output.stream
|
||||
else:
|
||||
output_stream = output
|
||||
|
||||
logging.info("Capturing to {}".format(output))
|
||||
|
||||
# Set resolution and stop stream recording if necessary
|
||||
|
|
@ -475,7 +461,7 @@ class PiCameraStreamer(BaseCamera):
|
|||
self.stop_stream_recording()
|
||||
|
||||
self.camera.capture(
|
||||
output_stream,
|
||||
output,
|
||||
format=fmt,
|
||||
quality=100,
|
||||
resize=resize,
|
||||
|
|
|
|||
|
|
@ -71,12 +71,12 @@ class ScanPlugin(MicroscopePlugin):
|
|||
|
||||
# Create output object
|
||||
output = self.microscope.camera.new_image(
|
||||
write_to_file=True, temporary=temporary, filename=filename, folder=folder
|
||||
temporary=temporary, filename=filename, folder=folder
|
||||
)
|
||||
|
||||
# Capture
|
||||
self.microscope.camera.capture(
|
||||
output, use_video_port=use_video_port, resize=resize, bayer=bayer
|
||||
output.file, use_video_port=use_video_port, resize=resize, bayer=bayer
|
||||
)
|
||||
|
||||
# Affix metadata
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue