diff --git a/README.md b/README.md index 0b35a72a..cfd381a9 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ tested compatibility with earlier SDK versions, please check out - [File Camera] - [Projector] - **Maintenance and Prevention** + - [Benchmarking Your System] - [Infield Correction] - [Warm-up] - [Firmware Update] @@ -87,9 +88,8 @@ from the camera can be used. normals and print a subset. - [CaptureHalconViaGenICam] - Capture and save a point cloud, with colors, using GenICam interface and Halcon C++ SDK. - - [CaptureHalconViaZivid] - Capture a point cloud, with colors, - using Zivid SDK, transform it to a Halcon point cloud and save it - using Halcon C++ SDK. + - [CaptureHalconViaZivid] - Capture a colored point cloud with the + Zivid SDK, convert it to a HALCON point cloud and save it. - [CaptureViaGenICam] - Capture using the GenICam interface. - **MultiCamera** - [MultiCameraCaptureInParallel] - Capture point clouds with @@ -106,17 +106,15 @@ from the camera can be used. - [CaptureWithDiagnostics] - Capture a 2D+3D frame and a 2D frame from the Zivid camera with diagnostics enabled. - [CheckHealth] - Poll the camera health check from a separate - thread while capturing in the main thread, printing the statuses - and values every second. + thread while capturing, printing statuses and values each second. - [ExploreSettingsMetaData] - Recursively iterates through all leaf parameters in a Zivid camera’s settings. - [FirmwareUpdater] - Update firmware on the Zivid camera. - [FrameInfo] - Read frame info from the Zivid camera. - [GetCameraIntrinsics] - Read intrinsic parameters from the Zivid camera (OpenCV model) or estimate them from the point cloud. - - [MeasureSceneConditions] - Measure ambient light conditions in the - scene and output the measured flickering frequency of the ambient - light if flickering is detected. + - [MeasureSceneConditions] - Measure ambient light in the scene and + report the flickering frequency when flickering is detected. - [SettingsInfo] - Read settings info from the Zivid camera. - [Warmup] - Short example of a basic way to warm up the camera with specified time and capture cycle. @@ -153,7 +151,7 @@ from the camera can be used. it. - **FileFormats** - [ConvertZDF] - Convert point cloud data from a ZDF file to your - preferred format + preferred format. - [ReadIterateZDF] - Read point cloud data from a ZDF file, iterate through it, and extract individual points. - **Advanced** @@ -176,17 +174,17 @@ from the camera can be used. with the Zivid SDK and perform RGBA to grayscale conversion on a CUDA device using OpenCV. - [CaptureAndConvertToDlpackTensorOnCuda] - Capture a 2D+3D frame - with the Zivid SDK and build DLPack tensors directly from the - Zivid DeviceArrays on the CUDA device. + and build DLPack tensors directly from the Zivid DeviceArrays on + the CUDA device. - **Transform** - [TransformPointCloudFromMillimetersToMeters] - Transform point cloud data from millimeters to meters. - [TransformPointCloudViaArucoMarker] - Transform a point cloud - from camera to ArUco marker coordinate frame by estimating the - marker's pose from the point cloud. + from camera to ArUco marker coordinate frame using the marker's + estimated pose. - [TransformPointCloudViaCheckerboard] - Transform a point cloud - from camera to checkerboard (Zivid Calibration Board) coordinate - frame by getting checkerboard pose from the API. + from camera to checkerboard coordinate frame using the pose from + the API. - **Roi** - [ROIBoxViaArucoMarker] - Filter the point cloud based on a ROI box given relative to the ArUco marker on a Zivid Calibration @@ -195,8 +193,8 @@ from the camera can be used. box given relative to the Zivid Calibration Board. - **Stitching** - [StitchContinuouslyRotatingObject] - Stitch point clouds from a - continuously rotating object without pre-alignment using Local - Point Cloud Registration and apply Voxel Downsample. + continuously rotating object using Local Point Cloud + Registration, then downsample. - [StitchUsingRobotMountedCamera] - Stitch multiple point clouds captured with a robot mounted camera. - [StitchViaLocalPointCloudRegistration] - Stitch two point clouds @@ -208,23 +206,23 @@ from the camera can be used. - **HandEyeCalibration** - [HandEyeCalibration] - Perform Hand-Eye calibration. - [PoseConversions] - Convert to/from Transformation Matrix - (Rotation Matrix + Translation Vector) - - [UtilizeHandEyeCalibration] - Transform single data point or - entire point cloud from camera to robot base reference frame - using Hand-Eye calibration + (Rotation Matrix + Translation Vector). + - [UtilizeHandEyeCalibration] - Transform a data point or entire + point cloud from camera to robot base frame using the Hand-Eye + calibration matrix. - **MultiCamera** - - [MultiCameraCalibration] - Use captures of a calibration object - to generate transformation matrices to a single coordinate - frame, from multiple connected cameras. + - [MultiCameraCalibration] - Generate transformation matrices to a + single coordinate frame from captures by multiple connected + cameras. - [MultiCameraCalibrationFromZDF] - Use captures of a calibration object to generate transformation matrices to a single coordinate frame, from a ZDF files. - - [StitchByTransformation] - Use transformation matrices from - Multi-Camera calibration to transform point clouds into single - coordinate frame, from connected cameras. - - [StitchByTransformationFromZDF] - Use transformation matrices - from Multi-Camera calibration to transform point clouds into - single coordinate frame, from a ZDF files. + - [StitchByTransformation] - Transform point clouds from connected + cameras into one coordinate frame using Multi-Camera + calibration. + - [StitchByTransformationFromZDF] - Transform point clouds from + ZDF files into one coordinate frame using Multi-Camera + calibration. - **Projector** - [ProjectAndFindMarker] - Show a marker using the projector, capture a set of 2D images to find the marker coordinates (2D @@ -232,7 +230,7 @@ from the camera can be used. - [ReadProjectAndCaptureImage] - Read a 2D image from file and project it using the camera projector. - [ReprojectPoints] - Illuminate checkerboard (Zivid Calibration - Board) centers by getting the checkerboard feature points + Board) centers by getting the checkerboard feature points. ## Installation @@ -329,7 +327,7 @@ The following HALCON versions have been tested and confirmed to work with Zivid cameras: - 19.05 Progress, 20.05 Progress, 21.11 Progress, 24.05 Progress, 24.11 - Progress-Steady, 25.05 Progress + Progress-Steady, 25.05 Progress, 25.11 Progress, 26.05 Progress We recommend using one of the HALCON versions we have tested. @@ -377,6 +375,7 @@ If your build hangs, try to increase the memory available to Docker. [Multiple Zivid Cameras]: https://support.zivid.com/en/latest/camera/academy/camera/multiple-zivid-cameras.html [File Camera]: https://support.zivid.com/en/latest/camera/academy/camera/file-camera.html [Projector]: https://support.zivid.com/en/latest/camera/academy/camera/2d-image-projection.html + [Benchmarking Your System]: https://support.zivid.com/en/latest/camera/api-reference/benchmarks/benchmarking-your-system.html [Infield Correction]: https://support.zivid.com/en/latest/camera/academy/camera/infield-correction.html [Warm-up]: https://support.zivid.com/en/latest/camera/academy/camera/warmup.html [Firmware Update]: https://support.zivid.com/en/latest/camera/academy/camera/firmware-update.html diff --git a/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp b/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp index bbaf9e1d..b592b40d 100644 --- a/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp +++ b/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp @@ -1,6 +1,5 @@ /* -Capture a 2D+3D frame with the Zivid SDK and build DLPack tensors directly from the Zivid DeviceArrays on the CUDA -device. +Capture a 2D+3D frame and build DLPack tensors directly from the Zivid DeviceArrays on the CUDA device. This sample captures a combined 2D+3D frame and converts two common buffers to DLPack: the point cloud (PointXYZ) and the color image (ColorRGBA_SRGB). It constructs a DLManagedTensor from the raw building blocks each DeviceArray exposes - diff --git a/source/Applications/Advanced/HandEyeCalibration/PoseConversions/PoseConversions.cpp b/source/Applications/Advanced/HandEyeCalibration/PoseConversions/PoseConversions.cpp index 772bff28..d4a79015 100644 --- a/source/Applications/Advanced/HandEyeCalibration/PoseConversions/PoseConversions.cpp +++ b/source/Applications/Advanced/HandEyeCalibration/PoseConversions/PoseConversions.cpp @@ -1,5 +1,5 @@ /* -Convert to/from Transformation Matrix (Rotation Matrix + Translation Vector) +Convert to/from Transformation Matrix (Rotation Matrix + Translation Vector). Zivid primarily operate with a (4x4) transformation matrix. This example shows how to use Eigen to convert to and from: AxisAngle, Rotation Vector, Roll-Pitch-Yaw, Quaternion diff --git a/source/Applications/Advanced/HandEyeCalibration/UtilizeHandEyeCalibration/UtilizeHandEyeCalibration.cpp b/source/Applications/Advanced/HandEyeCalibration/UtilizeHandEyeCalibration/UtilizeHandEyeCalibration.cpp index 70feb47b..2d578da3 100644 --- a/source/Applications/Advanced/HandEyeCalibration/UtilizeHandEyeCalibration/UtilizeHandEyeCalibration.cpp +++ b/source/Applications/Advanced/HandEyeCalibration/UtilizeHandEyeCalibration/UtilizeHandEyeCalibration.cpp @@ -1,6 +1,5 @@ /* -Transform single data point or entire point cloud from camera to robot base reference frame using Hand-Eye calibration -matrix. +Transform a data point or entire point cloud from camera to robot base frame using the Hand-Eye calibration matrix. This example shows how to utilize the result of Hand-Eye calibration to transform either (picking) point coordinates or the entire point cloud from the camera to the robot base reference frame. diff --git a/source/Applications/Advanced/MultiCamera/MultiCameraCalibration/MultiCameraCalibration.cpp b/source/Applications/Advanced/MultiCamera/MultiCameraCalibration/MultiCameraCalibration.cpp index 1bfde348..e1aa1fa8 100644 --- a/source/Applications/Advanced/MultiCamera/MultiCameraCalibration/MultiCameraCalibration.cpp +++ b/source/Applications/Advanced/MultiCamera/MultiCameraCalibration/MultiCameraCalibration.cpp @@ -1,5 +1,5 @@ /* -Use captures of a calibration object to generate transformation matrices to a single coordinate frame, from multiple connected cameras. +Generate transformation matrices to a single coordinate frame from captures by multiple connected cameras. For more information on multi-camera calibration, check out this tutorial: https://support.zivid.com/en/latest/camera/academy/applications/multi-camera-calibration.html diff --git a/source/Applications/Advanced/MultiCamera/StitchByTransformation/StitchByTransformation.cpp b/source/Applications/Advanced/MultiCamera/StitchByTransformation/StitchByTransformation.cpp index 0923fb37..e37d9d3e 100644 --- a/source/Applications/Advanced/MultiCamera/StitchByTransformation/StitchByTransformation.cpp +++ b/source/Applications/Advanced/MultiCamera/StitchByTransformation/StitchByTransformation.cpp @@ -1,5 +1,5 @@ /* -Use transformation matrices from Multi-Camera calibration to transform point clouds into single coordinate frame, from connected cameras. +Transform point clouds from connected cameras into one coordinate frame using Multi-Camera calibration. Note: This example uses experimental SDK features, which may be modified, moved, or deleted in the future without notice. diff --git a/source/Applications/Advanced/MultiCamera/StitchByTransformationFromZDF/StitchByTransformationFromZDF.cpp b/source/Applications/Advanced/MultiCamera/StitchByTransformationFromZDF/StitchByTransformationFromZDF.cpp index 92bbc868..b3b96f96 100644 --- a/source/Applications/Advanced/MultiCamera/StitchByTransformationFromZDF/StitchByTransformationFromZDF.cpp +++ b/source/Applications/Advanced/MultiCamera/StitchByTransformationFromZDF/StitchByTransformationFromZDF.cpp @@ -1,5 +1,5 @@ /* -Use transformation matrices from Multi-Camera calibration to transform point clouds into single coordinate frame, from a ZDF files. +Transform point clouds from ZDF files into one coordinate frame using Multi-Camera calibration. Note: This example uses experimental SDK features, which may be modified, moved, or deleted in the future without notice. diff --git a/source/Applications/Advanced/Projector/ReprojectPoints/ReprojectPoints.cpp b/source/Applications/Advanced/Projector/ReprojectPoints/ReprojectPoints.cpp index 860a9210..fc0fe66b 100644 --- a/source/Applications/Advanced/Projector/ReprojectPoints/ReprojectPoints.cpp +++ b/source/Applications/Advanced/Projector/ReprojectPoints/ReprojectPoints.cpp @@ -1,5 +1,5 @@ /* -Illuminate checkerboard (Zivid Calibration Board) centers by getting the checkerboard feature points +Illuminate checkerboard (Zivid Calibration Board) centers by getting the checkerboard feature points. and illuminating them with the projector. The checkerboard feature points are first found through the API. These points are then used to get the diff --git a/source/Applications/Advanced/Stitching/StitchContinuouslyRotatingObject/StitchContinuouslyRotatingObject.cpp b/source/Applications/Advanced/Stitching/StitchContinuouslyRotatingObject/StitchContinuouslyRotatingObject.cpp index a721c35b..5fb78d37 100644 --- a/source/Applications/Advanced/Stitching/StitchContinuouslyRotatingObject/StitchContinuouslyRotatingObject.cpp +++ b/source/Applications/Advanced/Stitching/StitchContinuouslyRotatingObject/StitchContinuouslyRotatingObject.cpp @@ -1,5 +1,5 @@ /* -Stitch point clouds from a continuously rotating object without pre-alignment using Local Point Cloud Registration and apply Voxel Downsample. +Stitch point clouds from a continuously rotating object using Local Point Cloud Registration, then downsample. It is assumed that the object is rotating around its own axis and the camera is stationary. The camera settings should have defined a region of interest box that removes unnecessary points, keeping only the object to be stitched. diff --git a/source/Applications/Advanced/Transform/TransformPointCloudViaArucoMarker/TransformPointCloudViaArucoMarker.cpp b/source/Applications/Advanced/Transform/TransformPointCloudViaArucoMarker/TransformPointCloudViaArucoMarker.cpp index 377a8c37..e1261149 100644 --- a/source/Applications/Advanced/Transform/TransformPointCloudViaArucoMarker/TransformPointCloudViaArucoMarker.cpp +++ b/source/Applications/Advanced/Transform/TransformPointCloudViaArucoMarker/TransformPointCloudViaArucoMarker.cpp @@ -1,5 +1,5 @@ /* -Transform a point cloud from camera to ArUco marker coordinate frame by estimating the marker's pose from the point cloud. +Transform a point cloud from camera to ArUco marker coordinate frame using the marker's estimated pose. The ZDF file for this sample can be found under the main instructions for Zivid samples. diff --git a/source/Applications/Advanced/Transform/TransformPointCloudViaCheckerboard/TransformPointCloudViaCheckerboard.cpp b/source/Applications/Advanced/Transform/TransformPointCloudViaCheckerboard/TransformPointCloudViaCheckerboard.cpp index 6b97d598..4956c7a7 100644 --- a/source/Applications/Advanced/Transform/TransformPointCloudViaCheckerboard/TransformPointCloudViaCheckerboard.cpp +++ b/source/Applications/Advanced/Transform/TransformPointCloudViaCheckerboard/TransformPointCloudViaCheckerboard.cpp @@ -1,5 +1,5 @@ /* -Transform a point cloud from camera to checkerboard (Zivid Calibration Board) coordinate frame by getting checkerboard pose from the API. +Transform a point cloud from camera to checkerboard coordinate frame using the pose from the API. The ZDF file for this sample can be found under the main instructions for Zivid samples. diff --git a/source/Applications/Advanced/Visualization/CaptureVis3DInLoop/CaptureVis3DInLoop.cpp b/source/Applications/Advanced/Visualization/CaptureVis3DInLoop/CaptureVis3DInLoop.cpp index e6a5289b..9f3bf093 100644 --- a/source/Applications/Advanced/Visualization/CaptureVis3DInLoop/CaptureVis3DInLoop.cpp +++ b/source/Applications/Advanced/Visualization/CaptureVis3DInLoop/CaptureVis3DInLoop.cpp @@ -5,7 +5,8 @@ Capture point clouds, with color, from the Zivid camera, and visualize them in a #include #include -#include +#include +#include #include #include @@ -26,49 +27,48 @@ int main() Zivid::Settings2D::Acquisitions{ Zivid::Settings2D::Acquisition{} } } } }; std::cout << "Capturing frame" << std::endl; - auto frame = camera.capture2D3D(settings); + const auto frame = camera.capture2D3D(settings); std::cout << "Settings:" << frame.settings() << std::endl; std::cout << "Setting up visualization" << std::endl; - std::atomic_bool visualizerRunning{ false }; - auto visualizerPromise = std::promise(); - auto visualizerFuture = visualizerPromise.get_future(); + auto visualizer = Zivid::Visualization::Visualizer(); - std::thread visualizationThread([&frame, &visualizerPromise, &visualizerRunning]() { - auto visualizer = Zivid::Visualization::Visualizer(); + std::cout << "Visualizing point cloud" << std::endl; + visualizer.showMaximized(); + visualizer.show(frame); + visualizer.resetToFit(); - // Pass the visualizer to the main thread - visualizerPromise.set_value(&visualizer); + std::atomic_bool visualizerRunning{ true }; + std::exception_ptr captureException; - std::cout << "Visualizing point cloud" << std::endl; - visualizer.showMaximized(); - visualizer.show(frame); - visualizer.resetToFit(); - - std::cout << "Running visualizer. Blocking until window closes." << std::endl; - visualizerRunning = true; - visualizer.run(); - visualizerRunning = false; + std::thread captureThread([&camera, &settings, &visualizer, &visualizerRunning, &captureException]() { + try + { + while(visualizerRunning) + { + const auto newFrame = camera.capture2D3D(settings); + if(visualizerRunning) + { + visualizer.show(newFrame); + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + catch(...) + { + captureException = std::current_exception(); + visualizer.close(); + } }); - // Get the visualizer handle in the main thread - auto visualizerHandle = visualizerFuture.get(); - - while(!visualizerRunning) - { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - while(visualizerRunning) + std::cout << "Running visualizer. Blocking until window closes." << std::endl; + visualizer.run(); + visualizerRunning = false; + captureThread.join(); + if(captureException) { - frame = camera.capture2D3D(settings); - if(!visualizerRunning) - { - break; - } - visualizerHandle->show(frame); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::rethrow_exception(captureException); } - visualizationThread.join(); std::cout << "Visualizer closed" << std::endl; } catch(const std::exception &e) diff --git a/source/Applications/Advanced/Visualization/CaptureVis3DInLoopWithKeypressExit/CaptureVis3DInLoopWithKeypressExit.cpp b/source/Applications/Advanced/Visualization/CaptureVis3DInLoopWithKeypressExit/CaptureVis3DInLoopWithKeypressExit.cpp index b3c95f71..ccceae7b 100644 --- a/source/Applications/Advanced/Visualization/CaptureVis3DInLoopWithKeypressExit/CaptureVis3DInLoopWithKeypressExit.cpp +++ b/source/Applications/Advanced/Visualization/CaptureVis3DInLoopWithKeypressExit/CaptureVis3DInLoopWithKeypressExit.cpp @@ -5,7 +5,8 @@ Capture point clouds, with color, from the Zivid camera, and visualize them in a #include #include -#include +#include +#include #include #include #ifdef _WIN32 @@ -71,70 +72,54 @@ int main() Zivid::Settings2D::Acquisitions{ Zivid::Settings2D::Acquisition{} } } } }; std::cout << "Capturing frame" << std::endl; - auto frame = camera.capture2D3D(settings); + const auto frame = camera.capture2D3D(settings); std::cout << "Setting up visualization" << std::endl; - std::atomic_bool visualizerRunning{ false }; - std::atomic_bool acceptEnd{ true }; - std::atomic_bool quitRequested{ false }; - auto visualizerPromise = std::promise(); - auto visualizerFuture = visualizerPromise.get_future(); + auto visualizer = Zivid::Visualization::Visualizer(); - std::thread visualizationThread([&frame, &visualizerPromise, &visualizerRunning, &acceptEnd, &quitRequested]() { - auto visualizer = Zivid::Visualization::Visualizer(); + std::cout << "Visualizing point cloud" << std::endl; + visualizer.showMaximized(); + visualizer.show(frame); + visualizer.resetToFit(); - // Pass the visualizer to the main thread - visualizerPromise.set_value(&visualizer); + std::atomic_bool visualizerRunning{ true }; + std::exception_ptr captureException; - std::cout << "Visualizing point cloud" << std::endl; - visualizer.showMaximized(); - visualizer.show(frame); - visualizer.resetToFit(); - - std::cout << "Running visualizer. Blocking until window closes." << std::endl; - do + std::thread captureThread([&camera, &settings, &visualizer, &visualizerRunning, &captureException]() { + try { - visualizerRunning = true; - visualizer.run(); - visualizerRunning = false; - - if(quitRequested) + std::cout << "Press 'q' in the terminal to quit " << std::endl; + while(visualizerRunning) { - break; + if(getKeyNonBlocking() == 'q') + { + std::cout << "Closing application because user pressed 'q'" << std::endl; + visualizer.close(); + break; + } + const auto newFrame = camera.capture2D3D(settings); + if(visualizerRunning) + { + visualizer.show(newFrame); + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - std::cout << "Visualizer window closed by user. It will be reopened if we're currently capturing." - << std::endl; - - } while(!acceptEnd); - }); - - // Get the visualizer handle in the main thread - auto visualizerHandle = visualizerFuture.get(); - - while(!visualizerRunning) - { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - std::cout << "Press 'q' in the terminal to quit " << std::endl; - while(visualizerRunning) - { - int key = getKeyNonBlocking(); - if(key == 'q') - { - std::cout << "Closing application because user pressed 'q'" << std::endl; - quitRequested = true; - visualizerHandle->close(); } - else + catch(...) { - acceptEnd = false; - frame = camera.capture2D3D(settings); - visualizerHandle->show(frame); - acceptEnd = true; + captureException = std::current_exception(); + visualizer.close(); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + }); + + std::cout << "Running visualizer. Blocking until the window closes or 'q' is pressed." << std::endl; + visualizer.run(); + visualizerRunning = false; + captureThread.join(); + if(captureException) + { + std::rethrow_exception(captureException); } - visualizationThread.join(); std::cout << "Visualizer closed" << std::endl; } catch(const std::exception &e) diff --git a/source/Applications/Basic/FileFormats/ConvertZDF/ConvertZDF.cpp b/source/Applications/Basic/FileFormats/ConvertZDF/ConvertZDF.cpp index d43331cc..01192343 100644 --- a/source/Applications/Basic/FileFormats/ConvertZDF/ConvertZDF.cpp +++ b/source/Applications/Basic/FileFormats/ConvertZDF/ConvertZDF.cpp @@ -1,5 +1,5 @@ /* -Convert point cloud data from a ZDF file to your preferred format +Convert point cloud data from a ZDF file to your preferred format. If a directory is provided, all ZDF files in the directory will be converted Available formats: diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ed5b7d9f..f44f9d0c 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -157,6 +157,7 @@ set(Clipp_DEPENDING CaptureViaGenICam CaptureWithSettingsFromYML CaptureWritePCLVis3D + Connect ConvertZDF GammaCorrection HandEyeCalibration diff --git a/source/Camera/Advanced/CaptureHalconViaZivid/CaptureHalconViaZivid.cpp b/source/Camera/Advanced/CaptureHalconViaZivid/CaptureHalconViaZivid.cpp index a02efa55..7cfcdb0c 100644 --- a/source/Camera/Advanced/CaptureHalconViaZivid/CaptureHalconViaZivid.cpp +++ b/source/Camera/Advanced/CaptureHalconViaZivid/CaptureHalconViaZivid.cpp @@ -1,5 +1,5 @@ /* -Capture a point cloud, with colors, using Zivid SDK, transform it to a Halcon point cloud and save it using Halcon C++ SDK. +Capture a colored point cloud with the Zivid SDK, convert it to a HALCON point cloud and save it. For more information about interfacing Zivid with Halcon, check out this article: https://support.zivid.com/en/latest/camera/api-reference/samples/halcon.html#instructions diff --git a/source/Camera/Basic/Connect/Connect.cpp b/source/Camera/Basic/Connect/Connect.cpp index 4f55aa6f..0adedb00 100644 --- a/source/Camera/Basic/Connect/Connect.cpp +++ b/source/Camera/Basic/Connect/Connect.cpp @@ -1,15 +1,43 @@ /* Connect to a Zivid camera using the different available methods. -Replace the IP address and serial number in the code with the ones of your camera. +Replace the IP address, serial number and hostname in the code with the ones of your camera, +or provide them with --serial, --ip and --hostname. */ #include +#include + #include namespace { + struct CameraIdentifiers + { + std::string serialNumber{ "2020C0DE" }; + std::string ipAddress{ "172.28.60.5" }; + std::string hostname{ "zivid-2020C0DE.local" }; + }; + + CameraIdentifiers parseOptions(int argc, char **argv) + { + CameraIdentifiers identifiers; + auto cli = + (clipp::option("--serial") + & clipp::value("serial number", identifiers.serialNumber).doc("Serial number of the camera"), + clipp::option("--ip") & clipp::value("ip address", identifiers.ipAddress).doc("IP address of the camera"), + clipp::option("--hostname") + & clipp::value("hostname", identifiers.hostname).doc("Hostname of the camera")); + + if(!clipp::parse(argc, argv, cli)) + { + std::cout << clipp::usage_lines(cli, "Connect") << std::endl; + throw std::runtime_error{ "Invalid usage" }; + } + return identifiers; + } + void printDiscoveredCameras(Zivid::Application &zivid) { std::cout << "Discovered cameras:" << std::endl; @@ -21,18 +49,16 @@ namespace } } // namespace -int main() +int main(int argc, char **argv) { try { + const auto identifiers = parseOptions(argc, argv); + Zivid::Application zivid; printDiscoveredCameras(zivid); - std::cout << "The serial number, IP address and hostname below are placeholders. " - "Replace them with the ones of your camera." - << std::endl; - { std::cout << "Connecting to the first available camera" << std::endl; auto camera = zivid.connectCamera(); @@ -40,22 +66,22 @@ int main() } { - std::cout << "Connecting to the camera with a specific serial number" << std::endl; - auto camera = zivid.connectCamera(Zivid::CameraInfo::SerialNumber{ "2020C0DE" }); + std::cout << "Connecting to the camera with serial number " << identifiers.serialNumber << std::endl; + auto camera = zivid.connectCamera(Zivid::CameraInfo::SerialNumber{ identifiers.serialNumber }); camera.disconnect(); } { - std::cout << "Connecting to the camera at a specific IP address" << std::endl; - auto camera = zivid.connectCamera(Zivid::CameraAddress{ "172.28.60.5" }); + std::cout << "Connecting to the camera at IP address " << identifiers.ipAddress << std::endl; + auto camera = zivid.connectCamera(Zivid::CameraAddress{ identifiers.ipAddress }); camera.disconnect(); } { - std::cout << "Connecting to the camera at a specific hostname" << std::endl; + std::cout << "Connecting to the camera at hostname " << identifiers.hostname << std::endl; // The default hostname format is "zivid-.local". // The hostname cannot be read or set through the SDK. - auto camera = zivid.connectCamera(Zivid::CameraAddress{ "zivid-2020C0DE.local" }); + auto camera = zivid.connectCamera(Zivid::CameraAddress{ identifiers.hostname }); camera.disconnect(); } diff --git a/source/Camera/InfoUtilOther/CheckHealth/CheckHealth.cpp b/source/Camera/InfoUtilOther/CheckHealth/CheckHealth.cpp index 1c9ee49a..29ac0c8a 100644 --- a/source/Camera/InfoUtilOther/CheckHealth/CheckHealth.cpp +++ b/source/Camera/InfoUtilOther/CheckHealth/CheckHealth.cpp @@ -1,5 +1,5 @@ /* -Poll the camera health check from a separate thread while capturing in the main thread, printing the statuses and values every second. +Poll the camera health check from a separate thread while capturing, printing statuses and values each second. */ #include diff --git a/source/Camera/InfoUtilOther/MeasureSceneConditions/MeasureSceneConditions.cpp b/source/Camera/InfoUtilOther/MeasureSceneConditions/MeasureSceneConditions.cpp index c870817e..b40c55ec 100644 --- a/source/Camera/InfoUtilOther/MeasureSceneConditions/MeasureSceneConditions.cpp +++ b/source/Camera/InfoUtilOther/MeasureSceneConditions/MeasureSceneConditions.cpp @@ -1,5 +1,5 @@ /* -Measure ambient light conditions in the scene and output the measured flickering frequency of the ambient light if flickering is detected. +Measure ambient light in the scene and report the flickering frequency when flickering is detected. For more information on measuring scene conditions, check out this article: https://support.zivid.com/en/latest/reference-articles/settings/2d-settings/2d-acquisition-settings/exposure-time.html#measuring-ambient-light-flicker-frequency diff --git a/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp b/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp index 4dd6d240..331e9b60 100644 --- a/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp +++ b/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp @@ -6,6 +6,9 @@ Note: This example uses experimental SDK features, which may be modified, moved, For more information about capture speed, check out this article: https://support.zivid.com/en/latest/camera/reference-articles/calculate-3d-capture-speed.html + +For how to run this benchmark, turn the results into a report, and share it with us, check out this tutorial: +https://support.zivid.com/en/latest/camera/api-reference/benchmarks/benchmarking-your-system.html */ #include @@ -13,14 +16,25 @@ For more information about capture speed, check out this article: #include #include +#include +#include +#include +#include #include #include #include #include +#include #include #include #include +#ifdef _WIN32 +# include +#else +# include +#endif + namespace { const int printWidth = 56; @@ -29,6 +43,8 @@ namespace using Duration = std::chrono::nanoseconds; // CSV Logger class for benchmark results + constexpr double unmeasuredStddev = -1.0; + class CSVLogger { private: @@ -75,7 +91,7 @@ namespace throw std::runtime_error("Failed to open CSV file for writing: " + filename); } csvFile - << "timestamp,test_category,test_name,iteration,median_ms,mean_ms,settings,camera_model,serial_number\n"; + << "timestamp,test_category,test_name,iteration,median_ms,mean_ms,stddev_ms,settings,camera_model,serial_number\n"; csvFile.flush(); } @@ -87,7 +103,8 @@ namespace double meanMs, const std::string &settings, const std::string &cameraModel, - const std::string &serialNumber) + const std::string &serialNumber, + double stddevMs = unmeasuredStddev) { if(!csvFile.is_open()) { @@ -96,8 +113,13 @@ namespace csvFile << getCurrentTimestamp() << "," << escapeCSV(category) << "," << escapeCSV(testName) << "," << iteration << "," << std::fixed << std::setprecision(3) << medianMs << "," << std::fixed - << std::setprecision(3) << meanMs << "," << escapeCSV(settings) << "," << escapeCSV(cameraModel) - << "," << escapeCSV(serialNumber) << "\n"; + << std::setprecision(3) << meanMs << ","; + if(stddevMs >= 0.0) + { + csvFile << std::fixed << std::setprecision(3) << stddevMs; + } + csvFile << "," << escapeCSV(settings) << "," << escapeCSV(cameraModel) << "," << escapeCSV(serialNumber) + << "\n"; csvFile.flush(); } @@ -108,12 +130,25 @@ namespace throw std::runtime_error("CSV file is not open for writing"); } - csvFile << getCurrentTimestamp() << ",system_info," << escapeCSV(key) << ",0,0,0," << escapeCSV(value) + csvFile << getCurrentTimestamp() << ",system_info," << escapeCSV(key) << ",0,0,0,," << escapeCSV(value) << ",,\n"; csvFile.flush(); } }; + double computeStandardDeviationMs(const std::vector &durations) + { + const auto mean = std::accumulate(durations.begin(), durations.end(), Duration{ 0 }).count() + / static_cast(durations.size()); + double sumOfSquaredDeviations = 0.0; + for(const auto &duration : durations) + { + const auto deviation = static_cast(duration.count()) - mean; + sumOfSquaredDeviations += deviation * deviation; + } + return std::sqrt(sumOfSquaredDeviations / static_cast(durations.size())) / 1000000.0; + } + Duration computeAverageDuration(const std::vector &durations) { return std::accumulate(durations.begin(), durations.end(), Duration{ 0 }) / durations.size(); @@ -138,11 +173,14 @@ namespace return ss.str(); } + double durationToMilliseconds(const Duration &duration) + { + return std::chrono::duration_cast>(duration).count(); + } + std::string formatDuration(const Duration &duration) { - return valueToStringWithPrecision( - std::chrono::duration_cast>(duration).count(), 3) - + " ms"; + return valueToStringWithPrecision(durationToMilliseconds(duration), 3) + " ms"; } template @@ -161,21 +199,30 @@ namespace return settingList; } + template + bool isFilterEnabled(const EnabledSetting &enabled) + { + return enabled.hasValue() && enabled.value(); + } + std::string makeFilterList(const Zivid::Settings &settings2D3D) { - if(settings2D3D.processing().filters().smoothing().gaussian().isEnabled().value()) + const auto &gaussian = settings2D3D.processing().filters().smoothing().gaussian(); + const bool reflectionEnabled = + isFilterEnabled(settings2D3D.processing().filters().reflection().removal().isEnabled()); + + if(isFilterEnabled(gaussian.isEnabled())) { - std::string gaussianString; - gaussianString = std::string{ "Gaussian (Sigma = " } - + settings2D3D.processing().filters().smoothing().gaussian().sigma().toString() + " )"; + const auto sigma = gaussian.sigma().hasValue() ? gaussian.sigma().toString() : "unset"; + const std::string gaussianString = std::string{ "Gaussian (Sigma = " } + sigma + " )"; - if(settings2D3D.processing().filters().reflection().removal().isEnabled().value()) + if(reflectionEnabled) { return "{ " + gaussianString + ", Reflection }"; } return "{ " + gaussianString + " }"; } - if(settings2D3D.processing().filters().reflection().removal().isEnabled().value()) + if(reflectionEnabled) { return "{ Reflection }"; } @@ -290,11 +337,15 @@ namespace { printFormatted({ name, formatDuration(durationMedian), formatDuration(durationMean) }); - const double medianMs = - std::chrono::duration_cast>(durationMedian).count(); - const double meanMs = - std::chrono::duration_cast>(durationMean).count(); - csvLogger.logBenchmarkResult(category, name, 1, medianMs, meanMs, settings, cameraModel, serialNumber); + csvLogger.logBenchmarkResult( + category, + name, + 1, + durationToMilliseconds(durationMedian), + durationToMilliseconds(durationMean), + settings, + cameraModel, + serialNumber); } void printResultsWithCSV( @@ -439,12 +490,83 @@ namespace printNegligibleFilters(); } + std::string currentProcessId() + { +#ifdef _WIN32 + return std::to_string(_getpid()); +#else + return std::to_string(getpid()); +#endif + } + + std::filesystem::path zividLogDirectory() + { +#ifdef _WIN32 + const auto *const localAppData = std::getenv("LOCALAPPDATA"); + if(localAppData == nullptr) + { + return {}; + } + const std::filesystem::path cacheDirectory{ localAppData }; +#else + const auto *const xdgCacheHome = std::getenv("XDG_CACHE_HOME"); + std::filesystem::path cacheDirectory; + if(xdgCacheHome != nullptr && *xdgCacheHome != '\0') + { + cacheDirectory = xdgCacheHome; + } + else + { + const auto *const home = std::getenv("HOME"); + if(home == nullptr) + { + return {}; + } + cacheDirectory = std::filesystem::path{ home } / ".cache"; + } +#endif + return cacheDirectory / "Zivid" / "API" / "Log"; + } + + std::string currentLogFile() + { + const auto directory = zividLogDirectory(); + if(directory.empty() || !std::filesystem::is_directory(directory)) + { + return {}; + } + + const auto suffix = "-" + currentProcessId() + ".log"; + std::filesystem::path newest; + std::filesystem::file_time_type newestWriteTime{}; + for(const auto &entry : std::filesystem::directory_iterator{ directory }) + { + const auto name = entry.path().filename().string(); + const auto isOurLog = name.rfind("Zivid-", 0) == 0 && name.size() > suffix.size() + && name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0; + if(!isOurLog) + { + continue; + } + const auto writeTime = entry.last_write_time(); + if(newest.empty() || writeTime > newestWriteTime) + { + newest = entry.path(); + newestWriteTime = writeTime; + } + } + return newest.string(); + } + void printZividInfo(const Zivid::Camera &camera, const Zivid::Application &zivid, CSVLogger &csvLogger) { + const auto logFile = currentLogFile(); + std::cout << "API: " << Zivid::Version::coreLibraryVersion() << std::endl; std::cout << "OS: " << OS_NAME << std::endl; std::cout << "Camera: " << camera << std::endl; std::cout << "Compute device: " << zivid.computeDevice() << std::endl; + std::cout << "Log file: " << (logFile.empty() ? "not found" : logFile) << std::endl; printPrimarySeparationLine(); printCentered("Starting Zivid Benchmark"); @@ -452,7 +574,11 @@ namespace csvLogger.logSystemInfo("OS", OS_NAME); csvLogger.logSystemInfo("Camera_Model", camera.info().model().toString()); csvLogger.logSystemInfo("Serial_Number", camera.info().serialNumber().toString()); + csvLogger.logSystemInfo("Compute_Device_Model", zivid.computeDevice().model()); + csvLogger.logSystemInfo("Compute_Device_Vendor", zivid.computeDevice().vendor()); csvLogger.logSystemInfo("Compute_Device", zivid.computeDevice().toString()); + csvLogger.logSystemInfo("Process_Id", currentProcessId()); + csvLogger.logSystemInfo("Log_File", logFile); } Zivid::Camera getFirstCamera(Zivid::Application &zivid, CSVLogger &csvLogger) @@ -1080,6 +1206,202 @@ namespace return totalDurations; } + struct NamedSettings + { + std::string name; + std::string file; + Zivid::Settings settings; + }; + + // TODO(ZIVID-13539): Read the presets through Zivid::Presets instead of parsing file + // names, once its header can be included from code built with -Wundefined-func-template. + std::string modelFileNameToken(const Zivid::CameraInfo::ModelName &modelName) + { + const std::map spelledOut{ { "2", "Two" }, { "3", "Three" } }; + std::string token; + std::istringstream words{ modelName.toString() }; + std::string word; + while(words >> word) + { + const bool hasPlus = !word.empty() && word.back() == '+'; + const auto spelledOutWord = spelledOut.find(hasPlus ? word.substr(0, word.size() - 1) : word); + std::string part = word; + if(spelledOutWord != spelledOut.end()) + { + part = hasPlus ? spelledOutWord->second + "_Plus" : spelledOutWord->second; + } + token += token.empty() ? part : "_" + part; + } + return token; + } + + bool isAmbientLightVariant(const std::string &stem) + { + const std::array endings{ "_50Hz", "_60Hz" }; + return std::any_of(endings.begin(), endings.end(), [&stem](const std::string &ending) { + return stem.size() >= ending.size() + && stem.compare(stem.size() - ending.size(), ending.size(), ending) == 0; + }); + } + + std::vector findInstalledPresets(const Zivid::Camera &camera, const bool allAmbientVariants) + { + const std::filesystem::path settingsDirectory = std::filesystem::path{ ZIVID_SAMPLE_DATA_DIR } / "Settings"; + if(!std::filesystem::is_directory(settingsDirectory)) + { + throw std::runtime_error( + "The Zivid presets are installed with the sample data, and " + settingsDirectory.string() + + " does not exist. Install the sample data, or pass a directory of your own settings files to " + "--presets."); + } + + const auto token = modelFileNameToken(camera.info().modelName()); + std::vector presets; + for(const auto &entry : std::filesystem::directory_iterator{ settingsDirectory }) + { + const auto name = entry.path().filename().string(); + if(entry.path().extension() != ".yml" || name.rfind(token + "_", 0) != 0) + { + continue; + } + if(allAmbientVariants || !isAmbientLightVariant(entry.path().stem().string())) + { + presets.push_back(entry.path()); + } + } + if(presets.empty()) + { + throw std::runtime_error( + "Found no presets named " + token + "_* in " + settingsDirectory.string() + + ". The sample data may predate this camera model - pass a directory of settings files to " + "--presets instead."); + } + std::sort(presets.begin(), presets.end()); + return presets; + } + + std::vector findSettingsFiles(const std::string &settingsDirectory) + { + const std::filesystem::path path{ settingsDirectory }; + + if(std::filesystem::is_regular_file(path)) + { + return { path }; + } + if(!std::filesystem::is_directory(path)) + { + throw std::runtime_error("Not a file or directory: " + settingsDirectory); + } + + std::vector files; + for(const auto &entry : std::filesystem::recursive_directory_iterator{ path }) + { + if(entry.is_regular_file() && entry.path().extension() == ".yml") + { + files.push_back(entry.path()); + } + } + if(files.empty()) + { + throw std::runtime_error("No .yml settings files found in " + settingsDirectory); + } + std::sort(files.begin(), files.end()); + return files; + } + + std::vector loadSettingsFiles(const std::vector &files) + { + std::vector loaded; + for(const auto &file : files) + { + try + { + loaded.push_back({ file.stem().string(), file.string(), Zivid::Settings{ file.string() } }); + } + catch(const std::exception &e) + { + throw std::runtime_error( + "Could not read 3D settings from " + file.string() + ": " + Zivid::toString(e)); + } + } + return loaded; + } + + void benchmarkPresets( + Zivid::Camera &camera, + const std::vector &presetFiles, + const std::string &presetSource, + const size_t numFrames, + CSVLogger &csvLogger) + { + const std::string cameraModel = camera.info().model().toString(); + const std::string serialNumber = camera.info().serialNumber().toString(); + + const auto presets = loadSettingsFiles(presetFiles); + + printHeaderLine("Capturing ", numFrames, " 3D frames per preset:"); + std::cout << " " << presets.size() << " settings files from " << presetSource << std::endl; + printSecondarySeparationLine(); + printFormatted({ " Total 3D capture time:", "Median", "Mean" }); + + for(const auto &preset : presets) + { + std::vector acquisitionDurations; + std::vector processDurations; + std::vector totalDurations; + try + { + for(size_t i = 0; i < 3; i++) // setup time + { + const auto data = + camera.capture3D(preset.settings).pointCloud().copyData(); + } + + for(size_t i = 0; i < numFrames; i++) + { + const auto beforeCapture = SteadyClock::now(); + const auto frame = camera.capture3D(preset.settings); + const auto afterCapture = SteadyClock::now(); + const auto data = frame.pointCloud().copyData(); + const auto afterProcess = SteadyClock::now(); + + acquisitionDurations.push_back(afterCapture - beforeCapture); + processDurations.push_back(afterProcess - afterCapture); + totalDurations.push_back(afterProcess - beforeCapture); + + // Adding a small delay to simulate a real-life capture cycle where there is some pause between captures, fex. when moving the robot + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + } + catch(const std::exception &e) + { + throw std::runtime_error( + "Capturing with " + preset.file + " failed on " + cameraModel + ": " + Zivid::toString(e)); + } + + const std::string settingsStr = makeSettingsString(preset.settings); + for(const auto &measurement : { std::make_pair(" acquisition", &acquisitionDurations), + std::make_pair(" processing", &processDurations), + std::make_pair(" total", &totalDurations) }) + { + const auto median = computeMedianDuration(*measurement.second); + const auto mean = computeAverageDuration(*measurement.second); + printFormatted( + { " " + preset.name + measurement.first, formatDuration(median), formatDuration(mean) }); + csvLogger.logBenchmarkResult( + "capture_preset", + preset.name + measurement.first, + 1, + durationToMilliseconds(median), + durationToMilliseconds(mean), + settingsStr, + cameraModel, + serialNumber, + computeStandardDeviationMs(*measurement.second)); + } + } + } + std::tuple benchmarkFilterProcessing( const std::vector &captureDuration, const std::vector &captureDurationFilter) @@ -1314,8 +1636,12 @@ int main(int argc, char **argv) bool settingsFromYML = false; bool settings2DFromYML = false; bool extendedTests = false; + bool presetTests = false; + bool settingsDirectoryTests = false; std::string settings2DFile; std::string settingsFile; + std::string settingsDirectory; + bool allAmbientVariants = false; auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); @@ -1328,7 +1654,11 @@ int main(int argc, char **argv) & clipp::value("settings-2d-file", settings2DFile)), (clipp::option("--settings").set(settingsFromYML, true) & clipp::value("settings-file", settingsFile)), (clipp::option("--csv-output") & clipp::value("csv-filename", csvFilename)), - clipp::option("--extended").set(extendedTests, true)); + clipp::option("--extended").set(extendedTests, true), + clipp::option("--presets").set(presetTests, true), + clipp::option("--all-ambient-variants").set(allAmbientVariants, true), + (clipp::option("--settings-dir").set(settingsDirectoryTests, true) + & clipp::value("dir-path", settingsDirectory))); if(!parse(argc, argv, cli)) { @@ -1349,6 +1679,7 @@ int main(int argc, char **argv) const size_t numFrames2D = 50; const size_t numFramesSave = 10; const size_t numCopies = 10; + const size_t numFramesPreset = 10; const std::chrono::microseconds exposureTime = getExposureTimeForAllModels(); const std::vector oneExposureTime{ exposureTime }; @@ -1400,6 +1731,24 @@ int main(int argc, char **argv) printHeader("TEST: 2D + 3D Capture"); benchmarkCapture2D3D(camera, settings2D3D, numFrames3D, csvLogger); + if(presetTests) + { + printHeader("TEST: Zivid presets"); + benchmarkPresets( + camera, + findInstalledPresets(camera, allAmbientVariants), + "the presets installed with the sample data", + numFramesPreset, + csvLogger); + } + + if(settingsDirectoryTests) + { + printHeader("TEST: Settings files"); + benchmarkPresets( + camera, findSettingsFiles(settingsDirectory), settingsDirectory, numFramesPreset, csvLogger); + } + printHeader("TEST: Copy Data"); benchmarkCopyData(camera, exposureTime, numCopies, csvLogger); printHeader("TEST: Save");