Loading openflexure_microscope/camera/base.py +10 −6 Original line number Diff line number Diff line Loading @@ -316,18 +316,20 @@ class BaseCamera(object): if not filename: filename = generate_numbered_basename(self.images) logging.debug(filename) filename = "{}.{}".format(filename, fmt) # Generate folder base_folder = self.paths['image_tmp'] if temporary else self.paths['image'] 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, filename=filename, folder=folder, fmt=fmt) filepath=filepath) # Update capture list self.shunt_captures(self.images) Loading Loading @@ -360,18 +362,20 @@ class BaseCamera(object): if not filename: filename = generate_numbered_basename(self.videos) logging.debug(filename) filename = "{}.{}".format(filename, fmt) # Generate folder base_folder = self.paths['video_tmp'] if temporary else self.paths['video'] 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, filename=filename, folder=folder, fmt=fmt) filepath=filepath) # Update capture list self.shunt_captures(self.videos) Loading openflexure_microscope/camera/capture.py +27 −79 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ THUMBNAIL_SIZE = (200, 150) BASE_CAPTURE_PATH = os.path.join(os.path.expanduser('~'), 'micrographs') #: str: Base path to store all captures TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, 'tmp') #: str: Base path to store all temporary captures (automatically emptied) # TODO: Move these methods to a camera utilities module? def clear_tmp(): """ Removes all files in the ``TEMP_CAPTURE_PATH`` directory Loading Loading @@ -75,11 +75,9 @@ def capture_from_dict(capture_dict): """ global EXIF_FORMATS capture = CaptureObject() # Create a placeholder capture # Get inherent capture information from database capture.file = capture_dict['path'] capture.temporary = capture_dict['temporary'] capture = CaptureObject( filepath=capture_dict['path'] ) # Create a placeholder capture capture.split_file_path(capture.file) if capture.format.upper() in EXIF_FORMATS: Loading Loading @@ -111,9 +109,7 @@ class CaptureObject(object): self, write_to_file: bool = False, temporary: bool = False, filename: str = '', folder: str = '', fmt: str = '') -> None: filepath: str = '') -> None: """Create a new StreamObject, to manage capture data.""" # Store a nice ID Loading @@ -121,22 +117,15 @@ class CaptureObject(object): logging.info("Created StreamObject {}".format(self.id)) self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #: str: Timestring of capture creation time # Store file format self.format = fmt #: str: Capture data format (e.g. jpeg, h264) # Keep on disk after close by default self.temporary = temporary #: bool: Mark the capture as temporary, to be deleted as the server closes, or as resources are required # Explicitally state if capture should be written to a file, not a bytestream self.write_to_file = write_to_file #: bool: Write capture data to disk immediately, rather than to memory initially (useful for video recordings) # Create file name. Default to UUID if not filename: filename = self.id self.filename = "{}.{}".format(filename, fmt) # Create folder path self.folder = folder if not filepath: self.file = self.build_file_path() else: self.file = filepath self.split_file_path(self.file) # Dictionary for storing custom metadata self._metadata = {} #: dict: Dictionary of custom metadata to be included in metadata file Loading @@ -144,15 +133,18 @@ class CaptureObject(object): # List for storing tags self.tags = [] #: list: List of tags. Essentially just as extra custom metadata field, but useful for quick organisation # Initialise the capture stream self.initialise_capture() # Byte bytestream properties self.bytestream = io.BytesIO() # Byte bytestream that data will be written to # 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 # Object lock self.locked = False # Thumbnail (populated only for PIL captures) self.thumb_bytes = None Loading @@ -165,7 +157,7 @@ class CaptureObject(object): self.context_manager = True # Used in metadata logging.info("Rebuilding as a temporary capture...") self.initialise_capture() self.build_file_path() return self Loading @@ -174,52 +166,13 @@ class CaptureObject(object): logging.info("Cleaning up {}".format(self.id)) self.close() def initialise_capture(self): """ Initialise the capture object. Creates a file name and builds a file path, creates an in-memory byte stream for capture data, and creates a metadata file on disk. """ self.build_file_path() # Byte bytestream properties self.bytestream = io.BytesIO() # Byte bytestream that data will be written to # Set default write target if not self.write_to_file: logging.debug("Target for {} set to 'bytestream'".format(self.id)) self.stream = self.bytestream else: logging.debug("Target for {} set to 'file'".format(self.id)) self.stream = self.file # Save initial metadata file self.save_metadata() def build_file_path(self): """ Construct a full file path, based on filename, folder, and file format. Defaults to UUID. """ 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(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 # 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: raise Exception("Captures cannot be stored in a lower-level directory than {}.".format(BASE_CAPTURE_PATH)) # Create folder and file if not os.path.exists(self.filefolder): os.makedirs(self.filefolder) return os.path.join(self.folder, self.filename) # Full file name by joining given folder to given name def split_file_path(self, filepath): """ Loading @@ -230,17 +183,11 @@ class CaptureObject(object): """ self.filefolder, self.filename = os.path.split(filepath) # Split the full file path into a folder and a filename self.basename = os.path.splitext(self.filename)[0] # Split the filename out from it's file extension if not self.format: self.format = self.filename.split('.')[-1] def lock(self): """Set locked flag to True.""" self.locked = True def unlock(self): """Set locked flag to False.""" self.locked = False # Create folder and file if not os.path.exists(self.filefolder): os.makedirs(self.filefolder) @property def stream_exists(self, auto_rewind=True) -> bool: Loading Loading @@ -325,7 +272,7 @@ class CaptureObject(object): Create basic metadata dictionary from basic capture data, and any added custom metadata and tags. """ d = {'id': self.id, 'filename': self.filename, 'path': self.file, 'time': self.timestring, d = {'id': self.id, 'filename': self.filename, 'time': self.timestring, 'format': self.format, 'tags': self.tags, 'custom': self._metadata} # Add custom metadata to dictionary Loading Loading @@ -354,7 +301,7 @@ class CaptureObject(object): """ # Create basic state dictionary d = {'path': self.file, 'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata} d = {'path': self.file, 'temporary': self.temporary, 'metadata': self.metadata} # Check bytestream if self.stream_exists: Loading Loading @@ -382,6 +329,7 @@ class CaptureObject(object): if self.stream_exists: # If data bytestream contains data # Create a copy of the bytestream bytes logging.debug("STREAM EXISTS") data = io.BytesIO(self.bytestream.getbuffer()) else: # If data bytestream is empty Loading Loading
openflexure_microscope/camera/base.py +10 −6 Original line number Diff line number Diff line Loading @@ -316,18 +316,20 @@ class BaseCamera(object): if not filename: filename = generate_numbered_basename(self.images) logging.debug(filename) filename = "{}.{}".format(filename, fmt) # Generate folder base_folder = self.paths['image_tmp'] if temporary else self.paths['image'] 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, filename=filename, folder=folder, fmt=fmt) filepath=filepath) # Update capture list self.shunt_captures(self.images) Loading Loading @@ -360,18 +362,20 @@ class BaseCamera(object): if not filename: filename = generate_numbered_basename(self.videos) logging.debug(filename) filename = "{}.{}".format(filename, fmt) # Generate folder base_folder = self.paths['video_tmp'] if temporary else self.paths['video'] 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, filename=filename, folder=folder, fmt=fmt) filepath=filepath) # Update capture list self.shunt_captures(self.videos) Loading
openflexure_microscope/camera/capture.py +27 −79 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ THUMBNAIL_SIZE = (200, 150) BASE_CAPTURE_PATH = os.path.join(os.path.expanduser('~'), 'micrographs') #: str: Base path to store all captures TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, 'tmp') #: str: Base path to store all temporary captures (automatically emptied) # TODO: Move these methods to a camera utilities module? def clear_tmp(): """ Removes all files in the ``TEMP_CAPTURE_PATH`` directory Loading Loading @@ -75,11 +75,9 @@ def capture_from_dict(capture_dict): """ global EXIF_FORMATS capture = CaptureObject() # Create a placeholder capture # Get inherent capture information from database capture.file = capture_dict['path'] capture.temporary = capture_dict['temporary'] capture = CaptureObject( filepath=capture_dict['path'] ) # Create a placeholder capture capture.split_file_path(capture.file) if capture.format.upper() in EXIF_FORMATS: Loading Loading @@ -111,9 +109,7 @@ class CaptureObject(object): self, write_to_file: bool = False, temporary: bool = False, filename: str = '', folder: str = '', fmt: str = '') -> None: filepath: str = '') -> None: """Create a new StreamObject, to manage capture data.""" # Store a nice ID Loading @@ -121,22 +117,15 @@ class CaptureObject(object): logging.info("Created StreamObject {}".format(self.id)) self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #: str: Timestring of capture creation time # Store file format self.format = fmt #: str: Capture data format (e.g. jpeg, h264) # Keep on disk after close by default self.temporary = temporary #: bool: Mark the capture as temporary, to be deleted as the server closes, or as resources are required # Explicitally state if capture should be written to a file, not a bytestream self.write_to_file = write_to_file #: bool: Write capture data to disk immediately, rather than to memory initially (useful for video recordings) # Create file name. Default to UUID if not filename: filename = self.id self.filename = "{}.{}".format(filename, fmt) # Create folder path self.folder = folder if not filepath: self.file = self.build_file_path() else: self.file = filepath self.split_file_path(self.file) # Dictionary for storing custom metadata self._metadata = {} #: dict: Dictionary of custom metadata to be included in metadata file Loading @@ -144,15 +133,18 @@ class CaptureObject(object): # List for storing tags self.tags = [] #: list: List of tags. Essentially just as extra custom metadata field, but useful for quick organisation # Initialise the capture stream self.initialise_capture() # Byte bytestream properties self.bytestream = io.BytesIO() # Byte bytestream that data will be written to # 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 # Object lock self.locked = False # Thumbnail (populated only for PIL captures) self.thumb_bytes = None Loading @@ -165,7 +157,7 @@ class CaptureObject(object): self.context_manager = True # Used in metadata logging.info("Rebuilding as a temporary capture...") self.initialise_capture() self.build_file_path() return self Loading @@ -174,52 +166,13 @@ class CaptureObject(object): logging.info("Cleaning up {}".format(self.id)) self.close() def initialise_capture(self): """ Initialise the capture object. Creates a file name and builds a file path, creates an in-memory byte stream for capture data, and creates a metadata file on disk. """ self.build_file_path() # Byte bytestream properties self.bytestream = io.BytesIO() # Byte bytestream that data will be written to # Set default write target if not self.write_to_file: logging.debug("Target for {} set to 'bytestream'".format(self.id)) self.stream = self.bytestream else: logging.debug("Target for {} set to 'file'".format(self.id)) self.stream = self.file # Save initial metadata file self.save_metadata() def build_file_path(self): """ Construct a full file path, based on filename, folder, and file format. Defaults to UUID. """ 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(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 # 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: raise Exception("Captures cannot be stored in a lower-level directory than {}.".format(BASE_CAPTURE_PATH)) # Create folder and file if not os.path.exists(self.filefolder): os.makedirs(self.filefolder) return os.path.join(self.folder, self.filename) # Full file name by joining given folder to given name def split_file_path(self, filepath): """ Loading @@ -230,17 +183,11 @@ class CaptureObject(object): """ self.filefolder, self.filename = os.path.split(filepath) # Split the full file path into a folder and a filename self.basename = os.path.splitext(self.filename)[0] # Split the filename out from it's file extension if not self.format: self.format = self.filename.split('.')[-1] def lock(self): """Set locked flag to True.""" self.locked = True def unlock(self): """Set locked flag to False.""" self.locked = False # Create folder and file if not os.path.exists(self.filefolder): os.makedirs(self.filefolder) @property def stream_exists(self, auto_rewind=True) -> bool: Loading Loading @@ -325,7 +272,7 @@ class CaptureObject(object): Create basic metadata dictionary from basic capture data, and any added custom metadata and tags. """ d = {'id': self.id, 'filename': self.filename, 'path': self.file, 'time': self.timestring, d = {'id': self.id, 'filename': self.filename, 'time': self.timestring, 'format': self.format, 'tags': self.tags, 'custom': self._metadata} # Add custom metadata to dictionary Loading Loading @@ -354,7 +301,7 @@ class CaptureObject(object): """ # Create basic state dictionary d = {'path': self.file, 'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata} d = {'path': self.file, 'temporary': self.temporary, 'metadata': self.metadata} # Check bytestream if self.stream_exists: Loading Loading @@ -382,6 +329,7 @@ class CaptureObject(object): if self.stream_exists: # If data bytestream contains data # Create a copy of the bytestream bytes logging.debug("STREAM EXISTS") data = io.BytesIO(self.bytestream.getbuffer()) else: # If data bytestream is empty Loading