API
 
Loading...
Searching...
No Matches
shmimIntegrator.hpp
Go to the documentation of this file.
1/** \file shmimIntegrator.hpp
2 * \brief The MagAO-X generic ImageStreamIO stream integrator
3 *
4 * \ingroup app_files
5 */
6
7#ifndef shmimIntegrator_hpp
8#define shmimIntegrator_hpp
9
10#include <limits>
11
12#include <mx/improc/eigenCube.hpp>
13#include <mx/improc/eigenImage.hpp>
14
15#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
16#include "../../magaox_git_version.h"
17
18namespace MagAOX
19{
20namespace app
21{
22
23struct darkShmimT
24{
25 static std::string configSection()
26 {
27 return "darkShmim";
28 };
29
30 static std::string indiPrefix()
31 {
32 return "dark";
33 };
34};
35
37{
38 static std::string configSection()
39 {
40 return "dark2Shmim";
41 };
42
43 static std::string indiPrefix()
44 {
45 return "dark2";
46 };
47};
48
49/** \defgroup shmimIntegrator ImageStreamIO Stream Integrator
50 * \brief Integrates (i.e. averages) an ImageStreamIO image stream.
51 *
52 * <a href="../handbook/operating/software/apps/shmimIntegrator.html">Application Documentation</a>
53 *
54 * \ingroup apps
55 *
56 */
57
58/** \defgroup shmimIntegrator_files ImageStreamIO Stream Integrator
59 * \ingroup shmimIntegrator
60 */
61
62/** MagAO-X application to control integrating (averaging) an ImageStreamIO stream
63 *
64 * \ingroup shmimIntegrator
65 *
66 */
67class shmimIntegrator : public MagAOXApp<true>,
68 public dev::shmimMonitor<shmimIntegrator>,
69 public dev::shmimMonitor<shmimIntegrator, darkShmimT>,
70 public dev::shmimMonitor<shmimIntegrator, dark2ShmimT>,
71 public dev::frameGrabber<shmimIntegrator>,
72 public dev::telemeter<shmimIntegrator>
73{
74
75 // Give the test harness access.
77
82 friend class dev::telemeter<shmimIntegrator>;
83
84 // The base shmimMonitor type
86
87 // The dark shmimMonitor type
89
90 // The dark shmimMonitor type for a 2nd dark
92
93 // The base frameGrabber type
95
97
98 /// Floating point type in which to do all calculations.
99 typedef float realT;
100
101 public:
102 /** \name app::dev Configurations
103 *@{
104 */
105
106 static constexpr bool c_frameGrabber_flippable =
107 false; ///< app:dev config to tell framegrabber these images can not be flipped
108
109 ///@}
110
111 protected:
112 /** \name Configurable Parameters
113 *@{
114 */
115
116 unsigned m_nAverageDefault{ 10 }; ///< The number of frames to average. Default 10.
117
118 std::string m_fpsSource; ///< Device name for getting fps if time-based averaging is used. This device should have
119 ///< *.fps.current.
120
121 float m_avgTime{ 0 }; ///< If non zero, then m_nAverage adjusts automatically to keep a constant averaging time
122 ///< [sec]. Default 0.
123
124 unsigned m_nUpdate{ 0 }; ///< The rate at which to update the average. If 0 < m_nUpdate < m_nAverage then this is a
125 ///< moving averager. Default 0.
126
127 bool m_continuous{ true }; ///< Set to false in configuration to have this run once then stop until triggered.
128
129 bool m_running{ true }; ///< Set to false in configuration to have it not start averaging until triggered.
130
131 std::string m_stateSource; ///< The source of the state string used for file management
132
133 bool m_fileSaver{ false }; ///< Set to true in configuration to have this save and reload files automatically.
134
135 ///@}
136
137 mx::improc::eigenCube<realT> m_accumImages; ///< Cube used to accumulate images
138
139 mx::improc::eigenImage<realT> m_avgImage; ///< The average image.
140
141 unsigned m_nAverage{ 10 };
142
143 float m_fps{ 0 }; ///< Current FPS from the FPS source.
144
145 unsigned m_nprocessed{ 0 };
146 size_t m_currImage{ 0 };
147 size_t m_sinceUpdate{ 0 };
148 bool m_updated{ false };
149
150 bool m_imageValid{ false };
151 std::string m_stateString;
152 bool m_stateStringValid{ false };
154 bool m_stateStringChanged{ false };
155 std::string m_fileSaveDir;
156
157 sem_t m_smSemaphore{ 0 }; ///< Semaphore used to synchronize the fg thread and the sm thread.
158
159 realT ( *pixget )( void *, size_t ){
160 nullptr }; ///< Pointer to a function to extract the image data as our desired type realT.
161
162 /// Mutex for locking dark operations.
163 std::mutex m_darkMutex;
164
165 mx::improc::eigenImage<realT> m_darkImage;
166 bool m_darkSet{ false };
167 bool m_darkValid{ false };
168 realT ( *dark_pixget )( void *, size_t ){
169 nullptr }; ///< Pointer to a function to extract the image data as our desired type realT.
170
171 mx::improc::eigenImage<realT> m_dark2Image;
172 bool m_dark2Set{ false };
173 bool m_dark2Valid{ false };
174 realT ( *dark2_pixget )( void *, size_t ){
175 nullptr }; ///< Pointer to a function to extract the image data as our desired type realT.
176
177 public:
178 /// Default c'tor.
180
181 /// D'tor, declared and defined for noexcept.
185
186 virtual void setupConfig();
187
188 /// Implementation of loadConfig logic, separated for testing.
189 /** This is called by loadConfig().
190 */
191 int loadConfigImpl(
192 mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
193
194 virtual void loadConfig();
195
196 /// Startup function
197 /**
198 *
199 */
200 virtual int appStartup();
201
202 /// Implementation of the FSM for shmimIntegrator.
203 /**
204 * \returns 0 on no critical error
205 * \returns -1 on an error requiring shutdown
206 */
207 virtual int appLogic();
208
209 /// Shutdown the app.
210 /**
211 *
212 */
213 virtual int appShutdown();
214
215 protected:
216 int allocate( const dev::shmimT &dummy /**< [in] tag to differentiate shmimMonitor parents.*/ );
217
218 int processImage( void *curr_src, ///< [in] pointer to start of current frame.
219 const dev::shmimT &dummy ///< [in] tag to differentiate shmimMonitor parents.
220 );
221
222 int allocate( const darkShmimT &dummy /**< [in] tag to differentiate shmimMonitor parents.*/ );
223
224 int processImage( void *curr_src, ///< [in] pointer to start of current frame.
225 const darkShmimT &dummy ///< [in] tag to differentiate shmimMonitor parents.
226 );
227
228 int allocate( const dark2ShmimT &dummy /**< [in] tag to differentiate shmimMonitor parents.*/ );
229
230 int processImage( void *curr_src, ///< [in] pointer to start of current frame.
231 const dark2ShmimT &dummy ///< [in] tag to differentiate shmimMonitor parents.
232 );
233
234 int findMatchingDark();
235
236 /** \name dev::frameGrabber interface
237 *
238 * @{
239 */
240
241 /// Implementation of the framegrabber configureAcquisition interface
242 /**
243 * \returns 0 on success
244 * \returns -1 on error
245 */
247
248 /// Implementation of the framegrabber fps interface
249 /**
250 * \todo this needs to infer the stream fps and return it
251 */
252 float fps()
253 {
254 if( m_fps > 0 && m_nAverage > 0 && ( m_running || m_continuous ) )
255 {
256 return m_fps / m_nAverage;
257 }
258 else
259 { // this will cause a default averaging
260
261 return 1.0;
262 }
263 }
264
265 /// Implementation of the framegrabber startAcquisition interface
266 /**
267 * \returns 0 on success
268 * \returns -1 on error
269 */
270 int startAcquisition();
271
272 /// Implementation of the framegrabber acquireAndCheckValid interface
273 /**
274 * \returns 0 on success
275 * \returns -1 on error
276 */
278
279 /// Implementation of the framegrabber loadImageIntoStream interface
280 /**
281 * \returns 0 on success
282 * \returns -1 on error
283 */
284 int loadImageIntoStream( void *dest /**< [in] */ );
285
286 /// Implementation of the framegrabber reconfig interface
287 /**
288 * \returns 0 on success
289 * \returns -1 on error
290 */
291 int reconfig();
292
293 ///@}
294
295 pcf::IndiProperty m_indiP_nAverage;
296
297 pcf::IndiProperty m_indiP_avgTime;
298
299 pcf::IndiProperty m_indiP_nUpdate;
300
301 pcf::IndiProperty m_indiP_startAveraging;
302
307
308 pcf::IndiProperty m_indiP_fpsSource;
310
311 pcf::IndiProperty m_indiP_fps; ///< this integrator's FPS
312
313 pcf::IndiProperty m_indiP_stateSource;
315
316 pcf::IndiProperty m_indiP_imageValid;
317
318 /** \name Telemeter Interface
319 *
320 * @{
321 */
322 int checkRecordTimes();
323
324 int recordTelem( const telem_fgtimings * );
325
326 ///@}
327};
328
329inline shmimIntegrator::shmimIntegrator() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
330{
332 return;
333}
334
336{
340
342 telemeterT::setupConfig( config );
343
344 config.add( "integrator.nAverage",
345 "",
346 "integrator.nAverage",
347 argType::Required,
348 "integrator",
349 "nAverage",
350 false,
351 "unsigned",
352 "The default number of frames to average. Default 10. Can be changed via INDI." );
353 config.add(
354 "integrator.fpsSource",
355 "",
356 "integrator.fpsSource",
357 argType::Required,
358 "integrator",
359 "fpsSource",
360 false,
361 "string",
362 "Device name for getting fps if time-based averaging is used. This device should have *.fps.current." );
363
364 config.add( "integrator.avgTime",
365 "",
366 "integrator.avgTime",
367 argType::Required,
368 "integrator",
369 "avgTime",
370 false,
371 "float",
372 "If non zero, then m_nAverage adjusts automatically to keep a constant averaging time [sec]. Default "
373 "0. Can be changed via INDI." );
374
375 config.add( "integrator.nUpdate",
376 "",
377 "integrator.nUpdate",
378 argType::Required,
379 "integrator",
380 "nUpdate",
381 false,
382 "unsigned",
383 "The rate at which to update the average. If 0 < m_nUpdate < m_nAverage then this is a moving "
384 "averager. Default 0. If 0, then it is a simple average." );
385
386 config.add( "integrator.continuous",
387 "",
388 "integrator.continuous",
389 argType::Required,
390 "integrator",
391 "continuous",
392 false,
393 "bool",
394 "Flag controlling whether averaging is continuous or only when triggered. Default true." );
395 config.add( "integrator.running",
396 "",
397 "integrator.running",
398 argType::Required,
399 "integrator",
400 "running",
401 false,
402 "bool",
403 "Flag controlling whether averaging is running at startup. Default true." );
404
405 config.add( "integrator.stateSource",
406 "",
407 "integrator.stateSource",
408 argType::Required,
409 "integrator",
410 "stateSource",
411 false,
412 "string",
413 "///< Device name for getting the state string for file management. This device should have "
414 "*.state_string.current." );
415 config.add( "integrator.fileSaver",
416 "",
417 "integrator.fileSaver",
418 argType::Required,
419 "integrator",
420 "fileSaver",
421 false,
422 "bool",
423 "Flag controlling whether this saves and reloads files automatically. Default false." );
424}
425
426inline int shmimIntegrator::loadConfigImpl( mx::app::appConfigurator &_config )
427{
428
430 darkMonitorT::loadConfig( config );
432
434 telemeterT::loadConfig( config );
435
436 _config( m_nAverageDefault, "integrator.nAverage" );
438 _config( m_fpsSource, "integrator.fpsSource" );
439 _config( m_avgTime, "integrator.avgTime" );
440 _config( m_nUpdate, "integrator.nUpdate" );
441
442 _config( m_continuous, "integrator.continuous" );
443
444 _config( m_running, "integrator.running" );
445
446 _config( m_stateSource, "integrator.stateSource" );
447 _config( m_fileSaver, "integrator.fileSaver" );
448
449 return 0;
450}
451
453{
454 loadConfigImpl( config );
455}
456
458{
459
461 m_indiP_nAverage, "nAverage", 1, std::numeric_limits<unsigned>::max(), 1, "%u" );
462 m_indiP_nAverage["current"].set( m_nAverage );
463 m_indiP_nAverage["target"].set( m_nAverage );
464
466 {
468 return -1;
469 }
470
471 createStandardIndiNumber<float>( m_indiP_avgTime, "avgTime", 0, std::numeric_limits<float>::max(), 0, "%0.1f" );
472 m_indiP_avgTime["current"].set( m_avgTime );
473 m_indiP_avgTime["target"].set( m_avgTime );
474
476 {
478 return -1;
479 }
480
481 createStandardIndiNumber<unsigned>( m_indiP_nUpdate, "nUpdate", 1, std::numeric_limits<unsigned>::max(), 1, "%u" );
482 m_indiP_nUpdate["current"].set( m_nUpdate );
483 m_indiP_nUpdate["target"].set( m_nUpdate );
484
486 {
488 return -1;
489 }
490
493 {
495 return -1;
496 }
497
498 if( m_fpsSource != "" )
499 {
500 REG_INDI_SETPROP( m_indiP_fpsSource, m_fpsSource, std::string( "fps" ) );
501 }
502
503 CREATE_REG_INDI_RO_NUMBER( m_indiP_fps, "fps", "", "" );
504 m_indiP_fps.add( pcf::IndiElement( "current" ) );
505 m_indiP_fps["current"].set( 0 );
506
507 if( m_fileSaver == true && m_stateSource != "" )
508 {
509 REG_INDI_SETPROP( m_indiP_stateSource, m_stateSource, std::string( "state_string" ) );
510
511 createROIndiText( m_indiP_imageValid, "image_valid", "flag", "Image Valid", "Image", "Valid" );
512 if( !m_imageValid ) // making sure we stay up with default
513 {
514 m_indiP_imageValid["flag"] = "no";
515 }
516 else
517 {
518 m_indiP_imageValid["flag"] = "yes";
519 }
520
522 {
524 return -1;
525 }
526
528
529 // Create save directory.
530 errno = 0;
531 if( mkdir( m_fileSaveDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) < 0 )
532 {
533 if( errno != EEXIST )
534 {
535 std::stringstream logss;
536 logss << "Failed to create image directory (" << m_fileSaveDir
537 << "). Errno says: " << strerror( errno );
539
540 return -1;
541 }
542 }
543 }
544
545 if( sem_init( &m_smSemaphore, 0, 0 ) < 0 )
546 {
547 log<software_critical>( { __FILE__, __LINE__, errno, 0, "Initializing S.M. semaphore" } );
548 return -1;
549 }
550
551 if( shmimMonitorT::appStartup() < 0 )
552 {
553 return log<software_error, -1>( { __FILE__, __LINE__ } );
554 }
555
556 if( darkMonitorT::appStartup() < 0 )
557 {
558 return log<software_error, -1>( { __FILE__, __LINE__ } );
559 }
560
561 if( dark2MonitorT::appStartup() < 0 )
562 {
563 return log<software_error, -1>( { __FILE__, __LINE__ } );
564 }
565
566 if( frameGrabberT::appStartup() < 0 )
567 {
568 return log<software_error, -1>( { __FILE__, __LINE__ } );
569 }
570
571 if( telemeterT::appStartup() < 0 )
572 {
573 return log<software_error, -1>( { __FILE__, __LINE__ } );
574 }
575
577
578 return 0;
579}
580
582{
583 if( shmimMonitorT::appLogic() < 0 )
584 {
585 return log<software_error, -1>( { __FILE__, __LINE__ } );
586 }
587
588 if( darkMonitorT::appLogic() < 0 )
589 {
590 return log<software_error, -1>( { __FILE__, __LINE__ } );
591 }
592
593 if( dark2MonitorT::appLogic() < 0 )
594 {
595 return log<software_error, -1>( { __FILE__, __LINE__ } );
596 }
597
598 if( frameGrabberT::appLogic() < 0 )
599 {
600 return log<software_error, -1>( { __FILE__, __LINE__ } );
601 }
602
603 if( telemeterT::appLogic() < 0 )
604 {
605 return log<software_error, -1>( { __FILE__, __LINE__ } );
606 }
607
608 std::unique_lock<std::mutex> lock( m_indiMutex );
609
610 if( shmimMonitorT::updateINDI() < 0 )
611 {
613 }
614
615 if( darkMonitorT::updateINDI() < 0 )
616 {
618 }
619
620 if( dark2MonitorT::updateINDI() < 0 )
621 {
623 }
624
625 if( frameGrabberT::updateINDI() < 0 )
626 {
628 }
629
630 if( m_running == false )
631 {
633 updateSwitchIfChanged( m_indiP_startAveraging, "toggle", pcf::IndiElement::Off, INDI_IDLE );
634
635 if( m_fileSaver )
636 {
637 if( m_stateStringChanged ) // So if not running and the state has changed, we check
638 {
639 if( findMatchingDark() < 0 )
640 {
642 }
643 }
644
645 if( !m_imageValid )
646 {
647 updateIfChanged( m_indiP_imageValid, "flag", "no" );
648 }
649 else
650 {
651 updateIfChanged( m_indiP_imageValid, "flag", "yes" );
652 }
653 }
654 }
655 else
656 {
658 updateSwitchIfChanged( m_indiP_startAveraging, "toggle", pcf::IndiElement::On, INDI_BUSY );
659 }
660
663
666
669
670 updateIfChanged( m_indiP_fps, "current", fps(), INDI_OK );
671
672 return 0;
673}
674
689
690inline int shmimIntegrator::allocate( const dev::shmimT &dummy )
691{
692 static_cast<void>( dummy ); // be unused
693
694 // This whole thing could invalidate the dark.
695 std::unique_lock<std::mutex> lock( m_darkMutex ); // Lock the mutex before messing with the dark.
696
697 if( m_avgTime > 0 && m_fps > 0 )
698 {
700 if( m_nAverage <= 0 )
701 {
702 m_nAverage = 1;
703 }
704 log<text_log>( "set nAverage to " + std::to_string( m_nAverage ) + " based on FPS", logPrio::LOG_NOTICE );
705 }
706 else if( m_avgTime > 0 && m_fps == 0 ) // Haven't gotten the update yet so we keep going for now
707 {
709 {
711 log<text_log>( "set nAverage to default " + std::to_string( m_nAverage ), logPrio::LOG_NOTICE );
712 }
713 }
714
715 if( m_nUpdate > 0 )
716 {
718 m_accumImages.setZero();
719 }
720 else
721 {
722 m_accumImages.resize( 1, 1, 1 );
723 }
724
725 m_nprocessed = 0;
726 m_currImage = 0;
727 m_sinceUpdate = 0;
728
730 // m_avgImage.setZero();
731
733
734 if( pixget == nullptr )
735 {
736 log<software_error>( { __FILE__, __LINE__, "bad data type" } );
737 return -1;
738 }
739
742
745
748
750 {
751 m_darkValid = true;
752 }
753 else
754 {
755 m_darkValid = false;
756 }
757
759 {
760 m_dark2Valid = true;
761 }
762 else
763 {
764 m_dark2Valid = false;
765 }
766
767 m_reconfig = true;
768
769 return 0;
770}
771
772inline int shmimIntegrator::processImage( void *curr_src, const dev::shmimT &dummy )
773{
774 static_cast<void>( dummy ); // be unused
775
776 if( !m_running )
777 return 0;
778
779 if( m_nUpdate == 0 )
780 {
781 if( m_updated )
782 return 0;
783 if( m_sinceUpdate == 0 )
784 m_avgImage.setZero();
785
786 realT *data = m_avgImage.data();
787
788 for( unsigned nn = 0; nn < shmimMonitorT::m_width * shmimMonitorT::m_height; ++nn )
789 {
790 data[nn] += pixget( curr_src, nn );
791 }
794 {
795 m_avgImage /= m_nAverage; ///\todo should this be /= m_sinceUpdate?
796
797 if( ( m_darkSet && m_darkValid ) && !( m_dark2Set && m_dark2Valid ) )
798 {
799 std::unique_lock<std::mutex> lock( m_darkMutex ); // Lock the mutex before messing with the dark.
801 }
802 else if( !( m_darkSet && m_darkValid ) && ( m_dark2Set && m_dark2Valid ) )
803 {
804 std::unique_lock<std::mutex> lock( m_darkMutex ); // Lock the mutex before messing with the dark.
806 }
807 else if( ( m_darkSet && m_darkValid ) && ( m_dark2Set && m_dark2Valid ) )
808 {
809 std::unique_lock<std::mutex> lock( m_darkMutex ); // Lock the mutex before messing with the dark.
811 }
812
813 m_updated = true;
814
815 // Now tell the f.g. to get going
816 if( sem_post( &m_smSemaphore ) < 0 )
817 {
818 log<software_critical>( { __FILE__, __LINE__, errno, 0, "Error posting to semaphore" } );
819 return -1;
820 }
821
822 m_sinceUpdate = 0;
823 if( !m_continuous )
824 {
825 m_running = false;
826 if( m_fileSaver )
827 {
829 {
830 m_imageValid = false;
831 log<text_log>( "state changed during acquisition, not saving", logPrio::LOG_NOTICE );
832 }
833 else
834 {
835 m_imageValid = true;
836 m_stateStringChanged = false;
837
838 ///\todo this should happen in a different less-real-time thread.
839 // Otherwise we save:
840 timespec fts;
842
843 tm uttime; // The broken down time.
844
845 if( gmtime_r( &fts.tv_sec, &uttime ) == 0 )
846 {
847 // Yell at operator but keep going
849 __LINE__,
850 errno,
851 0,
852 "gmtime_r error. possible loss of timing information." } );
853 }
854
855 char cts[] = "YYYYMMDDHHMMSSNNNNNNNNN";
856 int rv = snprintf( cts,
857 sizeof( cts ),
858 "%04i%02i%02i%02i%02i%02i%09i",
859 uttime.tm_year + 1900,
860 uttime.tm_mon + 1,
861 uttime.tm_mday,
862 uttime.tm_hour,
863 uttime.tm_min,
864 uttime.tm_sec,
865 static_cast<int>( fts.tv_nsec ) );
866
867 if( rv != sizeof( cts ) - 1 )
868 {
869 // Something is very wrong. Keep going to try to get it on disk.
871 { __FILE__, __LINE__, errno, rv, "did not write enough chars to timestamp" } );
872 }
873
874 std::string fname =
875 m_fileSaveDir + "/" + m_configName + "_" + m_stateString + "__T" + cts + ".fits";
876
877 mx::fits::fitsFile<float> ff;
878 ff.write( fname, m_avgImage );
879 log<text_log>( "Wrote " + fname );
880 }
881 }
882 }
883 }
884 }
885 else
886 {
887 realT *data = m_accumImages.image( m_currImage ).data();
888
889 for( unsigned nn = 0; nn < shmimMonitorT::m_width * shmimMonitorT::m_height; ++nn )
890 {
891 data[nn] = pixget( curr_src, nn );
892 }
893 ++m_nprocessed;
894 ++m_currImage;
895 if( m_currImage >= m_nAverage )
896 m_currImage = 0;
897
898 if( m_nprocessed < m_nAverage ) // Check that we are burned in on first pass through cube
899 {
900 return 0;
901 }
902
904
905 if( m_sinceUpdate >= m_nUpdate )
906 {
907 if( m_updated )
908 {
909 return 0; // In case f.g. thread is behind, we skip and come back.
910 }
911 // Don't use eigenCube functions to avoid any omp
912 m_avgImage.setZero();
913 for( size_t n = 0; n < m_nAverage; ++n )
914 {
915 for( size_t ii = 0; ii < shmimMonitorT::m_width; ++ii )
916 {
917 for( size_t jj = 0; jj < shmimMonitorT::m_height; ++jj )
918 {
919 m_avgImage( ii, jj ) += m_accumImages.image( n )( ii, jj );
920 }
921 }
922 }
924
925 if( m_darkValid && m_darkSet )
926 {
927 std::unique_lock<std::mutex> lock( m_darkMutex ); // Lock the mutex before messing with the dark.
929 }
930
931 m_updated = true;
932
933 // Now tell the f.g. to get going
934 if( sem_post( &m_smSemaphore ) < 0 )
935 {
936 log<software_critical>( { __FILE__, __LINE__, errno, 0, "Error posting to semaphore" } );
937 return -1;
938 }
939
940 m_sinceUpdate = 0;
941 }
942 }
943 return 0;
944}
945
946inline int shmimIntegrator::allocate( const darkShmimT &dummy )
947{
948 static_cast<void>( dummy ); // be unused
949
950 std::unique_lock<std::mutex> lock( m_darkMutex ); // Lock the mutex before messing with the dark.
951
953 m_darkImage.setZero();
954
956
957 if( dark_pixget == nullptr )
958 {
959 log<software_error>( { __FILE__, __LINE__, "bad data type" } );
960 m_darkSet = false;
961 m_darkValid = false;
962 return -1;
963 }
964
966 {
967 m_darkValid = true;
968 }
969 else
970 {
971 m_darkValid = false;
972 }
973
974 return 0;
975}
976
977inline int shmimIntegrator::processImage( void *curr_src, const darkShmimT &dummy )
978{
979 static_cast<void>( dummy ); // be unused
980
981 realT *data = m_darkImage.data();
982
983 for( unsigned nn = 0; nn < darkMonitorT::m_width * darkMonitorT::m_height; ++nn )
984 {
985 // data[nn] = *( (int16_t * ) (curr_src + nn*shmimMonitorT::m_typeSize));
986 data[nn] = dark_pixget( curr_src, nn );
987 }
988
989 m_darkSet = true; // There is a dark set and ready to use, but it may or may not be valid.
990
991 return 0;
992}
993
994inline int shmimIntegrator::allocate( const dark2ShmimT &dummy )
995{
996 static_cast<void>( dummy ); // be unused
997
998 std::unique_lock<std::mutex> lock( m_darkMutex ); // Lock the mutex before messing with the dark.
999
1001 m_dark2Image.setZero();
1002
1004
1005 if( dark2_pixget == nullptr )
1006 {
1007 log<software_error>( { __FILE__, __LINE__, "bad data type" } );
1008 m_dark2Set = false;
1009 m_dark2Valid = false;
1010 return -1;
1011 }
1012
1014 {
1015 m_dark2Valid = true;
1016 }
1017 else
1018 {
1019 m_dark2Valid = false;
1020 }
1021
1022 return 0;
1023}
1024
1025inline int shmimIntegrator::processImage( void *curr_src, const dark2ShmimT &dummy )
1026{
1027 static_cast<void>( dummy ); // be unused
1028
1029 realT *data = m_dark2Image.data();
1030
1031 for( unsigned nn = 0; nn < dark2MonitorT::m_width * dark2MonitorT::m_height; ++nn )
1032 {
1033 // data[nn] = *( (int16_t * ) (curr_src + nn*shmimMonitorT::m_typeSize));
1034 data[nn] = dark2_pixget( curr_src, nn );
1035 }
1036
1037 m_dark2Set = true; // There is a dark set and ready to use, but it may or may not be valid.
1038
1039 return 0;
1040}
1041
1043{
1044 std::vector<std::string> fnames = mx::ioutils::getFileNames( m_fileSaveDir, m_configName, "", ".fits" );
1045
1046 // getFileNames sorts, so these will be in oldest to newest order by lexical timestamp sort
1047 // So we search in reverse to always pick newest
1048 long N = fnames.size();
1049 for( long n = N - 1; n >= 0; --n )
1050 {
1051 std::string fn = mx::ioutils::pathStem( fnames[n] );
1052
1053 if( fn.size() < m_configName.size() + 1 )
1054 continue;
1055
1056 size_t st = m_configName.size() + 1;
1057 size_t ed = fn.find( "__T" );
1058 if( ed == std::string::npos || ed - st < 2 )
1059 continue;
1060 std::string stateStr = fn.substr( st, ed - st );
1061
1062 if( stateStr == m_stateString )
1063 {
1064 mx::fits::fitsFile<float> ff;
1065 ff.read( m_avgImage, fnames[n] );
1066
1068 {
1069 // Means the camera has changed but stream hasn't caught up
1070 //(This happens on startup before stream connection completes.)
1071
1072 // And possibly that we haven't turned the shmimMonitor on yet by switching to OPERATING
1074 {
1075 sleep( 1 ); // wait for everything else to get initialized
1078 m_reconfig = true;
1079 }
1080 else
1081 {
1082 if( m_running )
1083 return 0;
1084 m_imageValid = false;
1085 m_stateStringChanged = true; // So we let appLogic try again next time around.
1086 return 0;
1087 }
1088 }
1089
1090 if( m_running )
1091 return 0;
1092
1093 m_updated = true;
1094 // Now tell the f.g. to get going
1095 if( sem_post( &m_smSemaphore ) < 0 )
1096 {
1097 log<software_critical>( { __FILE__, __LINE__, errno, 0, "Error posting to semaphore" } );
1098 return -1;
1099 }
1100 m_imageValid = true;
1101 m_stateStringChanged = false;
1102
1103 log<text_log>( "loaded last matching dark from disk", logPrio::LOG_NOTICE );
1104
1105 return 0;
1106 }
1107 }
1108
1109 if( m_running )
1110 return 0;
1111
1112 m_imageValid = false;
1113 m_stateStringChanged = false; // stop trying b/c who else is going to add a dark?
1114
1115 log<text_log>( "dark is not valid", logPrio::LOG_WARNING );
1116
1117 return 0;
1118}
1119
1121{
1122 std::unique_lock<std::mutex> lock( m_indiMutex );
1123
1124 ///\todo potential but verrrrry unlikely bug: shmimMonitorT could change these before allocate sets the lock above.
1125 ///Should use a local set of w/h instead.
1127 {
1128 // This means we haven't connected to the stream to average. so wait.
1129 lock.unlock(); // don't hold the lock for a whole second.
1130 sleep( 1 );
1131 return -1;
1132 }
1133
1137
1138 return 0;
1139}
1140
1142{
1143 return 0;
1144}
1145
1147{
1148 timespec ts;
1149
1150 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
1151 {
1152 log<software_critical>( { __FILE__, __LINE__, errno, 0, "clock_gettime" } );
1153 return -1;
1154 }
1155
1156 ts.tv_sec += 1;
1157
1158 if( sem_timedwait( &m_smSemaphore, &ts ) == 0 )
1159 {
1160 if( m_updated )
1161 {
1163 return 0;
1164 }
1165 else
1166 {
1167 return 1;
1168 }
1169 }
1170 else
1171 {
1172 return 1;
1173 }
1174}
1175
1177{
1179 m_updated = false;
1180 return 0;
1181}
1182
1184{
1185 return 0;
1186}
1187
1188INDI_NEWCALLBACK_DEFN( shmimIntegrator, m_indiP_nAverage )( const pcf::IndiProperty &ipRecv )
1189{
1190 if( ipRecv.getName() != m_indiP_nAverage.getName() )
1191 {
1192 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1193 return -1;
1194 }
1195
1196 unsigned target;
1197
1198 if( indiTargetUpdate( m_indiP_nAverage, target, ipRecv, true ) < 0 )
1199 {
1200 log<software_error>( { __FILE__, __LINE__ } );
1201 return -1;
1202 }
1203
1204 m_nAverage = target;
1205
1206 if( m_avgTime > 0 && m_fps > 0 )
1207 {
1208 m_avgTime = m_nAverage / m_fps;
1209 }
1210
1211 updateIfChanged( m_indiP_nAverage, "current", m_nAverage, INDI_IDLE );
1212 updateIfChanged( m_indiP_nAverage, "target", m_nAverage, INDI_IDLE );
1213
1214 updateIfChanged( m_indiP_avgTime, "current", m_avgTime, INDI_IDLE );
1215 updateIfChanged( m_indiP_avgTime, "target", m_avgTime, INDI_IDLE );
1216
1217 shmimMonitorT::m_restart = true;
1218
1219 log<text_log>( "set nAverage to " + std::to_string( m_nAverage ), logPrio::LOG_NOTICE );
1220
1221 return 0;
1222}
1223
1224INDI_NEWCALLBACK_DEFN( shmimIntegrator, m_indiP_avgTime )( const pcf::IndiProperty &ipRecv )
1225{
1226 if( ipRecv.getName() != m_indiP_avgTime.getName() )
1227 {
1228 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1229 return -1;
1230 }
1231
1232 float target;
1233
1234 if( indiTargetUpdate( m_indiP_avgTime, target, ipRecv, true ) < 0 )
1235 {
1236 log<software_error>( { __FILE__, __LINE__ } );
1237 return -1;
1238 }
1239
1240 m_avgTime = target;
1241
1242 updateIfChanged( m_indiP_avgTime, "current", m_avgTime, INDI_IDLE );
1243 updateIfChanged( m_indiP_avgTime, "target", m_avgTime, INDI_IDLE );
1244
1245 shmimMonitorT::m_restart = true;
1246
1247 log<text_log>( "set avgTime to " + std::to_string( m_avgTime ), logPrio::LOG_NOTICE );
1248
1249 return 0;
1250}
1251
1252INDI_NEWCALLBACK_DEFN( shmimIntegrator, m_indiP_nUpdate )( const pcf::IndiProperty &ipRecv )
1253{
1254 if( ipRecv.getName() != m_indiP_nUpdate.getName() )
1255 {
1256 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1257 return -1;
1258 }
1259
1260 unsigned target;
1261
1262 if( indiTargetUpdate( m_indiP_nUpdate, target, ipRecv, true ) < 0 )
1263 {
1264 log<software_error>( { __FILE__, __LINE__ } );
1265 return -1;
1266 }
1267
1268 m_nUpdate = target;
1269
1270 updateIfChanged( m_indiP_nUpdate, "current", m_nUpdate, INDI_IDLE );
1271 updateIfChanged( m_indiP_nUpdate, "target", m_nUpdate, INDI_IDLE );
1272
1273 shmimMonitorT::m_restart = true;
1274
1275 log<text_log>( "set nUpdate to " + std::to_string( m_nUpdate ), logPrio::LOG_NOTICE );
1276
1277 return 0;
1278}
1279
1280INDI_NEWCALLBACK_DEFN( shmimIntegrator, m_indiP_startAveraging )( const pcf::IndiProperty &ipRecv )
1281{
1282 if( ipRecv.getName() != m_indiP_startAveraging.getName() )
1283 {
1284 log<software_error>( { __FILE__, __LINE__, "invalid indi property received" } );
1285 return -1;
1286 }
1287
1288 if( !ipRecv.find( "toggle" ) )
1289 return 0;
1290
1291 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1292 {
1293 std::unique_lock<std::mutex> lock( m_indiMutex );
1294
1295 m_running = false;
1296
1297 state( stateCodes::READY );
1298
1299 updateSwitchIfChanged( m_indiP_startAveraging, "toggle", pcf::IndiElement::Off, INDI_IDLE );
1300 }
1301
1302 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1303 {
1304 std::unique_lock<std::mutex> lock( m_indiMutex );
1305
1306 if( m_fileSaver && !m_continuous )
1307 m_stateStringChanged = false; // We reset this here so we can detect a change at the end of the integration
1308
1309 m_stateStringValidOnStart = m_stateStringValid;
1310 m_running = true;
1311
1312 state( stateCodes::OPERATING );
1313
1314 updateSwitchIfChanged( m_indiP_startAveraging, "toggle", pcf::IndiElement::On, INDI_BUSY );
1315 }
1316 return 0;
1317}
1318
1319INDI_SETCALLBACK_DEFN( shmimIntegrator, m_indiP_fpsSource )( const pcf::IndiProperty &ipRecv )
1320{
1321 if( ipRecv.getName() != m_indiP_fpsSource.getName() )
1322 {
1323 log<software_error>( { __FILE__, __LINE__, "Invalid INDI property." } );
1324 return -1;
1325 }
1326
1327 if( ipRecv.find( "current" ) != true ) // this isn't valie
1328 {
1329 return 0;
1330 }
1331
1332 std::lock_guard<std::mutex> guard( m_indiMutex );
1333
1334 realT fps = ipRecv["current"].get<float>();
1335
1336 if( fps != m_fps )
1337 {
1338 m_fps = fps;
1339 std::cout << "Got fps: " << m_fps << "\n";
1340 shmimMonitorT::m_restart = true;
1341 frameGrabberT::m_reconfig = true;
1342 }
1343
1344 return 0;
1345}
1346
1347INDI_SETCALLBACK_DEFN( shmimIntegrator, m_indiP_stateSource )( const pcf::IndiProperty &ipRecv )
1348{
1349 if( ipRecv.getName() != m_indiP_stateSource.getName() )
1350 {
1351 log<software_error>( { __FILE__, __LINE__, "Invalid INDI property." } );
1352 return -1;
1353 }
1354
1355 if( ipRecv.find( "valid" ) == true )
1356 {
1357 bool stateStringValid;
1358 if( ipRecv["valid"].get<std::string>() == "yes" )
1359 stateStringValid = true;
1360 else
1361 stateStringValid = false;
1362
1363 if( stateStringValid != m_stateStringValid )
1364 m_stateStringChanged = true;
1365
1366 m_stateStringValid = stateStringValid;
1367 }
1368
1369 if( ipRecv.find( "current" ) != true )
1370 {
1371 return 0;
1372 }
1373
1374 std::lock_guard<std::mutex> guard( m_indiMutex );
1375
1376 std::string ss = ipRecv["current"].get<std::string>();
1377
1378 if( ss != m_stateString )
1379 {
1380 m_stateString = ss;
1381 m_imageValid = false; // This will mark the current dark invalid
1382 updateIfChanged( m_indiP_imageValid, "flag", "no" );
1383 m_stateStringChanged = true; // We declare it changed. This can have two effects:
1384 // 1) if we are not currently integrating, it will start a lookup in appLogic
1385 // 2) if we are integrating, after it finishes it will not be declared valid and
1386 // then we'll lookup in appLogic
1387 }
1388
1389 return 0;
1390}
1391
1396
1398{
1399 return recordFGTimings( true );
1400}
1401
1402} // namespace app
1403} // namespace MagAOX
1404
1405#endif // shmimIntegrator_hpp
The base-class for MagAO-X applications.
Definition MagAOXApp.hpp:73
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
std::string m_configName
The name of the configuration file (minus .conf).
Definition MagAOXApp.hpp:83
stateCodes::stateCodeT state()
Get the current state code.
int registerIndiPropertyNew(pcf::IndiProperty &prop, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
int createStandardIndiToggleSw(pcf::IndiProperty &prop, const std::string &name, const std::string &label="", const std::string &group="")
Create a standard R/W INDI switch with a single toggle element.
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI switch element value if it has changed.
std::string m_calibDir
The path to calibration files for MagAOX.
Definition MagAOXApp.hpp:89
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
int createROIndiText(pcf::IndiProperty &prop, const std::string &propName, const std::string &elName, const std::string &propLabel="", const std::string &propGroup="", const std::string &elLabel="")
Create a standard ReadOnly INDI Text property, with at least one element.
std::mutex m_indiMutex
Mutex for locking INDI communications.
timespec m_currImageTimestamp
The timestamp of the current image.
uint32_t m_width
The width of the image, once deinterlaced etc.
int appShutdown()
Shuts down the framegrabber thread.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
size_t m_typeSize
The size of the type, in bytes. Result of sizeof.
int updateINDI()
Update the INDI properties for this device controller.
uint8_t m_dataType
The ImageStreamIO type code.
bool m_reconfig
Flag to set if a camera reconfiguration requires a framegrabber reset.
int appLogic()
Checks the framegrabber thread.
uint32_t m_height
The height of the image, once deinterlaced etc.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
uint32_t m_width
The width of the images in the stream.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
int updateINDI()
Update the INDI properties for this device controller.
int appLogic()
Checks the shmimMonitor thread.
uint32_t m_height
The height of the images in the stream.
int appShutdown()
Shuts down the shmimMonitor thread.
uint8_t m_dataType
The ImageStreamIO type code.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
int startAcquisition()
Implementation of the framegrabber startAcquisition interface.
dev::telemeter< shmimIntegrator > telemeterT
std::mutex m_darkMutex
Pointer to a function to extract the image data as our desired type realT.
dev::frameGrabber< shmimIntegrator > frameGrabberT
int allocate(const dev::shmimT &dummy)
realT(* pixget)(void *, size_t)
INDI_SETCALLBACK_DECL(shmimIntegrator, m_indiP_fpsSource)
float fps()
Implementation of the framegrabber fps interface.
INDI_NEWCALLBACK_DECL(shmimIntegrator, m_indiP_nAverage)
int recordTelem(const telem_fgtimings *)
mx::improc::eigenCube< realT > m_accumImages
Cube used to accumulate images.
INDI_NEWCALLBACK_DECL(shmimIntegrator, m_indiP_avgTime)
float realT
Floating point type in which to do all calculations.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
pcf::IndiProperty m_indiP_stateSource
pcf::IndiProperty m_indiP_startAveraging
int reconfig()
Implementation of the framegrabber reconfig interface.
shmimIntegrator()
Pointer to a function to extract the image data as our desired type realT.
pcf::IndiProperty m_indiP_fps
this integrator's FPS
dev::shmimMonitor< shmimIntegrator, dark2ShmimT > dark2MonitorT
realT(* dark_pixget)(void *, size_t)
float m_fps
Current FPS from the FPS source.
bool m_continuous
Set to false in configuration to have this run once then stop until triggered.
int acquireAndCheckValid()
Implementation of the framegrabber acquireAndCheckValid interface.
INDI_SETCALLBACK_DECL(shmimIntegrator, m_indiP_stateSource)
bool m_running
Set to false in configuration to have it not start averaging until triggered.
mx::improc::eigenImage< realT > m_dark2Image
Pointer to a function to extract the image data as our desired type realT.
int loadImageIntoStream(void *dest)
Implementation of the framegrabber loadImageIntoStream interface.
realT(* dark2_pixget)(void *, size_t)
int processImage(void *curr_src, const dev::shmimT &dummy)
virtual int appStartup()
Startup function.
sem_t m_smSemaphore
Semaphore used to synchronize the fg thread and the sm thread.
unsigned m_nAverageDefault
The number of frames to average. Default 10.
dev::shmimMonitor< shmimIntegrator > shmimMonitorT
std::string m_stateSource
The source of the state string used for file management.
virtual int appShutdown()
Shutdown the app.
virtual int appLogic()
Implementation of the FSM for shmimIntegrator.
INDI_NEWCALLBACK_DECL(shmimIntegrator, m_indiP_nUpdate)
INDI_NEWCALLBACK_DECL(shmimIntegrator, m_indiP_startAveraging)
~shmimIntegrator() noexcept
D'tor, declared and defined for noexcept.
int configureAcquisition()
Implementation of the framegrabber configureAcquisition interface.
mx::improc::eigenImage< realT > m_darkImage
dev::shmimMonitor< shmimIntegrator, darkShmimT > darkMonitorT
bool m_fileSaver
Set to true in configuration to have this save and reload files automatically.
static constexpr bool c_frameGrabber_flippable
app:dev config to tell framegrabber these images can not be flipped
mx::improc::eigenImage< realT > m_avgImage
The average image.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
#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.
@ OPERATING
The device is operating, other than homing.
@ READY
The device is ready for operation, but is not operating.
#define INDI_IDLE
Definition indiUtils.hpp:27
#define INDI_BUSY
Definition indiUtils.hpp:29
#define INDI_OK
Definition indiUtils.hpp:28
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:26
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.
static std::string indiPrefix()
static std::string configSection()
static std::string configSection()
static std::string indiPrefix()
A device base class which saves telemetry.
Definition telemeter.hpp:69
int appShutdown()
Perform telemeter application shutdown.
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
int appLogic()
Perform telemeter application logic.
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
int appStartup()
Starts the telemetry log thread.
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Software ERR log entry.
Log entry recording framegrabber timings.