diff --git a/README.md b/README.md index 481cc34f..0b35a72a 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,9 @@ from the camera can be used. - [CaptureAndConvertImageWithOpenCVOnCuda] - Capture a 2D image 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. - **Transform** - [TransformPointCloudFromMillimetersToMeters] - Transform point cloud data from millimeters to meters. @@ -301,7 +304,7 @@ Zivid offers two ways of interfacing with HALCON: Software][Install Zivid Software]. Zivid and HALCON are compatible with Windows 10 and 11, and Ubuntu -20.04, 22.04, 24.04. +20.04, 22.04, 24.04, 26.04. ------------------------------------------------------------------------ @@ -436,6 +439,7 @@ If your build hangs, try to increase the memory available to Docker. [CaptureVis3DInLoop]: https://github.com/zivid/zivid-cpp-samples/tree/master/source/Applications/Advanced/Visualization/CaptureVis3DInLoop/CaptureVis3DInLoop.cpp [CaptureVis3DInLoopWithKeypressExit]: https://github.com/zivid/zivid-cpp-samples/tree/master/source/Applications/Advanced/Visualization/CaptureVis3DInLoopWithKeypressExit/CaptureVis3DInLoopWithKeypressExit.cpp [CaptureAndConvertImageWithOpenCVOnCuda]: https://github.com/zivid/zivid-cpp-samples/tree/master/source/Applications/Advanced/Cuda/CaptureAndConvertImageWithOpenCVOnCuda/CaptureAndConvertImageWithOpenCVOnCuda.cpp + [CaptureAndConvertToDlpackTensorOnCuda]: https://github.com/zivid/zivid-cpp-samples/tree/master/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp [TransformPointCloudFromMillimetersToMeters]: https://github.com/zivid/zivid-cpp-samples/tree/master/source/Applications/Advanced/Transform/TransformPointCloudFromMillimetersToMeters/TransformPointCloudFromMillimetersToMeters.cpp [TransformPointCloudViaArucoMarker]: https://github.com/zivid/zivid-cpp-samples/tree/master/source/Applications/Advanced/Transform/TransformPointCloudViaArucoMarker/TransformPointCloudViaArucoMarker.cpp [TransformPointCloudViaCheckerboard]: https://github.com/zivid/zivid-cpp-samples/tree/master/source/Applications/Advanced/Transform/TransformPointCloudViaCheckerboard/TransformPointCloudViaCheckerboard.cpp diff --git a/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp b/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp new file mode 100644 index 00000000..bbaf9e1d --- /dev/null +++ b/source/Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda/CaptureAndConvertToDlpackTensorOnCuda.cpp @@ -0,0 +1,208 @@ +/* +Capture a 2D+3D frame with the Zivid SDK 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 - +the device pointer, the shape, the strides (in elements), the element data type, and the CUDA device - and validates its +fields. The DLPack data type is derived generically from the Zivid format's ValueType, so the same code path handles +float point data and 8-bit color without hardcoding per-format constants. This is the producer side of DLPack: a +consumer such as PyTorch (torch.from_dlpack), CuPy, or JAX would import the resulting tensor for zero-copy GPU +processing. The DeviceArray is kept alive by the tensor's manager context and released only when the tensor's deleter is +invoked, so the GPU memory stays valid for as long as the consumer holds the tensor. + +To use this sample, you need CUDA installed and the DLPack headers available (see https://github.com/dmlc/dlpack). +Point DLPACK_INCLUDE_DIR at the directory containing dlpack/dlpack.h when configuring CMake. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace +{ + void verifyCUDABackend(Zivid::ComputeBackend backend) + { + if(backend != Zivid::ComputeBackend::cuda) + { + throw std::runtime_error{ + "Only the CUDA compute backend is supported by the `CaptureAndConvertToDlpackTensorOnCuda` sample." + }; + } + } + + int cudaDeviceIdForPointer(const void *devicePointer) + { + cudaPointerAttributes attributes{}; + const auto status = cudaPointerGetAttributes(&attributes, devicePointer); + if(status != cudaSuccess) + { + throw std::runtime_error{ std::string{ "cudaPointerGetAttributes failed: " } + cudaGetErrorString(status) }; + } + return attributes.device; + } + + template + constexpr DLDataType dlDataType() + { + using ValueType = typename Format::ValueType; + constexpr auto bits = static_cast(sizeof(ValueType) * 8); + constexpr uint16_t lanes = 1; + if constexpr(std::is_same_v) + { + return DLDataType{ static_cast(kDLFloat), bits, lanes }; + } + else if constexpr(std::is_unsigned_v) + { + return DLDataType{ static_cast(kDLUInt), bits, lanes }; + } + else + { + static_assert( + std::is_integral_v && std::is_signed_v, + "Unsupported DeviceArray ValueType for DLPack conversion"); + return DLDataType{ static_cast(kDLInt), bits, lanes }; + } + } + + template + struct DLPackManagerContext + { + Zivid::DeviceArray deviceArray; + std::vector shape; + std::vector strides; + }; + + template + void deleteDLManagedTensor(DLManagedTensor *managedTensor) + { + delete static_cast *>(managedTensor->manager_ctx); + delete managedTensor; + } + + template + DLManagedTensor *createDLManagedTensor(const Zivid::DeviceArray &deviceArray) + { + const auto shape = deviceArray.shape(); + const auto strides = deviceArray.strides(); + + auto *context = new DLPackManagerContext{ + deviceArray, + std::vector{ shape.begin(), shape.end() }, + std::vector{ strides.begin(), strides.end() }, + }; + + auto *managedTensor = new DLManagedTensor{}; + managedTensor->dl_tensor.data = context->deviceArray.devicePointer(); + managedTensor->dl_tensor.device = + DLDevice{ kDLCUDA, static_cast(cudaDeviceIdForPointer(context->deviceArray.devicePointer())) }; + managedTensor->dl_tensor.ndim = static_cast(context->shape.size()); + managedTensor->dl_tensor.dtype = dlDataType(); + managedTensor->dl_tensor.shape = context->shape.data(); + managedTensor->dl_tensor.strides = context->strides.data(); + managedTensor->dl_tensor.byte_offset = 0; + managedTensor->manager_ctx = context; + managedTensor->deleter = &deleteDLManagedTensor; + + return managedTensor; + } + + void printDLManagedTensor(const DLManagedTensor &managedTensor) + { + const auto &tensor = managedTensor.dl_tensor; + std::cout << "DLManagedTensor:\n"; + std::cout << " - data: " << tensor.data << '\n'; + std::cout << " - device: type=kDLCUDA(" << static_cast(tensor.device.device_type) + << "), id=" << tensor.device.device_id << '\n'; + std::cout << " - ndim: " << tensor.ndim << '\n'; + std::cout << " - dtype: code=" << static_cast(tensor.dtype.code) + << ", bits=" << static_cast(tensor.dtype.bits) + << ", lanes=" << static_cast(tensor.dtype.lanes) << '\n'; + std::cout << " - shape: "; + for(int32_t dimension = 0; dimension < tensor.ndim; ++dimension) + { + std::cout << tensor.shape[dimension] << (dimension + 1 < tensor.ndim ? " x " : ""); + } + std::cout << '\n'; + std::cout << " - strides (elements): "; + for(int32_t dimension = 0; dimension < tensor.ndim; ++dimension) + { + std::cout << tensor.strides[dimension] << (dimension + 1 < tensor.ndim ? ", " : ""); + } + std::cout << '\n'; + } + + template + void convertAndValidate(const std::string &description, const Zivid::DeviceArray &deviceArray) + { + std::cout << "Building DLManagedTensor for " << description << " directly from the DeviceArray\n"; + auto *managedTensor = createDLManagedTensor(deviceArray); + + printDLManagedTensor(*managedTensor); + + if(managedTensor->dl_tensor.data != deviceArray.devicePointer()) + { + throw std::runtime_error{ "Zero-copy check failed: DLManagedTensor does not reference the Zivid buffer." }; + } + std::cout << "Zero-copy verified: DLManagedTensor references the Zivid device pointer.\n"; + + std::cout << "Releasing the DLManagedTensor via its deleter (this is what a consumer calls when done)\n"; + managedTensor->deleter(managedTensor); + } +} // namespace + +int main(int /*argc*/, char * /*argv*/[]) +{ + try + { + Zivid::Application zivid; + verifyCUDABackend(zivid.computeDevice().backend()); + std::cout << "Zivid SDK CUDA device: " << zivid.computeDevice().model() << '\n'; + + auto camera = zivid.connectCamera(); + + const Zivid::Settings2D settings2D{ Zivid::Settings2D::Acquisitions{ Zivid::Settings2D::Acquisition{} } }; + const Zivid::Settings settings{ Zivid::Settings::Acquisitions{ Zivid::Settings::Acquisition{} }, + Zivid::Settings::Color{ settings2D } }; + + std::cout << "Capturing a 2D+3D frame\n"; + const auto frame = camera.capture2D3D(settings); + const auto pointCloud = frame.pointCloud(); + + const auto streamOrQueue = zivid.computeDevice().sdkStreamOrQueue(); + + std::cout << "Getting GPU device buffers: point cloud XYZ (PointXYZ) and color (ColorRGBA_SRGB)\n"; + const auto pointsDeviceArray = pointCloud.devicePointsXYZ(streamOrQueue); + const auto colorDeviceArray = pointCloud.imageDeviceArray(streamOrQueue); + + convertAndValidate("point cloud (PointXYZ)", pointsDeviceArray); + convertAndValidate("color image (ColorRGBA_SRGB)", colorDeviceArray); + + std::cout << "Each DLManagedTensor owned a reference to its DeviceArray, so the GPU memory stayed valid\n" + "for a consumer (e.g. torch.from_dlpack) until the tensor's deleter was invoked.\n"; + } + catch(const std::exception &ex) + { + std::cerr << "Error: " << Zivid::toString(ex) << '\n'; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 1b149e54..ed5b7d9f 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -25,6 +25,7 @@ option(USE_GENTL "Enable samples which depend on GenTL" OFF) option(USE_HALCON "Enable samples which depend on Halcon" OFF) option(USE_CUDA "Enable samples which depend on CUDA" OFF) option(USE_OPENCV_CUDA "Enable samples which depend on OpenCV CUDA extensions" OFF) +option(USE_DLPACK "Enable samples which depend on DLPack headers" OFF) set(SAMPLES Applications/Basic/Visualization/CaptureAndVisualizeNormals @@ -56,6 +57,7 @@ set(SAMPLES Applications/Advanced/Visualization/CaptureVis3DInLoop Applications/Advanced/Visualization/CaptureVis3DInLoopWithKeypressExit Applications/Advanced/Cuda/CaptureAndConvertImageWithOpenCVOnCuda + Applications/Advanced/Cuda/CaptureAndConvertToDlpackTensorOnCuda Applications/Advanced/HandEyeCalibration/HandEyeCalibration Applications/Advanced/HandEyeCalibration/UtilizeHandEyeCalibration Applications/Advanced/HandEyeCalibration/PoseConversions @@ -182,7 +184,11 @@ set(Halcon_DEPENDING CaptureHalconViaGenICam CaptureHalconViaZivid ) -set(CUDA_DEPENDING CaptureAndConvertImageWithOpenCVOnCuda) +set(CUDA_DEPENDING + CaptureAndConvertImageWithOpenCVOnCuda + CaptureAndConvertToDlpackTensorOnCuda +) +set(DLPACK_DEPENDING CaptureAndConvertToDlpackTensorOnCuda) find_package(Zivid ${ZIVID_VERSION} COMPONENTS Core REQUIRED) find_package(Threads REQUIRED) @@ -301,6 +307,22 @@ else() disable_samples("CUDA") endif() +if(USE_DLPACK) + set(DLPACK_INCLUDE_DIR + "" + CACHE PATH + "Path to the root include directory of your DLPack installation (the folder containing dlpack/dlpack.h)" + ) + if(NOT DLPACK_INCLUDE_DIR OR NOT EXISTS "${DLPACK_INCLUDE_DIR}/dlpack/dlpack.h") + message( + FATAL_ERROR + "Please point DLPACK_INCLUDE_DIR to the root include directory of your DLPack installation (the folder containing dlpack/dlpack.h), or disable the DLPack samples (-DUSE_DLPACK=OFF)." + ) + endif() +else() + disable_samples("DLPACK") +endif() + message(STATUS "All samples: ${SAMPLES}") if(WIN32) @@ -364,6 +386,10 @@ foreach(SAMPLE ${SAMPLES}) target_link_libraries(${SAMPLE_NAME} CUDA::cudart_static) endif() + if(${SAMPLE_NAME} IN_LIST DLPACK_DEPENDING) + target_include_directories(${SAMPLE_NAME} SYSTEM PRIVATE ${DLPACK_INCLUDE_DIR}) + endif() + if(${SAMPLE_NAME} IN_LIST Halcon_DEPENDING) target_link_libraries( ${SAMPLE_NAME} diff --git a/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp b/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp index 5719d16b..4dd6d240 100644 --- a/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp +++ b/source/Camera/InfoUtilOther/ZividBenchmark/ZividBenchmark.cpp @@ -13,9 +13,12 @@ For more information about capture speed, check out this article: #include #include +#include #include +#include #include #include +#include #include namespace @@ -25,6 +28,92 @@ namespace using SteadyClock = std::chrono::steady_clock; using Duration = std::chrono::nanoseconds; + // CSV Logger class for benchmark results + class CSVLogger + { + private: + std::ofstream csvFile; + + static std::string escapeCSV(const std::string &field) + { + if(field.find(',') != std::string::npos || field.find('"') != std::string::npos + || field.find('\n') != std::string::npos) + { + std::string escaped = "\""; + for(char c : field) + { + if(c == '"') + { + escaped += "\"\""; + } + else + { + escaped += c; + } + } + escaped += "\""; + return escaped; + } + return field; + } + + static std::string getCurrentTimestamp() + { + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << std::put_time(std::gmtime(&time_t), "%Y-%m-%d %H:%M:%S"); + return ss.str(); + } + + public: + explicit CSVLogger(const std::string &filename) + { + csvFile.open(filename, std::ios::out); + if(!csvFile.is_open()) + { + 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"; + csvFile.flush(); + } + + void logBenchmarkResult( + const std::string &category, + const std::string &testName, + int iteration, + double medianMs, + double meanMs, + const std::string &settings, + const std::string &cameraModel, + const std::string &serialNumber) + { + if(!csvFile.is_open()) + { + throw std::runtime_error("CSV file is not open for writing"); + } + + 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"; + csvFile.flush(); + } + + void logSystemInfo(const std::string &key, const std::string &value) + { + if(!csvFile.is_open()) + { + throw std::runtime_error("CSV file is not open for writing"); + } + + csvFile << getCurrentTimestamp() << ",system_info," << escapeCSV(key) << ",0,0,0," << escapeCSV(value) + << ",,\n"; + csvFile.flush(); + } + }; + Duration computeAverageDuration(const std::vector &durations) { return std::accumulate(durations.begin(), durations.end(), Duration{ 0 }) / durations.size(); @@ -93,6 +182,20 @@ namespace return {}; } + std::string makeSettingsString(const Zivid::Settings &settings3D) + { + const std::string apertureStr = makeSettingList3D(settings3D); + const std::string exposureStr = makeSettingList3D(settings3D); + const std::string filtersStr = makeFilterList(settings3D); + + std::string result = "Aperture: " + apertureStr + "; Exposure: " + exposureStr; + if(!filtersStr.empty()) + { + result += "; Filters: " + filtersStr; + } + return result; + } + void printSeparationLine(const char &separator, const std::string &followingString) { std::cout << std::left << std::setfill(separator) << std::setw(printWidth) << followingString << std::endl; @@ -175,105 +278,168 @@ namespace printHeaderLine("Saving point cloud ", numFrames, " times each (be patient):"); } - void printResultLine(const std::string &name, const Duration &durationMedian, const Duration &durationMean) + void printResultLineWithCSV( + const std::string &category, + const std::string &name, + const Duration &durationMedian, + const Duration &durationMean, + const std::string &settings, + const std::string &cameraModel, + const std::string &serialNumber, + CSVLogger &csvLogger) { 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); } - void printResults(const std::vector &names, const std::vector &durations) + void printResultsWithCSV( + const std::string &category, + const std::vector &names, + const std::vector &durations, + const std::string &settings, + const std::string &cameraModel, + const std::string &serialNumber, + CSVLogger &csvLogger) { printSecondarySeparationLine(); printFormatted({ " Time:", "Median", "Mean" }); for(size_t i = 0; i < names.size(); i++) { - printResultLine(names.at(i), durations.at(i + i), durations.at(i + i + 1)); + printResultLineWithCSV( + category, + names.at(i), + durations.at(i + i), + durations.at(i + i + 1), + settings, + cameraModel, + serialNumber, + csvLogger); } } - void printConnectResults(const std::vector &durations) - { - printResults({ " Connect:", " Disconnect:" }, durations); - } - - void printCapture3DResults(const std::vector &durations) + void printNegligibleFilters() { - printResults( - { " 3D image acquisition time:", " Point cloud processing time:", " Total 3D capture time:" }, - durations); + const std::string negligible = "negligible"; + printFormatted({ " Noise", negligible, negligible }); + printFormatted({ " Outlier", negligible, negligible }); } - void printCapture3Dincl2DResults(const std::vector &durations) + void printCapture3Dincl2DResultsWithCSV( + const std::vector &durations, + const std::string &settings, + const std::string &cameraModel, + const std::string &serialNumber, + CSVLogger &csvLogger) { - printResults( + printResultsWithCSV( + "capture_3d_incl_2d", { " 2D+3D image acquisition time:", " Point cloud processing time:", " Total 3D capture time:" }, - durations); + durations, + settings, + cameraModel, + serialNumber, + csvLogger); } - void printCapture2D3DResults(const std::vector &durations) + void printCapture2D3DResultsWithCSV( + const std::vector &durations, + const std::string &settings, + const std::string &cameraModel, + const std::string &serialNumber, + CSVLogger &csvLogger) { - printResults( + printResultsWithCSV( + "capture_2d_then_3d", { " 2D image acquisition time:", " 3D image acquisition time:", " 2D + 3D acquisition time:", " 2D image processing time:", " Point cloud processing time:", " Total 2D + 3D capture time:" }, - durations); + durations, + settings, + cameraModel, + serialNumber, + csvLogger); } - void printCapture3D2DResults(const std::vector &durations) + void printCapture3D2DResultsWithCSV( + const std::vector &durations, + const std::string &settings, + const std::string &cameraModel, + const std::string &serialNumber, + CSVLogger &csvLogger) { - printResults( + printResultsWithCSV( + "capture_3d_then_2d", { " 3D image acquisition time:", " 2D image acquisition time:", " 3D + 2D acquisition time:", " Point cloud processing time:", " 2D image processing time:", " Total 3D + 2D capture time:" }, - durations); + durations, + settings, + cameraModel, + serialNumber, + csvLogger); } - void printNegligibleFilters() + void printCopyDataResultsWithCSV( + std::array, 9> &durations, + const size_t numCopies, + const std::string &cameraModel, + const std::string &serialNumber, + CSVLogger &csvLogger) { - const std::string negligible = "negligible"; - printFormatted({ " Noise", negligible, negligible }); - printFormatted({ " Outlier", negligible, negligible }); + printCopyHeader(numCopies); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[0], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[1], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[2], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[3], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[4], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[5], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[6], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyImageRGBA_SRGB(): " }, durations[7], "", cameraModel, serialNumber, csvLogger); + printResultsWithCSV( + "copy_data", { " copyData: " }, durations[8], "", cameraModel, serialNumber, csvLogger); } - void printFilterResults(const std::vector &durations) + void printFilterResultsWithCSV( + const std::vector &durations, + const std::string &settings, + const std::string &cameraModel, + const std::string &serialNumber, + CSVLogger &csvLogger) { printPrimarySeparationLine(); std::cout << "Filter processing time:" << std::endl; - printResults({ " Gaussian:", " Reflection:", " Gaussian and Reflection:" }, durations); + printResultsWithCSV( + "filter", + { " Gaussian:", " Reflection:", " Gaussian and Reflection:" }, + durations, + settings, + cameraModel, + serialNumber, + csvLogger); printSecondarySeparationLine(); printNegligibleFilters(); } - void printCapture2DResults(const std::vector &durations) - { - printResults({ " Total 2D capture time:" }, durations); - } - - void printCopyDataResults(std::array, 9> &durations, const size_t numCopies) - { - printCopyHeader(numCopies); - printResults({ " copyData: " }, durations[0]); - printResults({ " copyData: " }, durations[1]); - printResults({ " copyData: " }, durations[2]); - printResults({ " copyData: " }, durations[3]); - printResults({ " copyData: " }, durations[4]); - printResults({ " copyData: " }, durations[5]); - printResults({ " copyData: " }, durations[6]); - printResults({ " copyImageRGBA_SRGB(): " }, durations[7]); - printResults({ " copyData: " }, durations[8]); - } - - void printSaveResults(const std::vector &durations) - { - printResults({ " Save ZDF:", " Save PLY:", " Save PCD:", " Save XYZ:" }, durations); - } - - void printZividInfo(const Zivid::Camera &camera, const Zivid::Application &zivid) + void printZividInfo(const Zivid::Camera &camera, const Zivid::Application &zivid, CSVLogger &csvLogger) { std::cout << "API: " << Zivid::Version::coreLibraryVersion() << std::endl; std::cout << "OS: " << OS_NAME << std::endl; @@ -281,9 +447,15 @@ namespace std::cout << "Compute device: " << zivid.computeDevice() << std::endl; printPrimarySeparationLine(); printCentered("Starting Zivid Benchmark"); + + csvLogger.logSystemInfo("API_Version", Zivid::Version::coreLibraryVersion()); + 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", zivid.computeDevice().toString()); } - Zivid::Camera getFirstCamera(Zivid::Application &zivid) + Zivid::Camera getFirstCamera(Zivid::Application &zivid, CSVLogger &csvLogger) { const auto cameras = zivid.cameras(); for(const auto &camera : cameras) @@ -291,7 +463,7 @@ namespace if(camera.state().status() == Zivid::CameraState::Status::available) { std::cout << "Available camera: " << camera.info().serialNumber() << std::endl; - printZividInfo(camera, zivid); + printZividInfo(camera, zivid, csvLogger); return camera; } std::cout << "Camera " << camera.info().serialNumber() << "is not available. " @@ -430,7 +602,8 @@ namespace Zivid::Camera &camera, const Zivid::Settings &settings2D3D, const size_t numFrames, - const bool withProjectorFor2D) + const bool withProjectorFor2D, + CSVLogger &csvLogger) { auto settings2D = settings2D3D.color().value(); if(!withProjectorFor2D && doesNotSupportColorWithoutProjector(camera.info().model()) @@ -494,14 +667,21 @@ namespace allDurations.push_back(computeMedianDuration(totalDurations)); allDurations.push_back(computeAverageDuration(totalDurations)); - printCapture2D3DResults(allDurations); + const std::string settingsStr = makeSettingsString(settings2D3D); + printCapture2D3DResultsWithCSV( + allDurations, + settingsStr, + camera.info().model().toString(), + camera.info().serialNumber().toString(), + csvLogger); } void benchmarkCapture3DFirstAndThen2D( Zivid::Camera &camera, const Zivid::Settings &settings2D3D, const size_t numFrames, - const bool withProjectorFor2D) + const bool withProjectorFor2D, + CSVLogger &csvLogger) { auto settings2D = settings2D3D.color().value(); if(!withProjectorFor2D && doesNotSupportColorWithoutProjector(camera.info().model()) @@ -565,14 +745,21 @@ namespace allDurations.push_back(computeMedianDuration(totalDurations)); allDurations.push_back(computeAverageDuration(totalDurations)); - printCapture3D2DResults(allDurations); + const std::string settingsStr = makeSettingsString(settings2D3D); + printCapture3D2DResultsWithCSV( + allDurations, + settingsStr, + camera.info().model().toString(), + camera.info().serialNumber().toString(), + csvLogger); } void benchmarkCapture3DIncluding2D( Zivid::Camera &camera, const Zivid::Settings &baseSettings2D3D, const size_t numFrames, - bool withProjectorFor2D) + bool withProjectorFor2D, + CSVLogger &csvLogger) { auto settings2D3D = Zivid::Settings{ baseSettings2D3D }; if(!withProjectorFor2D && doesNotSupportColorWithoutProjector(camera.info().model()) @@ -621,13 +808,22 @@ namespace allDurations.push_back(computeMedianDuration(totalDurations)); allDurations.push_back(computeAverageDuration(totalDurations)); - printCapture3Dincl2DResults(allDurations); + const std::string settingsStr = makeSettingsString(baseSettings2D3D); + printCapture3Dincl2DResultsWithCSV( + allDurations, + settingsStr, + camera.info().model().toString(), + camera.info().serialNumber().toString(), + csvLogger); } - void benchmarkConnect(Zivid::Camera &camera, const size_t numConnects) + void benchmarkConnect(Zivid::Camera &camera, const size_t numConnects, CSVLogger &csvLogger) { printConnectHeader(numConnects); + const std::string cameraModel = camera.info().model().toString(); + const std::string serialNumber = camera.info().serialNumber().toString(); + std::vector connectDurations; std::vector disconnectDurations; std::vector allDurations; @@ -649,11 +845,26 @@ namespace allDurations.push_back(computeMedianDuration(disconnectDurations)); allDurations.push_back(computeAverageDuration(disconnectDurations)); - printConnectResults(allDurations); + printSecondarySeparationLine(); + printFormatted({ " Time:", "Median", "Mean" }); + printResultLineWithCSV( + "connect", " Connect:", allDurations.at(0), allDurations.at(1), "", cameraModel, serialNumber, csvLogger); + printResultLineWithCSV( + "connect", + " Disconnect:", + allDurations.at(2), + allDurations.at(3), + "", + cameraModel, + serialNumber, + csvLogger); } - std::vector - benchmarkCapture2D3D(Zivid::Camera &camera, const Zivid::Settings &settings2D3D, const size_t numFrames) + std::vector benchmarkCapture2D3D( + Zivid::Camera &camera, + const Zivid::Settings &settings2D3D, + const size_t numFrames, + CSVLogger &csvLogger) { printCapture2DHeader(numFrames, settings2D3D.color().value()); printCapture3DHeader(numFrames, settings2D3D); @@ -708,13 +919,22 @@ namespace allDurations.push_back(computeMedianDuration(totalDurations)); allDurations.push_back(computeAverageDuration(totalDurations)); - printCapture2D3DResults(allDurations); + const std::string settingsStr = makeSettingsString(settings2D3D); + printCapture2D3DResultsWithCSV( + allDurations, + settingsStr, + camera.info().model().toString(), + camera.info().serialNumber().toString(), + csvLogger); return totalDurations; } - std::vector - benchmarkCapture3D2D(Zivid::Camera &camera, const Zivid::Settings &settings2D3D, const size_t numFrames) + std::vector benchmarkCapture3D2D( + Zivid::Camera &camera, + const Zivid::Settings &settings2D3D, + const size_t numFrames, + CSVLogger &csvLogger) { printCapture3DHeader(numFrames, settings2D3D); printCapture2DHeader(numFrames, settings2D3D.color().value()); @@ -770,16 +990,29 @@ namespace allDurations.push_back(computeMedianDuration(totalDurations)); allDurations.push_back(computeAverageDuration(totalDurations)); - printCapture3D2DResults(allDurations); + const std::string settingsStr = makeSettingsString(settings2D3D); + printCapture3D2DResultsWithCSV( + allDurations, + settingsStr, + camera.info().model().toString(), + camera.info().serialNumber().toString(), + csvLogger); return totalDurations; } - std::vector - benchmarkCapture3D(Zivid::Camera &camera, const Zivid::Settings &settings2D3D, const size_t numFrames) + std::vector benchmarkCapture3D( + Zivid::Camera &camera, + const Zivid::Settings &settings2D3D, + const size_t numFrames, + CSVLogger &csvLogger) { printCapture3DHeader(numFrames, settings2D3D); + const std::string cameraModel = camera.info().model().toString(); + const std::string serialNumber = camera.info().serialNumber().toString(); + const std::string settingsStr = makeSettingsString(settings2D3D); + for(size_t i = 0; i < 5; i++) // setup time { const auto data = camera.capture3D(settings2D3D).pointCloud().copyData(); @@ -814,7 +1047,35 @@ namespace allDurations.push_back(computeMedianDuration(totalDurations)); allDurations.push_back(computeAverageDuration(totalDurations)); - printCapture3DResults(allDurations); + printSecondarySeparationLine(); + printFormatted({ " Time:", "Median", "Mean" }); + printResultLineWithCSV( + "capture_3d", + " 3D image acquisition time:", + allDurations.at(0), + allDurations.at(1), + settingsStr, + cameraModel, + serialNumber, + csvLogger); + printResultLineWithCSV( + "capture_3d", + " Point cloud processing time:", + allDurations.at(2), + allDurations.at(3), + settingsStr, + cameraModel, + serialNumber, + csvLogger); + printResultLineWithCSV( + "capture_3d", + " Total 3D capture time:", + allDurations.at(4), + allDurations.at(5), + settingsStr, + cameraModel, + serialNumber, + csvLogger); return totalDurations; } @@ -832,7 +1093,8 @@ namespace Zivid::Camera &camera, const std::vector &apertures, const std::vector &exposureTimes, - const size_t numFrames3D) + const size_t numFrames3D, + CSVLogger &csvLogger) { std::vector subtestName{ "Without filters", "With Gaussian filter", "With Reflection filter", "With Gaussian and Reflection filter" @@ -841,7 +1103,7 @@ namespace printSubtestHeader(subtestName.at(0)); const std::vector captureDurationWithoutFilter = - benchmarkCapture3D(camera, makeSettings3D(apertures, exposureTimes, false, false), numFrames3D); + benchmarkCapture3D(camera, makeSettings3D(apertures, exposureTimes, false, false), numFrames3D, csvLogger); const std::vector gaussian{ true, false, true }; const std::vector reflection{ false, true, true }; @@ -852,7 +1114,10 @@ namespace printSubtestHeader(subtestName.at(i + 1)); const std::vector captureDurationWithFilter = benchmarkCapture3D( - camera, makeSettings3D(apertures, exposureTimes, gaussian.at(i), reflection.at(i)), numFrames3D); + camera, + makeSettings3D(apertures, exposureTimes, gaussian.at(i), reflection.at(i)), + numFrames3D, + csvLogger); const auto meanAndAverageFilterDurations = benchmarkFilterProcessing(captureDurationWithoutFilter, captureDurationWithFilter); @@ -860,13 +1125,27 @@ namespace filterProcessingDurations.push_back(std::get<0>(meanAndAverageFilterDurations)); filterProcessingDurations.push_back(std::get<1>(meanAndAverageFilterDurations)); } - printFilterResults(filterProcessingDurations); + const std::string settingsStr = makeSettingsString(makeSettings3D(apertures, exposureTimes, false, false)); + printFilterResultsWithCSV( + filterProcessingDurations, + settingsStr, + camera.info().model().toString(), + camera.info().serialNumber().toString(), + csvLogger); } - void benchmarkCapture2D(Zivid::Camera &camera, const Zivid::Settings2D &settings2D, const size_t numFrames) + void benchmarkCapture2D( + Zivid::Camera &camera, + const Zivid::Settings2D &settings2D, + const size_t numFrames, + CSVLogger &csvLogger) { printCapture2DHeader(numFrames, settings2D); + const std::string cameraModel = camera.info().model().toString(); + const std::string serialNumber = camera.info().serialNumber().toString(); + const std::string settingsStr = "Exposure: " + settings2D.acquisitions().at(0).exposureTime().toString(); + for(size_t i = 0; i < 5; i++) // setup time { camera.capture2D(settings2D); @@ -891,7 +1170,17 @@ namespace allDurations.push_back(computeMedianDuration(captureDurations)); allDurations.push_back(computeAverageDuration(captureDurations)); - printCapture2DResults(allDurations); + printSecondarySeparationLine(); + printFormatted({ " Time:", "Median", "Mean" }); + printResultLineWithCSV( + "capture_2d", + " Total 2D capture time:", + allDurations.at(0), + allDurations.at(1), + settingsStr, + cameraModel, + serialNumber, + csvLogger); } template @@ -914,7 +1203,11 @@ namespace return afterCopyData - beforeCopyData; } - void benchmarkCopyData(Zivid::Camera &camera, const std::chrono::microseconds exposureTime, const size_t numCopies) + void benchmarkCopyData( + Zivid::Camera &camera, + const std::chrono::microseconds exposureTime, + const size_t numCopies, + CSVLogger &csvLogger) { constexpr int numData = 9; std::array, numData> copyDataDurations; @@ -968,21 +1261,34 @@ namespace allDurations[i].push_back(computeAverageDuration(copyDataDurations[i])); } - printCopyDataResults(allDurations, numCopies); + printCopyDataResultsWithCSV( + allDurations, + numCopies, + camera.info().model().toString(), + camera.info().serialNumber().toString(), + csvLogger); } - void benchmarkSave(Zivid::Camera &camera, const size_t numFrames) + void benchmarkSave(Zivid::Camera &camera, const size_t numFrames, CSVLogger &csvLogger) { printSaveHeader(numFrames); + const std::string cameraModel = camera.info().model().toString(); + const std::string serialNumber = camera.info().serialNumber().toString(); + const auto frame = camera.capture3D(Zivid::Settings{ Zivid::Settings::Acquisitions{ Zivid::Settings::Acquisition{} } }); frame.pointCloud(); - std::vector allDurations; std::vector dataFiles{ "Zivid3D.zdf", "Zivid3D.ply", "Zivid3D.pcd", "Zivid3D.xyz" }; - for(const auto &dataFile : dataFiles) + std::vector formatNames{ " Save ZDF:", " Save PLY:", " Save PCD:", " Save XYZ:" }; + + printSecondarySeparationLine(); + printFormatted({ " Time:", "Median", "Mean" }); + + for(size_t i = 0; i < dataFiles.size(); i++) { + const auto &dataFile = dataFiles[i]; std::vector durationsPerFormat; for(size_t j = 0; j < numFrames; j++) { @@ -993,10 +1299,11 @@ namespace durationsPerFormat.push_back(afterSave - beforeSave); } - allDurations.push_back(computeMedianDuration(durationsPerFormat)); - allDurations.push_back(computeAverageDuration(durationsPerFormat)); + const auto median = computeMedianDuration(durationsPerFormat); + const auto mean = computeAverageDuration(durationsPerFormat); + + printResultLineWithCSV("save_data", formatNames[i], median, mean, "", cameraModel, serialNumber, csvLogger); } - printSaveResults(allDurations); } } // namespace @@ -1006,13 +1313,22 @@ int main(int argc, char **argv) { bool settingsFromYML = false; bool settings2DFromYML = false; + bool extendedTests = false; std::string settings2DFile; std::string settingsFile; + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "zivid_benchmark_results_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".csv"; + std::string csvFilename = ss.str(); + auto cli = ((clipp::option("--settings-2d").set(settings2DFromYML, true) & clipp::value("settings-2d-file", settings2DFile)), - (clipp::option("--settings").set(settingsFromYML, true) & clipp::value("settings-file", settingsFile))); + (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)); if(!parse(argc, argv, cli)) { @@ -1023,7 +1339,10 @@ int main(int argc, char **argv) Zivid::Application zivid; - auto camera = getFirstCamera(zivid); + CSVLogger csvLogger(csvFilename); + std::cout << "CSV logging enabled. Results will be saved to: " << csvFilename << std::endl; + + auto camera = getFirstCamera(zivid, csvLogger); const size_t numConnects = 10; const size_t numFrames3D = 20; @@ -1054,51 +1373,55 @@ int main(int argc, char **argv) } printHeader("TEST: Connect/Disconnect"); - benchmarkConnect(camera, numConnects); + benchmarkConnect(camera, numConnects, csvLogger); camera.connect(); if(settingsFromYML) { printHeader("TEST: 3D Capture"); - benchmarkCapture3D(camera, settings2D3D, numFrames3D); + benchmarkCapture3D(camera, settings2D3D, numFrames3D, csvLogger); } else { printHeader("TEST: One Acquisition Capture"); - benchmarkCapture3DAndFilters(camera, oneAperture, oneExposureTime, numFrames3D); + benchmarkCapture3DAndFilters(camera, oneAperture, oneExposureTime, numFrames3D, csvLogger); printHeader("TEST: Two Acquisitions (HDR) Capture"); - benchmarkCapture3D(camera, makeSettings3D(twoApertures, twoExposureTimes, false, false), numFrames3D); + benchmarkCapture3D( + camera, makeSettings3D(twoApertures, twoExposureTimes, false, false), numFrames3D, csvLogger); printHeader("TEST: Three Acquisitions (HDR) Capture"); - benchmarkCapture3DAndFilters(camera, threeApertures, threeExposureTimes, numFrames3D); + benchmarkCapture3DAndFilters(camera, threeApertures, threeExposureTimes, numFrames3D, csvLogger); } printHeader("TEST: 2D Capture"); - benchmarkCapture2D(camera, settings2D3D.color().value(), numFrames2D); + benchmarkCapture2D(camera, settings2D3D.color().value(), numFrames2D, csvLogger); printHeader("TEST: 3D + 2D Capture"); - benchmarkCapture3D2D(camera, settings2D3D, numFrames3D); + benchmarkCapture3D2D(camera, settings2D3D, numFrames3D, csvLogger); printHeader("TEST: 2D + 3D Capture"); - benchmarkCapture2D3D(camera, settings2D3D, numFrames3D); + benchmarkCapture2D3D(camera, settings2D3D, numFrames3D, csvLogger); printHeader("TEST: Copy Data"); - benchmarkCopyData(camera, exposureTime, numCopies); + benchmarkCopyData(camera, exposureTime, numCopies, csvLogger); printHeader("TEST: Save"); - benchmarkSave(camera, numFramesSave); - - printHeader("TEST: 2D without projector followed by 3D"); - benchmarkCapture2DFirstAndThen3D(camera, settings2D3D, numFrames3D, false); - printHeader("TEST: 2D with projector followed by 3D"); - benchmarkCapture2DFirstAndThen3D(camera, settings2D3D, numFrames3D, true); - - printHeader("TEST: 3D followed by 2D without projector"); - benchmarkCapture3DFirstAndThen2D(camera, settings2D3D, numFrames3D, false); - printHeader("TEST: 3D followed by 2D with projector"); - benchmarkCapture3DFirstAndThen2D(camera, settings2D3D, numFrames3D, true); - - printHeader("TEST: 3D including 2D without projector"); - benchmarkCapture3DIncluding2D(camera, settings2D3D, numFrames3D, false); - printHeader("TEST: 3D including 2D with projector"); - benchmarkCapture3DIncluding2D(camera, settings2D3D, numFrames3D, true); + benchmarkSave(camera, numFramesSave, csvLogger); + + if(extendedTests) + { + printHeader("TEST: 2D without projector followed by 3D"); + benchmarkCapture2DFirstAndThen3D(camera, settings2D3D, numFrames3D, false, csvLogger); + printHeader("TEST: 2D with projector followed by 3D"); + benchmarkCapture2DFirstAndThen3D(camera, settings2D3D, numFrames3D, true, csvLogger); + + printHeader("TEST: 3D followed by 2D without projector"); + benchmarkCapture3DFirstAndThen2D(camera, settings2D3D, numFrames3D, false, csvLogger); + printHeader("TEST: 3D followed by 2D with projector"); + benchmarkCapture3DFirstAndThen2D(camera, settings2D3D, numFrames3D, true, csvLogger); + + printHeader("TEST: 3D including 2D without projector"); + benchmarkCapture3DIncluding2D(camera, settings2D3D, numFrames3D, false, csvLogger); + printHeader("TEST: 3D including 2D with projector"); + benchmarkCapture3DIncluding2D(camera, settings2D3D, numFrames3D, true, csvLogger); + } } catch(const std::exception &e) {