Loading openflexure_microscope/api/app.py +11 −5 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ from openflexure_microscope.camera.pi import StreamingCamera from openflexure_microscope.stage.sanga import SangaStage from openflexure_microscope.stage.mock import MockStage from openflexure_microscope.camera.capture import build_captures_from_exif from openflexure_microscope.config import USER_CONFIG_DIR import atexit Loading Loading @@ -58,6 +60,10 @@ else: # Create a dummy microscope object, with no hardware attachments api_microscope = Microscope(None, None) # Rebuild the capture list # TODO: Offload to a thread? stored_image_list = build_captures_from_exif() # Generate API URI based on version from filename def uri(suffix, api_version, base=None): if not base: Loading @@ -81,7 +87,7 @@ handler = JSONExceptionHandler(app) @app.before_first_request def attach_microscope(): # Create the microscope object globally (common to all spawned server threads) global api_microscope global api_microscope, capture_list logging.debug("First request made. Populating microscope with hardware...") logging.debug("Creating camera object...") Loading @@ -101,6 +107,10 @@ def attach_microscope(): api_stage ) logging.debug("Restoring captures...") if stored_image_list: api_microscope.camera.images = stored_image_list logging.debug("Microscope successfully attached!") Loading Loading @@ -153,10 +163,6 @@ def cleanup(): if api_microscope.rc: api_microscope.rc.save(backup=True) # Update capture DB and metadata files before exiting if api_microscope.camera: api_microscope.camera.store_captures() # Close down the microscope api_microscope.close() logging.debug("App teardown complete.") Loading openflexure_microscope/api/v1/blueprints/task.py +0 −2 Original line number Diff line number Diff line Loading @@ -44,8 +44,6 @@ class TaskListAPI(MicroscopeView): :>header Content-Type: application/json """ print(self.microscope.task.state) data = self.microscope.task.state return jsonify(data) Loading openflexure_microscope/camera/base.py +1 −59 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ except ImportError: except ImportError: from _thread import get_ident from .capture import CaptureObject, capture_from_dict, BASE_CAPTURE_PATH, TEMP_CAPTURE_PATH from .capture import CaptureObject, BASE_CAPTURE_PATH, TEMP_CAPTURE_PATH from openflexure_microscope.config import USER_CONFIG_DIR from openflexure_microscope.utilities import entry_by_id from openflexure_microscope.lock import StrictLock Loading Loading @@ -119,14 +119,6 @@ class BaseCamera(object): self.images = [] #: list: List of image capture objects self.videos = [] #: list: List of video recording objects # Capture data files self.images_db = os.path.join(USER_CONFIG_DIR, "images_db.yaml") self.videos_db = os.path.join(USER_CONFIG_DIR, "videos_db.yaml") # Load captures from persistent db self.images = self.load_capture_db(self.images_db) self.videos = self.load_capture_db(self.videos_db) def apply_config(self, config): """Update settings from a config dictionary""" self.config.update(config) Loading Loading @@ -244,50 +236,6 @@ class BaseCamera(object): """Return a video StreamObject with a matching ID.""" return entry_by_id(video_id, self.videos) # MANAGE CAPTURE DATABASE def save_capture_db(self, capture_list, db_path): capture_state = [capture.state for capture in capture_list] with open(db_path, 'w') as outfile: yaml.safe_dump(capture_state, outfile) def load_capture_db(self, db_path): if os.path.isfile(db_path): # Load list of capture dictionary representations from db_path with open(db_path, 'r') as infile: try: capture_dict_list = yaml.load(infile) except yaml.scanner.ScannerError as e: logging.error(e) logging.warning('Emptying malformed database. Your captures will not be deleted, but may not appear in the gallery.') capture_dict_list = [] # Create capture object list, and validate captures capture_list = [] if capture_dict_list: for capture_dict in capture_dict_list: if 'path' in capture_dict and os.path.isfile(capture_dict['path']): capture_list.append(capture_from_dict(capture_dict)) return capture_list else: return [] def store_captures(self): # Save metadata files #for image in self.images: # image.save_metadata() #for video in self.videos: # video.save_metadata() # Update capture database self.save_capture_db(self.images, self.images_db) self.save_capture_db(self.videos, self.videos_db) # CREATING NEW CAPTURES def shunt_captures(self, target_list: list): Loading Loading @@ -335,9 +283,6 @@ class BaseCamera(object): self.shunt_captures(self.images) self.images.append(output) # Update capture database self.save_capture_db(self.images, self.images_db) return output def new_video( Loading Loading @@ -381,9 +326,6 @@ class BaseCamera(object): self.shunt_captures(self.videos) self.videos.append(output) # Update capture database self.save_capture_db(self.videos, self.videos_db) return output # WORKER THREAD Loading openflexure_microscope/camera/capture.py +39 −39 Original line number Diff line number Diff line Loading @@ -18,6 +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(): """ Loading @@ -38,67 +39,66 @@ def pull_usercomment_dict(filepath): 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): """ Extracts a value from one of two dictionaries, prioritising one over the other. Second dictionary is used only if the key doesn't exist in the first. def make_file_list(directory, formats): files = [] for fmt in formats: files.extend(glob.glob('{}/**/*.{}'.format(directory, fmt.lower()), recursive=True)) Args: key: Key to search for best_dict: Ideal dictionary to use backup_dict: Fallback dictionary logging.info("{} capture files found on disk".format(len(files))) """ 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 return files def build_captures_from_exif(): global BASE_CAPTURE_PATH, EXIF_FORMATS logging.debug("Reloading captures from {}...".format(BASE_CAPTURE_PATH)) files = make_file_list(BASE_CAPTURE_PATH, EXIF_FORMATS) captures = [] for f in files: logging.debug("Reloading capture {}...".format(f)) exif = pull_usercomment_dict(f) capture = capture_from_exif(f, exif) captures.append(capture) logging.info("{} capture files successfully reloaded".format(len(captures))) def capture_from_dict(capture_dict): return captures def capture_from_exif(path, exif_dict): """ Creates an instance of CaptureObject from a dictionary of capture information. This is used when reloading the API server, to restore captures created in the previous session. Args: capture_dict (dict): Dictionary containing capture information exif_dict (dict): Dictionary containing capture information """ global EXIF_FORMATS # Create a placeholder capture capture = CaptureObject( filepath=capture_dict['path'] ) # Create a placeholder capture capture.split_file_path(capture.file) capture.temporary = capture_dict['temporary'] filepath=path ) if capture.format.upper() in EXIF_FORMATS: md_exif = pull_usercomment_dict(capture.file) else: logging.debug("Unsupported format for EXIF data. Skipping.") md_exif = {} md_database = capture_dict['metadata'] # Build file path information capture.split_file_path(capture.file) # 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.id = exif_dict['id'] capture.timestring = exif_dict['time'] capture.format = exif_dict['format'] capture._metadata = extract_with_priority('custom', md_exif, md_database) capture.tags = extract_with_priority('tags', md_exif, md_database) capture._metadata = exif_dict['custom'] capture.tags = exif_dict['tags'] return capture Loading @@ -119,7 +119,7 @@ class CaptureObject(object): # Store a nice ID self.id = uuid.uuid4().hex #: str: Unique capture ID logging.info("Created StreamObject {}".format(self.id)) logging.debug("Created StreamObject {}".format(self.id)) self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #: str: Timestring of capture creation time # Keep on disk after close by default Loading openflexure_microscope/camera/set_picamera_gain.py +6 −6 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ import picamera from picamera import mmal, mmalobj, exc from picamera.mmalobj import to_rational import time import logging MMAL_PARAMETER_ANALOG_GAIN = mmal.MMAL_PARAMETER_GROUP_CAMERA + 0x59 MMAL_PARAMETER_DIGITAL_GAIN = mmal.MMAL_PARAMETER_GROUP_CAMERA + 0x5A Loading Loading @@ -51,11 +51,11 @@ if __name__ == "__main__": # fix the shutter speed cam.shutter_speed = cam.exposure_speed print("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) logging.info("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) print("Attempting to set analogue gain to 1") logging.info("Attempting to set analogue gain to 1") set_analog_gain(cam, 1) print("Attempting to set digital gain to 1") logging.info("Attempting to set digital gain to 1") set_digital_gain(cam, 1) # The old code is left in here in case it is a useful example... #ret = mmal.mmal_port_parameter_set_rational(cam._camera.control._port, Loading @@ -65,9 +65,9 @@ if __name__ == "__main__": try: while True: print("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) logging.info("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) time.sleep(1) except KeyboardInterrupt: print("Stopping...") logging.info("Stopping...") Loading
openflexure_microscope/api/app.py +11 −5 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ from openflexure_microscope.camera.pi import StreamingCamera from openflexure_microscope.stage.sanga import SangaStage from openflexure_microscope.stage.mock import MockStage from openflexure_microscope.camera.capture import build_captures_from_exif from openflexure_microscope.config import USER_CONFIG_DIR import atexit Loading Loading @@ -58,6 +60,10 @@ else: # Create a dummy microscope object, with no hardware attachments api_microscope = Microscope(None, None) # Rebuild the capture list # TODO: Offload to a thread? stored_image_list = build_captures_from_exif() # Generate API URI based on version from filename def uri(suffix, api_version, base=None): if not base: Loading @@ -81,7 +87,7 @@ handler = JSONExceptionHandler(app) @app.before_first_request def attach_microscope(): # Create the microscope object globally (common to all spawned server threads) global api_microscope global api_microscope, capture_list logging.debug("First request made. Populating microscope with hardware...") logging.debug("Creating camera object...") Loading @@ -101,6 +107,10 @@ def attach_microscope(): api_stage ) logging.debug("Restoring captures...") if stored_image_list: api_microscope.camera.images = stored_image_list logging.debug("Microscope successfully attached!") Loading Loading @@ -153,10 +163,6 @@ def cleanup(): if api_microscope.rc: api_microscope.rc.save(backup=True) # Update capture DB and metadata files before exiting if api_microscope.camera: api_microscope.camera.store_captures() # Close down the microscope api_microscope.close() logging.debug("App teardown complete.") Loading
openflexure_microscope/api/v1/blueprints/task.py +0 −2 Original line number Diff line number Diff line Loading @@ -44,8 +44,6 @@ class TaskListAPI(MicroscopeView): :>header Content-Type: application/json """ print(self.microscope.task.state) data = self.microscope.task.state return jsonify(data) Loading
openflexure_microscope/camera/base.py +1 −59 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ except ImportError: except ImportError: from _thread import get_ident from .capture import CaptureObject, capture_from_dict, BASE_CAPTURE_PATH, TEMP_CAPTURE_PATH from .capture import CaptureObject, BASE_CAPTURE_PATH, TEMP_CAPTURE_PATH from openflexure_microscope.config import USER_CONFIG_DIR from openflexure_microscope.utilities import entry_by_id from openflexure_microscope.lock import StrictLock Loading Loading @@ -119,14 +119,6 @@ class BaseCamera(object): self.images = [] #: list: List of image capture objects self.videos = [] #: list: List of video recording objects # Capture data files self.images_db = os.path.join(USER_CONFIG_DIR, "images_db.yaml") self.videos_db = os.path.join(USER_CONFIG_DIR, "videos_db.yaml") # Load captures from persistent db self.images = self.load_capture_db(self.images_db) self.videos = self.load_capture_db(self.videos_db) def apply_config(self, config): """Update settings from a config dictionary""" self.config.update(config) Loading Loading @@ -244,50 +236,6 @@ class BaseCamera(object): """Return a video StreamObject with a matching ID.""" return entry_by_id(video_id, self.videos) # MANAGE CAPTURE DATABASE def save_capture_db(self, capture_list, db_path): capture_state = [capture.state for capture in capture_list] with open(db_path, 'w') as outfile: yaml.safe_dump(capture_state, outfile) def load_capture_db(self, db_path): if os.path.isfile(db_path): # Load list of capture dictionary representations from db_path with open(db_path, 'r') as infile: try: capture_dict_list = yaml.load(infile) except yaml.scanner.ScannerError as e: logging.error(e) logging.warning('Emptying malformed database. Your captures will not be deleted, but may not appear in the gallery.') capture_dict_list = [] # Create capture object list, and validate captures capture_list = [] if capture_dict_list: for capture_dict in capture_dict_list: if 'path' in capture_dict and os.path.isfile(capture_dict['path']): capture_list.append(capture_from_dict(capture_dict)) return capture_list else: return [] def store_captures(self): # Save metadata files #for image in self.images: # image.save_metadata() #for video in self.videos: # video.save_metadata() # Update capture database self.save_capture_db(self.images, self.images_db) self.save_capture_db(self.videos, self.videos_db) # CREATING NEW CAPTURES def shunt_captures(self, target_list: list): Loading Loading @@ -335,9 +283,6 @@ class BaseCamera(object): self.shunt_captures(self.images) self.images.append(output) # Update capture database self.save_capture_db(self.images, self.images_db) return output def new_video( Loading Loading @@ -381,9 +326,6 @@ class BaseCamera(object): self.shunt_captures(self.videos) self.videos.append(output) # Update capture database self.save_capture_db(self.videos, self.videos_db) return output # WORKER THREAD Loading
openflexure_microscope/camera/capture.py +39 −39 Original line number Diff line number Diff line Loading @@ -18,6 +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(): """ Loading @@ -38,67 +39,66 @@ def pull_usercomment_dict(filepath): 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): """ Extracts a value from one of two dictionaries, prioritising one over the other. Second dictionary is used only if the key doesn't exist in the first. def make_file_list(directory, formats): files = [] for fmt in formats: files.extend(glob.glob('{}/**/*.{}'.format(directory, fmt.lower()), recursive=True)) Args: key: Key to search for best_dict: Ideal dictionary to use backup_dict: Fallback dictionary logging.info("{} capture files found on disk".format(len(files))) """ 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 return files def build_captures_from_exif(): global BASE_CAPTURE_PATH, EXIF_FORMATS logging.debug("Reloading captures from {}...".format(BASE_CAPTURE_PATH)) files = make_file_list(BASE_CAPTURE_PATH, EXIF_FORMATS) captures = [] for f in files: logging.debug("Reloading capture {}...".format(f)) exif = pull_usercomment_dict(f) capture = capture_from_exif(f, exif) captures.append(capture) logging.info("{} capture files successfully reloaded".format(len(captures))) def capture_from_dict(capture_dict): return captures def capture_from_exif(path, exif_dict): """ Creates an instance of CaptureObject from a dictionary of capture information. This is used when reloading the API server, to restore captures created in the previous session. Args: capture_dict (dict): Dictionary containing capture information exif_dict (dict): Dictionary containing capture information """ global EXIF_FORMATS # Create a placeholder capture capture = CaptureObject( filepath=capture_dict['path'] ) # Create a placeholder capture capture.split_file_path(capture.file) capture.temporary = capture_dict['temporary'] filepath=path ) if capture.format.upper() in EXIF_FORMATS: md_exif = pull_usercomment_dict(capture.file) else: logging.debug("Unsupported format for EXIF data. Skipping.") md_exif = {} md_database = capture_dict['metadata'] # Build file path information capture.split_file_path(capture.file) # 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.id = exif_dict['id'] capture.timestring = exif_dict['time'] capture.format = exif_dict['format'] capture._metadata = extract_with_priority('custom', md_exif, md_database) capture.tags = extract_with_priority('tags', md_exif, md_database) capture._metadata = exif_dict['custom'] capture.tags = exif_dict['tags'] return capture Loading @@ -119,7 +119,7 @@ class CaptureObject(object): # Store a nice ID self.id = uuid.uuid4().hex #: str: Unique capture ID logging.info("Created StreamObject {}".format(self.id)) logging.debug("Created StreamObject {}".format(self.id)) self.timestring = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") #: str: Timestring of capture creation time # Keep on disk after close by default Loading
openflexure_microscope/camera/set_picamera_gain.py +6 −6 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ import picamera from picamera import mmal, mmalobj, exc from picamera.mmalobj import to_rational import time import logging MMAL_PARAMETER_ANALOG_GAIN = mmal.MMAL_PARAMETER_GROUP_CAMERA + 0x59 MMAL_PARAMETER_DIGITAL_GAIN = mmal.MMAL_PARAMETER_GROUP_CAMERA + 0x5A Loading Loading @@ -51,11 +51,11 @@ if __name__ == "__main__": # fix the shutter speed cam.shutter_speed = cam.exposure_speed print("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) logging.info("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) print("Attempting to set analogue gain to 1") logging.info("Attempting to set analogue gain to 1") set_analog_gain(cam, 1) print("Attempting to set digital gain to 1") logging.info("Attempting to set digital gain to 1") set_digital_gain(cam, 1) # The old code is left in here in case it is a useful example... #ret = mmal.mmal_port_parameter_set_rational(cam._camera.control._port, Loading @@ -65,9 +65,9 @@ if __name__ == "__main__": try: while True: print("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) logging.info("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)) time.sleep(1) except KeyboardInterrupt: print("Stopping...") logging.info("Stopping...")