aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormorefigs <morefigs@gmail.com>2019-02-11 17:07:41 +1100
committermorefigs <morefigs@gmail.com>2019-02-11 17:07:41 +1100
commita3ad21c6b05754315825f8ae62def71a0c73988a (patch)
treeb3f0c6f61f28b32c553f95bcf5238d0a4e0658ef
parent367295804b27d3eeef27bb85826426828a6bda03 (diff)
downloadpymba-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.py36
-rw-r--r--examples/camera/opencv_acquire_streaming_images.py39
-rw-r--r--examples/camera/opencv_capture_image.py34
-rw-r--r--examples/camera/opencv_capture_image_with_callback.py44
-rw-r--r--pymba/__init__.py3
-rw-r--r--pymba/camera.py138
-rw-r--r--pymba/vimba_exception.py12
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