Loading openflexure_microscope/camera/base.py +40 −1 Original line number Diff line number Diff line Loading @@ -27,11 +27,17 @@ class FrameStream(io.BytesIO): def __init__(self, *args, **kwargs): # Array of TrackerFrame objects io.BytesIO.__init__(self, *args, **kwargs) # Array of TrackerFramer objects self.frames = [] # Last acquired TrackerFramer object self.last = None # Are we currently tracking frame sizes? self.tracking = False # Event to track if a new frame is available since the last getvalue() call self.new_frame = threading.Event() def start_tracking(self): if not self.tracking: logging.debug("Started tracking frame data") Loading @@ -46,11 +52,36 @@ class FrameStream(io.BytesIO): self.frames = [] def write(self, s): """ Write a new frame to the FrameStream. Does a few things: 1. If tracking frame size, store the size in self.frames 2. Rewind and truncate the stream (delete previous frame) 3. Store the new frame image 4. Set the new_frame event """ if self.tracking: frame = TrackerFrame(size=len(s), time=time.time()) self.frames.append(frame) self.last = frame io.BytesIO.write(self, s) # Reset the stream for the next frame super().seek(0) super().truncate() # Write the new frame super().write(s) # Set the new frame event self.new_frame.set() def getvalue(self) -> bytes: """ Clear tne new_frame event and return frame data """ self.new_frame.clear() return super().getvalue() def getframe(self) -> bytes: """Wait for a new frame to be available, then return it""" self.new_frame.wait() return self.getvalue() class BaseCamera(metaclass=ABCMeta): Loading @@ -65,7 +96,10 @@ class BaseCamera(metaclass=ABCMeta): self.lock = StrictLock(name="Camera", timeout=None) self.stream = FrameStream() # Iterator function that yields new frames as they are acquired self.frames_iterator = None self.frame = None self.last_access = 0 self.event = ClientEvent() Loading Loading @@ -180,13 +214,18 @@ class BaseCamera(metaclass=ABCMeta): def _thread(self): """Camera background thread.""" # Set the camera object's frame iterator self.frames_iterator = self.frames() logging.debug("Entering worker thread.") self.stream_active = True for frame in self.frames_iterator: # Store most recent frame self.frame = frame # Signal to clients that a new frame is available # We use this event because each client could be # reading frames slower than we're acquiring them. self.event.set() # send signal to clients try: Loading openflexure_microscope/camera/mock.py +3 −13 Original line number Diff line number Diff line Loading @@ -198,20 +198,10 @@ class MissingCamera(BaseCamera): try: while True: time.sleep(1) # Only serve frames at 1fps # Reset stream self.stream.seek(0) self.stream.truncate() # Generate new dumm image # Generate new dummy image self.generate_new_dummy_image() # Get frame data frame = self.stream.getvalue() # ensure the size of package is right if len(frame) == 0: pass else: yield frame # Wait for the next frame and then yield it yield self.stream.getframe() # When GeneratorExit or StopIteration raised, run cleanup code finally: Loading openflexure_microscope/camera/pi.py +2 −18 Original line number Diff line number Diff line Loading @@ -565,24 +565,8 @@ class PiCameraStreamer(BaseCamera): # While the iterator is not closed try: while True: # Reset the stream for the next frame self.stream.seek(0) self.stream.truncate() # Sleep for the approximate camera framerate time.sleep(1 / self.camera.framerate * 0.1) # Get the latest frame data from the stream frame = self.stream.getvalue() # This loop iterates faster than the PiCamera will collect # frames. On iterations between frames, the buffer will be # empty (because we truncated it earlier). In these cases # we just pass and loop again until a frame is actually present. # This also means that the size of FrameStream is always 1 frame, # since we truncate it here before a new frame can be appended. if len(frame) == 0: pass else: yield frame # Wait for the next frame and then yield it yield self.stream.getframe() # When GeneratorExit or StopIteration raised, run cleanup code finally: # Stop stream recording (and set resolution) Loading Loading
openflexure_microscope/camera/base.py +40 −1 Original line number Diff line number Diff line Loading @@ -27,11 +27,17 @@ class FrameStream(io.BytesIO): def __init__(self, *args, **kwargs): # Array of TrackerFrame objects io.BytesIO.__init__(self, *args, **kwargs) # Array of TrackerFramer objects self.frames = [] # Last acquired TrackerFramer object self.last = None # Are we currently tracking frame sizes? self.tracking = False # Event to track if a new frame is available since the last getvalue() call self.new_frame = threading.Event() def start_tracking(self): if not self.tracking: logging.debug("Started tracking frame data") Loading @@ -46,11 +52,36 @@ class FrameStream(io.BytesIO): self.frames = [] def write(self, s): """ Write a new frame to the FrameStream. Does a few things: 1. If tracking frame size, store the size in self.frames 2. Rewind and truncate the stream (delete previous frame) 3. Store the new frame image 4. Set the new_frame event """ if self.tracking: frame = TrackerFrame(size=len(s), time=time.time()) self.frames.append(frame) self.last = frame io.BytesIO.write(self, s) # Reset the stream for the next frame super().seek(0) super().truncate() # Write the new frame super().write(s) # Set the new frame event self.new_frame.set() def getvalue(self) -> bytes: """ Clear tne new_frame event and return frame data """ self.new_frame.clear() return super().getvalue() def getframe(self) -> bytes: """Wait for a new frame to be available, then return it""" self.new_frame.wait() return self.getvalue() class BaseCamera(metaclass=ABCMeta): Loading @@ -65,7 +96,10 @@ class BaseCamera(metaclass=ABCMeta): self.lock = StrictLock(name="Camera", timeout=None) self.stream = FrameStream() # Iterator function that yields new frames as they are acquired self.frames_iterator = None self.frame = None self.last_access = 0 self.event = ClientEvent() Loading Loading @@ -180,13 +214,18 @@ class BaseCamera(metaclass=ABCMeta): def _thread(self): """Camera background thread.""" # Set the camera object's frame iterator self.frames_iterator = self.frames() logging.debug("Entering worker thread.") self.stream_active = True for frame in self.frames_iterator: # Store most recent frame self.frame = frame # Signal to clients that a new frame is available # We use this event because each client could be # reading frames slower than we're acquiring them. self.event.set() # send signal to clients try: Loading
openflexure_microscope/camera/mock.py +3 −13 Original line number Diff line number Diff line Loading @@ -198,20 +198,10 @@ class MissingCamera(BaseCamera): try: while True: time.sleep(1) # Only serve frames at 1fps # Reset stream self.stream.seek(0) self.stream.truncate() # Generate new dumm image # Generate new dummy image self.generate_new_dummy_image() # Get frame data frame = self.stream.getvalue() # ensure the size of package is right if len(frame) == 0: pass else: yield frame # Wait for the next frame and then yield it yield self.stream.getframe() # When GeneratorExit or StopIteration raised, run cleanup code finally: Loading
openflexure_microscope/camera/pi.py +2 −18 Original line number Diff line number Diff line Loading @@ -565,24 +565,8 @@ class PiCameraStreamer(BaseCamera): # While the iterator is not closed try: while True: # Reset the stream for the next frame self.stream.seek(0) self.stream.truncate() # Sleep for the approximate camera framerate time.sleep(1 / self.camera.framerate * 0.1) # Get the latest frame data from the stream frame = self.stream.getvalue() # This loop iterates faster than the PiCamera will collect # frames. On iterations between frames, the buffer will be # empty (because we truncated it earlier). In these cases # we just pass and loop again until a frame is actually present. # This also means that the size of FrameStream is always 1 frame, # since we truncate it here before a new frame can be appended. if len(frame) == 0: pass else: yield frame # Wait for the next frame and then yield it yield self.stream.getframe() # When GeneratorExit or StopIteration raised, run cleanup code finally: # Stop stream recording (and set resolution) Loading