Loading openflexure_microscope/camera/base.py +0 −11 Original line number Diff line number Diff line Loading @@ -248,17 +248,6 @@ class BaseCamera(object): for capture in capture_list: if capture.file_exists: valid_captures.append(capture) else: if os.path.isfile(capture.metadata_file): logging.warning("Capture data missing. Deleting metadata file {}.".format(capture.metadata_file)) os.remove(capture.metadata_file) # Validate and repair captures for capture in valid_captures: # If the captures metadata file is missing if not os.path.isfile(capture.metadata_file): # Recreate the metadata file capture.save_metadata() return valid_captures Loading openflexure_microscope/camera/capture.py +57 −43 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ import logging from PIL import Image import atexit import piexif pil_formats = ['JPG', 'JPEG', 'PNG', 'TIF', 'TIFF'] thumbnail_size = (200, 150) Loading @@ -30,6 +32,30 @@ def clear_tmp(): logging.debug("Cleared {}.".format(TEMP_CAPTURE_PATH)) def pull_usercomment_dict(filepath): """ Reads UserComment Exif data from a file, and returns the contained bytes as a dictionary. Args: filepath: Path to the Exif-containing file """ exif_dict = piexif.load(filepath) if 'Exif' in exif_dict and 37510 in exif_dict['Exif']: return yaml.load(exif_dict['Exif'][37510].decode()) else: return {} def extract_with_priority(key, best_dict, backup_dict): if key in best_dict: return best_dict[key] elif key in backup_dict: logging.warning("Key {} not found in primary dictionary. Falling back to backup.".format(key)) return backup_dict[key] else: logging.error("Key {} not found in either dictionary!".format(key)) return None def capture_from_dict(capture_dict): """ Creates an instance of CaptureObject from a dictionary of capture information. Loading @@ -39,22 +65,24 @@ def capture_from_dict(capture_dict): Args: capture_dict (dict): Dictionary containing capture information """ capture = CaptureObject(create_metadata_file=False) # Create a placeholder capture # Populate capture parameters capture = CaptureObject() # Create a placeholder capture # Get inherent capture information from database capture.file = capture_dict['path'] capture.split_file_path(capture.file) capture.temporary = capture_dict['temporary'] capture.id = capture_dict['metadata']['id'] capture.timestring = capture_dict['metadata']['time'] capture.format = capture_dict['metadata']['format'] if 'custom' in capture_dict['metadata']: capture._metadata = capture_dict['metadata']['custom'] md_exif = pull_usercomment_dict(capture.file) md_database = capture_dict['metadata'] if 'tags' in capture_dict['metadata']: capture.tags = capture_dict['metadata']['tags'] # Populate capture parameters capture.id = extract_with_priority('id', md_exif, md_database) capture.timestring = extract_with_priority('time', md_exif, md_database) capture.format = extract_with_priority('format', md_exif, md_database) capture.file = capture_dict['metadata']['path'] capture.split_file_path(capture.file) capture._metadata = extract_with_priority('custom', md_exif, md_database) capture.tags = extract_with_priority('tags', md_exif, md_database) return capture Loading @@ -72,8 +100,7 @@ class CaptureObject(object): temporary: bool = False, filename: str = '', folder: str = '', fmt: str = '', create_metadata_file: bool = True) -> None: fmt: str = '') -> None: """Create a new StreamObject, to manage capture data.""" # Store a nice ID Loading Loading @@ -107,7 +134,7 @@ class CaptureObject(object): 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(create_metadata_file=create_metadata_file) self.initialise_capture() # Log if created by context manager self.context_manager = False Loading Loading @@ -136,14 +163,12 @@ class CaptureObject(object): logging.info("Cleaning up {}".format(self.id)) self.close() def initialise_capture(self, create_metadata_file=True): 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. Args: create_metadata_file (bool): Skips creating a metadata file if False (useful for captures being processed but not stored) """ self.build_file_path(self.filename, self.folder) Loading @@ -159,7 +184,6 @@ class CaptureObject(object): self.stream = self.file # Save initial metadata file if create_metadata_file: self.save_metadata() def build_file_path( Loading Loading @@ -263,13 +287,6 @@ class CaptureObject(object): # HANDLE METADATA @property def metadata_file(self) -> str: """ Path to the associated on-disk metadata YAML file. """ return "{}.yaml".format(os.path.splitext(self.file)[0]) def put_metadata(self, data: dict) -> None: """ Merge metadata from a passed dictionary into the capture metadata, and saves. Loading @@ -282,19 +299,21 @@ class CaptureObject(object): def save_metadata(self) -> None: """ Save metadata to on-disk metadata YAML file Save metadata to exif """ logging.debug("Writing metadata to file {}".format(self.metadata_file)) with open(self.metadata_file, 'w') as outfile: yaml.dump(self.metadata, outfile, default_flow_style=False) def delete_metadata(self) -> None: """ Delete on-disk metadata YAML file, if it exists """ if os.path.isfile(self.metadata_file): logging.info("Deleting file {}".format(self.metadata_file)) os.remove(self.metadata_file) if self.file_exists: logging.debug("Writing exif data to capture file") # Extract current Exif data exif_dict = piexif.load(self.file) # Serialize metadata metadata_string = yaml.safe_dump(self.metadata) # Insert metadata into exif_dict exif_dict['Exif'][piexif.ExifIFD.UserComment] = metadata_string.encode() # Convert new exif dict to exif bytes exif_bytes = piexif.dump(exif_dict) # Insert exif into file piexif.insert(exif_bytes, self.file) @property def metadata(self) -> dict: Loading Loading @@ -331,8 +350,7 @@ class CaptureObject(object): """ # Create basic state dictionary d = {'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata, 'metadata_path': self.metadata_file} d = {'path': self.file, 'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata} # Check bytestream if self.stream_exists: Loading Loading @@ -445,9 +463,6 @@ class CaptureObject(object): def delete_file(self) -> bool: """If the StreamObject has been saved, delete the file.""" if os.path.isfile(self.metadata_file): logging.info("Deleting file {}".format(self.metadata_file)) os.remove(self.metadata_file) if os.path.isfile(self.file): logging.info("Deleting file {}".format(self.file)) Loading @@ -462,7 +477,6 @@ class CaptureObject(object): logging.info("Deleting {}".format(self.id)) self.delete_stream() self.delete_file() self.delete_metadata() def shunt(self): """Demote the StreamObject from being stored in memory.""" Loading Loading
openflexure_microscope/camera/base.py +0 −11 Original line number Diff line number Diff line Loading @@ -248,17 +248,6 @@ class BaseCamera(object): for capture in capture_list: if capture.file_exists: valid_captures.append(capture) else: if os.path.isfile(capture.metadata_file): logging.warning("Capture data missing. Deleting metadata file {}.".format(capture.metadata_file)) os.remove(capture.metadata_file) # Validate and repair captures for capture in valid_captures: # If the captures metadata file is missing if not os.path.isfile(capture.metadata_file): # Recreate the metadata file capture.save_metadata() return valid_captures Loading
openflexure_microscope/camera/capture.py +57 −43 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ import logging from PIL import Image import atexit import piexif pil_formats = ['JPG', 'JPEG', 'PNG', 'TIF', 'TIFF'] thumbnail_size = (200, 150) Loading @@ -30,6 +32,30 @@ def clear_tmp(): logging.debug("Cleared {}.".format(TEMP_CAPTURE_PATH)) def pull_usercomment_dict(filepath): """ Reads UserComment Exif data from a file, and returns the contained bytes as a dictionary. Args: filepath: Path to the Exif-containing file """ exif_dict = piexif.load(filepath) if 'Exif' in exif_dict and 37510 in exif_dict['Exif']: return yaml.load(exif_dict['Exif'][37510].decode()) else: return {} def extract_with_priority(key, best_dict, backup_dict): if key in best_dict: return best_dict[key] elif key in backup_dict: logging.warning("Key {} not found in primary dictionary. Falling back to backup.".format(key)) return backup_dict[key] else: logging.error("Key {} not found in either dictionary!".format(key)) return None def capture_from_dict(capture_dict): """ Creates an instance of CaptureObject from a dictionary of capture information. Loading @@ -39,22 +65,24 @@ def capture_from_dict(capture_dict): Args: capture_dict (dict): Dictionary containing capture information """ capture = CaptureObject(create_metadata_file=False) # Create a placeholder capture # Populate capture parameters capture = CaptureObject() # Create a placeholder capture # Get inherent capture information from database capture.file = capture_dict['path'] capture.split_file_path(capture.file) capture.temporary = capture_dict['temporary'] capture.id = capture_dict['metadata']['id'] capture.timestring = capture_dict['metadata']['time'] capture.format = capture_dict['metadata']['format'] if 'custom' in capture_dict['metadata']: capture._metadata = capture_dict['metadata']['custom'] md_exif = pull_usercomment_dict(capture.file) md_database = capture_dict['metadata'] if 'tags' in capture_dict['metadata']: capture.tags = capture_dict['metadata']['tags'] # Populate capture parameters capture.id = extract_with_priority('id', md_exif, md_database) capture.timestring = extract_with_priority('time', md_exif, md_database) capture.format = extract_with_priority('format', md_exif, md_database) capture.file = capture_dict['metadata']['path'] capture.split_file_path(capture.file) capture._metadata = extract_with_priority('custom', md_exif, md_database) capture.tags = extract_with_priority('tags', md_exif, md_database) return capture Loading @@ -72,8 +100,7 @@ class CaptureObject(object): temporary: bool = False, filename: str = '', folder: str = '', fmt: str = '', create_metadata_file: bool = True) -> None: fmt: str = '') -> None: """Create a new StreamObject, to manage capture data.""" # Store a nice ID Loading Loading @@ -107,7 +134,7 @@ class CaptureObject(object): 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(create_metadata_file=create_metadata_file) self.initialise_capture() # Log if created by context manager self.context_manager = False Loading Loading @@ -136,14 +163,12 @@ class CaptureObject(object): logging.info("Cleaning up {}".format(self.id)) self.close() def initialise_capture(self, create_metadata_file=True): 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. Args: create_metadata_file (bool): Skips creating a metadata file if False (useful for captures being processed but not stored) """ self.build_file_path(self.filename, self.folder) Loading @@ -159,7 +184,6 @@ class CaptureObject(object): self.stream = self.file # Save initial metadata file if create_metadata_file: self.save_metadata() def build_file_path( Loading Loading @@ -263,13 +287,6 @@ class CaptureObject(object): # HANDLE METADATA @property def metadata_file(self) -> str: """ Path to the associated on-disk metadata YAML file. """ return "{}.yaml".format(os.path.splitext(self.file)[0]) def put_metadata(self, data: dict) -> None: """ Merge metadata from a passed dictionary into the capture metadata, and saves. Loading @@ -282,19 +299,21 @@ class CaptureObject(object): def save_metadata(self) -> None: """ Save metadata to on-disk metadata YAML file Save metadata to exif """ logging.debug("Writing metadata to file {}".format(self.metadata_file)) with open(self.metadata_file, 'w') as outfile: yaml.dump(self.metadata, outfile, default_flow_style=False) def delete_metadata(self) -> None: """ Delete on-disk metadata YAML file, if it exists """ if os.path.isfile(self.metadata_file): logging.info("Deleting file {}".format(self.metadata_file)) os.remove(self.metadata_file) if self.file_exists: logging.debug("Writing exif data to capture file") # Extract current Exif data exif_dict = piexif.load(self.file) # Serialize metadata metadata_string = yaml.safe_dump(self.metadata) # Insert metadata into exif_dict exif_dict['Exif'][piexif.ExifIFD.UserComment] = metadata_string.encode() # Convert new exif dict to exif bytes exif_bytes = piexif.dump(exif_dict) # Insert exif into file piexif.insert(exif_bytes, self.file) @property def metadata(self) -> dict: Loading Loading @@ -331,8 +350,7 @@ class CaptureObject(object): """ # Create basic state dictionary d = {'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata, 'metadata_path': self.metadata_file} d = {'path': self.file, 'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata} # Check bytestream if self.stream_exists: Loading Loading @@ -445,9 +463,6 @@ class CaptureObject(object): def delete_file(self) -> bool: """If the StreamObject has been saved, delete the file.""" if os.path.isfile(self.metadata_file): logging.info("Deleting file {}".format(self.metadata_file)) os.remove(self.metadata_file) if os.path.isfile(self.file): logging.info("Deleting file {}".format(self.file)) Loading @@ -462,7 +477,6 @@ class CaptureObject(object): logging.info("Deleting {}".format(self.id)) self.delete_stream() self.delete_file() self.delete_metadata() def shunt(self): """Demote the StreamObject from being stored in memory.""" Loading