27namespace modalPSDsTest
35 using snapshotT = ampCircBuffT::snapshotT;
37 modalPSDs_test(
const std::string &device )
39 m_configName = device;
48 void setWindowSizes( cbIndexT tsSize, cbIndexT meanSize )
51 m_meanSize = meanSize;
52 m_tsPtrs.resize( m_tsSize );
53 m_meanPtrs.resize( m_meanSize );
56 void setModeCount(
size_t nModes )
61 void setCircBuffEntries( cbIndexT entries )
63 m_ampCircBuff.maxEntries( entries );
66 cbIndexT circBuffSize()
const
68 return m_ampCircBuff.size();
71 void pushSample( realT *ptr )
73 m_ampCircBuff.nextEntry( ptr );
76 int processImageForTest( realT *ptr )
79 return processImage( ptr, dummy );
82 bool loadWindows( snapshotT &sn )
84 return loadPsdInputWindows( sn );
87 realT tsValue(
size_t n )
const
89 return *( m_tsPtrs.at( n ) );
92 realT meanValue(
size_t n )
const
94 return *( m_meanPtrs.at( n ) );
97 cbIndexT latestRef(
const snapshotT &sn, cbIndexT count )
const
99 return latestWindowRefEntry( sn, count );
102 cbIndexT precedingRef(
const snapshotT &sn, cbIndexT refEntry, cbIndexT count )
const
104 return precedingWindowRefEntry( sn, refEntry, count );
107 void setPSDTiming( realT psdTime, realT psdAvgTime, realT meanTime, realT psdOverlapFraction )
109 m_psdTime.store( psdTime );
110 m_psdAvgTime.store( psdAvgTime );
111 m_meanTime.store( meanTime );
112 m_psdOverlapFraction = psdOverlapFraction;
115 void setPSDHistoryFloor(
int nPSDHistory )
117 m_nPSDHistory = nPSDHistory;
121 configureLoopState(
const std::string &device,
const std::string &property,
const std::string &element =
"toggle" )
123 m_loopStateDevice = device;
124 m_loopStateProperty = property;
125 m_loopStateElement = element;
126 m_useLoopState = !m_loopStateDevice.empty();
127 m_loopClosed.store( m_useLoopState ==
false, std::memory_order_release );
129 m_indiP_loop.setDevice( device );
130 m_indiP_loop.setName( property );
133 void setLoopClosedForTest(
bool loopClosed )
135 m_loopClosed.store( loopClosed, std::memory_order_release );
138 bool acceptLoopStateFrameForTest()
const
140 return acceptLoopStateFrame();
143 bool usesLoopStateForTest()
const
145 return m_useLoopState;
148 bool loopClosedForTest()
const
150 return m_loopClosed.load( std::memory_order_acquire );
153 int desiredPSDAverageCountForTest()
const
155 return desiredPSDAverageCount();
158 cbIndexT desiredMeanSampleCountForTest( realT fps )
const
160 return desiredMeanSampleCount( fps );
163 cbIndexT requiredInputHistoryDepthForTest()
165 return requiredInputHistoryDepth();
168 static cbIndexT circularEntryAdvanceForTest( cbIndexT from, cbIndexT to, cbIndexT maxEntries )
170 return circularEntryAdvance( from, to, maxEntries );
173 std::vector<double> recomputeMeanSumsForTest()
const
175 std::vector<double> meanSums;
176 recomputeMeanSums( meanSums );
180 std::vector<realT> cacheMeanHeadForTest( cbIndexT count )
const
182 std::vector<realT> meanHeadCache;
183 cacheMeanHead( meanHeadCache, count );
184 return meanHeadCache;
187 void rollMeanSumsForTest( std::vector<double> &meanSums,
188 const std::vector<realT> &meanHeadCache,
189 cbIndexT advance )
const
191 rollMeanSums( meanSums, meanHeadCache, advance );
194 static void updatePlaneSumForTest( std::vector<double> &planeSum,
195 const std::vector<realT> &addPlane,
196 const std::vector<realT> *removePlane =
nullptr )
199 planeSum, addPlane.data(), removePlane ==
nullptr ?
nullptr : removePlane->data(), addPlane.size() );
202 uint32_t rawPSDHistoryDepthForTest()
const
204 return rawPSDHistoryDepth();
207 uint32_t publishedRawPSDHistoryDepthForTest()
const
209 return publishedRawPSDHistoryDepth();
221 #ifdef MODALPSDS_TEST_DOXYGEN_REF
222 modalPSDs::newCallBack_m_indiP_psdTime( pcf::IndiProperty() );
223 modalPSDs::newCallBack_m_indiP_psdAvgTime( pcf::IndiProperty() );
224 modalPSDs::newCallBack_m_indiP_meanTime( pcf::IndiProperty() );
225 modalPSDs::setCallBack_m_indiP_fpsSource( pcf::IndiProperty() );
241TEST_CASE(
"modalPSDs PSD averaging and mean windows are decoupled",
"[modalPSDs]" )
243 modalPSDs_test app(
"modalPSDs_test" );
246 #ifdef MODALPSDS_TEST_DOXYGEN_REF
254 app.setPSDHistoryFloor( 100 );
256 app.setPSDTiming( 1.0F, 10.0F, 60.0F, 0.5F );
257 app.setWindowSizes( 2000, app.desiredMeanSampleCountForTest( 1000.0F ) );
258 REQUIRE( app.desiredPSDAverageCountForTest() == 20 );
259 REQUIRE( app.desiredMeanSampleCountForTest( 1000.0F ) == 60000 );
260 REQUIRE( app.requiredInputHistoryDepthForTest() == 62002 );
261 REQUIRE( app.rawPSDHistoryDepthForTest() == 0 );
262 REQUIRE( app.publishedRawPSDHistoryDepthForTest() == 100 );
264 app.setPSDTiming( 1.0F, 60.0F, 60.0F, 0.5F );
265 app.setWindowSizes( 2000, app.desiredMeanSampleCountForTest( 1000.0F ) );
266 REQUIRE( app.desiredPSDAverageCountForTest() == 120 );
267 REQUIRE( app.desiredMeanSampleCountForTest( 1000.0F ) == 60000 );
268 REQUIRE( app.requiredInputHistoryDepthForTest() == 62002 );
269 REQUIRE( app.rawPSDHistoryDepthForTest() == 21 );
270 REQUIRE( app.publishedRawPSDHistoryDepthForTest() == 100 );
272 app.setPSDTiming( 1.0F, 60.0F, 15.0F, 0.5F );
273 app.setWindowSizes( 2000, app.desiredMeanSampleCountForTest( 1000.0F ) );
274 REQUIRE( app.desiredPSDAverageCountForTest() == 120 );
275 REQUIRE( app.desiredMeanSampleCountForTest( 1000.0F ) == 15000 );
276 REQUIRE( app.requiredInputHistoryDepthForTest() == 17002 );
277 REQUIRE( app.rawPSDHistoryDepthForTest() == 21 );
280SCENARIO(
"PSD input windows come from one validated snapshot",
"[modalPSDs]" )
282 GIVEN(
"A full fixed-size circular buffer and configured PSD window lengths" )
284 modalPSDs_test app(
"modalPSDs_test" );
285 app.setWindowSizes( 3, 2 );
286 app.setCircBuffEntries( 8 );
289 for(
int n = 0; n < 9; ++n )
294 for(
int n = 0; n < 8; ++n )
296 app.pushSample( &samples[n] );
299 WHEN(
"The PSD and mean windows are loaded before wraparound" )
301 modalPSDs_test::snapshotT sn;
303 REQUIRE( app.loadWindows( sn ) );
304 REQUIRE( sn.maxEntries == 8 );
305 REQUIRE( sn.validEntries == 8 );
307 REQUIRE( app.tsValue( 0 ) == 4 );
308 REQUIRE( app.tsValue( 1 ) == 5 );
309 REQUIRE( app.tsValue( 2 ) == 6 );
311 REQUIRE( app.meanValue( 0 ) == 2 );
312 REQUIRE( app.meanValue( 1 ) == 3 );
315 WHEN(
"The buffer has wrapped once" )
317 app.pushSample( &samples[8] );
319 modalPSDs_test::snapshotT sn;
321 REQUIRE( app.loadWindows( sn ) );
322 REQUIRE( sn.latest == 0 );
323 REQUIRE( sn.earliest == 1 );
325 REQUIRE( app.tsValue( 0 ) == 5 );
326 REQUIRE( app.tsValue( 1 ) == 6 );
327 REQUIRE( app.tsValue( 2 ) == 7 );
329 REQUIRE( app.meanValue( 0 ) == 3 );
330 REQUIRE( app.meanValue( 1 ) == 4 );
339TEST_CASE(
"modalPSDs PSD input windows use the exact required history depth",
"[modalPSDs]" )
341 modalPSDs_test app(
"modalPSDs_test_required_history_depth" );
343 app.setWindowSizes( 3, 2 );
344 app.setCircBuffEntries( 7 );
347 for(
int n = 0; n < 7; ++n )
350 app.pushSample( &samples[n] );
353 modalPSDs_test::snapshotT sn;
355 REQUIRE( app.loadWindows( sn ) );
356 REQUIRE( sn.maxEntries == 7 );
357 REQUIRE( sn.validEntries == 7 );
359 REQUIRE( app.meanValue( 0 ) == 1 );
360 REQUIRE( app.meanValue( 1 ) == 2 );
362 REQUIRE( app.tsValue( 0 ) == 3 );
363 REQUIRE( app.tsValue( 1 ) == 4 );
364 REQUIRE( app.tsValue( 2 ) == 5 );
371TEST_CASE(
"modalPSDs rolling mean update matches full recompute",
"[modalPSDs]" )
373 modalPSDs_test app(
"modalPSDs_test_rolling_mean" );
376 #ifdef MODALPSDS_TEST_DOXYGEN_REF
384 app.setModeCount( 2 );
385 app.setWindowSizes( 4, 4 );
386 app.setCircBuffEntries( 10 );
389 for(
int n = 0; n < 12; ++n )
395 for(
int n = 0; n < 10; ++n )
397 app.pushSample( samples[n] );
400 modalPSDs_test::snapshotT firstSnap;
401 REQUIRE( app.loadWindows( firstSnap ) );
403 auto firstSums = app.recomputeMeanSumsForTest();
404 auto firstHead = app.cacheMeanHeadForTest( 2 );
409 app.pushSample( samples[10] );
410 app.pushSample( samples[11] );
412 modalPSDs_test::snapshotT secondSnap;
413 REQUIRE( app.loadWindows( secondSnap ) );
418 REQUIRE( modalPSDs_test::circularEntryAdvanceForTest( firstMeanRef, secondMeanRef, secondSnap.maxEntries ) == 2 );
420 auto rolledSums = firstSums;
421 app.rollMeanSumsForTest( rolledSums, firstHead, 2 );
423 auto recomputedSums = app.recomputeMeanSumsForTest();
425 REQUIRE( rolledSums.size() == recomputedSums.size() );
426 for(
size_t n = 0; n < rolledSums.size(); ++n )
428 REQUIRE( rolledSums[n] == Approx( recomputedSums[n] ) );
436TEST_CASE(
"modalPSDs rolling PSD sum update matches full recompute",
"[modalPSDs]" )
439 #ifdef MODALPSDS_TEST_DOXYGEN_REF
444 std::vector<double> rollingSum{ 12.0, 15.0, 18.0 };
445 std::vector<double> recomputedSum( 3, 0.0 );
446 std::vector<modalPSDs::realT> oldPlane{ 1.0F, 2.0F, 3.0F };
447 std::vector<modalPSDs::realT> keepPlaneA{ 4.0F, 5.0F, 6.0F };
448 std::vector<modalPSDs::realT> keepPlaneB{ 7.0F, 8.0F, 9.0F };
449 std::vector<modalPSDs::realT> newPlane{ 10.0F, 11.0F, 12.0F };
451 modalPSDs_test::updatePlaneSumForTest( rollingSum, newPlane, &oldPlane );
453 modalPSDs_test::updatePlaneSumForTest( recomputedSum, keepPlaneA );
454 modalPSDs_test::updatePlaneSumForTest( recomputedSum, keepPlaneB );
455 modalPSDs_test::updatePlaneSumForTest( recomputedSum, newPlane );
457 REQUIRE( rollingSum.size() == recomputedSum.size() );
458 for(
size_t n = 0; n < rollingSum.size(); ++n )
460 REQUIRE( rollingSum[n] == Approx( recomputedSum[n] ) );
468TEST_CASE(
"modalPSDs loop-state gating is optional and blocks open-loop frames",
"[modalPSDs]" )
470 modalPSDs_test app(
"modalPSDs_test_loop_state" );
473 #ifdef MODALPSDS_TEST_DOXYGEN_REF
475 modalPSDs::setCallBack_m_indiP_loop( pcf::IndiProperty() );
481 app.setCircBuffEntries( 4 );
482 REQUIRE( app.usesLoopStateForTest() ==
false );
483 REQUIRE( app.acceptLoopStateFrameForTest() ==
true );
484 REQUIRE( app.processImageForTest( sample ) == 0 );
485 REQUIRE( app.circBuffSize() == 1 );
487 app.setCircBuffEntries( 4 );
488 app.configureLoopState(
"loopdev",
"loop_state" );
489 REQUIRE( app.usesLoopStateForTest() ==
true );
490 REQUIRE( app.loopClosedForTest() ==
false );
491 REQUIRE( app.acceptLoopStateFrameForTest() ==
false );
492 REQUIRE( app.processImageForTest( sample ) == 0 );
493 REQUIRE( app.circBuffSize() == 0 );
495 app.setLoopClosedForTest(
true );
496 REQUIRE( app.loopClosedForTest() ==
true );
497 REQUIRE( app.acceptLoopStateFrameForTest() ==
true );
498 REQUIRE( app.processImageForTest( sample ) == 0 );
499 REQUIRE( app.circBuffSize() == 1 );
502SCENARIO(
"Snapshot-based circular-buffer loads reject stale snapshots",
"[modalPSDs]" )
504 GIVEN(
"A full fixed-size circular buffer" )
507 buff.maxEntries( 4 );
510 for(
int n = 0; n < 5; ++n )
515 for(
int n = 0; n < 4; ++n )
517 buff.nextEntry( &samples[n] );
522 WHEN(
"The producer has not advanced" )
524 auto sn = buff.snapshot();
526 REQUIRE( buff.loadSequence( sn, 1, 2, dest ) );
527 REQUIRE( *dest[0] == 1 );
528 REQUIRE( *dest[1] == 2 );
531 WHEN(
"The producer advances after the snapshot" )
533 auto sn = buff.snapshot();
534 buff.nextEntry( &samples[4] );
536 REQUIRE_FALSE( buff.loadSequence( sn, 1, 2, dest ) );