API
 
Loading...
Searching...
No Matches
modalPSDs.hpp
Go to the documentation of this file.
1/** \file modalPSDs.hpp
2 * \brief The MagAO-X modalPSDs app header file
3 *
4 * \ingroup modalPSDs_files
5 */
6
7#ifndef modalPSDs_hpp
8#define modalPSDs_hpp
9
10#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
11#include "../../magaox_git_version.h"
12
13#include <atomic>
14#include <condition_variable>
15#include <mx/sigproc/circularBuffer.hpp>
16#include <mx/sigproc/signalWindows.hpp>
17
18#include <mx/math/ft/fftwEnvironment.hpp>
19#include <mx/math/ft/fftT.hpp>
20
21/** \defgroup modalPSDs
22 * \brief An application to calculate rolling PSDs of modal amplitudes
23 *
24 * <a href="../handbook/operating/software/apps/modalPSDs.html">Application Documentation</a>
25 *
26 * \ingroup apps
27 *
28 */
29
30/** \defgroup modalPSDs_files
31 * \ingroup modalPSDs
32 */
33
34namespace MagAOX
35{
36namespace app
37{
38
39/// Class for application to calculate rolling PSDs of modal amplitudes.
40/**
41 * \ingroup modalPSDs
42 */
43class modalPSDs : public MagAOXApp<true>, public dev::shmimMonitor<modalPSDs>
44{
45 // Give the test harness access.
46 friend class modalPSDs_test;
47
48 friend class dev::shmimMonitor<modalPSDs>;
49
50 public:
51 typedef float realT;
52
53 typedef std::complex<realT> complexT;
54
55 typedef int32_t cbIndexT; ///< The index for the circular buffer
56
57 /// The base shmimMonitor type
59
60 /// The amplitude circular buffer type
61 typedef mx::sigproc::circularBufferIndex<realT *, cbIndexT, true> ampCircBuffT;
62
63 protected:
64 /** \name Configurable Parameters
65 *@{
66 */
67
68 int m_loopNumber{ 1 };
69
70 std::string m_shmimBase; /**< The base name for the PSD shmims. If empty, the default,
71 then aolN where N is the loop number is used*/
72
73 std::string m_shmimTag{ "cl" }; /**< The tag to apply to the front of psds in the shmim name. Default is cl. */
74
75 std::string m_fpsDevice; ///< Device name for getting fps to set circular buffer length.
76 std::string m_fpsProperty{ "fps" }; ///< Property name for getting fps to set circular buffer length.
77 std::string m_fpsElement{ "current" }; ///< Element name for getting fps to set circular buffer length.
78
79 std::string m_loopStateDevice; ///< Optional device name providing loop-state gating updates.
80 std::string m_loopStateProperty{ "loop_state" }; ///< Optional property name providing loop-state gating updates.
81 std::string m_loopStateElement{ "toggle" }; ///< Element name used to interpret closed-loop state.
82
83 bool m_useLoopState{ false }; ///< Whether PSD ingestion is gated by an external loop-state property.
84
85 std::atomic<bool> m_loopClosed{ true }; ///< Current closed-loop state used to gate PSD ingestion and processing.
86
87 realT m_fpsTol{ 0 }; ///< The tolerance for detecting a change in FPS.
88
89 std::atomic<realT> m_psdTime{ 1 }; ///< The length of time over which to calculate PSDs. The default is 1 sec.
90 std::atomic<realT> m_psdAvgTime{ 10 }; ///< The time over which to average PSD estimates. The default is 10 sec.
91 std::atomic<realT> m_meanTime{
92 10 }; ///< The time over which to calculate the mean for detrending. The default is 10 sec.
93
94 // realT m_overSize {10}; ///< Multiplicative factor by which to oversize the circular buffer, to give good mean
95 // estimates and account for time-to-calculate.
96
97 realT m_psdOverlapFraction{ 0.5 }; ///< The fraction of the sample time to overlap by.
98
99 int m_nPSDHistory{ 100 }; ///< Minimum number of raw PSD estimates to retain in the history stream.
100
101 ///@}
102
103 size_t m_nModes{ 0 }; ///< the number of modes to calculate PSDs for.
104
106
107 // std::vector<ampCircBuffT> m_ampCircBuffs;
108
109 std::atomic<realT> m_fps{ 0 }; ///< The current input frame rate used to size PSD windows.
110
112
113 cbIndexT m_tsSize{ 2000 }; ///< The length of the time series sample over which the PSD is calculated
114
115 cbIndexT m_tsOverlapSize{ 1000 }; ///< The number of samples in the overlap
116
117 cbIndexT m_meanSize{ 20000 }; ///< The length of the time series over which to calculate the mean
118
119 std::vector<realT> m_win; ///< The window function. By default this is Hann.
120
121 std::vector<realT *> m_tsPtrs; ///< Snapshot of the latest time-series window pointers for PSD computation.
122 std::vector<realT *> m_meanPtrs; ///< Snapshot of the latest mean-window pointers for PSD computation.
123
124 realT *m_tsWork{ nullptr };
125 size_t m_tsWorkSize{ 0 };
126
127 std::complex<realT> *m_fftWork{ nullptr };
128 size_t m_fftWorkSize{ 0 };
129
130 std::vector<realT> m_psd;
131
132 mx::math::ft::fftT<realT, std::complex<realT>, 1, 0> m_fft;
133 mx::math::ft::fftwEnvironment<realT> m_fftEnv;
134
135 /** \name PSD Calculation Thread
136 * Handling of offloads from the average woofer shape
137 * @{
138 */
139 int m_psdThreadPrio{ 0 }; ///< Priority of the PSD Calculation thread.
140
141 std::string m_psdThreadCpuset; ///< The cpuset to use for the PSD Calculation thread.
142
143 std::thread m_psdThread; ///< The PSD Calculation thread.
144
145 bool m_psdThreadInit{ true }; ///< Initialization flag for the PSD Calculation thread.
146
147 std::atomic<bool> m_psdRestarting{ true }; /**< Synchronization flag. This will only become false
148 after a successful call to allocate.*/
149
150 bool m_psdWaiting{ false }; ///< Synchronization flag protected by m_psdMutex.
151
152 std::mutex m_psdMutex; ///< Protects restart handoff between allocate() and the PSD thread.
153
154 std::condition_variable m_psdCond; ///< Coordinates quiescence during restart.
155
156 pid_t m_psdThreadID{ 0 }; ///< PSD Calculation thread PID.
157
158 pcf::IndiProperty m_psdThreadProp; ///< The property to hold the PSD Calculation thread details.
159
160 /// PS Calculation thread starter function
161 static void psdThreadStart( modalPSDs *p /**< [in] pointer to this */ );
162
163 /// PSD Calculation thread function
164 /** Runs until m_shutdown is true.
165 */
166 void psdThreadExec();
167
168 /// Compute the reference entry for the latest logical window while preserving historical semantics.
169 /** The returned window ends at the sample immediately preceding snapshot.latest. This matches the
170 * longstanding modalPSDs behavior and keeps the writable/latest publication edge out of the PSD window.
171 */
172 static cbIndexT latestWindowRefEntry( const ampCircBuffT::snapshotT &sn, ///< [in] the snapshot to use
173 cbIndexT count ///< [in] the requested window length
174 );
175
176 /// Compute the reference entry for a logical window immediately preceding another logical window.
177 static cbIndexT precedingWindowRefEntry( const ampCircBuffT::snapshotT &sn, ///< [in] the snapshot to use
178 cbIndexT refEntry, ///< [in] later window reference entry
179 cbIndexT count ///< [in] the requested window length
180 );
181
182 /// Compute the forward logical advance between two circular-buffer reference entries.
183 static cbIndexT circularEntryAdvance( cbIndexT from, ///< [in] earlier logical entry
184 cbIndexT to, ///< [in] later logical entry
185 cbIndexT maxEntries ///< [in] circular-buffer capacity
186 );
187
188 /// Load the PSD and mean pointer windows from a single validated snapshot.
189 bool loadPsdInputWindows( ampCircBuffT::snapshotT &sn ///< [out] the snapshot used for both windows
190 );
191
192 /// Recompute the per-mode sums for the full mean window from the currently loaded pointers.
193 void recomputeMeanSums( std::vector<double> &meanSums /**< [out] per-mode sums over the full mean window */ ) const;
194
195 /// Update per-mode mean sums using the cached oldest slice and the newest loaded mean-window slice.
196 void
197 rollMeanSums( std::vector<double> &meanSums, /**< [in,out] per-mode sums to update */
198 const std::vector<realT> &meanHeadCache, /**< [in] cached oldest slice from the prior mean window */
199 cbIndexT advance /**< [in] number of samples by which the mean window advanced */
200 ) const;
201
202 /// Cache the oldest slice of the currently loaded mean window for the next rolling-mean update.
203 void cacheMeanHead( std::vector<realT> &meanHeadCache, /**< [out] storage for the cached oldest slice */
204 cbIndexT count /**< [in] number of mean-window samples to cache */
205 ) const;
206
207 /// Count how many raw PSD planes are currently retained across the published and overflow histories.
209
210 /// Locate a retained raw PSD plane by age, where age 0 is the newest plane.
211 const realT *rawPSDPlaneByAge( uint64_t age, /**< [in] age of the requested raw PSD plane */
212 size_t planeElements /**< [in] number of elements per raw PSD plane */
213 ) const;
214
215 /// Recompute the averaged-PSD running sum from the newest `windowCount` retained raw PSD planes.
216 void recomputeAveragedPSDSum( std::vector<double> &avgPsdSum, /**< [out] running sum over the averaging window */
217 uint64_t windowCount, /**< [in] number of raw PSD planes to sum */
218 size_t planeElements /**< [in] number of elements per raw PSD plane */
219 ) const;
220
221 /// Add one raw PSD plane and optionally subtract one outgoing raw PSD plane from the running average sum.
222 static void updatePlaneSum( std::vector<double> &planeSum, /**< [in,out] running sum to update */
223 const realT *addPlane, /**< [in] newest raw PSD plane to add */
224 const realT *removePlane, /**< [in] outgoing raw PSD plane to subtract, or nullptr */
225 size_t planeElements /**< [in] number of elements per raw PSD plane */
226 );
227
228 /// Determine whether incoming frames should currently be accepted into the PSD history.
229 bool acceptLoopStateFrame() const;
230
231 /// Calculate how many raw PSD estimates are needed to cover the requested averaging time.
232 int desiredPSDAverageCount() const;
233
234 /// Calculate how many samples are needed for the mean-subtraction window at the current FPS.
235 cbIndexT desiredMeanSampleCount( realT fps /**< [in] frame rate used to convert mean time into samples */ ) const;
236
237 /// Calculate the total input-history depth needed to read both windows safely from the fixed-size circular buffer.
239
240 /// Calculate the additional PSD history depth needed beyond the published raw-PSD shmim.
242
243 /// Calculate the published raw PSD history depth retained in shared memory.
245
246 IMAGE *m_freqStream{ nullptr }; ///< The ImageStreamIO shared memory buffer to hold the frequency scale
247
248 mx::improc::eigenImage<realT> m_psdBuffer;
249
250 std::vector<realT> m_rawPSDHistory; ///< Heap-backed overflow history for raw PSD estimates beyond the shmim depth.
251
252 uint32_t m_rawPSDHistoryDepth{ 0 }; ///< Number of overflow PSD estimates currently allocated in `m_rawPSDHistory`.
253
254 uint32_t m_rawPSDHistoryNext{ 0 }; ///< Next overflow slot to overwrite in `m_rawPSDHistory`.
255
256 uint64_t m_rawPSDHistoryCount{ 0 }; ///< Number of overflow PSD estimates stored since the last restart.
257
258 IMAGE *m_rawpsdStream{ nullptr }; ///< The ImageStreamIO shared memory buffer to hold the raw psds
259
260 IMAGE *m_avgpsdStream{ nullptr }; ///< The ImageStreamIO shared memory buffer to hold the average psds
261
262 public:
263 /// Default c'tor.
264 modalPSDs();
265
266 /// D'tor, declared and defined for noexcept.
268 {
269 }
270
271 virtual void setupConfig();
272
273 /// Implementation of loadConfig logic, separated for testing.
274 /** This is called by loadConfig().
275 */
276 int loadConfigImpl( mx::app::appConfigurator &_config /**< [in] an application configuration
277 from which to load values*/ );
278
279 virtual void loadConfig();
280
281 /// Startup function
282 /**
283 *
284 */
285 virtual int appStartup();
286
287 /// Implementation of the FSM for modalPSDs.
288 /**
289 * \returns 0 on no critical error
290 * \returns -1 on an error requiring shutdown
291 */
292 virtual int appLogic();
293
294 /// Shutdown the app.
295 /**
296 *
297 */
298 virtual int appShutdown();
299
300 // shmimMonitor Interface
301 protected:
302 int allocate( const dev::shmimT &dummy /**< [in] tag to differentiate shmimMonitor parents.*/ );
303
304 int allocatePSDStreams();
305
306 int processImage( void *curr_src, ///< [in] pointer to start of current frame.
307 const dev::shmimT &dummy ///< [in] tag to differentiate shmimMonitor parents.
308 );
309
310 // INDI Interface
311 protected:
312 pcf::IndiProperty m_indiP_psdTime;
313 pcf::IndiProperty m_indiP_psdAvgTime;
314 pcf::IndiProperty m_indiP_meanTime;
315 pcf::IndiProperty m_indiP_overSize;
316 pcf::IndiProperty m_indiP_fpsSource;
317 pcf::IndiProperty m_indiP_loop;
318 pcf::IndiProperty m_indiP_fps;
319
320 public:
327};
328
329modalPSDs::modalPSDs() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
330{
331 return;
332}
333
335{
337
338 config.add( "loop.number",
339 "",
340 "loop.number",
341 argType::Required,
342 "loop",
343 "number",
344 false,
345 "string",
346 "Loop number, as in aolN" );
347
348 config.add( "psds.shmimBase",
349 "",
350 "psds.shmimBase",
351 argType::Required,
352 "psds",
353 "shmimBase",
354 false,
355 "string",
356 "The base name for the PSD shmims. If empty, the default, "
357 "then aolN where N is the loop number is used" );
358
359 config.add( "psds.shmimTag",
360 "",
361 "psds.shmimTag",
362 argType::Required,
363 "psds",
364 "shmimTag",
365 false,
366 "string",
367 "The tag to apply to the front of psds in the shmim name. Default is cl. " );
368
369 config.add( "circBuff.fpsDevice",
370 "",
371 "circBuff.fpsDevice",
372 argType::Required,
373 "circBuff",
374 "fpsDevice",
375 false,
376 "string",
377 "Device name for getting fps to set circular buffer length." );
378
379 config.add( "circBuff.fpsProperty",
380 "",
381 "circBuff.fpsProperty",
382 argType::Required,
383 "circBuff",
384 "fpsProperty",
385 false,
386 "string",
387 "Property name for getting fps to set circular buffer length. Default is 'fps'." );
388
389 config.add( "circBuff.fpsElement",
390 "",
391 "circBuff.fpsElement",
392 argType::Required,
393 "circBuff",
394 "fpsElement",
395 false,
396 "string",
397 "Property name for getting fps to set circular buffer length. Default is 'current'." );
398
399 config.add( "circBuff.fpsTol",
400 "",
401 "circBuff.fpsTol",
402 argType::Required,
403 "circBuff",
404 "fpsTol",
405 false,
406 "float",
407 "Tolerance for detecting a change in FPS. Default is 0." );
408
409 config.add( "circBuff.defaultFPS",
410 "",
411 "circBuff.defaultFPS",
412 argType::Required,
413 "circBuff",
414 "defaultFPS",
415 false,
416 "realT",
417 "Default FPS at startup, will enable changing average length with psdTime before INDI available." );
418
419 config.add( "circBuff.loopStateDevice",
420 "",
421 "circBuff.loopStateDevice",
422 argType::Required,
423 "circBuff",
424 "loopStateDevice",
425 false,
426 "string",
427 "Optional device name providing loop-state gating. If unset, PSDs ignore loop state." );
428
429 config.add( "circBuff.loopStateProperty",
430 "",
431 "circBuff.loopStateProperty",
432 argType::Required,
433 "circBuff",
434 "loopStateProperty",
435 false,
436 "string",
437 "Optional property name providing loop-state gating. Default is 'loop_state'." );
438
439 config.add( "circBuff.loopStateElement",
440 "",
441 "circBuff.loopStateElement",
442 argType::Required,
443 "circBuff",
444 "loopStateElement",
445 false,
446 "string",
447 "Element name interpreted as the closed-loop state. Default is 'toggle'." );
448
449 config.add( "circBuff.psdTime",
450 "",
451 "circBuff.psdTime",
452 argType::Required,
453 "circBuff",
454 "psdTime",
455 false,
456 "realT",
457 "The length of time over which to calculate PSDs. The default is 1 sec." );
458
459 config.add( "circBuff.psdAvgTime",
460 "",
461 "circBuff.psdAvgTime",
462 argType::Required,
463 "circBuff",
464 "psdAvgTime",
465 false,
466 "realT",
467 "The length of time over which to average PSD estimates. The default is 10 sec." );
468
469 config.add( "circBuff.meanTime",
470 "",
471 "circBuff.meanTime",
472 argType::Required,
473 "circBuff",
474 "meanTime",
475 false,
476 "realT",
477 "The length of time over which to calculate the detrending mean. The default is 10 sec." );
478}
479
480int modalPSDs::loadConfigImpl( mx::app::appConfigurator &_config )
481{
483
484 _config( m_loopNumber, "loop.number" );
485
486 m_shmimBase = "aol" + std::to_string( m_loopNumber );
487 _config( m_shmimBase, "psds.shmimBase" );
488
489 _config( m_shmimTag, "psds.shmimTag" );
490
491 _config( m_fpsDevice, "circBuff.fpsDevice" );
492 _config( m_fpsProperty, "circBuff.fpsProperty" );
493 _config( m_fpsElement, "circBuff.fpsElement" );
494 _config( m_fpsTol, "circBuff.fpsTol" );
495 _config( m_loopStateDevice, "circBuff.loopStateDevice" );
496 _config( m_loopStateProperty, "circBuff.loopStateProperty" );
497 _config( m_loopStateElement, "circBuff.loopStateElement" );
498
500 m_loopClosed.store( m_useLoopState == false, std::memory_order_release );
501
502 realT psdTime = m_psdTime.load();
503 _config( psdTime, "circBuff.psdTime" );
504 m_psdTime.store( psdTime );
505
507 _config( psdAvgTime, "circBuff.psdAvgTime" );
508 m_psdAvgTime.store( psdAvgTime );
509
510 realT meanTime = m_meanTime.load();
511 _config( meanTime, "circBuff.meanTime" );
512 m_meanTime.store( meanTime );
513
514 return 0;
515}
516
518{
519 loadConfigImpl( config );
520}
521
523{
524 CREATE_REG_INDI_NEW_NUMBERF( m_indiP_psdTime, "psdTime", 0, 60, 0.1, "%0.1f", "PSD time", "PSD Setup" );
525 m_indiP_psdTime["current"].set( m_psdTime.load() );
526 m_indiP_psdTime["target"].set( m_psdTime.load() );
527
528 CREATE_REG_INDI_NEW_NUMBERU( m_indiP_psdAvgTime, "psdAvgTime", 0, 60, 0.1, "%0.1f", "PSD Avg. Time", "PSD Setup" );
529 m_indiP_psdAvgTime["current"].set( m_psdAvgTime.load() );
530 m_indiP_psdAvgTime["target"].set( m_psdAvgTime.load() );
531
532 CREATE_REG_INDI_NEW_NUMBERU( m_indiP_meanTime, "meanTime", 0, 600, 0.1, "%0.1f", "Mean Time", "PSD Setup" );
533 m_indiP_meanTime["current"].set( m_meanTime.load() );
534 m_indiP_meanTime["target"].set( m_meanTime.load() );
535
536 if( m_fpsDevice == "" )
537 {
538 return log<software_critical, -1>(
539 { __FILE__, __LINE__, "FPS source is not configurated (circBuff.fpsDevice)" } );
540 }
541
543
544 if( m_useLoopState == true )
545 {
547 }
548
549 CREATE_REG_INDI_RO_NUMBER( m_indiP_fps, "fps", "current", "Circular Buffer" );
550 m_indiP_fps.add( pcf::IndiElement( "current" ) );
551 m_indiP_fps["current"] = m_fps.load();
552
554
561 "psdcalc",
563
565
566 return 0;
567}
568
570{
572
573 XWCAPP_THREAD_CHECK( m_psdThread, "psdcalc" );
574
575 std::unique_lock<std::mutex> lock( m_indiMutex );
576
578
579 return 0;
580}
581
583{
584 { // mutex scope
585 std::lock_guard<std::mutex> lock( m_psdMutex );
586 m_psdWaiting = false;
587 }
588 m_psdCond.notify_all();
589
591
593
594 if( m_rawpsdStream )
595 {
598 }
599
600 if( m_avgpsdStream )
601 {
604 }
605
606 if( m_freqStream )
607 {
610 }
611
612 if( m_tsWork )
613 {
615 }
616
617 if( m_fftWork )
618 {
620 }
621
622 return 0;
623}
624
626{
627 static_cast<void>( dummy );
628
629 m_psdRestarting.store( true, std::memory_order_release );
630
631 // Wait for FPS to become not 0
632 // We wait indefinitely, the other process just might not be alive
633 bool logged = false;
634 while( m_fps.load( std::memory_order_acquire ) <= 0 && !shutdown() )
635 {
636 if( !logged ) // log every thirty seconds
637 {
638 log<text_log>( "waiting for FPS...", logPrio::LOG_NOTICE );
639 logged = true;
640 }
641 mx::sys::sleep( 1 );
642 }
643
644 { // mutex scope
645 std::unique_lock<std::mutex> lock( m_psdMutex );
646 m_psdCond.wait( lock, [this]() { return m_psdWaiting || shutdown(); } );
647 }
648
649 std::cerr << "PSD waiting \n";
650
651 if( shutdown() )
652 {
653 return 0; // If shutdown() is true then shmimMonitor will cleanup
654 }
655
656 // Check for unsupported type (must be realT)
658 {
659 // must be a vector of size 1 on one axis
660 log<software_error>( { __FILE__, __LINE__, "unsupported data type: must be realT" } );
661 return -1;
662 }
663
664 // Check for unexpected format
666 {
667 // must be a vector of size 1 on one axis
668 log<software_error>( { __FILE__, __LINE__, "unexpected shmim format" } );
669 return -1;
670 }
671
672 std::cerr << "connected to " << shmimMonitorT::m_shmimName << " " << shmimMonitorT::m_width << " "
674
676
677 realT fps = m_fps.load( std::memory_order_acquire );
678 realT psdTime = m_psdTime.load( std::memory_order_acquire );
679
680 m_tsSize = fps * psdTime;
681
682 // Adjust length if odd to ensure we get the Nyquist frequency
683 if( m_tsSize % 2 == 1 )
684 {
685 m_tsSize += 1;
686 }
687
689
690 if( m_tsOverlapSize == 0 || !std::isnormal( m_tsOverlapSize ) )
691 {
693 { __FILE__, __LINE__, "bad m_tsOverlapSize value: " + std::to_string( m_tsOverlapSize ) } );
694 return -1;
695 }
696
698
699 if( static_cast<uint32_t>( m_tsSize + 2 ) >= shmimMonitorT::m_depth )
700 {
701 log<software_error>( { __FILE__, __LINE__, "input circ buff is not long enough for psd and mean windows" } );
702 return -1;
703 }
704
706 if( m_meanSize > maxMeanSize )
707 {
708 log<text_log>( "input circ buff is not long enough for meanTime, truncating to " +
709 std::to_string( static_cast<double>( maxMeanSize ) / fps ) + " sec",
712 }
713
715
716 m_tsPtrs.resize( m_tsSize );
717 m_meanPtrs.resize( m_meanSize );
718
719 // Create the window
720 m_win.resize( m_tsSize );
721 mx::sigproc::window::hann( m_win );
722
723 // Set up the FFT and working memory
724 m_fft.plan( m_tsSize, mx::math::ft::dir::forward, false );
725
726 if( m_tsWork )
727 {
729 }
730 m_tsWork = mx::math::ft::fftw_malloc<realT>( m_tsSize );
731
732 if( m_fftWork )
733 {
735 }
736
737 m_fftWork = mx::math::ft::fftw_malloc<std::complex<realT>>( ( m_tsSize / 2 + 1 ) );
738
739 m_psd.resize( m_tsSize / 2 + 1 );
740
741 m_df = 1.0 / ( m_tsSize / fps );
742
743 // Create the shared memory images
744 uint32_t imsize[3];
745
746 // First the frequency
747 imsize[0] = 1;
748 imsize[1] = m_psd.size();
749 imsize[2] = 1;
750
751 if( m_freqStream )
752 {
755 }
756 m_freqStream = static_cast<IMAGE *>( malloc( sizeof( IMAGE ) ) );
757
759 ( m_shmimBase + "_freq" ).c_str(),
760 3,
761 imsize,
763 -1,
764 1,
766 0,
768 0 );
769 m_freqStream->md->write = 1;
770 for( size_t n = 0; n < m_psd.size(); ++n )
771 {
772 m_freqStream->array.F[n] = n * m_df;
773 }
774
775 // Set the time of last write
776 clock_gettime( CLOCK_REALTIME, &m_freqStream->md->writetime );
777 m_freqStream->md->atime = m_freqStream->md->writetime;
778
779 // Update cnt1
780 m_freqStream->md->cnt1 = 0;
781
782 // Update cnt0
783 m_freqStream->md->cnt0 = 0;
784
785 m_freqStream->md->write = 0;
787
789
790 std::cerr << "done restarting\n";
791
792 { // mutex scope
793 std::lock_guard<std::mutex> lock( m_psdMutex );
794 m_psdWaiting = false;
795 m_psdRestarting.store( false, std::memory_order_release );
796 }
797 m_psdCond.notify_all();
798
799 return 0;
800}
801
803{
804 if( m_rawpsdStream )
805 {
808 }
809
810 uint32_t imsize[3];
811 imsize[0] = m_psd.size();
812 imsize[1] = m_nModes;
814
815 m_rawpsdStream = static_cast<IMAGE *>( malloc( sizeof( IMAGE ) ) );
816
818 ( m_shmimBase + "_raw_" + m_shmimTag + "psds" ).c_str(),
819 3,
820 imsize,
822 -1,
823 1,
825 0,
827 0 );
828
829 if( m_avgpsdStream )
830 {
833 }
834
835 imsize[0] = m_psd.size();
836 imsize[1] = m_nModes;
837 imsize[2] = 1;
838
839 m_avgpsdStream = static_cast<IMAGE *>( malloc( sizeof( IMAGE ) ) );
841 ( m_shmimBase + "_" + m_shmimTag + "psds" ).c_str(),
842 3,
843 imsize,
845 -1,
846 1,
848 0,
850 0 );
851
852 m_psdBuffer.resize( m_psd.size(), m_nModes );
853
854 size_t planeElements = m_psdBuffer.rows() * m_psdBuffer.cols();
856 m_rawPSDHistory.assign( planeElements * static_cast<size_t>( m_rawPSDHistoryDepth ), 0 );
859
860 return 0;
861}
862
863int modalPSDs::processImage( void *curr_src, const dev::shmimT &dummy )
864{
865 static_cast<void>( dummy );
866
867 if( acceptLoopStateFrame() == false )
868 {
869 return 0;
870 }
871
872 float *f_src = static_cast<float *>( curr_src );
873
874 m_ampCircBuff.nextEntry( f_src );
875
876 return 0;
877}
878
880{
881 p->psdThreadExec();
882}
883
885{
887
888 std::vector<double> meanSums;
889 std::vector<realT> meanHeadCache;
890 ampCircBuffT::snapshotT prevSnap;
892 bool haveMeanSums = false;
893 std::vector<double> avgPsdSum;
895
896 while( m_psdThreadInit == true && shutdown() == 0 )
897 {
898 sleep( 1 );
899 }
900
901 while( shutdown() == 0 )
902 {
903 { // mutex scope
904 std::unique_lock<std::mutex> lock( m_psdMutex );
905
906 if( m_psdRestarting.load( std::memory_order_acquire ) == true || m_ampCircBuff.maxEntries() == 0 )
907 {
908 m_psdWaiting = true;
909 m_psdCond.notify_all();
910 }
911
912 m_psdCond.wait( lock,
913 [this]()
914 {
915 return shutdown() || ( m_psdRestarting.load( std::memory_order_acquire ) == false &&
916 m_ampCircBuff.maxEntries() != 0 );
917 } );
918
919 m_psdWaiting = false;
920 }
921
922 if( shutdown() )
923 {
924 break;
925 }
926
927 if( m_ampCircBuff.maxEntries() == 0 )
928 {
929 log<software_error>( { __FILE__, __LINE__, "amp circ buff has zero size" } );
930 return;
931 }
932
933 std::cerr << "waiting to grow\n";
934 while( m_ampCircBuff.size() < m_ampCircBuff.maxEntries() &&
935 m_psdRestarting.load( std::memory_order_acquire ) == false && !shutdown() )
936 {
937 // shrinking sleep
938 double stime = ( 1.0 * m_ampCircBuff.maxEntries() - 1.0 * m_ampCircBuff.size() ) / m_fps.load() * 0.5 * 1e9;
939 mx::sys::nanoSleep( stime );
940 }
941
942 std::cerr << "all grown. starting to calculate\n";
943
944 meanSums.assign( m_nModes, 0 );
945 meanHeadCache.clear();
946 prevSnap = ampCircBuffT::snapshotT();
948 haveMeanSums = false;
949 avgPsdSum.clear();
951
952 while( m_psdRestarting.load( std::memory_order_acquire ) == false && !shutdown() )
953 {
954 // Used to check if we are getting too behind
956
957 ampCircBuffT::snapshotT tsSnap;
959 {
960 continue;
961 }
962
965
967 if( meanCacheCount <= 0 )
968 {
969 meanCacheCount = 1;
970 }
971
972 bool canRollMean = false;
973 if( haveMeanSums == true && prevSnap.maxEntries == tsSnap.maxEntries && meanHeadCache.size() > 0 )
974 {
977
978 if( canRollMean == true )
979 {
981 }
982 }
983
984 if( canRollMean == false )
985 {
987 }
988
992 haveMeanSums = true;
993
994 for( size_t m = 0; m < m_nModes; ++m ) // Loop over each mode
995 {
996 realT mn = static_cast<realT>( meanSums[m] / m_meanSize );
997
998 double var = 0;
999
1000 for( cbIndexT n = 0; n < m_tsSize; ++n )
1001 {
1002 m_tsWork[n] = m_tsPtrs[n][m] - mn; // load mean subtracted chunk
1003
1004 var += pow( m_tsWork[n], 2 );
1005
1006 m_tsWork[n] *= m_win[n];
1007 }
1008 var /= m_tsSize;
1009
1011
1012 double nm = 0;
1013 for( size_t n = 0; n < m_psd.size(); ++n )
1014 {
1015 m_psd[n] = norm( m_fftWork[n] );
1016 nm += m_psd[n] * m_df;
1017 }
1018
1019 // Put it in the buffer for uploading to shmim
1020 for( size_t n = 0; n < m_psd.size(); ++n )
1021 {
1022 // m_psd[n] *= ( var / nm );
1023 m_psdBuffer( n, m ) = m_psd[n] * ( var / nm );
1024 }
1025 }
1026
1027 //------------------------- the raw psds ---------------------------
1028 m_rawpsdStream->md->write = 1;
1029
1030 // Set the time of last write
1031 clock_gettime( CLOCK_REALTIME, &m_rawpsdStream->md->writetime );
1032 m_rawpsdStream->md->atime = m_rawpsdStream->md->writetime;
1033
1034 uint64_t cnt1 = m_rawpsdStream->md->cnt1 + 1;
1035 if( cnt1 >= m_rawpsdStream->md->size[2] )
1036 {
1037 cnt1 = 0;
1038 }
1039
1040 // Move to next pointer
1041 float *F = m_rawpsdStream->array.F + m_psdBuffer.rows() * m_psdBuffer.cols() * cnt1;
1042
1043 if( m_rawPSDHistoryDepth > 0 && m_rawpsdStream->md->cnt0 >= m_rawpsdStream->md->size[2] )
1044 {
1045 size_t planeElements = m_psdBuffer.rows() * m_psdBuffer.cols();
1047
1048 memcpy( H, F, planeElements * sizeof( realT ) );
1049
1053 {
1055 }
1056 }
1057
1058 memcpy( F, m_psdBuffer.data(), m_psdBuffer.rows() * m_psdBuffer.cols() * sizeof( float ) );
1059
1060 // Update cnt1
1061 m_rawpsdStream->md->cnt1 = cnt1;
1062
1063 // Update cnt0
1064 ++m_rawpsdStream->md->cnt0;
1065
1066 m_rawpsdStream->md->write = 0;
1068
1069 //-------------------------- now average the psds ----------------------------
1070
1071 size_t planeElements = m_psdBuffer.rows() * m_psdBuffer.cols();
1072 if( avgPsdSum.size() != planeElements )
1073 {
1074 avgPsdSum.assign( planeElements, 0 );
1076 }
1077
1081
1083
1084 bool recomputeAvgPsd = ( nextWindowCount == 0 || latestPlane == nullptr || avgPsdWindowCount == 0 ||
1086
1087 if( recomputeAvgPsd == true )
1088 {
1090 }
1091 else if( nextWindowCount == avgPsdWindowCount + 1 )
1092 {
1094 }
1095 else
1096 {
1098 if( outgoingPlane == nullptr )
1099 {
1101 }
1102 else
1103 {
1105 }
1106 }
1107
1110
1111 for( size_t n = 0; n < planeElements; ++n )
1112 {
1113 m_psdBuffer.data()[n] = static_cast<realT>( avgPsdSum[n] / avgPsdDivisor );
1114 }
1115
1116 m_avgpsdStream->md->write = 1;
1117
1118 // Set the time of last write
1119 clock_gettime( CLOCK_REALTIME, &m_avgpsdStream->md->writetime );
1120 m_avgpsdStream->md->atime = m_avgpsdStream->md->writetime;
1121
1122 // Move to next pointer
1123 F = m_avgpsdStream->array.F;
1124
1125 memcpy( F, m_psdBuffer.data(), m_psdBuffer.rows() * m_psdBuffer.cols() * sizeof( float ) );
1126
1127 // Update cnt1
1128 m_avgpsdStream->md->cnt1 = 0;
1129
1130 // Update cnt0
1131 ++m_avgpsdStream->md->cnt0;
1132
1133 m_avgpsdStream->md->write = 0;
1135
1136 // double t1 = mx::sys::get_curr_time();
1137 // std::cerr << "done " << t1 - t0 << "\n";
1138
1139 // Have to be cycling within the overlap
1140 if( m_ampCircBuff.mono() - mono0 >= static_cast<uint32_t>( m_tsOverlapSize ) )
1141 {
1142 log<text_log>( "PSD calculations getting behind, skipping ahead.", logPrio::LOG_WARNING );
1143 }
1144 else
1145 {
1146 while( m_ampCircBuff.mono() - mono0 < static_cast<uint32_t>( m_tsOverlapSize ) &&
1147 !m_psdRestarting.load( std::memory_order_acquire ) )
1148 {
1149 mx::sys::microSleep( 0.2 * 1000000.0 / m_fps.load( std::memory_order_acquire ) );
1150 }
1151 }
1152
1153 if( m_psdRestarting.load( std::memory_order_acquire ) )
1154 {
1155 continue;
1156 }
1157 }
1158 }
1159}
1160
1161modalPSDs::cbIndexT modalPSDs::latestWindowRefEntry( const ampCircBuffT::snapshotT &sn, cbIndexT count )
1162{
1163 if( sn.latest >= count )
1164 {
1165 return sn.latest - count;
1166 }
1167
1168 return sn.maxEntries + sn.latest - count;
1169}
1170
1172modalPSDs::precedingWindowRefEntry( const ampCircBuffT::snapshotT &sn, cbIndexT refEntry, cbIndexT count )
1173{
1174 if( refEntry >= count )
1175 {
1176 return refEntry - count;
1177 }
1178
1179 return sn.maxEntries + refEntry - count;
1180}
1181
1183{
1184 if( maxEntries <= 0 )
1185 {
1186 return 0;
1187 }
1188
1189 if( to >= from )
1190 {
1191 return to - from;
1192 }
1193
1194 return maxEntries + to - from;
1195}
1196
1197bool modalPSDs::loadPsdInputWindows( ampCircBuffT::snapshotT &sn )
1198{
1199 for( int retry = 0; retry < 3; ++retry )
1200 {
1201 ampCircBuffT::snapshotT seedSnap = m_ampCircBuff.snapshot();
1202 if( seedSnap.validEntries < m_ampCircBuff.maxEntries() )
1203 {
1204 return false;
1205 }
1206
1208
1209 if( !m_ampCircBuff.loadSequence( tsRefEntry, m_tsSize, m_tsPtrs.data(), sn ) )
1210 {
1211 return false;
1212 }
1213
1214 if( sn.mono != seedSnap.mono )
1215 {
1216 continue;
1217 }
1218
1220
1221 ampCircBuffT::snapshotT meanSnap;
1222 if( !m_ampCircBuff.loadSequence( meanRefEntry, m_meanSize, m_meanPtrs.data(), meanSnap ) )
1223 {
1224 return false;
1225 }
1226
1227 if( meanSnap.mono == sn.mono )
1228 {
1229 return true;
1230 }
1231 }
1232
1233 return false;
1234}
1235
1236void modalPSDs::recomputeMeanSums( std::vector<double> &meanSums ) const
1237{
1238 meanSums.assign( m_nModes, 0 );
1239
1240 for( cbIndexT n = 0; n < m_meanSize; ++n )
1241 {
1242 const realT *sample = m_meanPtrs[n];
1243 for( size_t m = 0; m < m_nModes; ++m )
1244 {
1245 meanSums[m] += sample[m];
1246 }
1247 }
1248}
1249
1250void modalPSDs::rollMeanSums( std::vector<double> &meanSums,
1251 const std::vector<realT> &meanHeadCache,
1252 cbIndexT advance ) const
1253{
1254 if( advance <= 0 )
1255 {
1256 return;
1257 }
1258
1259 for( cbIndexT n = 0; n < advance; ++n )
1260 {
1261 const realT *oldSample = meanHeadCache.data() + static_cast<size_t>( n ) * m_nModes;
1263
1264 for( size_t m = 0; m < m_nModes; ++m )
1265 {
1266 meanSums[m] += newSample[m] - oldSample[m];
1267 }
1268 }
1269}
1270
1271void modalPSDs::cacheMeanHead( std::vector<realT> &meanHeadCache, cbIndexT count ) const
1272{
1273 if( count <= 0 )
1274 {
1275 meanHeadCache.clear();
1276 return;
1277 }
1278
1279 meanHeadCache.resize( static_cast<size_t>( count ) * m_nModes );
1280
1281 for( cbIndexT n = 0; n < count; ++n )
1282 {
1283 memcpy( meanHeadCache.data() + static_cast<size_t>( n ) * m_nModes, m_meanPtrs[n], m_nModes * sizeof( realT ) );
1284 }
1285}
1286
1288{
1289 return ( m_useLoopState == false ) || m_loopClosed.load( std::memory_order_acquire );
1290}
1291
1293{
1294 return std::min<uint64_t>( m_rawpsdStream->md->cnt0, publishedRawPSDHistoryDepth() + m_rawPSDHistoryDepth );
1295}
1296
1297const modalPSDs::realT *modalPSDs::rawPSDPlaneByAge( uint64_t age, size_t planeElements ) const
1298{
1299 const uint64_t publishedCount = std::min<uint64_t>( m_rawpsdStream->md->cnt0, publishedRawPSDHistoryDepth() );
1300
1301 if( age < publishedCount )
1302 {
1303 uint64_t publishedDepth = m_rawpsdStream->md->size[2];
1304 uint64_t slot = ( m_rawpsdStream->md->cnt1 + publishedDepth - age ) % publishedDepth;
1305 return m_rawpsdStream->array.F + planeElements * slot;
1306 }
1307
1311 {
1312 return nullptr;
1313 }
1314
1316 return m_rawPSDHistory.data() + planeElements * slot;
1317}
1318
1319void modalPSDs::recomputeAveragedPSDSum( std::vector<double> &avgPsdSum,
1320 uint64_t windowCount,
1321 size_t planeElements ) const
1322{
1323 avgPsdSum.assign( planeElements, 0 );
1324
1325 for( uint64_t age = 0; age < windowCount; ++age )
1326 {
1327 const realT *plane = rawPSDPlaneByAge( age, planeElements );
1328 if( plane == nullptr )
1329 {
1330 break;
1331 }
1332
1334 }
1335}
1336
1337void modalPSDs::updatePlaneSum( std::vector<double> &planeSum,
1338 const realT *addPlane,
1339 const realT *removePlane,
1340 size_t planeElements )
1341{
1342 for( size_t n = 0; n < planeElements; ++n )
1343 {
1344 if( addPlane != nullptr )
1345 {
1346 planeSum[n] += addPlane[n];
1347 }
1348
1349 if( removePlane != nullptr )
1350 {
1351 planeSum[n] -= removePlane[n];
1352 }
1353 }
1354}
1355
1357{
1358 realT psdTime = m_psdTime.load( std::memory_order_acquire );
1359 realT psdAvgTime = m_psdAvgTime.load( std::memory_order_acquire );
1360
1361 if( psdTime <= 0 || m_psdOverlapFraction <= 0 )
1362 {
1363 return 1;
1364 }
1365
1366 int nPSDAverage = static_cast<int>( std::ceil( psdAvgTime / ( psdTime * m_psdOverlapFraction ) ) );
1367
1368 if( nPSDAverage <= 0 )
1369 {
1370 return 1;
1371 }
1372
1373 return nPSDAverage;
1374}
1375
1377{
1378 realT meanTime = m_meanTime.load( std::memory_order_acquire );
1379
1380 if( fps <= 0 || meanTime <= 0 )
1381 {
1382 return 1;
1383 }
1384
1385 cbIndexT meanSize = fps * meanTime;
1386
1387 if( meanSize <= 0 )
1388 {
1389 return 1;
1390 }
1391
1392 return meanSize;
1393}
1394
1396{
1397 if( m_tsSize <= 0 || m_meanSize <= 0 )
1398 {
1399 return 0;
1400 }
1401
1402 // Leave one slot for the excluded latest sample and one for the unreadable overwrite edge.
1403 return m_tsSize + m_meanSize + 2;
1404}
1405
1407{
1408 const uint32_t desired = static_cast<uint32_t>( desiredPSDAverageCount() );
1410
1411 if( desired + 1 <= published )
1412 {
1413 return 0;
1414 }
1415
1416 return desired + 1 - published;
1417}
1418
1420{
1421 return std::max<uint32_t>( 1, static_cast<uint32_t>( m_nPSDHistory ) );
1422}
1423
1424INDI_NEWCALLBACK_DEFN( modalPSDs, m_indiP_psdTime )( const pcf::IndiProperty &ipRecv )
1425{
1426 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_psdTime, ipRecv );
1427
1428 realT target;
1429
1430 if( indiTargetUpdate( m_indiP_psdTime, target, ipRecv, true ) < 0 )
1431 {
1432 log<software_error>( { __FILE__, __LINE__ } );
1433 return -1;
1434 }
1435
1436 if( m_psdTime.load( std::memory_order_acquire ) != target )
1437 {
1438 std::lock_guard<std::mutex> guard( m_indiMutex );
1439
1440 m_psdTime.store( target, std::memory_order_release );
1441
1442 updateIfChanged( m_indiP_psdTime, "current", m_psdTime.load(), INDI_IDLE );
1443 updateIfChanged( m_indiP_psdTime, "target", m_psdTime.load(), INDI_IDLE );
1444
1445 shmimMonitorT::m_restart = true;
1446
1447 log<text_log>( "set psdTime to " + std::to_string( m_psdTime.load() ), logPrio::LOG_NOTICE );
1448 }
1449
1450 return 0;
1451} // INDI_NEWCALLBACK_DEFN(modalPSDs, m_indiP_psdTime)
1452
1453INDI_NEWCALLBACK_DEFN( modalPSDs, m_indiP_psdAvgTime )( const pcf::IndiProperty &ipRecv )
1454{
1455 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_psdAvgTime, ipRecv );
1456
1457 realT target;
1458
1459 if( indiTargetUpdate( m_indiP_psdAvgTime, target, ipRecv, true ) < 0 )
1460 {
1461 log<software_error>( { __FILE__, __LINE__ } );
1462 return -1;
1463 }
1464
1465 if( m_psdAvgTime.load( std::memory_order_acquire ) != target )
1466 {
1467 std::lock_guard<std::mutex> guard( m_indiMutex );
1468
1469 m_psdAvgTime.store( target, std::memory_order_release );
1470
1471 updateIfChanged( m_indiP_psdAvgTime, "current", m_psdAvgTime.load(), INDI_IDLE );
1472 updateIfChanged( m_indiP_psdAvgTime, "target", m_psdAvgTime.load(), INDI_IDLE );
1473
1474 shmimMonitorT::m_restart = true;
1475
1476 log<text_log>( "set psdAvgTime to " + std::to_string( m_psdAvgTime.load() ), logPrio::LOG_NOTICE );
1477 }
1478
1479 return 0;
1480} // INDI_NEWCALLBACK_DEFN(modalPSDs, m_indiP_psdAvgTime)
1481
1482INDI_NEWCALLBACK_DEFN( modalPSDs, m_indiP_meanTime )( const pcf::IndiProperty &ipRecv )
1483{
1484 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_meanTime, ipRecv );
1485
1486 realT target;
1487
1488 if( indiTargetUpdate( m_indiP_meanTime, target, ipRecv, true ) < 0 )
1489 {
1490 log<software_error>( { __FILE__, __LINE__ } );
1491 return -1;
1492 }
1493
1494 if( m_meanTime.load( std::memory_order_acquire ) != target )
1495 {
1496 std::lock_guard<std::mutex> guard( m_indiMutex );
1497
1498 m_meanTime.store( target, std::memory_order_release );
1499
1500 updateIfChanged( m_indiP_meanTime, "current", m_meanTime.load(), INDI_IDLE );
1501 updateIfChanged( m_indiP_meanTime, "target", m_meanTime.load(), INDI_IDLE );
1502
1503 shmimMonitorT::m_restart = true;
1504
1505 log<text_log>( "set meanTime to " + std::to_string( m_meanTime.load() ), logPrio::LOG_NOTICE );
1506 }
1507
1508 return 0;
1509} // INDI_NEWCALLBACK_DEFN(modalPSDs, m_indiP_meanTime)
1510
1511INDI_SETCALLBACK_DEFN( modalPSDs, m_indiP_fpsSource )( const pcf::IndiProperty &ipRecv )
1512{
1513 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_fpsSource, ipRecv );
1514
1515 if( ipRecv.find( m_fpsElement ) != true ) // this isn't valid
1516 {
1517 log<software_error>( { __FILE__, __LINE__, "No current property in fps source." } );
1518 return 0;
1519 }
1520
1521 std::lock_guard<std::mutex> guard( m_indiMutex );
1522
1523 realT fps = ipRecv[m_fpsElement].get<realT>();
1524
1525 if( fabs( fps - m_fps.load( std::memory_order_acquire ) ) > m_fpsTol + .0001 )
1526 {
1527 m_fps.store( fps, std::memory_order_release );
1528 log<text_log>( "set fps to " + std::to_string( m_fps.load() ), logPrio::LOG_NOTICE );
1529 updateIfChanged( m_indiP_fps, "current", m_fps.load(), INDI_IDLE );
1530
1531 shmimMonitorT::m_restart = true;
1532 }
1533
1534 return 0;
1535
1536} // INDI_SETCALLBACK_DEFN(modalPSDs, m_indiP_fpsSource)
1537
1538INDI_SETCALLBACK_DEFN( modalPSDs, m_indiP_loop )( const pcf::IndiProperty &ipRecv )
1539{
1540 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_loop, ipRecv );
1541
1542 if( ipRecv.find( m_loopStateElement ) != true )
1543 {
1544 log<software_error>( { __FILE__, __LINE__, "No configured loop-state element in loop source." } );
1545 return 0;
1546 }
1547
1548 bool loopClosed = ( ipRecv[m_loopStateElement].getSwitchState() == pcf::IndiElement::On );
1549
1550 if( loopClosed != m_loopClosed.load( std::memory_order_acquire ) )
1551 {
1552 std::lock_guard<std::mutex> guard( m_indiMutex );
1553
1554 m_loopClosed.store( loopClosed, std::memory_order_release );
1555 shmimMonitorT::m_restart = true;
1556
1557 log<text_log>( std::string( "loop state is now " ) + ( loopClosed ? "closed" : "open" ), logPrio::LOG_NOTICE );
1558 }
1559
1560 return 0;
1561
1562} // INDI_SETCALLBACK_DEFN(modalPSDs, m_indiP_loop)
1563
1564} // namespace app
1565} // namespace MagAOX
1566
1567#endif // modalPSDs_hpp
#define IMAGESTRUCT_FLOAT
#define XWCAPP_THREAD_CHECK(thrdSt, thrdName)
Error handling wrapper for checking on thread status with tryjoin.
#define XWCAPP_THREAD_START(thrdSt, thrdInit, thrdId, thrdProp, thrdPrio, thrdCpuset, thrdName, thrdStart)
Error handling wrapper for the threadStart function of the XWCApp.
#define XWCAPP_THREAD_STOP(thrdSt)
Error handlng wrapper for stopping a thread.
The base-class for XWCTk applications.
stateCodes::stateCodeT state()
Get the current state code.
int shutdown()
Get the value of the shutdown flag.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::mutex m_indiMutex
Mutex for locking INDI communications.
uint32_t m_depth
The depth of the circular buffer in the stream.
uint32_t m_width
The width of the images in the stream.
uint32_t m_height
The height of the images in the stream.
uint8_t m_dataType
The ImageStreamIO type code.
Class for application to calculate rolling PSDs of modal amplitudes.
Definition modalPSDs.hpp:44
realT m_psdOverlapFraction
The fraction of the sample time to overlap by.
Definition modalPSDs.hpp:97
std::atomic< realT > m_psdAvgTime
The time over which to average PSD estimates. The default is 10 sec.
Definition modalPSDs.hpp:90
uint32_t m_rawPSDHistoryNext
Next overflow slot to overwrite in m_rawPSDHistory.
pcf::IndiProperty m_psdThreadProp
The property to hold the PSD Calculation thread details.
pcf::IndiProperty m_indiP_psdAvgTime
INDI_NEWCALLBACK_DECL(modalPSDs, m_indiP_overSize)
void cacheMeanHead(std::vector< realT > &meanHeadCache, cbIndexT count) const
Cache the oldest slice of the currently loaded mean window for the next rolling-mean update.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
pid_t m_psdThreadID
PSD Calculation thread PID.
mx::improc::eigenImage< realT > m_psdBuffer
std::string m_fpsElement
Element name for getting fps to set circular buffer length.
Definition modalPSDs.hpp:77
INDI_NEWCALLBACK_DECL(modalPSDs, m_indiP_meanTime)
std::thread m_psdThread
The PSD Calculation thread.
pcf::IndiProperty m_indiP_overSize
std::atomic< bool > m_loopClosed
Current closed-loop state used to gate PSD ingestion and processing.
Definition modalPSDs.hpp:85
INDI_SETCALLBACK_DECL(modalPSDs, m_indiP_loop)
std::complex< realT > * m_fftWork
INDI_NEWCALLBACK_DECL(modalPSDs, m_indiP_psdTime)
uint32_t publishedRawPSDHistoryDepth() const
Calculate the published raw PSD history depth retained in shared memory.
void recomputeMeanSums(std::vector< double > &meanSums) const
Recompute the per-mode sums for the full mean window from the currently loaded pointers.
realT m_fpsTol
The tolerance for detecting a change in FPS.
Definition modalPSDs.hpp:87
friend class modalPSDs_test
Definition modalPSDs.hpp:46
cbIndexT desiredMeanSampleCount(realT fps) const
Calculate how many samples are needed for the mean-subtraction window at the current FPS.
static void psdThreadStart(modalPSDs *p)
PS Calculation thread starter function.
cbIndexT m_tsSize
The length of the time series sample over which the PSD is calculated.
std::vector< realT > m_win
The window function. By default this is Hann.
cbIndexT requiredInputHistoryDepth() const
Calculate the total input-history depth needed to read both windows safely from the fixed-size circul...
std::string m_loopStateProperty
Optional property name providing loop-state gating updates.
Definition modalPSDs.hpp:80
virtual int appStartup()
Startup function.
void rollMeanSums(std::vector< double > &meanSums, const std::vector< realT > &meanHeadCache, cbIndexT advance) const
Update per-mode mean sums using the cached oldest slice and the newest loaded mean-window slice.
bool m_useLoopState
Whether PSD ingestion is gated by an external loop-state property.
Definition modalPSDs.hpp:83
std::condition_variable m_psdCond
Coordinates quiescence during restart.
INDI_SETCALLBACK_DECL(modalPSDs, m_indiP_fpsSource)
INDI_NEWCALLBACK_DECL(modalPSDs, m_indiP_psdAvgTime)
uint64_t m_rawPSDHistoryCount
Number of overflow PSD estimates stored since the last restart.
IMAGE * m_avgpsdStream
The ImageStreamIO shared memory buffer to hold the average psds.
size_t m_nModes
the number of modes to calculate PSDs for.
std::string m_loopStateElement
Element name used to interpret closed-loop state.
Definition modalPSDs.hpp:81
std::atomic< realT > m_psdTime
The length of time over which to calculate PSDs. The default is 1 sec.
Definition modalPSDs.hpp:89
IMAGE * m_rawpsdStream
The ImageStreamIO shared memory buffer to hold the raw psds.
std::vector< realT * > m_tsPtrs
Snapshot of the latest time-series window pointers for PSD computation.
mx::math::ft::fftwEnvironment< realT > m_fftEnv
const realT * rawPSDPlaneByAge(uint64_t age, size_t planeElements) const
Locate a retained raw PSD plane by age, where age 0 is the newest plane.
std::atomic< realT > m_meanTime
The time over which to calculate the mean for detrending. The default is 10 sec.
Definition modalPSDs.hpp:91
modalPSDs()
Default c'tor.
static cbIndexT precedingWindowRefEntry(const ampCircBuffT::snapshotT &sn, cbIndexT refEntry, cbIndexT count)
Compute the reference entry for a logical window immediately preceding another logical window.
uint64_t storedRawPSDCount() const
Count how many raw PSD planes are currently retained across the published and overflow histories.
void recomputeAveragedPSDSum(std::vector< double > &avgPsdSum, uint64_t windowCount, size_t planeElements) const
Recompute the averaged-PSD running sum from the newest windowCount retained raw PSD planes.
mx::sigproc::circularBufferIndex< realT *, cbIndexT, true > ampCircBuffT
The amplitude circular buffer type.
Definition modalPSDs.hpp:61
virtual void loadConfig()
std::string m_fpsDevice
Device name for getting fps to set circular buffer length.
Definition modalPSDs.hpp:75
pcf::IndiProperty m_indiP_fps
virtual int appLogic()
Implementation of the FSM for modalPSDs.
cbIndexT m_meanSize
The length of the time series over which to calculate the mean.
int processImage(void *curr_src, const dev::shmimT &dummy)
IMAGE * m_freqStream
The ImageStreamIO shared memory buffer to hold the frequency scale.
static cbIndexT latestWindowRefEntry(const ampCircBuffT::snapshotT &sn, cbIndexT count)
Compute the reference entry for the latest logical window while preserving historical semantics.
mx::math::ft::fftT< realT, std::complex< realT >, 1, 0 > m_fft
std::vector< realT * > m_meanPtrs
Snapshot of the latest mean-window pointers for PSD computation.
virtual void setupConfig()
ampCircBuffT m_ampCircBuff
int m_psdThreadPrio
Priority of the PSD Calculation thread.
static void updatePlaneSum(std::vector< double > &planeSum, const realT *addPlane, const realT *removePlane, size_t planeElements)
Add one raw PSD plane and optionally subtract one outgoing raw PSD plane from the running average sum...
std::string m_psdThreadCpuset
The cpuset to use for the PSD Calculation thread.
std::string m_shmimBase
Definition modalPSDs.hpp:70
std::complex< realT > complexT
Definition modalPSDs.hpp:53
int allocate(const dev::shmimT &dummy)
int m_nPSDHistory
Minimum number of raw PSD estimates to retain in the history stream.
Definition modalPSDs.hpp:99
std::mutex m_psdMutex
Protects restart handoff between allocate() and the PSD thread.
bool m_psdWaiting
Synchronization flag protected by m_psdMutex.
bool acceptLoopStateFrame() const
Determine whether incoming frames should currently be accepted into the PSD history.
int32_t cbIndexT
The index for the circular buffer.
Definition modalPSDs.hpp:55
cbIndexT m_tsOverlapSize
The number of samples in the overlap.
int desiredPSDAverageCount() const
Calculate how many raw PSD estimates are needed to cover the requested averaging time.
pcf::IndiProperty m_indiP_psdTime
std::string m_loopStateDevice
Optional device name providing loop-state gating updates.
Definition modalPSDs.hpp:79
bool loadPsdInputWindows(ampCircBuffT::snapshotT &sn)
Load the PSD and mean pointer windows from a single validated snapshot.
uint32_t m_rawPSDHistoryDepth
Number of overflow PSD estimates currently allocated in m_rawPSDHistory.
std::vector< realT > m_rawPSDHistory
Heap-backed overflow history for raw PSD estimates beyond the shmim depth.
static cbIndexT circularEntryAdvance(cbIndexT from, cbIndexT to, cbIndexT maxEntries)
Compute the forward logical advance between two circular-buffer reference entries.
pcf::IndiProperty m_indiP_loop
pcf::IndiProperty m_indiP_fpsSource
void psdThreadExec()
PSD Calculation thread function.
bool m_psdThreadInit
Initialization flag for the PSD Calculation thread.
uint32_t rawPSDHistoryDepth() const
Calculate the additional PSD history depth needed beyond the published raw-PSD shmim.
pcf::IndiProperty m_indiP_meanTime
std::atomic< bool > m_psdRestarting
std::string m_fpsProperty
Property name for getting fps to set circular buffer length.
Definition modalPSDs.hpp:76
std::atomic< realT > m_fps
The current input frame rate used to size PSD windows.
virtual int appShutdown()
Shutdown the app.
~modalPSDs() noexcept
D'tor, declared and defined for noexcept.
std::vector< realT > m_psd
dev::shmimMonitor< modalPSDs > shmimMonitorT
The base shmimMonitor type.
Definition modalPSDs.hpp:58
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define CREATE_REG_INDI_NEW_NUMBERU(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as unsigned int, using the standard call...
#define CREATE_REG_INDI_NEW_NUMBERF(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as float, using the standard callback na...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
#define CREATE_REG_INDI_RO_NUMBER(prop, name, label, group)
Create and register a RO INDI property as a number, using the standard callback name.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
#define INDI_IDLE
Definition indiUtils.hpp:27
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:19
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
#define SHMIMMONITOR_APP_SHUTDOWN
Call shmimMonitorT::appShutdown with error checking for shmimMonitor.
#define SHMIMMONITOR_APP_LOGIC
Call shmimMonitorT::appLogic with error checking for shmimMonitor.
#define SHMIMMONITOR_APP_STARTUP
Call shmimMonitorT::appStartup with error checking for shmimMonitor.
#define SHMIMMONITOR_LOAD_CONFIG(cfig)
Call shmimMonitorT::loadConfig with error checking for shmimMonitor.
#define SHMIMMONITOR_UPDATE_INDI
Call shmimMonitorT::updateINDI with error checking for shmimMonitor.
#define SHMIMMONITOR_SETUP_CONFIG(cfig)
Call shmimMonitorT::setupConfig with error checking for shmimMonitor.
@ OPERATING
The device is operating, other than homing.
Software CRITICAL log entry.