diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a44c368..574b015 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,7 @@ jobs: - self-hosted - spinnaker - BFLY-U3-23S6M-C # for tests + - ORX-10GS-51S5M-C # for tests - windows permissions: diff --git a/.github/workflows/test_pr.yml b/.github/workflows/test_pr.yml index 43e8af4..bc9f2df 100644 --- a/.github/workflows/test_pr.yml +++ b/.github/workflows/test_pr.yml @@ -16,6 +16,7 @@ jobs: - self-hosted - spinnaker - BFLY-U3-23S6M-C # for tests + - ORX-10GS-51S5M-C # for tests permissions: actions: write diff --git a/README.md b/README.md index ff69f48..d23338f 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,4 @@ This is an acquire driver that supports some Teledyne FLIR cameras using the Spi ### Camera - [Blackfly USB3: BFLY-U3-23S6M-C](https://www.flir.com/products/blackfly-usb3/?model=BFLY-U3-23S6M-C&vertical=machine+vision&segment=iis) +- [Oryx 10GigE: ORX-10GS-51S5M-C](https://www.flir.com/products/oryx-10gige/?model=ORX-10GS-51S5M-C&vertical=machine+vision&segment=iis) \ No newline at end of file diff --git a/src/spinnaker.cpp b/src/spinnaker.cpp index 00755c5..2702239 100644 --- a/src/spinnaker.cpp +++ b/src/spinnaker.cpp @@ -42,10 +42,15 @@ namespace { // and to avoid temporary gcstring instances when used with Spinnaker. const Spinnaker::GenICam::gcstring genicam_off("Off"); const Spinnaker::GenICam::gcstring genicam_on("On"); +const Spinnaker::GenICam::gcstring genicam_acquisition_start( + "AcquisitionStart"); const Spinnaker::GenICam::gcstring genicam_frame_start("FrameStart"); const Spinnaker::GenICam::gcstring genicam_line_1("Line1"); +const Spinnaker::GenICam::gcstring genicam_software("Software"); +const Spinnaker::GenICam::gcstring genicam_input("Input"); const Spinnaker::GenICam::gcstring genicam_output("Output"); const Spinnaker::GenICam::gcstring genicam_exposure_active("ExposureActive"); +const Spinnaker::GenICam::gcstring genicam_trigger_wait("FrameTriggerWait"); const Spinnaker::GenICam::gcstring genicam_user_output_1("UserOutput1"); const Spinnaker::GenICam::gcstring genicam_user_output_value_1( "UserOutputValue1"); @@ -113,20 +118,19 @@ to_pixel_format(SampleType sample_type) } // Maps Acquire line numbers to GenICam line source strings. -// Define the software line as acquire's line 2 because the Blackfly USB3 camera -// only has two physical lines (0 and 1). +// Define the software line as acquire's line 7 to leave lines +// 0-6 to map directly to the Oryx camera's lines. const std::unordered_map trigger_line_to_source{ - { 0, "Line0" }, - { 1, "Line1" }, - { 2, "Software" }, + { 0, "Line0" }, { 1, "Line1" }, { 2, "Line2" }, { 3, "Line3" }, + { 4, "Line4" }, { 5, "Line5" }, { 6, "Line6" }, { 7, "Software" }, }; uint8_t to_trigger_line(const Spinnaker::GenICam::gcstring& source) { - // Acquire's line 3 is unassigned here so use it for an unknown line. - return inv_at_or(trigger_line_to_source, source, 3); + // Acquire's line 8 is unassigned here so use it for an unknown line. + return inv_at_or(trigger_line_to_source, source, 8); } const Spinnaker::GenICam::gcstring& @@ -135,13 +139,6 @@ to_trigger_source(uint8_t line) return trigger_line_to_source.at(line); } -// Returns true if two triggers are equal in value, false otherwise. -bool -is_equal(const Trigger& lhs, const Trigger& rhs) -{ - return memcmp(&lhs, &rhs, sizeof(Trigger)) == 0; -} - // Returns true if a Spinnaker node is writable, false otherwise. bool check_node_writable(Spinnaker::GenApi::INode& node) @@ -193,6 +190,15 @@ set_float_node(Spinnaker::GenApi::IFloat& node, double value) } } +// Returns true if the given name is a valid entry for the given enum node, +// false otherwise. +bool +is_enum_name(Spinnaker::GenApi::IEnumeration& node, + const Spinnaker::GenICam::gcstring& name) +{ + return IsReadable(node.GetEntryByName(name)); +} + // Returns a representative Spinnaker binning node. // Spinnaker supports independent horizontal and vertical binning, but // Acquire only supports one binning value. @@ -216,6 +222,76 @@ check_spinnaker_camera_id(uint64_t id) EXPECT(id < limit, "Expected an unsigned int device index. Got: %llu", id); } +// Some utilities for updating input and output triggers/lines in place +// based on the current state of the camera. + +template +Trigger* +find_first_enabled_trigger(T& triggers) +{ + size_t n_triggers = sizeof(triggers) / sizeof(struct Trigger); + auto trigger = (struct Trigger*)&triggers; + for (int i = 0; i < n_triggers; ++i) { + if (trigger[i].enable) + return trigger + i; + } + return nullptr; +} + +void +update_input_trigger(Spinnaker::CameraPtr& camera, Trigger& trigger) +{ + trigger.kind = Signal_Input; + trigger.enable = true; + trigger.line = to_trigger_line(*(camera->TriggerSource)); + // TriggerActivation can be non-readable when using the Software + // trigger for some cameras (e.g. Oryx). + trigger.edge = IsReadable(camera->TriggerActivation) + ? to_trigger_edge(*(camera->TriggerActivation)) + : TriggerEdge_Unknown; +} + +void +update_output_trigger(Spinnaker::CameraPtr& camera, Trigger& trigger) +{ + trigger.kind = Signal_Output; + trigger.enable = true; + trigger.line = to_trigger_line(*(camera->LineSelector)); + trigger.edge = + camera->LineInverter() ? TriggerEdge_LevelLow : TriggerEdge_LevelHigh; +} + +void +set_input_trigger(Spinnaker::CameraPtr& camera, + const Trigger* trigger, + const Spinnaker::GenICam::gcstring& name) +{ + if (is_enum_name(camera->TriggerSelector, name)) { + set_enum_node(camera->TriggerSelector, name); + set_enum_node(camera->TriggerSource, to_trigger_source(trigger->line)); + // TriggerActivation can be non-writable when using the Software trigger + // for some cameras (e.g. Oryx). + if (IsWritable(camera->TriggerActivation)) { + set_enum_node(camera->TriggerActivation, + to_trigger_activation(trigger->edge)); + } + set_enum_node(camera->TriggerMode, + trigger->enable ? genicam_on : genicam_off); + } +} + +void +set_output_trigger(Spinnaker::CameraPtr& camera, + const Trigger& trigger, + const Spinnaker::GenICam::gcstring& name) +{ + if (trigger.enable && is_enum_name(camera->LineSource, name)) { + set_enum_node(camera->LineSelector, to_trigger_source(trigger.line)); + set_enum_node(camera->LineMode, genicam_output); + set_enum_node(camera->LineSource, name); + } +} + // // Camera declaration // @@ -251,18 +327,15 @@ struct SpinnakerCamera final : private Camera void query_roi_offset_capabilities_(CameraPropertyMetadata* meta) const; void query_roi_shape_capabilities_(CameraPropertyMetadata* meta) const; void query_pixel_type_capabilities_(CameraPropertyMetadata* meta) const; - static void query_triggering_capabilities_(CameraPropertyMetadata* meta); + void query_triggering_capabilities_(CameraPropertyMetadata* meta) const; void maybe_set_roi_(uint8_t binning, CameraProperties::camera_properties_offset_s offset, CameraProperties::camera_properties_shape_s shape); void maybe_set_sample_type_(SampleType target); void maybe_set_exposure_time_us_(float target_us); - void maybe_set_input_trigger_frame_start(Trigger& target); - void maybe_set_output_trigger_exposure_(Trigger& target); - - void update_input_trigger_(Trigger& trigger); - void update_output_trigger_exposure_(Trigger& trigger); + void maybe_set_input_trigger_(struct CameraProperties* properties); + void maybe_set_output_triggers_(struct CameraProperties* properties); }; // @@ -412,13 +485,6 @@ SpinnakerCamera::SpinnakerCamera(Spinnaker::CameraPtr camera) , started_(false) { camera->Init(); - - // Acquire only supports certain values of some node values, so set these - // once on initialization before getting or setting any other node values - // which may depend on them. - set_enum_node(camera_->ExposureAuto, genicam_off); - set_enum_node(camera_->ExposureMode, genicam_timed); - get(&last_known_settings_); } @@ -441,9 +507,15 @@ SpinnakerCamera::set(struct CameraProperties* properties) const std::scoped_lock lock(lock_); maybe_set_roi_(properties->binning, properties->offset, properties->shape); maybe_set_sample_type_(properties->pixel_type); - maybe_set_exposure_time_us_(properties->exposure_time_us); - maybe_set_input_trigger_frame_start(properties->input_triggers.frame_start); - maybe_set_output_trigger_exposure_(properties->output_triggers.exposure); + maybe_set_input_trigger_(properties); + maybe_set_output_triggers_(properties); + + // Setting exposure time may not be compatible with certain triggers + // (e.g. ExposureActive), so set it last. + if (find_first_enabled_trigger(properties->input_triggers) != + &properties->input_triggers.exposure) { + maybe_set_exposure_time_us_(properties->exposure_time_us); + } } void @@ -542,6 +614,11 @@ SpinnakerCamera::maybe_set_sample_type_(SampleType target) void SpinnakerCamera::maybe_set_exposure_time_us_(float target_us) { + // Always set the exposure mode because enabling ExposureActive + // may automatically switch this to be TriggerWidth. + set_enum_node(camera_->ExposureMode, genicam_timed); + set_enum_node(camera_->ExposureAuto, genicam_off); + if (target_us != last_known_settings_.exposure_time_us) { set_float_node(camera_->ExposureTime, (double)target_us); last_known_settings_.exposure_time_us = (float)camera_->ExposureTime(); @@ -549,64 +626,41 @@ SpinnakerCamera::maybe_set_exposure_time_us_(float target_us) } void -SpinnakerCamera::update_input_trigger_(Trigger& trigger) +SpinnakerCamera::maybe_set_input_trigger_(struct CameraProperties* properties) { - trigger.kind = Signal_Input; - trigger.enable = *(camera_->TriggerMode) == genicam_on; - trigger.line = to_trigger_line(*(camera_->TriggerSource)); - trigger.edge = to_trigger_edge(*(camera_->TriggerActivation)); -} - -void -SpinnakerCamera::maybe_set_input_trigger_frame_start(Trigger& target) -{ - if (!is_equal(target, last_known_settings_.input_triggers.frame_start)) { - // Always disable trigger before any other configuration as in the - // Spinnaker Trigger.cpp example. + // First turn triggering off before any other configuration as in + // Spinnaker's Trigger.cpp example. + set_enum_node(camera_->TriggerMode, genicam_off); + + // Then disable all triggering. + Spinnaker::GenApi::StringList_t selectors; + camera_->TriggerSelector.GetSymbolics(selectors); + for (const auto& selector : selectors) { + set_enum_node(camera_->TriggerSelector, selector); set_enum_node(camera_->TriggerMode, genicam_off); + } - set_enum_node(camera_->TriggerSelector, genicam_frame_start); - set_enum_node(camera_->TriggerSource, to_trigger_source(target.line)); - set_enum_node(camera_->TriggerActivation, - to_trigger_activation(target.edge)); - set_enum_node(camera_->TriggerMode, - target.enable ? genicam_on : genicam_off); - - update_input_trigger_(last_known_settings_.input_triggers.frame_start); + // Finally, use the first trigger enabled in acquire, if any. + auto& input_triggers = properties->input_triggers; + Trigger* trigger = find_first_enabled_trigger(input_triggers); + if (trigger == &input_triggers.acquisition_start) { + set_input_trigger(camera_, trigger, genicam_acquisition_start); + } else if (trigger == &input_triggers.frame_start) { + set_input_trigger(camera_, trigger, genicam_frame_start); + } else if (trigger == &input_triggers.exposure) { + set_input_trigger(camera_, trigger, genicam_exposure_active); + } else if (trigger != nullptr) { + LOGE("Found an unrecognized trigger."); } } void -SpinnakerCamera::update_output_trigger_exposure_(Trigger& trigger) +SpinnakerCamera::maybe_set_output_triggers_(struct CameraProperties* properties) { - trigger.kind = Signal_Output; - trigger.enable = (*(camera_->LineSelector) == genicam_line_1) && - (*(camera_->LineSource) == genicam_exposure_active); - trigger.line = 1; - // Line inverter tells us the type of edge, but is only readable when the - // selected line is a physical output line (i.e. not software, nor input). - trigger.edge = IsReadable(camera_->LineInverter) && camera_->LineInverter() - ? TriggerEdge_LevelLow - : TriggerEdge_LevelHigh; -} - -void -SpinnakerCamera::maybe_set_output_trigger_exposure_(Trigger& target) -{ - if (!is_equal(target, last_known_settings_.output_triggers.exposure)) { - set_enum_node(camera_->LineSelector, genicam_line_1); - set_enum_node(camera_->LineMode, genicam_output); - if (target.enable) { - set_enum_node(camera_->LineSource, genicam_exposure_active); - } else { - set_enum_node(camera_->LineSource, genicam_user_output_1); - set_enum_node(camera_->UserOutputSelector, - genicam_user_output_value_1); - set_bool_node(camera_->UserOutputValue, false); - } - update_output_trigger_exposure_( - last_known_settings_.output_triggers.exposure); - } + auto& triggers = properties->output_triggers; + set_output_trigger(camera_, triggers.exposure, genicam_exposure_active); + set_output_trigger(camera_, triggers.frame_start, genicam_frame_start); + set_output_trigger(camera_, triggers.trigger_wait, genicam_trigger_wait); } void @@ -628,12 +682,52 @@ SpinnakerCamera::get(struct CameraProperties* properties) }, }; - if (*(camera_->TriggerSelector) == genicam_frame_start) { - update_input_trigger_(properties->input_triggers.frame_start); + // Zero out all the input triggers, then only update if the currently + // selected trigger is enabled. + memset(&properties->input_triggers, 0, sizeof(properties->input_triggers)); + if (*(camera_->TriggerMode) == genicam_on) { + const Spinnaker::GenICam::gcstring trigger = + *(camera_->TriggerSelector); + if (trigger == genicam_acquisition_start) { + update_input_trigger(camera_, + properties->input_triggers.acquisition_start); + } else if (trigger == genicam_frame_start) { + update_input_trigger(camera_, + properties->input_triggers.frame_start); + } else if (trigger == genicam_exposure_active) { + update_input_trigger(camera_, properties->input_triggers.exposure); + } else { + LOGE("Found an unrecognized trigger."); + } } - if (*(camera_->LineSelector) == genicam_line_1) { - update_output_trigger_exposure_(properties->output_triggers.exposure); + // Zero out all the output triggers, then iterate over all the lines, + // allowing multiple output triggers to be enabled for Acquire. + memset( + &properties->output_triggers, 0, sizeof(properties->output_triggers)); + Spinnaker::GenApi::StringList_t lines; + camera_->LineSelector.GetSymbolics(lines); + auto& triggers = properties->output_triggers; + for (auto& line : lines) { + camera_->LineSelector = line; + // Some lines do not have a readable LineMode (e.g. Oryx Line 6), so + // skip them. + if (!IsReadable(camera_->LineMode) || + *(camera_->LineMode) != genicam_output) { + continue; + } + // Spinnaker allows us to use the same source for multiple output + // lines, so only enable each trigger for the first line found. + const Spinnaker::GenICam::gcstring source = *(camera_->LineSource); + if (!triggers.exposure.enable && source == genicam_exposure_active) { + update_output_trigger(camera_, triggers.exposure); + } else if (!triggers.frame_start.enable && + source == genicam_frame_start) { + update_output_trigger(camera_, triggers.frame_start); + } else if (!triggers.trigger_wait.enable && + source == genicam_trigger_wait) { + update_output_trigger(camera_, triggers.trigger_wait); + } } last_known_settings_ = *properties; @@ -676,6 +770,7 @@ SpinnakerCamera::query_binning_capabilities_(CameraPropertyMetadata* meta) const .type = PropertyType_FixedPrecision, }; } + void SpinnakerCamera::query_roi_offset_capabilities_( CameraPropertyMetadata* meta) const @@ -695,6 +790,7 @@ SpinnakerCamera::query_roi_offset_capabilities_( }, }; } + void SpinnakerCamera::query_roi_shape_capabilities_( CameraPropertyMetadata* meta) const @@ -724,29 +820,79 @@ SpinnakerCamera::query_pixel_type_capabilities_( camera_->PixelFormat.GetSymbolics(pixel_formats); for (const Spinnaker::GenICam::gcstring format : pixel_formats) { const SampleType sample_type = to_sample_type(format); - meta->supported_pixel_types |= (1ULL << sample_type); + if (sample_type != SampleType_Unknown) { + meta->supported_pixel_types |= (1ULL << sample_type); + } } } void -SpinnakerCamera::query_triggering_capabilities_(CameraPropertyMetadata* meta) +SpinnakerCamera::query_triggering_capabilities_( + CameraPropertyMetadata* meta) const { - // These are based on inspection of blackfly camera properties in SpinView. - // ExposureActive can be selected using the Trigger Selector in SpinView, - // but Spinnaker does not have a corresponding enum value in - // TriggerSelectorEnums, so do not enable it as an input trigger. + // Acquire can represent at most 8 lines. + // The first supported blackfly camera only has two (line 0 and software). + // The first supported oryx camera has seven (lines 0-6 and software). + // Therefore, we allow the line numbers to corresponding to their line + // names and use line 7 for software for all cameras. meta->digital_lines = { - .line_count = 3, + .line_count = 8, .names = { "Line0", "Line1", + "Line2", + "Line3", + "Line4", + "Line5", + "Line6", "Software", }, }; - meta->triggers = { - .exposure = { .input = 0, .output = 0b0010 }, - .frame_start = { .input = 0b0101, .output = 0 }, - }; + + memset(&meta->triggers, 0, sizeof(meta->triggers)); + + for (int i = 0; i < meta->digital_lines.line_count; ++i) { + const Spinnaker::GenICam::gcstring name(meta->digital_lines.names[i]); + // The available sources should not be dependent on the currently + // selected trigger, so no need to change TriggerSelector. + if (is_enum_name(camera_->TriggerSource, name)) { + if (is_enum_name(camera_->TriggerSelector, + genicam_acquisition_start)) { + meta->triggers.acquisition_start.input |= (1ULL << i); + } + if (is_enum_name(camera_->TriggerSelector, genicam_frame_start)) { + meta->triggers.frame_start.input |= (1ULL << i); + } + if (is_enum_name(camera_->TriggerSelector, + genicam_exposure_active)) { + meta->triggers.exposure.input |= (1ULL << i); + } + } + } + + const Spinnaker::GenICam::gcstring original_line = *(camera_->LineSelector); + for (int i = 0; i < meta->digital_lines.line_count; ++i) { + const Spinnaker::GenICam::gcstring name(meta->digital_lines.names[i]); + // Each line can have different modes and therefore different sources, + // we iterate over the currently selected line. + if (is_enum_name(camera_->LineSelector, name)) { + camera_->LineSelector = name; + if (is_enum_name(camera_->LineMode, genicam_output)) { + if (is_enum_name(camera_->LineSource, + genicam_acquisition_start)) { + meta->triggers.acquisition_start.output |= (1ULL << i); + } + if (is_enum_name(camera_->LineSource, genicam_frame_start)) { + meta->triggers.frame_start.output |= (1ULL << i); + } + if (is_enum_name(camera_->LineSource, + genicam_exposure_active)) { + meta->triggers.exposure.output |= (1ULL << i); + } + } + } + } + camera_->LineSelector = original_line; } void @@ -794,7 +940,7 @@ SpinnakerCamera::stop() } // Could possibly use camera_->IsStreaming instead, but the Spinnaker // docs are not clear enough about what that means. It's critical - // that we call EndAcquisiton exactly when needed so that associated + // that we call EndAcquisition exactly when needed so that associated // buffers are freed. if (started_) { camera_->EndAcquisition(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ad540e0..b77c992 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,17 +11,36 @@ else() # set(project acquire-driver-spinnaker) # CMAKE_PROJECT_NAME gets overridden if this is a subtree of another project + # + # Define setup fixtures + # + set(tgt set-packet-size) + add_executable(${tgt} set-packet-size.cpp) + target_link_libraries(${tgt} spinnaker) + # Ensure that the packet size of the Oryx camera is set to 1000 bytes + # before any of its tests run. + add_test(NAME test-${tgt} COMMAND ${tgt} ORX-10GS-51S5M 1000) + set_tests_properties(test-${tgt} PROPERTIES FIXTURES_SETUP setup-oryx) + # # Tests # set(tests list-devices - one-video-stream - repeat-start - repeat-start-no-stop - configure-properties - configure-triggering - abort-while-waiting-for-trigger + blackfly-metadata + blackfly-one-video-stream + blackfly-repeat-start + blackfly-repeat-start-no-stop + blackfly-configure-properties + blackfly-configure-triggering + blackfly-abort-while-waiting-for-trigger + oryx-metadata + oryx-one-video-stream + oryx-repeat-start + oryx-repeat-start-no-stop + oryx-configure-properties + oryx-configure-triggering + oryx-abort-while-waiting-for-trigger ) foreach(name ${tests}) @@ -40,6 +59,9 @@ else() add_test(NAME test-${tgt} COMMAND ${tgt}) set_tests_properties(test-${tgt} PROPERTIES LABELS acquire-driver-spinnaker) + if(name MATCHES "^oryx") + set_tests_properties(test-${tgt} PROPERTIES FIXTURES_REQUIRED setup-oryx) + endif() endforeach() # diff --git a/tests/abort-while-waiting-for-trigger.cpp b/tests/blackfly-abort-while-waiting-for-trigger.cpp similarity index 92% rename from tests/abort-while-waiting-for-trigger.cpp rename to tests/blackfly-abort-while-waiting-for-trigger.cpp index 5088b1a..f2af337 100644 --- a/tests/abort-while-waiting-for-trigger.cpp +++ b/tests/blackfly-abort-while-waiting-for-trigger.cpp @@ -56,18 +56,16 @@ main() DEVOK(device_manager_select(dm, DeviceKind_Camera, - SIZED(".*BFLY.*") - 1, + SIZED(".*BFLY-U3-23S6M.*") - 1, &props.video[0].camera.identifier)); DEVOK(device_manager_select(dm, DeviceKind_Storage, SIZED("trash") - 1, &props.video[0].storage.identifier)); - // Acquire's line 2 is defined to be the software trigger. - props.video[0].camera.settings.input_triggers.frame_start.line = 2; + // Acquire's line 7 is defined to be the software trigger. + props.video[0].camera.settings.input_triggers.frame_start.line = 7; props.video[0].camera.settings.input_triggers.frame_start.enable = 1; - props.video[0].camera.settings.input_triggers.frame_start.edge = - TriggerEdge_Rising; props.video[0].max_frame_count = 10; OK(acquire_configure(runtime, &props)); diff --git a/tests/configure-properties.cpp b/tests/blackfly-configure-properties.cpp similarity index 91% rename from tests/configure-properties.cpp rename to tests/blackfly-configure-properties.cpp index 29f9d12..7d4d452 100644 --- a/tests/configure-properties.cpp +++ b/tests/blackfly-configure-properties.cpp @@ -67,7 +67,7 @@ main() DEVOK(device_manager_select(dm, DeviceKind_Camera, - SIZED(".*BFLY.*") - 1, + SIZED(".*BFLY-U3-23S6M.*") - 1, &props.video[0].camera.identifier)); DEVOK(device_manager_select(dm, DeviceKind_Storage, @@ -80,22 +80,18 @@ main() // Binning, shape, and offset are coupled since certain combinations do // not define valid regions on the sensor. The following tests cover // some important cases, but not everything. - + // First set the region to be the whole sensor at native resolution. props.video[0].camera.settings.binning = 1; - OK(acquire_configure(runtime, &props)); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.binning, 1); - props.video[0].camera.settings.offset = { .x = 0, .y = 0 }; + props.video[0].camera.settings.shape = { .x = 1920, .y = 1200 }; OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.binning, 1); ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.x, 0); ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.y, 0); - - props.video[0].camera.settings.shape = { .x = 1920, .y = 1200 }; - OK(acquire_configure(runtime, &props)); ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 1920); ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 1200); - + // Trying to set the offset should have no effect because the current // shape prevents it. props.video[0].camera.settings.offset = { .x = 120, .y = 100 }; @@ -104,7 +100,7 @@ main() ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.y, 0); // But we can shrink the shape and change the offset together. - props.video[0].camera.settings.offset = { .x = 120, .y = 100}; + props.video[0].camera.settings.offset = { .x = 120, .y = 100 }; props.video[0].camera.settings.shape = { .x = 960, .y = 600 }; OK(acquire_configure(runtime, &props)); ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 960); @@ -128,7 +124,8 @@ main() ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 1920); ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 1200); - // Trying to set binning while the shape is too large should also do nothing. + // Trying to set binning while the shape is too large should also do + // nothing. props.video[0].camera.settings.binning = 2; OK(acquire_configure(runtime, &props)); ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.binning, 1); @@ -166,19 +163,25 @@ main() SampleType_u8); // Exposure time is tricky because only certain values are supported. - // Here we set to the min and max values because those should be supported - // and can also be compared with equality. + // Here we set to the min and max values because those should be + // supported and can also be compared with equality. AcquirePropertyMetadata metadata = { 0 }; OK(acquire_get_configuration_metadata(runtime, &metadata)); - props.video[0].camera.settings.exposure_time_us = metadata.video[0].camera.exposure_time_us.low; + props.video[0].camera.settings.exposure_time_us = + metadata.video[0].camera.exposure_time_us.low; OK(acquire_configure(runtime, &props)); - ASSERT_EQ(float, "%f", - props.video[0].camera.settings.exposure_time_us, metadata.video[0].camera.exposure_time_us.low); - props.video[0].camera.settings.exposure_time_us = metadata.video[0].camera.exposure_time_us.high; + ASSERT_EQ(float, + "%f", + props.video[0].camera.settings.exposure_time_us, + metadata.video[0].camera.exposure_time_us.low); + props.video[0].camera.settings.exposure_time_us = + metadata.video[0].camera.exposure_time_us.high; OK(acquire_configure(runtime, &props)); - ASSERT_EQ(float, "%f", - props.video[0].camera.settings.exposure_time_us, metadata.video[0].camera.exposure_time_us.high); + ASSERT_EQ(float, + "%f", + props.video[0].camera.settings.exposure_time_us, + metadata.video[0].camera.exposure_time_us.high); OK(acquire_shutdown(runtime)); LOG("OK"); @@ -190,4 +193,4 @@ main() } acquire_shutdown(runtime); return 1; -} +} \ No newline at end of file diff --git a/tests/configure-triggering.cpp b/tests/blackfly-configure-triggering.cpp similarity index 58% rename from tests/configure-triggering.cpp rename to tests/blackfly-configure-triggering.cpp index d006164..4cad749 100644 --- a/tests/configure-triggering.cpp +++ b/tests/blackfly-configure-triggering.cpp @@ -67,7 +67,7 @@ main() DEVOK(device_manager_select(dm, DeviceKind_Camera, - SIZED(".*BFLY.*") - 1, + SIZED(".*BFLY-U3-23S6M.*") - 1, &props.video[0].camera.identifier)); DEVOK(device_manager_select(dm, DeviceKind_Storage, @@ -78,34 +78,97 @@ main() props.video[0].camera.settings.input_triggers.frame_start.line = 0; props.video[0].camera.settings.input_triggers.frame_start.enable = 1; OK(acquire_configure(runtime, &props)); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.input_triggers.frame_start.line, 0); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.input_triggers.frame_start.enable, 1); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.line, + 0); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.enable, + 1); - // Enable frame start input trigger on as a software trigger. - props.video[0].camera.settings.input_triggers.frame_start.line = 2; + // Enable frame start input trigger as a software trigger. + props.video[0].camera.settings.input_triggers.frame_start.line = 7; OK(acquire_configure(runtime, &props)); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.input_triggers.frame_start.line, 2); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.input_triggers.frame_start.enable, 1); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.line, + 7); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.enable, + 1); // Disable frame start input trigger on line 0. props.video[0].camera.settings.input_triggers.frame_start.line = 0; props.video[0].camera.settings.input_triggers.frame_start.enable = 0; OK(acquire_configure(runtime, &props)); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.input_triggers.frame_start.line, 0); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.input_triggers.frame_start.enable, 0); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.line, + 0); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.enable, + 0); + + // Enable exposure input trigger on line 0. + props.video[0].camera.settings.input_triggers.exposure.line = 0; + props.video[0].camera.settings.input_triggers.exposure.enable = 1; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.exposure.line, + 0); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.exposure.enable, + 1); + + // Enable exposure input trigger as a software trigger. + props.video[0].camera.settings.input_triggers.exposure.line = 7; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.exposure.line, + 7); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.exposure.enable, + 1); + + // Disable exposure input trigger on line 0. + props.video[0].camera.settings.input_triggers.exposure.line = 0; + props.video[0].camera.settings.input_triggers.exposure.enable = 0; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.exposure.line, + 0); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.exposure.enable, + 0); // Enable exposure output trigger on line 1. props.video[0].camera.settings.output_triggers.exposure.line = 1; props.video[0].camera.settings.output_triggers.exposure.enable = 1; OK(acquire_configure(runtime, &props)); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.output_triggers.exposure.line, 1); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.output_triggers.exposure.enable, 1); - - // Disable exposure output trigger on line 1. - props.video[0].camera.settings.output_triggers.exposure.enable = 0; - OK(acquire_configure(runtime, &props)); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.output_triggers.exposure.line, 1); - ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.output_triggers.exposure.enable, 0); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.output_triggers.exposure.line, + 1); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.output_triggers.exposure.enable, + 1); OK(acquire_shutdown(runtime)); diff --git a/tests/blackfly-metadata.cpp b/tests/blackfly-metadata.cpp new file mode 100644 index 0000000..cc594a0 --- /dev/null +++ b/tests/blackfly-metadata.cpp @@ -0,0 +1,150 @@ +// Checks various property setting manipulation +#include "acquire.h" + +#include "device/hal/device.manager.h" +#include "device/props/camera.h" +#include "device/props/components.h" +#include "logger.h" +#include +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +/// example: `ASSERT_EQ(int, "%d", 42, meaning_of_life())` +#define ASSERT_EQ(T, fmt, a, b) \ + do { \ + T a_ = (T)(a); \ + T b_ = (T)(b); \ + EXPECT(a_ == b_, "Expected %s==%s but " fmt "!=" fmt, #a, #b, a_, b_); \ + } while (0) + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + auto stream = is_error ? stderr : stdout; + fprintf(stream, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); + fflush(stream); +} + +int +main() +{ + AcquireRuntime* runtime = 0; + try { + runtime = acquire_init(reporter); + auto dm = acquire_device_manager(runtime); + CHECK(runtime); + CHECK(dm); + + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*BFLY-U3-23S6M.*") - 1, + &props.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("Trash") - 1, + &props.video[0].storage.identifier)); + + // Some metadata is dependent on some properties. + // Reset the properties to something sensible. + props.video[0].camera.settings.binning = 1; + props.video[0].camera.settings.offset = { .x = 0, .y = 0 }; + props.video[0].camera.settings.shape = { .x = 1920, .y = 1200 }; + OK(acquire_configure(runtime, &props)); + + AcquirePropertyMetadata metadata = { 0 }; + OK(acquire_get_configuration_metadata(runtime, &metadata)); + const CameraPropertyMetadata& meta = metadata.video[0].camera; + + // Expected values determined by inspecting blackfly metadata in + // spinview. + ASSERT_EQ(uint8_t, "%d", meta.exposure_time_us.writable, 1); + ASSERT_EQ(int, + "%d", + meta.exposure_time_us.type, + PropertyType_FloatingPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.line_interval_us.writable, 0); + ASSERT_EQ(uint8_t, "%d", meta.readout_direction.writable, 0); + + ASSERT_EQ(uint8_t, "%d", meta.binning.writable, 1); + ASSERT_EQ(float, "%g", meta.binning.low, 1); + ASSERT_EQ(float, "%g", meta.binning.high, 4); + ASSERT_EQ(int, "%d", meta.binning.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.shape.x.writable, 1); + ASSERT_EQ(float, "%g", meta.shape.x.low, 4); + ASSERT_EQ(float, "%g", meta.shape.x.high, 1920); + ASSERT_EQ(int, "%d", meta.shape.x.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.shape.y.writable, 1); + ASSERT_EQ(float, "%g", meta.shape.y.low, 2); + ASSERT_EQ(float, "%g", meta.shape.y.high, 1200); + ASSERT_EQ(int, "%d", meta.shape.y.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.offset.x.writable, 1); + ASSERT_EQ(float, "%g", meta.offset.x.low, 0); + ASSERT_EQ(float, "%g", meta.offset.x.high, 0); + ASSERT_EQ(int, "%d", meta.offset.x.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.offset.y.writable, 1); + ASSERT_EQ(float, "%g", meta.offset.y.low, 0); + ASSERT_EQ(float, "%g", meta.offset.y.high, 0); + ASSERT_EQ(int, "%d", meta.offset.y.type, PropertyType_FixedPrecision); + + ASSERT_EQ(unsigned int, + "0x%x", + (unsigned int)meta.supported_pixel_types, + (1U << SampleType_u8) | (1U << SampleType_u16)); + + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.acquisition_start.input, 0); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.acquisition_start.output, 0); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.exposure.input, 0b1000'0001); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.exposure.output, 0b0000'0010); + ASSERT_EQ( + uint8_t, "0x%x", meta.triggers.frame_start.input, 0b1000'0001); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.frame_start.output, 0); + + OK(acquire_shutdown(runtime)); + LOG("OK"); + return 0; + } catch (const std::runtime_error& e) { + ERR("Runtime error: %s", e.what()); + } catch (...) { + ERR("Uncaught exception"); + } + acquire_shutdown(runtime); + return 1; +} \ No newline at end of file diff --git a/tests/one-video-stream.cpp b/tests/blackfly-one-video-stream.cpp similarity index 93% rename from tests/one-video-stream.cpp rename to tests/blackfly-one-video-stream.cpp index e6a4809..f7d8df9 100644 --- a/tests/one-video-stream.cpp +++ b/tests/blackfly-one-video-stream.cpp @@ -60,15 +60,19 @@ main() DEVOK(device_manager_select(dm, DeviceKind_Camera, - SIZED(".*BFLY.*") - 1, + SIZED(".*BFLY-U3-23S6M.*") - 1, &props.video[0].camera.identifier)); DEVOK(device_manager_select(dm, DeviceKind_Storage, SIZED("tiff") - 1, &props.video[0].storage.identifier)); - storage_properties_init( - &props.video[0].storage.settings, 0, SIZED("out.tif"), 0, 0, { .x = 1, .y = 1 }); + storage_properties_init(&props.video[0].storage.settings, + 0, + SIZED("out.tif"), + 0, + 0, + { .x = 1, .y = 1 }); props.video[0].camera.settings.binning = 1; props.video[0].camera.settings.pixel_type = SampleType_u8; diff --git a/tests/repeat-start-no-stop.cpp b/tests/blackfly-repeat-start-no-stop.cpp similarity index 85% rename from tests/repeat-start-no-stop.cpp rename to tests/blackfly-repeat-start-no-stop.cpp index 72fea0e..c39fbb7 100644 --- a/tests/repeat-start-no-stop.cpp +++ b/tests/blackfly-repeat-start-no-stop.cpp @@ -60,14 +60,16 @@ main() AcquireProperties properties = {}; { OK(acquire_get_configuration(runtime, &properties)); - DEVOK(device_manager_select(dm, - DeviceKind_Camera, - SIZED(".*BFLY.*") - 1, - &properties.video[0].camera.identifier)); - DEVOK(device_manager_select(dm, - DeviceKind_Storage, - SIZED("Trash") - 1, - &properties.video[0].storage.identifier)); + DEVOK( + device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*BFLY-U3-23S6M.*") - 1, + &properties.video[0].camera.identifier)); + DEVOK( + device_manager_select(dm, + DeviceKind_Storage, + SIZED("Trash") - 1, + &properties.video[0].storage.identifier)); properties.video[0].max_frame_count = 1000; diff --git a/tests/repeat-start.cpp b/tests/blackfly-repeat-start.cpp similarity index 87% rename from tests/repeat-start.cpp rename to tests/blackfly-repeat-start.cpp index 687a463..fd79c52 100644 --- a/tests/repeat-start.cpp +++ b/tests/blackfly-repeat-start.cpp @@ -56,13 +56,13 @@ main() OK(acquire_get_configuration(runtime, &properties)); DEVOK(device_manager_select(dm, - DeviceKind_Camera, - SIZED(".*BFLY.*") - 1, - &properties.video[0].camera.identifier)); + DeviceKind_Camera, + SIZED(".*BFLY-U3-23S6M.*") - 1, + &properties.video[0].camera.identifier)); DEVOK(device_manager_select(dm, - DeviceKind_Storage, - SIZED("Trash") - 1, - &properties.video[0].storage.identifier)); + DeviceKind_Storage, + SIZED("Trash") - 1, + &properties.video[0].storage.identifier)); properties.video[0].max_frame_count = 10; OK(acquire_configure(runtime, &properties)); diff --git a/tests/oryx-abort-while-waiting-for-trigger.cpp b/tests/oryx-abort-while-waiting-for-trigger.cpp new file mode 100644 index 0000000..7aeb527 --- /dev/null +++ b/tests/oryx-abort-while-waiting-for-trigger.cpp @@ -0,0 +1,86 @@ +#include "acquire.h" +#include "device/hal/device.manager.h" +#include "device/props/components.h" +#include "logger.h" +#include "platform.h" +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + auto stream = is_error ? stderr : stdout; + fprintf(stream, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); + fflush(stream); +} + +int +main() +{ + auto runtime = acquire_init(reporter); + try { + CHECK(runtime); + auto dm = acquire_device_manager(runtime); + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*ORX-10GS-51S5M.*") - 1, + &props.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("trash") - 1, + &props.video[0].storage.identifier)); + + // Acquire's line 7 is defined to be the software trigger. + props.video[0].camera.settings.input_triggers.frame_start.line = 7; + props.video[0].camera.settings.input_triggers.frame_start.enable = 1; + props.video[0].max_frame_count = 10; + OK(acquire_configure(runtime, &props)); + + OK(acquire_start(runtime)); + clock_sleep_ms(0, 500); + OK(acquire_abort(runtime)); + + OK(acquire_shutdown(runtime)); + LOG("OK"); + return 0; + } catch (const std::runtime_error& e) { + ERR("Runtime error: %s", e.what()); + } catch (...) { + ERR("Uncaught exception"); + } + acquire_shutdown(runtime); + return 1; +} diff --git a/tests/oryx-configure-properties.cpp b/tests/oryx-configure-properties.cpp new file mode 100644 index 0000000..627ec37 --- /dev/null +++ b/tests/oryx-configure-properties.cpp @@ -0,0 +1,196 @@ +// Checks various property setting manipulation +#include "acquire.h" +#include "device/hal/device.manager.h" +#include "logger.h" +#include +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +/// example: `ASSERT_EQ(int, "%d", 42, meaning_of_life())` +#define ASSERT_EQ(T, fmt, a, b) \ + do { \ + T a_ = (T)(a); \ + T b_ = (T)(b); \ + EXPECT(a_ == b_, "Expected %s==%s but " fmt "!=" fmt, #a, #b, a_, b_); \ + } while (0) + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + auto stream = is_error ? stderr : stdout; + fprintf(stream, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); + fflush(stream); +} + +int +main() +{ + AcquireRuntime* runtime = 0; + try { + runtime = acquire_init(reporter); + auto dm = acquire_device_manager(runtime); + CHECK(runtime); + CHECK(dm); + + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*ORX-10GS-51S5M.*") - 1, + &props.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("Trash") - 1, + &props.video[0].storage.identifier)); + + // After this tests passes, the camera should have reasonable + // default property values. + + // Binning, shape, and offset are coupled since certain combinations do + // not define valid regions on the sensor. The following tests cover + // some important cases, but not everything. + + // First set the region to be the whole sensor at native resolution. + props.video[0].camera.settings.binning = 1; + props.video[0].camera.settings.offset = { .x = 0, .y = 0 }; + props.video[0].camera.settings.shape = { .x = 2448, .y = 2048 }; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.binning, 1); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.x, 0); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.y, 0); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 2448); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 2048); + + // Trying to set the offset should have no effect because the current + // shape prevents it. + props.video[0].camera.settings.offset = { .x = 248, .y = 48 }; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.x, 0); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.y, 0); + + // But we can shrink the shape and change the offset together. + props.video[0].camera.settings.offset = { .x = 248, .y = 48 }; + props.video[0].camera.settings.shape = { .x = 2200, .y = 2000 }; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 2200); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 2000); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.x, 248); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.y, 48); + + // Now trying to set the shape should have no effect because the current + // offset prevents it. + props.video[0].camera.settings.shape = { .x = 2448, .y = 2048 }; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 2200); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 2000); + + // So change the shape and offset together. + props.video[0].camera.settings.offset = { .x = 0, .y = 0 }; + props.video[0].camera.settings.shape = { .x = 2448, .y = 2048 }; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.x, 0); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.y, 0); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 2448); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 2048); + + // Trying to set binning while the shape is too large should also do + // nothing. + props.video[0].camera.settings.binning = 2; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.binning, 1); + + // So change binning and shape together. + props.video[0].camera.settings.binning = 2; + props.video[0].camera.settings.shape = { .x = 1224, .y = 1024 }; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.binning, 2); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 1224); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 1024); + + // Reset the region back to the full sensor at native resolution + // before changing other properties. + props.video[0].camera.settings.binning = 1; + props.video[0].camera.settings.shape = { .x = 2448, .y = 2048 }; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, "%d", props.video[0].camera.settings.binning, 1); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.x, 0); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.offset.y, 0); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.x, 2448); + ASSERT_EQ(uint32_t, "%d", props.video[0].camera.settings.shape.y, 2048); + + props.video[0].camera.settings.pixel_type = SampleType_u16; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(SampleType, + "%d", + props.video[0].camera.settings.pixel_type, + SampleType_u16); + props.video[0].camera.settings.pixel_type = SampleType_u8; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(SampleType, + "%d", + props.video[0].camera.settings.pixel_type, + SampleType_u8); + + // Exposure time is tricky because only certain values are supported. + // Here we set to the min and max values because those should be + // supported and can also be compared with equality. + AcquirePropertyMetadata metadata = { 0 }; + OK(acquire_get_configuration_metadata(runtime, &metadata)); + + props.video[0].camera.settings.exposure_time_us = + metadata.video[0].camera.exposure_time_us.low; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(float, + "%f", + props.video[0].camera.settings.exposure_time_us, + metadata.video[0].camera.exposure_time_us.low); + props.video[0].camera.settings.exposure_time_us = + metadata.video[0].camera.exposure_time_us.high; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(float, + "%f", + props.video[0].camera.settings.exposure_time_us, + metadata.video[0].camera.exposure_time_us.high); + + OK(acquire_shutdown(runtime)); + LOG("OK"); + return 0; + } catch (const std::runtime_error& e) { + ERR("Runtime error: %s", e.what()); + } catch (...) { + ERR("Uncaught exception"); + } + acquire_shutdown(runtime); + return 1; +} diff --git a/tests/oryx-configure-triggering.cpp b/tests/oryx-configure-triggering.cpp new file mode 100644 index 0000000..958d9f5 --- /dev/null +++ b/tests/oryx-configure-triggering.cpp @@ -0,0 +1,193 @@ +// Checks various trigger setting manipulation +#include "acquire.h" +#include "device/hal/device.manager.h" +#include "logger.h" +#include +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +/// example: `ASSERT_EQ(int, "%d", 42, meaning_of_life())` +#define ASSERT_EQ(T, fmt, a, b) \ + do { \ + T a_ = (T)(a); \ + T b_ = (T)(b); \ + EXPECT(a_ == b_, "Expected %s==%s but " fmt "!=" fmt, #a, #b, a_, b_); \ + } while (0) + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + auto stream = is_error ? stderr : stdout; + fprintf(stream, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); + fflush(stream); +} + +int +main() +{ + AcquireRuntime* runtime = 0; + try { + runtime = acquire_init(reporter); + auto dm = acquire_device_manager(runtime); + CHECK(runtime); + CHECK(dm); + + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*ORX-10GS-51S5M.*") - 1, + &props.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("Trash") - 1, + &props.video[0].storage.identifier)); + + // Enable acquisition start input trigger on line 2. + props.video[0].camera.settings.input_triggers.acquisition_start.line = + 2; + props.video[0].camera.settings.input_triggers.acquisition_start.enable = + 1; + props.video[0].camera.settings.input_triggers.frame_start.enable = 0; + props.video[0].camera.settings.input_triggers.exposure.enable = 0; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.acquisition_start.line, + 2); + ASSERT_EQ(uint8_t, + "%d", + props.video[0] + .camera.settings.input_triggers.acquisition_start.enable, + 1); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.enable, + 0); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.exposure.enable, + 0); + + // Enable acquisition start input trigger as software trigger. + props.video[0].camera.settings.input_triggers.acquisition_start.line = + 7; + props.video[0].camera.settings.input_triggers.acquisition_start.enable = + 1; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.acquisition_start.line, + 7); + ASSERT_EQ(uint8_t, + "%d", + props.video[0] + .camera.settings.input_triggers.acquisition_start.enable, + 1); + + // Disable acquisition start and enable frame start input trigger on + // line 0. + props.video[0].camera.settings.input_triggers.acquisition_start.enable = + 0; + props.video[0].camera.settings.input_triggers.frame_start.line = 0; + props.video[0].camera.settings.input_triggers.frame_start.enable = 1; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, + "%d", + props.video[0] + .camera.settings.input_triggers.acquisition_start.enable, + 0); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.line, + 0); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.enable, + 1); + + // Enable frame start input trigger on as a software trigger. + props.video[0].camera.settings.input_triggers.frame_start.line = 7; + props.video[0].camera.settings.input_triggers.frame_start.enable = 1; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.line, + 7); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.enable, + 1); + + // Disable frame start input trigger on line 2. + props.video[0].camera.settings.input_triggers.frame_start.enable = 0; + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.input_triggers.frame_start.enable, + 0); + + // Enable exposure output trigger on line 1. + props.video[0].camera.settings.output_triggers.exposure.line = 1; + props.video[0].camera.settings.output_triggers.exposure.enable = 1; + OK(acquire_configure(runtime, &props)); + ASSERT_EQ(uint8_t, + "%d", + props.video[0].camera.settings.output_triggers.exposure.line, + 1); + ASSERT_EQ( + uint8_t, + "%d", + props.video[0].camera.settings.output_triggers.exposure.enable, + 1); + + OK(acquire_shutdown(runtime)); + + LOG("OK"); + return 0; + } catch (const std::runtime_error& e) { + ERR("Runtime error: %s", e.what()); + } catch (...) { + ERR("Uncaught exception"); + } + acquire_shutdown(runtime); + return 1; +} diff --git a/tests/oryx-metadata.cpp b/tests/oryx-metadata.cpp new file mode 100644 index 0000000..fa3bfa3 --- /dev/null +++ b/tests/oryx-metadata.cpp @@ -0,0 +1,151 @@ +// Checks various property setting manipulation +#include "acquire.h" + +#include "device/hal/device.manager.h" +#include "device/props/camera.h" +#include "device/props/components.h" +#include "logger.h" +#include +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +/// example: `ASSERT_EQ(int, "%d", 42, meaning_of_life())` +#define ASSERT_EQ(T, fmt, a, b) \ + do { \ + T a_ = (T)(a); \ + T b_ = (T)(b); \ + EXPECT(a_ == b_, "Expected %s==%s but " fmt "!=" fmt, #a, #b, a_, b_); \ + } while (0) + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + auto stream = is_error ? stderr : stdout; + fprintf(stream, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); + fflush(stream); +} + +int +main() +{ + AcquireRuntime* runtime = 0; + try { + runtime = acquire_init(reporter); + auto dm = acquire_device_manager(runtime); + CHECK(runtime); + CHECK(dm); + + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*ORX-10GS-51S5M.*") - 1, + &props.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("Trash") - 1, + &props.video[0].storage.identifier)); + + // Some metadata is dependent on some properties. + // Reset the properties to something sensible. + props.video[0].camera.settings.binning = 1; + props.video[0].camera.settings.offset = { .x = 0, .y = 0 }; + props.video[0].camera.settings.shape = { .x = 2448, .y = 2048 }; + OK(acquire_configure(runtime, &props)); + + AcquirePropertyMetadata metadata = { 0 }; + OK(acquire_get_configuration_metadata(runtime, &metadata)); + const CameraPropertyMetadata& meta = metadata.video[0].camera; + + // Expected values determined by inspecting oryx metadata in spinview. + + ASSERT_EQ(uint8_t, "%d", meta.exposure_time_us.writable, 1); + ASSERT_EQ(int, + "%d", + meta.exposure_time_us.type, + PropertyType_FloatingPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.line_interval_us.writable, 0); + ASSERT_EQ(uint8_t, "%d", meta.readout_direction.writable, 0); + + ASSERT_EQ(uint8_t, "%d", meta.binning.writable, 1); + ASSERT_EQ(float, "%g", meta.binning.low, 1); + ASSERT_EQ(float, "%g", meta.binning.high, 4); + ASSERT_EQ(int, "%d", meta.binning.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.shape.x.writable, 1); + ASSERT_EQ(float, "%g", meta.shape.x.low, 8); + ASSERT_EQ(float, "%g", meta.shape.x.high, 2448); + ASSERT_EQ(int, "%d", meta.shape.x.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.shape.y.writable, 1); + ASSERT_EQ(float, "%g", meta.shape.y.low, 6); + ASSERT_EQ(float, "%g", meta.shape.y.high, 2048); + ASSERT_EQ(int, "%d", meta.shape.y.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.offset.x.writable, 1); + ASSERT_EQ(float, "%g", meta.offset.x.low, 0); + ASSERT_EQ(float, "%g", meta.offset.x.high, 0); + ASSERT_EQ(int, "%d", meta.offset.x.type, PropertyType_FixedPrecision); + + ASSERT_EQ(uint8_t, "%d", meta.offset.y.writable, 1); + ASSERT_EQ(float, "%g", meta.offset.y.low, 0); + ASSERT_EQ(float, "%g", meta.offset.y.high, 0); + ASSERT_EQ(int, "%d", meta.offset.y.type, PropertyType_FixedPrecision); + + ASSERT_EQ(unsigned int, + "0x%x", + (unsigned int)meta.supported_pixel_types, + (1U << SampleType_u8) | (1U << SampleType_u16)); + + ASSERT_EQ( + uint8_t, "0x%x", meta.triggers.acquisition_start.input, 0b1010'1101); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.acquisition_start.output, 0); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.exposure.input, 0); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.exposure.output, 0b0011'0110); + ASSERT_EQ( + uint8_t, "0x%x", meta.triggers.frame_start.input, 0b1010'1101); + ASSERT_EQ(uint8_t, "0x%x", meta.triggers.frame_start.output, 0); + + OK(acquire_shutdown(runtime)); + LOG("OK"); + return 0; + } catch (const std::runtime_error& e) { + ERR("Runtime error: %s", e.what()); + } catch (...) { + ERR("Uncaught exception"); + } + acquire_shutdown(runtime); + return 1; +} \ No newline at end of file diff --git a/tests/oryx-one-video-stream.cpp b/tests/oryx-one-video-stream.cpp new file mode 100644 index 0000000..3faa84e --- /dev/null +++ b/tests/oryx-one-video-stream.cpp @@ -0,0 +1,143 @@ +#include "acquire.h" +#include "device/hal/device.manager.h" +#include "platform.h" +#include "logger.h" + +#include +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + auto stream = is_error ? stderr : stdout; + fprintf(stream, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); + fflush(stream); +} + +int +main() +{ + auto runtime = acquire_init(reporter); + + try { + CHECK(runtime); + auto dm = acquire_device_manager(runtime); + CHECK(dm); + + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*ORX-10GS-51S5M.*") - 1, + &props.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("tiff") - 1, + &props.video[0].storage.identifier)); + + storage_properties_init(&props.video[0].storage.settings, + 0, + SIZED("out.tif"), + 0, + 0, + { .x = 1, .y = 1 }); + + props.video[0].camera.settings.binning = 1; + props.video[0].camera.settings.pixel_type = SampleType_u8; + props.video[0].camera.settings.shape = { .x = 2448, .y = 2048 }; + props.video[0].camera.settings.exposure_time_us = 1e4; + props.video[0].max_frame_count = 10; + + OK(acquire_configure(runtime, &props)); + + const auto next = [](VideoFrame* cur) -> VideoFrame* { + return (VideoFrame*)(((uint8_t*)cur) + cur->bytes_of_frame); + }; + + const auto consumed_bytes = [](const VideoFrame* const cur, + const VideoFrame* const end) -> size_t { + return (uint8_t*)end - (uint8_t*)cur; + }; + + struct clock clock; + static double time_limit_ms = 20000.0; + clock_init(&clock); + clock_shift_ms(&clock, time_limit_ms); + OK(acquire_start(runtime)); + uint64_t nframes = 0; + while (nframes < props.video[0].max_frame_count) { + struct clock throttle; + clock_init(&throttle); + EXPECT(clock_cmp_now(&clock) < 0, + "Timeout at %f ms", + clock_toc_ms(&clock) + time_limit_ms); + VideoFrame *beg, *end, *cur; + OK(acquire_map_read(runtime, 0, &beg, &end)); + for (cur = beg; cur < end; cur = next(cur)) { + LOG("stream %d counting frame w id %d", 0, cur->frame_id); + CHECK(cur->shape.dims.width == + props.video[0].camera.settings.shape.x); + CHECK(cur->shape.dims.height == + props.video[0].camera.settings.shape.y); + ++nframes; + } + { + size_t n = consumed_bytes(beg, end); + OK(acquire_unmap_read(runtime, 0, n)); + if (n) + LOG("stream %d consumed bytes %d", 0, n); + } + clock_sleep_ms(&throttle, 100.0f); + + LOG("stream %d nframes %d. remaining time %f s", + 0, + nframes, + -1e-3 * clock_toc_ms(&clock)); + } + + CHECK(nframes == props.video[0].max_frame_count); + + OK(acquire_stop(runtime)); + OK(acquire_shutdown(runtime)); + LOG("OK"); + return 0; + } catch (const std::runtime_error& e) { + ERR("Runtime error: %s", e.what()); + } catch (...) { + ERR("Uncaught exception"); + } + acquire_shutdown(runtime); + return 1; +} diff --git a/tests/oryx-repeat-start-no-stop.cpp b/tests/oryx-repeat-start-no-stop.cpp new file mode 100644 index 0000000..82f1a3b --- /dev/null +++ b/tests/oryx-repeat-start-no-stop.cpp @@ -0,0 +1,104 @@ +/// Calling acquire_start() twice without stopping in between should return an +/// error. + +#include "acquire.h" +#include "device/hal/device.manager.h" +#include "platform.h" +#include "logger.h" + +#include +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + auto stream = is_error ? stderr : stdout; + fprintf(stream, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); + fflush(stream); +} + +int +main() +{ + AcquireRuntime* runtime = nullptr; + try { + CHECK(runtime = acquire_init(reporter)); + const DeviceManager* dm; + CHECK(dm = acquire_device_manager(runtime)); + + AcquireProperties properties = {}; + { + OK(acquire_get_configuration(runtime, &properties)); + DEVOK( + device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*ORX-10GS-51S5M.*") - 1, + &properties.video[0].camera.identifier)); + DEVOK( + device_manager_select(dm, + DeviceKind_Storage, + SIZED("Trash") - 1, + &properties.video[0].storage.identifier)); + + properties.video[0].max_frame_count = 1000; + + OK(acquire_configure(runtime, &properties)); + } + + OK(acquire_start(runtime)); + + // await some data + { + VideoFrame *beg = nullptr, *end = nullptr; + while (beg == end) { + OK(acquire_map_read(runtime, 0, &beg, &end)); + clock_sleep_ms(nullptr, 50.0); + } + OK(acquire_unmap_read(runtime, 0, (uint8_t*)end - (uint8_t*)beg)); + } + + CHECK(AcquireStatus_Error == acquire_start(runtime)); + OK(acquire_abort(runtime)); + + OK(acquire_shutdown(runtime)); + LOG("OK"); + return 0; + } catch (const std::exception& e) { + ERR("Exception: %s", e.what()); + } catch (...) { + ERR("Exception: (unknown)"); + } + acquire_shutdown(runtime); + return 1; +} diff --git a/tests/oryx-repeat-start.cpp b/tests/oryx-repeat-start.cpp new file mode 100644 index 0000000..889987b --- /dev/null +++ b/tests/oryx-repeat-start.cpp @@ -0,0 +1,87 @@ +#include "acquire.h" +#include "device/hal/device.manager.h" +#include "platform.h" +#include "logger.h" + +#include +#include +#include + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +static void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + printf("%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); +} + +int +main() +{ + AcquireRuntime* runtime = 0; + try { + CHECK(runtime = acquire_init(reporter)); + const DeviceManager* dm; + CHECK(dm = acquire_device_manager(runtime)); + + AcquireProperties properties = {}; + OK(acquire_get_configuration(runtime, &properties)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED(".*ORX-10GS-51S5M.*") - 1, + &properties.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("Trash") - 1, + &properties.video[0].storage.identifier)); + + properties.video[0].max_frame_count = 10; + OK(acquire_configure(runtime, &properties)); + + for (auto i = 0; i < 10; ++i) { + struct clock clock = {}; + clock_init(&clock); + OK(acquire_start(runtime)); + OK(acquire_stop(runtime)); + LOG("Start/Stop cycle took %f ms", clock_toc_ms(&clock)); + } + OK(acquire_shutdown(runtime)); + LOG("OK"); + return 0; + } catch (const std::exception& e) { + ERR("Exception: %s", e.what()); + } catch (...) { + ERR("Exception: (unknown)"); + } + acquire_shutdown(runtime); + return 1; +} diff --git a/tests/set-packet-size.cpp b/tests/set-packet-size.cpp new file mode 100644 index 0000000..efaeaad --- /dev/null +++ b/tests/set-packet-size.cpp @@ -0,0 +1,60 @@ +#include "Spinnaker.h" +#include "SpinGenApi/SpinnakerGenApi.h" + +#include + +Spinnaker::CameraPtr +find_camera_with_model_name(const Spinnaker::SystemPtr system, + const char* model_name) +{ + Spinnaker::CameraList cameras = system->GetCameras(); + for (unsigned int i = 0; i < cameras.GetSize(); ++i) { + Spinnaker::CameraPtr camera = cameras[i]; + Spinnaker::GenApi::INodeMap& node_map = camera->GetTLDeviceNodeMap(); + const Spinnaker::GenApi::CStringPtr node = + node_map.GetNode("DeviceModelName"); + if (node->GetValue() == model_name) { + return camera; + } + } + return nullptr; +} + +int +main(int argc, char** argv) +{ + if (argc != 3) { + std::cerr << "Expected model name and packet size in bytes as 2 " + "positional arguments." + << std::endl; + return -1; + } + + int result = 0; + const char* model_name = argv[1]; + const int packet_size_bytes = std::stoi(argv[2]); + std::cout << "Setting packet size for " << model_name << " to " + << packet_size_bytes << " bytes." << std::endl; + + Spinnaker::SystemPtr system = Spinnaker::System::GetInstance(); + Spinnaker::CameraPtr camera = + find_camera_with_model_name(system, model_name); + + if (camera.IsValid()) { + std::cout << "Found camera model " << model_name << std::endl; + camera->Init(); + camera->GevSCPSPacketSize = packet_size_bytes; + std::cout << "Set packet size to " << camera->GevSCPSPacketSize() + << std::endl; + camera->DeInit(); + } else { + std::cerr << "Failed to find camera model " << model_name + << ". Is it connected?" << std::endl; + result = -1; + } + + // Clean up camera before releasing the instance. + camera = nullptr; + system->ReleaseInstance(); + return result; +}