Skip to content

Commit

Permalink
Merge pull request #296 from zivid/ZIVID-9207-wrap-new-capture-methods
Browse files Browse the repository at this point in the history
Add new capture methods capture_2d/capture_3d/capture_2d_3d
  • Loading branch information
johningve authored Dec 9, 2024
2 parents f5199b8 + 22ee714 commit 5673aef
Show file tree
Hide file tree
Showing 15 changed files with 335 additions and 44 deletions.
75 changes: 45 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,57 +65,72 @@ this option is considered experimental/unofficial.
### Point cloud capture

To quickly capture a point cloud using default settings, run the following code:

import zivid
app = zivid.Application()
camera = app.connect_camera()
settings = zivid.Settings(acquisitions=[zivid.Settings.Acquisition()])
frame = camera.capture(settings)
frame.save("result.zdf")
```python
import zivid
app = zivid.Application()
camera = app.connect_camera()
settings = zivid.Settings(
acquisitions=[zivid.Settings.Acquisition()],
color=zivid.Settings2D(acquisitions=[zivid.Settings2D.Acquisition()]),
)
frame = camera.capture_2d_3d(settings)
frame.save("result.zdf")
```

Instead of using the API to define capture settings, it is also possible to load them from YML files that
have been exported from [Zivid Studio][zivid-studio-guide-url] or downloaded from the Zivid Knowledge Base
[settings library][zivid-two-standard-settings-url]. This can be done by providing the filesystem path to
such a file, for example:

settings = Settings.load("ZividTwo_Settings_2xHDR_Normal.yml")
frame = camera.capture(settings)
```python
settings = Settings.load("ZividTwo_Settings_2xHDR_Normal.yml")
frame = camera.capture_2d_3d(settings)
```

### Point cloud data access

Data can easily be accessed in the form of Numpy arrays:

import zivid
app = zivid.Application()
camera = app.connect_camera()
settings = zivid.Settings(acquisitions=[zivid.Settings.Acquisition()])
frame = camera.capture(settings)
xyz = frame.point_cloud().copy_data("xyz") # Get point coordinates as [Height,Width,3] float array
rgba = frame.point_cloud().copy_data("rgba") # Get point colors as [Height,Width,4] uint8 array
bgra = frame.point_cloud().copy_data("bgra") # Get point colors as [Height,Width,4] uint8 array
```python
import zivid
app = zivid.Application()
camera = app.connect_camera()
settings = zivid.Settings(
acquisitions=[zivid.Settings.Acquisition()],
color=zivid.Settings2D(acquisitions=[zivid.Settings2D.Acquisition()]),
)
frame = camera.capture_2d_3d(settings)
xyz = frame.point_cloud().copy_data("xyz") # Get point coordinates as [Height,Width,3] float array
rgba = frame.point_cloud().copy_data("rgba") # Get point colors as [Height,Width,4] uint8 array
bgra = frame.point_cloud().copy_data("bgra") # Get point colors as [Height,Width,4] uint8 array
```

### Capture Assistant

Instead of manually adjusting settings, the Capture Assistant may be used to find the optimal settings for your scene:

import zivid
app = zivid.Application()
camera = app.connect_camera()
capture_assistant_params = zivid.capture_assistant.SuggestSettingsParameters()
settings = zivid.capture_assistant.suggest_settings(camera, capture_assistant_params)
frame = camera.capture(settings)
frame.save("result.zdf")
```python
import zivid
app = zivid.Application()
camera = app.connect_camera()
capture_assistant_params = zivid.capture_assistant.SuggestSettingsParameters()
settings = zivid.capture_assistant.suggest_settings(camera, capture_assistant_params)
frame = camera.capture_2d_3d(settings)
frame.save("result.zdf")
```

### Using camera emulation

If you do not have a camera, you can use the `FileCameraZivid2M70.zfc` file in the [Sample Data][zivid-download-sampledata-url] to emulate a camera.

import zivid
app = zivid.Application()
camera = app.create_file_camera("path/to/FileCameraZivid2M70.zfc")
settings = zivid.Settings(acquisitions=[zivid.Settings.Acquisition()])
frame = camera.capture(settings)
frame.save("result.zdf")
```python
import zivid
app = zivid.Application()
camera = app.create_file_camera("path/to/FileCameraZivid2M70.zfc")
settings = zivid.Settings(acquisitions=[zivid.Settings.Acquisition()])
frame = camera.capture_3d(settings)
frame.save("result.zdf")
```

## Examples

Expand Down
123 changes: 122 additions & 1 deletion modules/zivid/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,125 @@ def __str__(self):
def __eq__(self, other):
return self.__impl == other._Camera__impl

def capture_2d_3d(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 `capture_3d` 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 `capture_2d_3d` or `capture_3d` 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.capture_2d_3d(_to_internal_settings(settings)))

def capture_3d(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 `capture_2d_3d` 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 `capture_2d_3d` 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.capture_3d(_to_internal_settings(settings)))

def capture_2d(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 `capture_2d_3d` above. Those remarks apply for both
2D, 3D, and 2D+3D captures.
Args:
settings: Settings to use for the capture. Can be either a Settings2D instance or a Settings instance.
If a Settings instance is provided, only the Settings.color part is used. An exception is thrown
if the Settings.color part is not set.
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.capture_2d(_to_internal_settings2d(settings)))
if isinstance(settings, Settings):
return Frame2D(self.__impl.capture_2d(_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
`capture_2d_3d` instead for capturing 2D+3D frames, use `capture_3d` for capturing 3D frames without a 2D color
image, or use `capture_2d` for capturing a 2D color image only.
This method shares the common remarks about capture functions as found under `capture_2d_3d`.
Args:
settings: Settings to be used to capture. Can be either a Settings or Settings2D instance
Expand All @@ -64,7 +180,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):
Expand Down
2 changes: 1 addition & 1 deletion samples/sample_calibrate_eye_to_hand.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def _acquire_checkerboard_frame(camera):
settings = zivid.capture_assistant.suggest_settings(
camera, suggest_settings_parameters
)
return camera.capture(settings)
return camera.capture_2d_3d(settings)


def _enter_robot_pose(index):
Expand Down
11 changes: 9 additions & 2 deletions samples/sample_capture.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Capture sample."""

import datetime
from zivid import Application, Settings
from zivid import Application, Settings, Settings2D


def _main():
Expand All @@ -14,7 +14,14 @@ def _main():
settings.processing.filters.outlier.removal.enabled = True
settings.processing.filters.outlier.removal.threshold = 5.0

with camera.capture(settings) as frame:
settings.color = Settings2D()
settings.color.acquisitions.append(Settings2D.Acquisition())
settings.color.acquisitions[0].aperture = 5.6
settings.color.acquisitions[0].exposure_time = datetime.timedelta(
microseconds=8333
)

with camera.capture_2d_3d(settings) as frame:
frame.save("result.zdf")


Expand Down
2 changes: 1 addition & 1 deletion samples/sample_capture_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def _main():
microseconds=10000
)

with camera.capture(settings_2d) as frame_2d:
with camera.capture_2d(settings_2d) as frame_2d:
image = frame_2d.image_rgba()
image.save("result.png")

Expand Down
2 changes: 1 addition & 1 deletion samples/sample_capture_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def _main():
camera, suggest_settings_parameters
)

with camera.capture(settings) as frame:
with camera.capture_2d_3d(settings) as frame:
frame.save("result.zdf")


Expand Down
2 changes: 1 addition & 1 deletion samples/sample_capture_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

def _capture_sync(cameras: list[zivid.Camera]) -> list[zivid.Frame]:
return [
camera.capture(
camera.capture_3d(
zivid.Settings(
acquisitions=[
zivid.Settings.Acquisition(
Expand Down
9 changes: 6 additions & 3 deletions samples/sample_capture_from_file.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""File camera capture sample."""

from zivid import Application, Settings
from zivid import Application, Settings, Settings2D


def _main():
app = Application()
with app.create_file_camera("FileCameraZivid2M70.zfc") as camera:
settings = Settings(acquisitions=[Settings.Acquisition()])
settings = Settings(
acquisitions=[Settings.Acquisition()],
color=Settings2D(acquisitions=[Settings2D.Acquisition()]),
)

with camera.capture(settings) as frame:
with camera.capture_2d_3d(settings) as frame:
frame.save("result.zdf")


Expand Down
14 changes: 12 additions & 2 deletions samples/sample_capture_hdr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""HDR capture sample."""

from zivid import Application, Settings
import datetime

from zivid import Application, Settings, Settings2D


def _main():
Expand All @@ -10,7 +12,15 @@ def _main():
acquisitions=[
Settings.Acquisition(aperture=aperture)
for aperture in (10.90, 5.80, 2.83)
]
],
color=Settings2D(
acquisitions=[
Settings2D.Acquisition(
exposure_time=datetime.timedelta(microseconds=exposure_time)
)
for exposure_time in (1677, 5000, 10000)
]
),
)
with camera.capture(settings) as hdr_frame:
hdr_frame.save("result.zdf")
Expand Down
2 changes: 1 addition & 1 deletion samples/sample_presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def _main():
]

print("Capturing point cloud with preset '{}' ...".format(chosen_preset.name))
with camera.capture(chosen_preset.settings) as frame:
with camera.capture_2d_3d(chosen_preset.settings) as frame:
frame.save("result.zdf")

settings_file = chosen_preset.name + ".yml"
Expand Down
4 changes: 3 additions & 1 deletion samples/sample_project_on_checkerboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ def _detect_checkerboard(camera):
print("Detecting checkerboard...")
settings = Settings()
settings.acquisitions.append(Settings.Acquisition())
with camera.capture(settings) as frame:
settings.color = Settings2D()
settings.color.acquisitions.append(Settings2D.Acquisition())
with camera.capture_2d_3d(settings) as frame:
detection_result = detect_feature_points(frame.point_cloud())
if not detection_result.valid():
raise RuntimeError("Failed to detect checkerboard")
Expand Down
8 changes: 8 additions & 0 deletions src/ReleasableCamera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ namespace ZividPython
.def(py::self != py::self) // NOLINT
.def("disconnect", &ReleasableCamera::disconnect)
.def("connect", &ReleasableCamera::connect)
.def("capture_2d_3d", &ReleasableCamera::capture2D3D)
.def("capture_3d", &ReleasableCamera::capture3D)
.def("capture_2d",
py::overload_cast<const Zivid::Settings2D &>(&ReleasableCamera::capture2D),
py::arg("settings_2d"))
.def("capture_2d",
py::overload_cast<const Zivid::Settings &>(&ReleasableCamera::capture2D),
py::arg("settings"))
.def("capture", py::overload_cast<const Zivid::Settings &>(&ReleasableCamera::capture), py::arg("settings"))
.def("capture",
py::overload_cast<const Zivid::Settings2D &>(&ReleasableCamera::capture),
Expand Down
4 changes: 4 additions & 0 deletions src/include/ZividPython/ReleasableCamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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)
Expand Down
Loading

0 comments on commit 5673aef

Please sign in to comment.