Isolated source snapshot; original checkout and index unchanged. This migration does not claim new build or hardware validation.
98 lines
6.2 KiB
C++
98 lines
6.2 KiB
C++
#include <target_exposure_control/exposure_control.hpp>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
|
|
int main() {
|
|
using namespace target_exposure;
|
|
if(std::string(kVersion)!="1.3.0") return 9;
|
|
const cv::Mat offlineImage(8,8,CV_8UC1,cv::Scalar(123));
|
|
Roi offlineRoi; offlineRoi.rectangles.emplace_back(0,0,8,8);
|
|
const auto offline=measureImage(offlineImage,offlineRoi,true);
|
|
if(!offline.target.valid() || offline.target.arithmetic_gray!=123.0 || offline.global.arithmetic_gray!=123.0) return 4;
|
|
const auto globalOnly=measureImage(offlineImage,{},false);
|
|
if(globalOnly.target.status!=MeterStatus::NoTarget || !globalOnly.global.valid()) return 5;
|
|
MeteringConfig preciseConfig; preciseConfig.min_target_pixels=1; preciseConfig.min_retained_pixels=1;
|
|
const cv::Mat source16=(cv::Mat_<unsigned short>(1,4)<<0,1000,1001,65535);
|
|
const cv::Mat validMask=(cv::Mat_<unsigned char>(1,4)<<0,1,255,0);
|
|
Roi preciseRoi; preciseRoi.rectangles={{0,0,3,1},{1,0,3,1}};
|
|
const auto precise=measureImage(source16,preciseRoi,true,preciseConfig,validMask);
|
|
if(!precise.target.valid() || precise.global.pixels!=2 || precise.target.pixels!=2 ||
|
|
precise.global.source_arithmetic_gray!=1000.5 || precise.target.source_arithmetic_gray!=1000.5 ||
|
|
precise.target.source_bit_depth!=16 || precise.target.source_max_gray!=65535.0 ||
|
|
std::abs(*precise.target.arithmetic_gray-1000.5*255/65535)>1e-12) return 8;
|
|
Config c;
|
|
c.min_exposure_us = 100; c.max_exposure_us = 20'000; c.empirical_exposure_us = 1'000;
|
|
c.detect_confirm_frames = 1;
|
|
ExposureControl module;
|
|
for (TimestampUs t : {TimestampUs{1'000'000}, TimestampUs{2'000'000}}) {
|
|
const auto pre = module.preCapture(t, c);
|
|
if (!pre.accepted) return 1;
|
|
FrameInput f;
|
|
f.image = cv::Mat(8, 8, CV_8UC1, cv::Scalar(40));
|
|
f.roi.rectangles.emplace_back(0, 0, 8, 8);
|
|
f.has_usable_target = true; f.timestamp_us = t; f.actual_exposure_us = 1'000;
|
|
const auto result = module.processFrame(f);
|
|
if (!result.accepted || result.state != State::TRACK) return 2;
|
|
if (t == 2'000'000 && (!result.adjustment_suggested || *result.suggested_exposure_us != 1'250)) return 3;
|
|
}
|
|
ContinuousConfig continuousConfig;
|
|
continuousConfig.min_exposure_us = 100; continuousConfig.max_exposure_us = 20000;
|
|
ContinuousExposureControl continuous;
|
|
ContinuousFrameInput input;
|
|
input.metering = measureImage(cv::Mat(8, 8, CV_8UC1, cv::Scalar(20)), {}, false);
|
|
input.source = FeedbackSource::YoloRegions;
|
|
input.actual_exposure_us = 1000;
|
|
const auto decision = continuous.update(input, continuousConfig);
|
|
if (!decision.accepted || decision.effective_source != FeedbackSource::Global ||
|
|
decision.fallback_reason != FeedbackFallbackReason::RegionUnavailable ||
|
|
!decision.adjustment_suggested || *decision.suggested_exposure_us != 1250) return 6;
|
|
if (std::string(toString(decision.state)) != "Adjusting") return 7;
|
|
// Exercise the installed additive API, including sparse captures and override.
|
|
continuousConfig.preserve_confirmation_on_idle = true;
|
|
continuousConfig.global_controller.target_gray = 100;
|
|
input.metering = measureImage(cv::Mat(8, 8, CV_8UC1, cv::Scalar(80)), {}, false);
|
|
input.timestamp_us = 1;
|
|
if (continuous.update(input, continuousConfig).reason != Reason::ConfirmingError) return 10;
|
|
input.timestamp_us += continuousConfig.idle_timeout_us + 1;
|
|
const auto sparse = continuous.update(input, continuousConfig);
|
|
if (!sparse.accepted || !sparse.adjustment_suggested || !sparse.control.integral_suppressed ||
|
|
sparse.control.integral != 0 || sparse.control.same_direction_count != 2) return 11;
|
|
++input.timestamp_us;
|
|
input.suppress_integral = true;
|
|
const auto overridden = continuous.update(input, continuousConfig);
|
|
if (!overridden.accepted || !overridden.control.integral_suppressed || overridden.control.integral != 0 ||
|
|
overridden.control.same_direction_count != 2) return 12;
|
|
continuousConfig.time_aware = true;
|
|
continuous.reset(FeedbackResetReason::ClockChanged);
|
|
input.suppress_integral = false;
|
|
input.timestamp_us = 0;
|
|
const auto initial = continuous.update(input, continuousConfig);
|
|
if (initial.reason != Reason::ConfirmingError || !initial.control.time_aware ||
|
|
initial.raw_control_gray != 80.0 || initial.control.filtered_gray != 80.0) return 13;
|
|
input.timestamp_us = 100'000;
|
|
const auto early = continuous.update(input, continuousConfig);
|
|
if (early.reason != Reason::ConfirmingError || early.control.confirmation_time_satisfied ||
|
|
early.control.elapsed_us != 100'000 || std::abs(early.control.effective_max_step_ratio - .005) > 1e-12) return 14;
|
|
input.timestamp_us = 1'000'000;
|
|
const auto ready = continuous.update(input, continuousConfig);
|
|
if (!ready.adjustment_suggested || !ready.control.confirmation_time_satisfied ||
|
|
ready.control.same_direction_duration_us != 1'000'000) return 15;
|
|
continuous.reset(FeedbackResetReason::ClockChanged);
|
|
input = {};
|
|
input.actual_exposure_us = 10000;
|
|
input.source = FeedbackSource::ManualRoi;
|
|
input.metering = measureImage(cv::Mat(8, 8, CV_8UC1, cv::Scalar(110)), offlineRoi, true);
|
|
input.predicted_gray = 102;
|
|
continuous.update(input, continuousConfig); // Startup observes the real image.
|
|
input.timestamp_us = 5'000'000;
|
|
const auto confirmingPrediction = continuous.update(input, continuousConfig);
|
|
if (!confirmingPrediction.prediction_used || confirmingPrediction.reason != Reason::ConfirmingError ||
|
|
confirmingPrediction.adjustment_suggested) return 16;
|
|
input.timestamp_us = 10'000'000;
|
|
const auto sharedPrediction = continuous.update(input, continuousConfig);
|
|
if (!sharedPrediction.prediction_used || !sharedPrediction.adjustment_suggested ||
|
|
sharedPrediction.feedback_reason != Reason::Deadband || sharedPrediction.feedback_control_gray != 110 ||
|
|
sharedPrediction.feedback_suggested_exposure_us != 10000 || sharedPrediction.metering.target.gray != 110) return 17;
|
|
std::cout << "Installed package consumer passed; Gray8/Gray16, original and continuous control, sparse suppression, elapsed-time filtering/confirmation/rate trace; exposure unit us.\n";
|
|
}
|