diff options
| author | morefigs <morefigs@gmail.com> | 2019-02-11 17:07:41 +1100 |
|---|---|---|
| committer | morefigs <morefigs@gmail.com> | 2019-02-11 17:07:41 +1100 |
| commit | a3ad21c6b05754315825f8ae62def71a0c73988a (patch) | |
| tree | b3f0c6f61f28b32c553f95bcf5238d0a4e0658ef | |
| parent | 367295804b27d3eeef27bb85826426828a6bda03 (diff) | |
| download | pymba-a3ad21c6b05754315825f8ae62def71a0c73988a.tar.gz pymba-a3ad21c6b05754315825f8ae62def71a0c73988a.zip | |
added convenience functions for acquiring single images and streaming images indefinitely, with examples.
| -rw-r--r-- | examples/camera/opencv_acquire_image.py | 36 | ||||
| -rw-r--r-- | examples/camera/opencv_acquire_streaming_images.py | 39 | ||||
| -rw-r--r-- | examples/camera/opencv_capture_image.py | 34 | ||||
| -rw-r--r-- | examples/camera/opencv_capture_image_with_callback.py | 44 | ||||
| -rw-r--r-- | pymba/__init__.py | 3 | ||||
| -rw-r--r-- | pymba/camera.py | 138 | ||||
| -rw-r--r-- | pymba/vimba_exception.py | 12 |
7 files changed, 221 insertions, 85 deletions
diff --git a/examples/camera/opencv_acquire_image.py b/examples/camera/opencv_acquire_image.py new file mode 100644 index 0000000..e067959 --- /dev/null +++ b/examples/camera/opencv_acquire_image.py @@ -0,0 +1,36 @@ +import cv2 +from pymba import Vimba, Frame + + +def process_frame(frame: Frame): + """ + Processes the acquired frame. + """ + print(f'frame {frame.data.frameID} callback') + + # get a copy of the frame data + image = frame.buffer_data_numpy() + + # display image + cv2.imshow('Image', image) + + # wait for user to close window + cv2.waitKey(0) + + +if __name__ == '__main__': + + with Vimba() as vimba: + camera = vimba.camera(0) + camera.open() + + camera.arm('SingleFrame') + + # capture a single frame, more than once if desired + for i in range(3): + frame_ = camera.acquire_frame() + process_frame(frame_) + + camera.disarm() + + camera.close() diff --git a/examples/camera/opencv_acquire_streaming_images.py b/examples/camera/opencv_acquire_streaming_images.py new file mode 100644 index 0000000..a3efb6b --- /dev/null +++ b/examples/camera/opencv_acquire_streaming_images.py @@ -0,0 +1,39 @@ +from time import sleep +import cv2 +from pymba import Vimba, Frame + + +def process_frame(frame: Frame): + """ + Process the streaming frames. Consider sending the frame data to another thread/process if this is long running to + avoid dropping frames. + """ + print(f'frame {frame.data.frameID} callback') + + # get a copy of the frame data + image = frame.buffer_data_numpy() + + # display image + cv2.imshow('Image', image) + cv2.waitKey(1) + + +if __name__ == '__main__': + + with Vimba() as vimba: + camera = vimba.camera(0) + camera.open() + + # arm the camera and provide a function to be called upon frame ready + camera.arm('Continuous', process_frame) + camera.start_frame_acquisition() + + # stream images for a while... + sleep(5) + + # stop frame acquisition + # start_frame_acquisition can simply be called again if the camera is still armed + camera.stop_frame_acquisition() + camera.disarm() + + camera.close() diff --git a/examples/camera/opencv_capture_image.py b/examples/camera/opencv_capture_image.py deleted file mode 100644 index c185f6d..0000000 --- a/examples/camera/opencv_capture_image.py +++ /dev/null @@ -1,34 +0,0 @@ -import cv2 -from pymba import Vimba - - -if __name__ == '__main__': - - with Vimba() as vimba: - camera = vimba.camera(0) - camera.open() - - # setup camera and frame and capture a single image - camera.AcquisitionMode = 'SingleFrame' - frame = camera.new_frame() - frame.announce() - camera.start_capture() - frame.queue_for_capture() - camera.run_feature_command('AcquisitionStart') - frame.wait_for_capture() - camera.run_feature_command('AcquisitionStop') - - # get the image data as a numpy array - image = frame.buffer_data_numpy() - - # display image - cv2.imshow(camera.camera_id, image) - # waits for user to close image - cv2.waitKey(0) - - # stop capturing and clean up - camera.end_capture() - camera.flush_capture_queue() - camera.revoke_all_frames() - - camera.close() diff --git a/examples/camera/opencv_capture_image_with_callback.py b/examples/camera/opencv_capture_image_with_callback.py deleted file mode 100644 index 67cabdf..0000000 --- a/examples/camera/opencv_capture_image_with_callback.py +++ /dev/null @@ -1,44 +0,0 @@ -from time import sleep -import cv2 -from pymba import Vimba -from pymba.frame import Frame - - -def on_callback(completed_frame: Frame): - print('Callback called!') - - # get the image data as a numpy array - image = completed_frame.buffer_data_numpy() - - # display image - cv2.imshow(camera.camera_id, image) - # waits for user to close image - cv2.waitKey(0) - - -if __name__ == '__main__': - - with Vimba() as vimba: - camera = vimba.camera(0) - camera.open() - - # setup camera and frame and capture a single image - camera.AcquisitionMode = 'SingleFrame' - frame = camera.new_frame() - frame.announce() - camera.start_capture() - frame.queue_for_capture(on_callback) - camera.run_feature_command('AcquisitionStart') - camera.run_feature_command('AcquisitionStop') - - # wait long enough for the frame callback to be called - for _ in range(100): - sleep(0.1) - print('.', end='') - - # stop capturing and clean up - camera.end_capture() - camera.flush_capture_queue() - camera.revoke_all_frames() - - camera.close() diff --git a/pymba/__init__.py b/pymba/__init__.py index b067302..9095a32 100644 --- a/pymba/__init__.py +++ b/pymba/__init__.py @@ -1,4 +1,5 @@ from .vimba import Vimba, VimbaException +from .frame import Frame -PYMBA_VERSION = 0.2 +PYMBA_VERSION = '0.3' diff --git a/pymba/camera.py b/pymba/camera.py index 23ecd13..f4ae94e 100644 --- a/pymba/camera.py +++ b/pymba/camera.py @@ -1,5 +1,6 @@ from ctypes import byref, sizeof, c_uint32 -from typing import Optional, List +from typing import Optional, List, Callable +import gc from .vimba_object import VimbaObject from .vimba_exception import VimbaException @@ -30,6 +31,9 @@ PIXEL_FORMAT_BYTES = { "BayerGR12Packed": 1.5, } +SINGLE_FRAME = 'SingleFrame' +CONTINUOUS = 'Continuous' + def _camera_infos() -> List[vimba_c.VmbCameraInfo]: """ @@ -89,6 +93,17 @@ class Camera(VimbaObject): self._camera_id = camera_id super().__init__() + # remember state + self._is_armed = False + self._is_acquiring = False + self._acquisition_mode = None + + # frame to reuse when in single frame mode + self._single_frame = None + + # user registered callback function + self._user_callback = None + @property def handle(self): return self._handle @@ -159,3 +174,124 @@ class Camera(VimbaObject): Creates and returns a new frame object. Multiple frames per camera can therefore be returned. """ return Frame(self) + + def arm(self, mode: str, callback: Optional[Callable] = None, frame_buffer_size: Optional[int] = 3) -> None: + """ + Arm the camera by starting the capture engine and creating frames. + :param mode: Either 'SingleFrame' to acquire a single frame or 'Continuous' for streaming frames. + :param callback: A function reference to call when each frame is ready. Applies to 'Continuous' acquisition + mode only. The callback function should execute relatively quickly to avoid dropping frames (if the camera + captures a frame but no frame is currently queued for capture then the frame will be dropped. Therefore the + callback function should execute (on average) at least as fast as the camera frame rate. It may be desirable + for the callback to copy frame data and pass the data to a separate thread/process for processing. + :param frame_buffer_size: number of frames to create and use for the acquisition buffer. Increasing this may + help if frames are being dropped. + """ + if self._is_armed: + raise VimbaException(VimbaException.ERR_INVALID_CAMERA_MODE) + + if mode not in (SINGLE_FRAME, CONTINUOUS): + raise ValueError('unknown mode') + + # set and cache mode + self.AcquisitionMode = mode + self._acquisition_mode = mode + + if mode == SINGLE_FRAME: + self._arm_single_frame() + elif mode == CONTINUOUS: + if callback is None: + raise ValueError('a callback function must be provided in continuous mode') + self._arm_continuous(callback, frame_buffer_size) + + self._is_armed = True + + def _arm_single_frame(self) -> None: + self._single_frame = self.new_frame() + self._single_frame.announce() + + self.start_capture() + + def acquire_frame(self) -> Frame: + """ + Acquire and return a single frame when the camera is armed in 'SingleFrame' acquisition mode. Can be called + multiple times in a row, but don't call again until the frame has been copied or processed the internal frame + object is reused. + """ + if not self._is_armed or self._acquisition_mode != SINGLE_FRAME: + raise VimbaException(VimbaException.ERR_INVALID_CAMERA_MODE) + + # capture a single frame + self._single_frame.queue_for_capture() + self.run_feature_command('AcquisitionStart') + self._single_frame.wait_for_capture() + self.run_feature_command('AcquisitionStop') + + return self._single_frame + + def _arm_continuous(self, callback: Callable, frame_buffer_size: int) -> None: + self._user_callback = callback + + # create frame buffer and announce frames to camera + _frame_buffer = tuple(self.new_frame() for _ in range(frame_buffer_size)) + for frame in _frame_buffer: + frame.announce() + + self.start_capture() + + # queue + for frame in _frame_buffer: + frame.queue_for_capture(self._streaming_callback) + + def start_frame_acquisition(self) -> None: + """ + Acquire and stream frames (to the specified callback function) indefinitely when the camera is armed in + 'Continuous' acquisition mode. + """ + # no need to check self._is_acquiring + if not self._is_armed or self._acquisition_mode != CONTINUOUS: + raise VimbaException(VimbaException.ERR_INVALID_CAMERA_MODE) + + # safe to call multiple times + self.run_feature_command('AcquisitionStart') + self._is_acquiring = True + + def _streaming_callback(self, frame: Frame) -> None: + """ + Called upon the frame ready event. Wraps the user's callback and requeues the frame. + """ + self._user_callback(frame) + + # streaming may have stopped by now, especially if callback is long running + if self._is_armed and self._acquisition_mode == CONTINUOUS: + frame.queue_for_capture(self._streaming_callback) + + def stop_frame_acquisition(self) -> None: + """ + Stop acquiring and streaming frames. + """ + # implies both is armed and in continuous mode + if self._is_acquiring: + self._is_acquiring = False + self.run_feature_command('AcquisitionStop') + + def disarm(self) -> None: + """ + Disarm the camera by stopping the capture engine and cleaning up frames. + """ + # among other things this prevents callback from requeuing frames + self._is_armed = False + + # automatically stop acquisition if required + if self._is_acquiring: + self.stop_frame_acquisition() + + # clean up + self.end_capture() + self.flush_capture_queue() + self.revoke_all_frames() + + self._single_frame = None + + # encourage garbage collection of frame buffer memory + gc.collect() diff --git a/pymba/vimba_exception.py b/pymba/vimba_exception.py index 992e299..0e19da3 100644 --- a/pymba/vimba_exception.py +++ b/pymba/vimba_exception.py @@ -3,7 +3,7 @@ class VimbaException(Exception): # 0 ERR_NO_ERROR, - # -1 to -19 + # -1, -2, ... ERR_UNEXPECTED_FAULT, ERR_STARTUP_NOT_CALLED, ERR_INSTANCE_NOT_FOUND, @@ -24,13 +24,14 @@ class VimbaException(Exception): ERR_FEATURE_NOT_SUPPORTED, ERR_PARTIAL_REGISTER_ACCESS, - # -50 to -53 + # -50, -51, ... ERR_UNDEFINED_ERROR_CODE, ERR_FRAME_BUFFER_MEMORY, ERR_NOT_IMPLEMENTED_IN_PYMBA, ERR_COMMAND_MUST_BE_CALLED, + ERR_INVALID_CAMERA_MODE, ) = tuple(range(0, -20, -1)) + \ - tuple(range(-50, -54, -1)) + tuple(range(-50, -55, -1)) ERRORS = { # Vimba C API specific errors @@ -58,8 +59,9 @@ class VimbaException(Exception): # Custom errors ERR_UNDEFINED_ERROR_CODE: 'Undefined error code', ERR_FRAME_BUFFER_MEMORY: 'Not enough memory to assign frame buffer.', - ERR_NOT_IMPLEMENTED_IN_PYMBA: 'This function is not yet implemented in Pymba', - ERR_COMMAND_MUST_BE_CALLED: 'Cannot get or set the value of a command feature type, call the command instead.' + ERR_NOT_IMPLEMENTED_IN_PYMBA: 'This function is not yet implemented in Pymba.', + ERR_COMMAND_MUST_BE_CALLED: 'Cannot get or set the value of a command feature type, call the command instead.', + ERR_INVALID_CAMERA_MODE: 'Invalid camera mode for the requested operation.', } @property |
