From 34c46f0b6a761bb59e9e4426040eea54ec901475 Mon Sep 17 00:00:00 2001 From: John Ingve Olsen Date: Wed, 20 Nov 2024 16:13:14 +0100 Subject: [PATCH] Add new capture methods capture2d/capture3d/capture2d3d These methods are intended to replace `capture` which is now deprecated. --- modules/zivid/camera.py | 125 ++++++++++++++++++++- src/ReleasableCamera.cpp | 6 + src/include/ZividPython/ReleasableCamera.h | 4 + test/test_camera_capture.py | 86 ++++++++++++++ 4 files changed, 220 insertions(+), 1 deletion(-) diff --git a/modules/zivid/camera.py b/modules/zivid/camera.py index 57a41e12..fae74945 100644 --- a/modules/zivid/camera.py +++ b/modules/zivid/camera.py @@ -47,9 +47,127 @@ def __str__(self): def __eq__(self, other): return self.__impl == other._Camera__impl + def capture2d3d(self, settings): + """Capture a 2D+3D frame. + + This method captures both a 3D point cloud and a 2D color image. Use this method when you want to capture + colored point clouds. This method will throw if `Settings.color` is not set. Use `capture3d` for capturing a 3D + point cloud without a 2D color image. + + These remarks below apply for all capture functions: + + This method returns right after the acquisition of the images is complete, and the camera has stopped projecting + patterns. Therefore, after this method has returned, the camera can be moved, or objects in the scene can be + moved, or a capture from another camera with overlapping field of view can be triggered, without affecting the + point cloud. + + When this method returns, there is still remaining data to transfer from the camera to the PC, and the + processing of the final point cloud is not completed. Transfer and processing of the point cloud will continue + in the background. When you call a method on the returned `Frame` object that requires the capture to be + finished, for example `Frame.point_cloud`, that method will block until the processing is finished and the point + cloud is available. If an exception occurs after the acquisition of images is complete (during transfer or + processing of the capture), then that exception is instead thrown when you access the `Frame` object. + + The capture functions can be invoked back-to-back, for doing rapid back-to-back acquisition of multiple (2D or + 3D) captures on the same camera. This is for example useful if you want to do one high-resolution 2D capture + followed by a lower-resolution 3D capture. The acquisition of the next capture will begin quickly after + acquisition of the previous capture completed, even when there is remaining transfer and processing for the + first capture. This allows pipelining several 2D and/or 3D captures, by doing acquisition in parallel with data + transfer and processing. + + Note: There can be maximum of two in-progress uncompleted 3D (or 2D+3D) captures simultaneously per Zivid + camera. If you invoke `capture2d3d` or `capture3d` when there are two uncompleted 3D captures in-progress, then + the capture will not start until the first of the in-progress 3D captures has finished all transfer and + processing. There is a similar limit of maximum two in-process 2D captures per camera. + + Capture functions can also be called on multiple cameras simultaneously. However, if the cameras have + overlapping field-of-view then you need to take consideration and sequence the capture calls to avoid the + captures interfering with each other. + + Args: + settings: Settings to use for the capture. + + Returns: + A frame containing a 3D point cloud, a 2D color image, and metadata. + + Raises: + TypeError: If the settings argument is not a Settings instance. + """ + if not isinstance(settings, Settings): + raise TypeError( + "Unsupported type for argument settings. Got {}, expected {}.".format( + type(settings), Settings.__name__ + ) + ) + return Frame(self.__impl.capture2D3D(_to_internal_settings(settings))) + + def capture3d(self, settings): + """Capture a single 3D frame. + This method is used to capture a 3D frame without a 2D color image. It ignores all color settings in the input + settings. See `capture2d3d` for capturing a 2D+3D frame. + + This method returns right after the acquisition of the images is complete, and the camera has stopped projecting + patterns. For more information, see the remarks section of `capture2d3d` above. Those remarks apply for both 2D, + 3D, and 2D+3D captures. + + Args: + settings: Settings to use for the capture. + + Returns: + A frame containing a 3D point cloud and metadata. + + Raises: + TypeError: If the settings argument is not a Settings + """ + if not isinstance(settings, Settings): + raise TypeError( + "Unsupported type for argument settings. Got {}, expected {}.".format( + type(settings), Settings.__name__ + ) + ) + return Frame(self.__impl.capture3D(_to_internal_settings(settings))) + + def capture2d(self, settings): + """Capture a single 2D frame. + + This method returns right after the acquisition of the images is complete, and the camera has stopped projecting + patterns. For more information, see the remarks section of `capture2d3d` above. Those remarks apply for both 2D, + 3D, and 2D+3D captures. + + This overload is provided for convenience. Note that only the Settings2D part under `Settings.color` will be + used for the capture. The other parts of the settings will be ignored. + + This method will throw if `Settings.color` is not set. + + Args: + settings: Settings to use for the capture. Can be either a Settings2D instance or a Settings instance. + + Returns: + A Frame2D containing a 2D image and metadata + + Raises: + TypeError: If the settings argument is not a Settings2D or a Settings + """ + if isinstance(settings, Settings2D): + return Frame2D(self.__impl.capture2D(_to_internal_settings2d(settings))) + if isinstance(settings, Settings): + return Frame2D(self.__impl.capture2D(_to_internal_settings(settings))) + raise TypeError( + "Unsupported settings type, expected: {expected_types}, got: {value_type}".format( + expected_types=" or ".join([Settings.__name__, Settings2D.__name__]), + value_type=type(settings), + ) + ) + def capture(self, settings): """Capture a single frame or a single 2D frame. + This method is deprecated as of SDK 2.14, and will be removed in the next SDK major version (3.0). Use + `capture2d3d` instead for capturing 2D+3D frames, use `capture3d` for capturing 3D frames without a 2D color + image, or use `capture2d` for capturing a 2D color image only. + + This method shares the common remarks about capture functions as found under `capture2d3d`. + Args: settings: Settings to be used to capture. Can be either a Settings or Settings2D instance @@ -63,7 +181,12 @@ def capture(self, settings): return Frame(self.__impl.capture(_to_internal_settings(settings))) if isinstance(settings, Settings2D): return Frame2D(self.__impl.capture(_to_internal_settings2d(settings))) - raise TypeError("Unsupported settings type: {}".format(type(settings))) + raise TypeError( + "Unsupported settings type, expected: {expected_types}, got: {value_type}".format( + expected_types=" or ".join([Settings.__name__, Settings2D.__name__]), + value_type=type(settings), + ) + ) @property def info(self): diff --git a/src/ReleasableCamera.cpp b/src/ReleasableCamera.cpp index d0386144..191fcf81 100644 --- a/src/ReleasableCamera.cpp +++ b/src/ReleasableCamera.cpp @@ -17,6 +17,12 @@ namespace ZividPython .def(py::self != py::self) // NOLINT .def("disconnect", &ReleasableCamera::disconnect) .def("connect", &ReleasableCamera::connect) + .def("capture2D3D", &ReleasableCamera::capture2D3D) + .def("capture3D", &ReleasableCamera::capture3D) + .def("capture2D", py::overload_cast(&ReleasableCamera::capture2D), + py::arg("settings_2d")) + .def("capture2D", py::overload_cast(&ReleasableCamera::capture2D), + py::arg("settings")) .def("capture", py::overload_cast(&ReleasableCamera::capture), py::arg("settings")) .def("capture", py::overload_cast(&ReleasableCamera::capture), diff --git a/src/include/ZividPython/ReleasableCamera.h b/src/include/ZividPython/ReleasableCamera.h index 65930b19..1f73aa82 100644 --- a/src/include/ZividPython/ReleasableCamera.h +++ b/src/include/ZividPython/ReleasableCamera.h @@ -17,6 +17,10 @@ namespace ZividPython ZIVID_PYTHON_ADD_COMPARE(!=) ZIVID_PYTHON_FORWARD_0_ARGS_WRAP_RETURN(ReleasableCamera, connect) ZIVID_PYTHON_FORWARD_0_ARGS(disconnect) + ZIVID_PYTHON_FORWARD_1_ARGS_WRAP_RETURN(ReleasableFrame, capture2D3D, const Zivid::Settings &, settings) + ZIVID_PYTHON_FORWARD_1_ARGS_WRAP_RETURN(ReleasableFrame, capture3D, const Zivid::Settings &, settings) + ZIVID_PYTHON_FORWARD_1_ARGS_WRAP_RETURN(ReleasableFrame2D, capture2D, const Zivid::Settings2D &, settings2D) + ZIVID_PYTHON_FORWARD_1_ARGS_WRAP_RETURN(ReleasableFrame2D, capture2D, const Zivid::Settings &, settings) ZIVID_PYTHON_FORWARD_1_ARGS_WRAP_RETURN(ReleasableFrame, capture, const Zivid::Settings &, settings) ZIVID_PYTHON_FORWARD_1_ARGS_WRAP_RETURN(ReleasableFrame2D, capture, const Zivid::Settings2D &, settings2D) ZIVID_PYTHON_FORWARD_0_ARGS(state) diff --git a/test/test_camera_capture.py b/test/test_camera_capture.py index 8be699e1..be910dcb 100644 --- a/test/test_camera_capture.py +++ b/test/test_camera_capture.py @@ -1,6 +1,92 @@ import pytest +def test_capture2d3d_one_2d_and_one_3d(shared_file_camera): + import zivid + + acquisitions3d = [zivid.Settings.Acquisition()] + acquisitions2d = [zivid.Settings2D.Acquisition()] + settings = zivid.Settings( + acquisitions=acquisitions3d, color=zivid.Settings2D(acquisitions=acquisitions2d) + ) + + with shared_file_camera.capture2d3d(settings) as frame: + assert frame + assert isinstance(frame, zivid.frame.Frame) + + +def test_capture2d3d_two_2d_and_one_3d(shared_file_camera): + import zivid + + acquisitions3d = [zivid.Settings.Acquisition()] + acquisitions2d = [zivid.Settings2D.Acquisition(), zivid.Settings2D.Acquisition()] + settings = zivid.Settings( + acquisitions=acquisitions3d, color=zivid.Settings2D(acquisitions=acquisitions2d) + ) + + with shared_file_camera.capture2d3d(settings) as frame: + assert frame + assert isinstance(frame, zivid.frame.Frame) + + +def test_capture2d3d_one_2d_and_two_3d(shared_file_camera): + import zivid + + acquisitions3d = [zivid.Settings.Acquisition(), zivid.Settings.Acquisition()] + acquisitions2d = [zivid.Settings2D.Acquisition()] + settings = zivid.Settings( + acquisitions=acquisitions3d, color=zivid.Settings2D(acquisitions=acquisitions2d) + ) + + with shared_file_camera.capture2d3d(settings) as frame: + assert frame + assert isinstance(frame, zivid.frame.Frame) + + +def test_capture2d3d_two_2d_and_two_3d(shared_file_camera): + import zivid + + acquisitions3d = [zivid.Settings.Acquisition(), zivid.Settings.Acquisition()] + acquisitions2d = [zivid.Settings2D.Acquisition(), zivid.Settings2D.Acquisition()] + settings = zivid.Settings( + acquisitions=acquisitions3d, color=zivid.Settings2D(acquisitions=acquisitions2d) + ) + + with shared_file_camera.capture2d3d(settings) as frame: + assert frame + assert isinstance(frame, zivid.frame.Frame) + + +def test_capture3d_one_acquisition(shared_file_camera): + import zivid + + acquisitions = [zivid.Settings.Acquisition()] + settings = zivid.Settings(acquisitions=acquisitions) + with shared_file_camera.capture3d(settings) as frame: + assert frame + assert isinstance(frame, zivid.frame.Frame) + + +def test_capture2d_with_settings2d(shared_file_camera): + import zivid + + acquisitions = [zivid.Settings2D.Acquisition()] + settings = zivid.Settings2D(acquisitions=acquisitions) + with shared_file_camera.capture2d(settings) as frame: + assert frame + assert isinstance(frame, zivid.Frame2D) + + +def test_capture2d_with_settings(shared_file_camera): + import zivid + + acquisitions = [zivid.Settings2D.Acquisition()] + settings = zivid.Settings(color=zivid.Settings2D(acquisitions=acquisitions)) + with shared_file_camera.capture2d(settings) as frame: + assert frame + assert isinstance(frame, zivid.Frame2D) + + def test_one_acquisition_in_list(shared_file_camera): import zivid