Loading openflexure_microscope/camera/base.py +35 −20 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ from .capture import StreamObject 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 return object_list[-1] # Return the latest captured image else: Loading @@ -23,6 +24,7 @@ def last_entry(object_list: list): def entry_by_id(id: str, object_list: list): """Return an object from a list, if <object>.id matches id argument.""" found = None for o in object_list: if o.id == id: Loading @@ -31,14 +33,17 @@ def entry_by_id(id: str, object_list: list): class CameraEvent(object): """An Event-like class that signals all active clients when a new frame is available. """ def __init__(self): """ Create a frame-signaller object for StreamingCamera. An event-like class that signals all active clients when a new frame is available. """ self.events = {} def wait(self, timeout: int=5): """Invoked from each client's thread to wait for the next frame.""" """Wait for the next frame (invoked from each client's thread).""" ident = get_ident() if ident not in self.events: # this is a new client Loading @@ -48,7 +53,7 @@ class CameraEvent(object): return self.events[ident][0].wait(timeout) def set(self): """Invoked by the camera thread when a new frame is available.""" """Signal that a new frame is available.""" now = time.time() remove = None for ident, event in self.events.items(): Loading @@ -68,7 +73,7 @@ class CameraEvent(object): del self.events[remove] def clear(self): """Invoked from each client's thread after a frame was processed.""" """Clear frame event, once processed.""" self.events[get_ident()][0].clear() Loading Loading @@ -98,7 +103,7 @@ class BaseCamera(object): self.close() def close(self): """Close the BaseCamera and all attached StreamObjects""" """Close the BaseCamera and all attached StreamObjects.""" # Close all StreamObjects for capture_list in [self.images, self.videos]: for stream_object in capture_list: Loading Loading @@ -139,14 +144,14 @@ class BaseCamera(object): return True def stop_worker(self, timeout: int=5) -> bool: """Flags worker thread for stop. Waits for thread to close, or times out.""" """Flag worker thread for stop. Waits for thread close or timeout.""" logging.debug("Stopping worker thread") timeout_time = time.time() + timeout self.stop = True while self.thread: if time.time() > timeout_time: raise TimeoutError("Timeout waiting for worker thread to close.") raise TimeoutError("Timeout waiting for worker thread close.") else: time.sleep(0) return True Loading @@ -164,33 +169,37 @@ class BaseCamera(object): return self.frame def frames(self): """Generator that returns frames from the camera.""" """Create generator that returns frames from the camera.""" raise RuntimeError('Must be implemented by subclasses.') # RETURNING CAPTURES @property def image(self): """Return the latest captured image""" """Return the latest captured image.""" return last_entry(self.images) @property def video(self): """Return the latest recorded video""" """Return the latest recorded video.""" return last_entry(self.videos) def image_from_id(self, id): """Returns an image StreamObject with a matching ID""" """Return an image StreamObject with a matching ID.""" return entry_by_id(id, self.images) def video_from_id(self, id): """Returns a video StreamObject with a matching ID""" """Return a video StreamObject with a matching ID.""" return entry_by_id(id, self.videos) # CREATING NEW CAPTURES def new_stream_object(self, stream_object, target_list: list, shunt_others: bool=True): """Add a new capture to a list of captures, and shunt all others""" def new_stream_object( self, stream_object, target_list: list, shunt_others: bool=True): """Add a new capture to a list of captures, and shunt all others.""" if shunt_others: # If shunting all older captures for obj in target_list: # For each older capture obj.shunt() # Shunt capture from memory to storage Loading @@ -198,12 +207,18 @@ class BaseCamera(object): return stream_object def new_image(self, stream_object, shunt_others=True): """Add a new capture to the image list, and shunt all others""" return self.new_stream_object(stream_object, self.images, shunt_others=shunt_others) """Add a new capture to the image list, and shunt all others.""" return self.new_stream_object( stream_object, self.images, shunt_others=shunt_others) def new_video(self, stream_object, shunt_others=True): """Add a new capture to the video list, and shunt all others""" return self.new_stream_object(stream_object, self.videos, shunt_others=shunt_others) """Add a new capture to the video list, and shunt all others.""" return self.new_stream_object( stream_object, self.videos, shunt_others=shunt_others) # WORKER THREAD Loading openflexure_microscope/camera/capture.py +42 −24 Original line number Diff line number Diff line Loading @@ -5,8 +5,15 @@ import datetime import copy import logging class StreamObject(object): def __init__(self, write_to_file: bool=None, filename: str=None, folder: str=None, fmt: str='file') -> None: def __init__( self, write_to_file: bool=None, filename: str=None, folder: str=None, fmt: str='file') -> None: """Create a new StreamObject, to manage capture data.""" # Store a nice ID self.id = uuid.uuid4().hex logging.info("Created {}".format(self.id)) Loading @@ -17,7 +24,11 @@ class StreamObject(object): while os.path.isfile(f_name): # While file already exists iterator += 1 # Add a file name iterator f_name = self.build_file_path(filename, folder, fmt, iterator=iterator) # Rebuild file name f_name = self.build_file_path( filename, folder, fmt, iterator=iterator) # Rebuild file name self.file = f_name Loading @@ -27,10 +38,10 @@ class StreamObject(object): # Set default write target self.write_to_file = write_to_file if self.write_to_file is False: logging.debug("Default target for {} set to 'stream'".format(self.id)) logging.debug("Target for {} set to 'stream'".format(self.id)) self.target = self.stream else: logging.debug("Default target for {} set to 'file'".format(self.id)) logging.debug("Target for {} set to 'file'".format(self.id)) self.target = self.file # Keep on disk after close by default Loading @@ -40,9 +51,10 @@ class StreamObject(object): self.context_manager = False # Object lock self.locked = False # Is the StreamObject locked for writing? (Handled by StreamingCamera) self.locked = False def __enter__(self): """Create StreamObject in context, to auto-clean disk data.""" logging.debug("Entering context for {}.\ Stored files will be cleaned up automatically.".format(self.id)) self.keep_on_disk = False # Flag file to be removed on close. Loading @@ -50,14 +62,21 @@ class StreamObject(object): 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, filename: str, folder: str, fmt: str, iterator: int=0) -> str: def build_file_path( self, filename: str, folder: str, fmt: str, iterator: int=0) -> str: """ Construct a full file path, based on filename, folder, and file format. Defaults to datestamp. Iterator adds a numeric increment to the file name. Defaults to datestamp. Iterator adds a numeric increment to the file name. """ if filename: file_name = "{}.{}".format(filename, fmt) Loading Loading @@ -88,7 +107,7 @@ class StreamObject(object): @property def stream_exists(self, auto_rewind=True) -> bool: """Check if BytesIO stream is empty""" """Check if BytesIO stream is empty.""" if auto_rewind: self.stream.seek(0) # Rewind the data bytes for reading if self.stream.getvalue(): # If data stream contains data Loading @@ -100,7 +119,7 @@ class StreamObject(object): @property def file_exists(self) -> bool: """Check if corresponding file exists""" """Check if corresponding file exists.""" if os.path.isfile(self.file): return True else: Loading @@ -108,6 +127,7 @@ class StreamObject(object): @property def metadata(self) -> dict: """Return dictionary of StreamObject properties.""" d = { 'id': self.id, 'locked': self.locked, Loading Loading @@ -140,16 +160,18 @@ class StreamObject(object): self.stream.seek(0) # Rewind the data bytes for reading if self.stream_exists: # If data stream contains data data = io.BytesIO(self.stream.getbuffer()) # Create a copy of the stream bytes # Create a copy of the stream bytes data = io.BytesIO(self.stream.getbuffer()) else: # If data stream is empty if self.file_exists: # If data file exists # TODO: Streamline this bit logging.info("No stream data. Opening from file {}".format(self.file)) logging.info("Opening from file {}".format(self.file)) with open(self.file, 'rb') as f: d = io.BytesIO(f.read()) # Load bytes from file in disk d = io.BytesIO(f.read()) # Load bytes from file d.seek(0) # Rewind loaded stream data = io.BytesIO(d.getbuffer()) # Create a copy of the stream bytes # Create a copy of the stream bytes data = io.BytesIO(d.getbuffer()) else: data = None Loading @@ -165,16 +187,14 @@ class StreamObject(object): if self.file_exists: # If data file exists # TODO: Streamline this bit with open(self.file, 'rb') as f: self.stream = io.BytesIO(f.read()) # Load bytes from file in disk self.stream = io.BytesIO(f.read()) # Load bytes from file self.stream.seek(0) # Rewind data bytes again return True else: return False def save_file(self) -> bool: """ Write the StreamObjects stream to a file. """ """Write the StreamObjects stream to a file.""" if self.stream_exists: # If there's a stream to save with open(self.file, 'ab') as f: # Load file as bytes logging.debug("Writing stream to file {}".format(self.file)) Loading @@ -185,13 +205,11 @@ class StreamObject(object): return False def delete_stream(self): """Clears the BytesIO stream of the StreamObject.""" """Clear the BytesIO stream of the StreamObject.""" self.stream = io.BytesIO() def delete_file(self) -> bool: """ If the StreamObject has been saved to disk, deletes the file and returns True. """ """If the StreamObject has been saved, delete the file.""" if os.path.isfile(self.file): logging.info("Deleting file {}".format(self.file)) os.remove(self.file) Loading @@ -206,7 +224,7 @@ class StreamObject(object): self.delete_stream() # Delete the stream from memory def close(self): """Both clear the stream, and delete any associated on-disk data""" """Both clear the stream, and delete any associated on-disk data.""" logging.info("Closing {}".format(self.id)) self.delete_stream() if not self.keep_on_disk: Loading openflexure_microscope/camera/pi.py +44 −27 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ class StreamingCamera(BaseCamera): pass def close(self): """Close the Raspberry Pi StreamingCamera""" """Close the Raspberry Pi StreamingCamera.""" # Run BaseCamera close method BaseCamera.close(self) # Detatch Pi camera Loading Loading @@ -135,7 +135,9 @@ class StreamingCamera(BaseCamera): if 'awb_mode' in config: self.camera.awb_mode = config['awb_mode'] if 'red_gain' in config and 'blue_gain' in config: self.camera.awb_gains = (config['red_gain'], config['blue_gain']) self.camera.awb_gains = ( config['red_gain'], config['blue_gain']) # Camera framerate if 'framerate' in config: Loading @@ -158,7 +160,8 @@ class StreamingCamera(BaseCamera): self.resume_stream_for_capture() else: raise Exception("Cannot update camera settings while recording is active.") raise Exception( "Cannot update camera settings while recording is active.") def change_zoom(self, zoom_value: int=1) -> None: """Change the camera zoom, handling recentering and scaling.""" Loading Loading @@ -213,7 +216,6 @@ class StreamingCamera(BaseCamera): fmt (str): Format of the capture. (default 'h264') """ # Start recording method only if a current recording is not running if not self.state['record_active']: Loading @@ -235,14 +237,14 @@ class StreamingCamera(BaseCamera): target_obj = target # Start the camera video recording on port 2 logging.info("Starting record at {}".format(self.settings['video_resolution'])) logging.info("Recording to {}".format(target)) self.camera.start_recording( target, format=fmt, splitter_port=2, resize=self.settings['video_resolution'], quality=quality) logging.debug("Recording started successfully.") # Update state dictionary self.state['record_active'] = True Loading @@ -250,12 +252,13 @@ class StreamingCamera(BaseCamera): return target_obj else: print("Cannot start a new recording until the current recording has stopped.") print( "Cannot start a new recording\ until the current recording has stopped.") return None def stop_recording(self) -> bool: """Stop the last started video recording on splitter port 2.""" # Stop the camera video recording on port 2 logging.info("Stopping recording") self.camera.stop_recording(splitter_port=2) Loading @@ -264,7 +267,10 @@ class StreamingCamera(BaseCamera): # Update state dictionary self.state['record_active'] = False def pause_stream_for_capture(self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: def pause_stream_for_capture( self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: """ Pause capture on a splitter port. Loading @@ -283,7 +289,10 @@ class StreamingCamera(BaseCamera): # Increase the resolution for taking an image self.camera.resolution = resolution def resume_stream_for_capture(self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: def resume_stream_for_capture( self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: """ Resume capture on a splitter port. Loading Loading @@ -332,11 +341,14 @@ class StreamingCamera(BaseCamera): (default 'h264') resize ((int, int)): Resize the captured image. """ # If no target is specified, store to StreamingCamera if not target: # Create a new image and add to the image list target_obj = self.new_image(StreamObject(write_to_file=write_to_file, filename=filename, folder=folder, fmt=fmt)) target_obj = self.new_image(StreamObject( write_to_file=write_to_file, filename=filename, folder=folder, fmt=fmt)) target = target_obj.target # Store to the StreamObject BytesIO else: Loading Loading @@ -373,13 +385,17 @@ class StreamingCamera(BaseCamera): return target_obj def array(self, rgb=False, use_video_port: bool=True, resize: Tuple[int, int]=None) -> np.ndarray: def array( self, rgb=False, use_video_port: bool=True, resize: Tuple[int, int]=None) -> np.ndarray: """Capture an uncompressed still YUV image to a Numpy array. use_video_port (bool): Capture from the video port used for streaming (lower resolution, faster) use_video_port (bool): Capture from the video port used for streaming. (lower resolution, faster) resize ((int, int)): Resize the captured image. """ if use_video_port: resolution = self.settings['video_resolution'] else: Loading @@ -395,7 +411,8 @@ class StreamingCamera(BaseCamera): logging.debug("Creating PiYUVArray") with picamera.array.PiYUVArray(self.camera, size=size) as output: logging.debug("Capturing to PiYUVArray from {}".format(self.camera)) logging.info("Capturing to {}".format(output)) self.camera.capture( output, Loading @@ -403,8 +420,6 @@ class StreamingCamera(BaseCamera): format='yuv', use_video_port=use_video_port) logging.debug("Capturing complete") if not use_video_port: self.resume_stream_for_capture() Loading @@ -418,10 +433,10 @@ class StreamingCamera(BaseCamera): def frames(self): """ Create iterator used by worker thread to generate stream frames. Create generator that returns frames from the camera. Holds a Pi camera in context, records video from port 1 to a byte stream, and iterates sequential frames. Records video from port 1 to a byte stream, and iterates sequential frames. """ # Run this initialisation method self.initialisation() Loading @@ -447,7 +462,8 @@ class StreamingCamera(BaseCamera): logging.debug("STREAM ACTIVE") self.state['stream_active'] = True try: # While the iterator is not closed # While the iterator is not closed try: while True: # reset stream for next frame self.stream.seek(0) Loading @@ -462,7 +478,8 @@ class StreamingCamera(BaseCamera): pass else: yield frame finally: # When GeneratorExit or StopIteration raised, run cleanup code # When GeneratorExit or StopIteration raised, run cleanup code finally: logging.debug("Stopping stream recording on port 1") self.camera.stop_recording(splitter_port=1) self.state['stream_active'] = False Loading openflexure_microscope/camera/__init__.py +1 −1 File changed.Contains only whitespace changes. Show changes Loading
openflexure_microscope/camera/base.py +35 −20 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ from .capture import StreamObject 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 return object_list[-1] # Return the latest captured image else: Loading @@ -23,6 +24,7 @@ def last_entry(object_list: list): def entry_by_id(id: str, object_list: list): """Return an object from a list, if <object>.id matches id argument.""" found = None for o in object_list: if o.id == id: Loading @@ -31,14 +33,17 @@ def entry_by_id(id: str, object_list: list): class CameraEvent(object): """An Event-like class that signals all active clients when a new frame is available. """ def __init__(self): """ Create a frame-signaller object for StreamingCamera. An event-like class that signals all active clients when a new frame is available. """ self.events = {} def wait(self, timeout: int=5): """Invoked from each client's thread to wait for the next frame.""" """Wait for the next frame (invoked from each client's thread).""" ident = get_ident() if ident not in self.events: # this is a new client Loading @@ -48,7 +53,7 @@ class CameraEvent(object): return self.events[ident][0].wait(timeout) def set(self): """Invoked by the camera thread when a new frame is available.""" """Signal that a new frame is available.""" now = time.time() remove = None for ident, event in self.events.items(): Loading @@ -68,7 +73,7 @@ class CameraEvent(object): del self.events[remove] def clear(self): """Invoked from each client's thread after a frame was processed.""" """Clear frame event, once processed.""" self.events[get_ident()][0].clear() Loading Loading @@ -98,7 +103,7 @@ class BaseCamera(object): self.close() def close(self): """Close the BaseCamera and all attached StreamObjects""" """Close the BaseCamera and all attached StreamObjects.""" # Close all StreamObjects for capture_list in [self.images, self.videos]: for stream_object in capture_list: Loading Loading @@ -139,14 +144,14 @@ class BaseCamera(object): return True def stop_worker(self, timeout: int=5) -> bool: """Flags worker thread for stop. Waits for thread to close, or times out.""" """Flag worker thread for stop. Waits for thread close or timeout.""" logging.debug("Stopping worker thread") timeout_time = time.time() + timeout self.stop = True while self.thread: if time.time() > timeout_time: raise TimeoutError("Timeout waiting for worker thread to close.") raise TimeoutError("Timeout waiting for worker thread close.") else: time.sleep(0) return True Loading @@ -164,33 +169,37 @@ class BaseCamera(object): return self.frame def frames(self): """Generator that returns frames from the camera.""" """Create generator that returns frames from the camera.""" raise RuntimeError('Must be implemented by subclasses.') # RETURNING CAPTURES @property def image(self): """Return the latest captured image""" """Return the latest captured image.""" return last_entry(self.images) @property def video(self): """Return the latest recorded video""" """Return the latest recorded video.""" return last_entry(self.videos) def image_from_id(self, id): """Returns an image StreamObject with a matching ID""" """Return an image StreamObject with a matching ID.""" return entry_by_id(id, self.images) def video_from_id(self, id): """Returns a video StreamObject with a matching ID""" """Return a video StreamObject with a matching ID.""" return entry_by_id(id, self.videos) # CREATING NEW CAPTURES def new_stream_object(self, stream_object, target_list: list, shunt_others: bool=True): """Add a new capture to a list of captures, and shunt all others""" def new_stream_object( self, stream_object, target_list: list, shunt_others: bool=True): """Add a new capture to a list of captures, and shunt all others.""" if shunt_others: # If shunting all older captures for obj in target_list: # For each older capture obj.shunt() # Shunt capture from memory to storage Loading @@ -198,12 +207,18 @@ class BaseCamera(object): return stream_object def new_image(self, stream_object, shunt_others=True): """Add a new capture to the image list, and shunt all others""" return self.new_stream_object(stream_object, self.images, shunt_others=shunt_others) """Add a new capture to the image list, and shunt all others.""" return self.new_stream_object( stream_object, self.images, shunt_others=shunt_others) def new_video(self, stream_object, shunt_others=True): """Add a new capture to the video list, and shunt all others""" return self.new_stream_object(stream_object, self.videos, shunt_others=shunt_others) """Add a new capture to the video list, and shunt all others.""" return self.new_stream_object( stream_object, self.videos, shunt_others=shunt_others) # WORKER THREAD Loading
openflexure_microscope/camera/capture.py +42 −24 Original line number Diff line number Diff line Loading @@ -5,8 +5,15 @@ import datetime import copy import logging class StreamObject(object): def __init__(self, write_to_file: bool=None, filename: str=None, folder: str=None, fmt: str='file') -> None: def __init__( self, write_to_file: bool=None, filename: str=None, folder: str=None, fmt: str='file') -> None: """Create a new StreamObject, to manage capture data.""" # Store a nice ID self.id = uuid.uuid4().hex logging.info("Created {}".format(self.id)) Loading @@ -17,7 +24,11 @@ class StreamObject(object): while os.path.isfile(f_name): # While file already exists iterator += 1 # Add a file name iterator f_name = self.build_file_path(filename, folder, fmt, iterator=iterator) # Rebuild file name f_name = self.build_file_path( filename, folder, fmt, iterator=iterator) # Rebuild file name self.file = f_name Loading @@ -27,10 +38,10 @@ class StreamObject(object): # Set default write target self.write_to_file = write_to_file if self.write_to_file is False: logging.debug("Default target for {} set to 'stream'".format(self.id)) logging.debug("Target for {} set to 'stream'".format(self.id)) self.target = self.stream else: logging.debug("Default target for {} set to 'file'".format(self.id)) logging.debug("Target for {} set to 'file'".format(self.id)) self.target = self.file # Keep on disk after close by default Loading @@ -40,9 +51,10 @@ class StreamObject(object): self.context_manager = False # Object lock self.locked = False # Is the StreamObject locked for writing? (Handled by StreamingCamera) self.locked = False def __enter__(self): """Create StreamObject in context, to auto-clean disk data.""" logging.debug("Entering context for {}.\ Stored files will be cleaned up automatically.".format(self.id)) self.keep_on_disk = False # Flag file to be removed on close. Loading @@ -50,14 +62,21 @@ class StreamObject(object): 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, filename: str, folder: str, fmt: str, iterator: int=0) -> str: def build_file_path( self, filename: str, folder: str, fmt: str, iterator: int=0) -> str: """ Construct a full file path, based on filename, folder, and file format. Defaults to datestamp. Iterator adds a numeric increment to the file name. Defaults to datestamp. Iterator adds a numeric increment to the file name. """ if filename: file_name = "{}.{}".format(filename, fmt) Loading Loading @@ -88,7 +107,7 @@ class StreamObject(object): @property def stream_exists(self, auto_rewind=True) -> bool: """Check if BytesIO stream is empty""" """Check if BytesIO stream is empty.""" if auto_rewind: self.stream.seek(0) # Rewind the data bytes for reading if self.stream.getvalue(): # If data stream contains data Loading @@ -100,7 +119,7 @@ class StreamObject(object): @property def file_exists(self) -> bool: """Check if corresponding file exists""" """Check if corresponding file exists.""" if os.path.isfile(self.file): return True else: Loading @@ -108,6 +127,7 @@ class StreamObject(object): @property def metadata(self) -> dict: """Return dictionary of StreamObject properties.""" d = { 'id': self.id, 'locked': self.locked, Loading Loading @@ -140,16 +160,18 @@ class StreamObject(object): self.stream.seek(0) # Rewind the data bytes for reading if self.stream_exists: # If data stream contains data data = io.BytesIO(self.stream.getbuffer()) # Create a copy of the stream bytes # Create a copy of the stream bytes data = io.BytesIO(self.stream.getbuffer()) else: # If data stream is empty if self.file_exists: # If data file exists # TODO: Streamline this bit logging.info("No stream data. Opening from file {}".format(self.file)) logging.info("Opening from file {}".format(self.file)) with open(self.file, 'rb') as f: d = io.BytesIO(f.read()) # Load bytes from file in disk d = io.BytesIO(f.read()) # Load bytes from file d.seek(0) # Rewind loaded stream data = io.BytesIO(d.getbuffer()) # Create a copy of the stream bytes # Create a copy of the stream bytes data = io.BytesIO(d.getbuffer()) else: data = None Loading @@ -165,16 +187,14 @@ class StreamObject(object): if self.file_exists: # If data file exists # TODO: Streamline this bit with open(self.file, 'rb') as f: self.stream = io.BytesIO(f.read()) # Load bytes from file in disk self.stream = io.BytesIO(f.read()) # Load bytes from file self.stream.seek(0) # Rewind data bytes again return True else: return False def save_file(self) -> bool: """ Write the StreamObjects stream to a file. """ """Write the StreamObjects stream to a file.""" if self.stream_exists: # If there's a stream to save with open(self.file, 'ab') as f: # Load file as bytes logging.debug("Writing stream to file {}".format(self.file)) Loading @@ -185,13 +205,11 @@ class StreamObject(object): return False def delete_stream(self): """Clears the BytesIO stream of the StreamObject.""" """Clear the BytesIO stream of the StreamObject.""" self.stream = io.BytesIO() def delete_file(self) -> bool: """ If the StreamObject has been saved to disk, deletes the file and returns True. """ """If the StreamObject has been saved, delete the file.""" if os.path.isfile(self.file): logging.info("Deleting file {}".format(self.file)) os.remove(self.file) Loading @@ -206,7 +224,7 @@ class StreamObject(object): self.delete_stream() # Delete the stream from memory def close(self): """Both clear the stream, and delete any associated on-disk data""" """Both clear the stream, and delete any associated on-disk data.""" logging.info("Closing {}".format(self.id)) self.delete_stream() if not self.keep_on_disk: Loading
openflexure_microscope/camera/pi.py +44 −27 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ class StreamingCamera(BaseCamera): pass def close(self): """Close the Raspberry Pi StreamingCamera""" """Close the Raspberry Pi StreamingCamera.""" # Run BaseCamera close method BaseCamera.close(self) # Detatch Pi camera Loading Loading @@ -135,7 +135,9 @@ class StreamingCamera(BaseCamera): if 'awb_mode' in config: self.camera.awb_mode = config['awb_mode'] if 'red_gain' in config and 'blue_gain' in config: self.camera.awb_gains = (config['red_gain'], config['blue_gain']) self.camera.awb_gains = ( config['red_gain'], config['blue_gain']) # Camera framerate if 'framerate' in config: Loading @@ -158,7 +160,8 @@ class StreamingCamera(BaseCamera): self.resume_stream_for_capture() else: raise Exception("Cannot update camera settings while recording is active.") raise Exception( "Cannot update camera settings while recording is active.") def change_zoom(self, zoom_value: int=1) -> None: """Change the camera zoom, handling recentering and scaling.""" Loading Loading @@ -213,7 +216,6 @@ class StreamingCamera(BaseCamera): fmt (str): Format of the capture. (default 'h264') """ # Start recording method only if a current recording is not running if not self.state['record_active']: Loading @@ -235,14 +237,14 @@ class StreamingCamera(BaseCamera): target_obj = target # Start the camera video recording on port 2 logging.info("Starting record at {}".format(self.settings['video_resolution'])) logging.info("Recording to {}".format(target)) self.camera.start_recording( target, format=fmt, splitter_port=2, resize=self.settings['video_resolution'], quality=quality) logging.debug("Recording started successfully.") # Update state dictionary self.state['record_active'] = True Loading @@ -250,12 +252,13 @@ class StreamingCamera(BaseCamera): return target_obj else: print("Cannot start a new recording until the current recording has stopped.") print( "Cannot start a new recording\ until the current recording has stopped.") return None def stop_recording(self) -> bool: """Stop the last started video recording on splitter port 2.""" # Stop the camera video recording on port 2 logging.info("Stopping recording") self.camera.stop_recording(splitter_port=2) Loading @@ -264,7 +267,10 @@ class StreamingCamera(BaseCamera): # Update state dictionary self.state['record_active'] = False def pause_stream_for_capture(self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: def pause_stream_for_capture( self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: """ Pause capture on a splitter port. Loading @@ -283,7 +289,10 @@ class StreamingCamera(BaseCamera): # Increase the resolution for taking an image self.camera.resolution = resolution def resume_stream_for_capture(self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: def resume_stream_for_capture( self, splitter_port: int=1, resolution: Tuple[int, int]=None) -> None: """ Resume capture on a splitter port. Loading Loading @@ -332,11 +341,14 @@ class StreamingCamera(BaseCamera): (default 'h264') resize ((int, int)): Resize the captured image. """ # If no target is specified, store to StreamingCamera if not target: # Create a new image and add to the image list target_obj = self.new_image(StreamObject(write_to_file=write_to_file, filename=filename, folder=folder, fmt=fmt)) target_obj = self.new_image(StreamObject( write_to_file=write_to_file, filename=filename, folder=folder, fmt=fmt)) target = target_obj.target # Store to the StreamObject BytesIO else: Loading Loading @@ -373,13 +385,17 @@ class StreamingCamera(BaseCamera): return target_obj def array(self, rgb=False, use_video_port: bool=True, resize: Tuple[int, int]=None) -> np.ndarray: def array( self, rgb=False, use_video_port: bool=True, resize: Tuple[int, int]=None) -> np.ndarray: """Capture an uncompressed still YUV image to a Numpy array. use_video_port (bool): Capture from the video port used for streaming (lower resolution, faster) use_video_port (bool): Capture from the video port used for streaming. (lower resolution, faster) resize ((int, int)): Resize the captured image. """ if use_video_port: resolution = self.settings['video_resolution'] else: Loading @@ -395,7 +411,8 @@ class StreamingCamera(BaseCamera): logging.debug("Creating PiYUVArray") with picamera.array.PiYUVArray(self.camera, size=size) as output: logging.debug("Capturing to PiYUVArray from {}".format(self.camera)) logging.info("Capturing to {}".format(output)) self.camera.capture( output, Loading @@ -403,8 +420,6 @@ class StreamingCamera(BaseCamera): format='yuv', use_video_port=use_video_port) logging.debug("Capturing complete") if not use_video_port: self.resume_stream_for_capture() Loading @@ -418,10 +433,10 @@ class StreamingCamera(BaseCamera): def frames(self): """ Create iterator used by worker thread to generate stream frames. Create generator that returns frames from the camera. Holds a Pi camera in context, records video from port 1 to a byte stream, and iterates sequential frames. Records video from port 1 to a byte stream, and iterates sequential frames. """ # Run this initialisation method self.initialisation() Loading @@ -447,7 +462,8 @@ class StreamingCamera(BaseCamera): logging.debug("STREAM ACTIVE") self.state['stream_active'] = True try: # While the iterator is not closed # While the iterator is not closed try: while True: # reset stream for next frame self.stream.seek(0) Loading @@ -462,7 +478,8 @@ class StreamingCamera(BaseCamera): pass else: yield frame finally: # When GeneratorExit or StopIteration raised, run cleanup code # When GeneratorExit or StopIteration raised, run cleanup code finally: logging.debug("Stopping stream recording on port 1") self.camera.stop_recording(splitter_port=1) self.state['stream_active'] = False Loading
openflexure_microscope/camera/__init__.py +1 −1 File changed.Contains only whitespace changes. Show changes