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>, public dev::shmimMonitor<shmimIntegrator>, public dev::shmimMonitor<shmimIntegrator,darkShmimT>,
68 public dev::shmimMonitor<shmimIntegrator,dark2ShmimT>, public dev::frameGrabber<shmimIntegrator>, public dev::telemeter<shmimIntegrator>
69{
70
71 //Give the test harness access.
73
78 friend class dev::telemeter<shmimIntegrator>;
79
80 //The base shmimMonitor type
82
83 //The dark shmimMonitor type
85
86 //The dark shmimMonitor type for a 2nd dark
88
89 //The base frameGrabber type
91
93
94 ///Floating point type in which to do all calculations.
95 typedef float realT;
96
97public:
98 /** \name app::dev Configurations
99 *@{
100 */
101
102 static constexpr bool c_frameGrabber_flippable = false; ///< app:dev config to tell framegrabber these images can not be flipped
103
104 ///@}
105
106protected:
107
108 /** \name Configurable Parameters
109 *@{
110 */
111
112 unsigned m_nAverageDefault {10}; ///< The number of frames to average. Default 10.
113
114 std::string m_fpsSource; ///< Device name for getting fps if time-based averaging is used. This device should have *.fps.current.
115
116 float m_avgTime {0}; ///< If non zero, then m_nAverage adjusts automatically to keep a constant averaging time [sec]. Default 0.
117
118 unsigned m_nUpdate {0}; ///< The rate at which to update the average. If 0 < m_nUpdate < m_nAverage then this is a moving averager. Default 0.
119
120 bool m_continuous {true}; ///< Set to false in configuration to have this run once then stop until triggered.
121
122 bool m_running {true}; ///< Set to false in configuration to have it not start averaging until triggered.
123
124 std::string m_stateSource; ///< The source of the state string used for file management
125
126 bool m_fileSaver {false}; ///< Set to true in configuration to have this save and reload files automatically.
127
128 ///@}
129
130 mx::improc::eigenCube<realT> m_accumImages; ///< Cube used to accumulate images
131
132 mx::improc::eigenImage<realT> m_avgImage; ///< The average image.
133
134 unsigned m_nAverage {10};
135
136 float m_fps {0}; ///< Current FPS from the FPS source.
137
138 unsigned m_nprocessed {0};
139 size_t m_currImage {0};
140 size_t m_sinceUpdate {0};
141 bool m_updated {false};
142
143 bool m_imageValid {false};
144 std::string m_stateString;
145 bool m_stateStringValid {false};
148 std::string m_fileSaveDir;
149
150
151 sem_t m_smSemaphore {0}; ///< Semaphore used to synchronize the fg thread and the sm thread.
152
153 realT (*pixget)(void *, size_t) {nullptr}; ///< Pointer to a function to extract the image data as our desired type realT.
154
155 ///Mutex for locking dark operations.
156 std::mutex m_darkMutex;
157
158 mx::improc::eigenImage<realT> m_darkImage;
159 bool m_darkSet {false};
160 bool m_darkValid {false};
161 realT (*dark_pixget)(void *, size_t) {nullptr}; ///< Pointer to a function to extract the image data as our desired type realT.
162
163 mx::improc::eigenImage<realT> m_dark2Image;
164 bool m_dark2Set {false};
165 bool m_dark2Valid {false};
166 realT (*dark2_pixget)(void *, size_t) {nullptr}; ///< Pointer to a function to extract the image data as our desired type realT.
167
168
169public:
170 /// Default c'tor.
172
173 /// D'tor, declared and defined for noexcept.
176
177 virtual void setupConfig();
178
179 /// Implementation of loadConfig logic, separated for testing.
180 /** This is called by loadConfig().
181 */
182 int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
183
184 virtual void loadConfig();
185
186 /// Startup function
187 /**
188 *
189 */
190 virtual int appStartup();
191
192 /// Implementation of the FSM for shmimIntegrator.
193 /**
194 * \returns 0 on no critical error
195 * \returns -1 on an error requiring shutdown
196 */
197 virtual int appLogic();
198
199 /// Shutdown the app.
200 /**
201 *
202 */
203 virtual int appShutdown();
204
205protected:
206
207 int allocate( const dev::shmimT & dummy /**< [in] tag to differentiate shmimMonitor parents.*/);
208
209 int processImage( void * curr_src, ///< [in] pointer to start of current frame.
210 const dev::shmimT & dummy ///< [in] tag to differentiate shmimMonitor parents.
211 );
212
213 int allocate( const darkShmimT & dummy /**< [in] tag to differentiate shmimMonitor parents.*/);
214
215 int processImage( void * curr_src, ///< [in] pointer to start of current frame.
216 const darkShmimT & dummy ///< [in] tag to differentiate shmimMonitor parents.
217 );
218
219 int allocate( const dark2ShmimT & dummy /**< [in] tag to differentiate shmimMonitor parents.*/);
220
221 int processImage( void * curr_src, ///< [in] pointer to start of current frame.
222 const dark2ShmimT & dummy ///< [in] tag to differentiate shmimMonitor parents.
223 );
224
225 int findMatchingDark();
226
227
228 /** \name dev::frameGrabber interface
229 *
230 * @{
231 */
232
233 /// Implementation of the framegrabber configureAcquisition interface
234 /**
235 * \returns 0 on success
236 * \returns -1 on error
237 */
239
240 /// Implementation of the framegrabber fps interface
241 /**
242 * \todo this needs to infer the stream fps and return it
243 */
244 float fps()
245 {
246 if(m_fps > 0 && m_nAverage > 0 && (m_running || m_continuous))
247 {
248 return m_fps/m_nAverage;
249 }
250 else
251 { //this will cause a default averaging
252
253 return 1.0;
254 }
255 }
256
257 /// Implementation of the framegrabber startAcquisition interface
258 /**
259 * \returns 0 on success
260 * \returns -1 on error
261 */
262 int startAcquisition();
263
264 /// Implementation of the framegrabber acquireAndCheckValid interface
265 /**
266 * \returns 0 on success
267 * \returns -1 on error
268 */
270
271 /// Implementation of the framegrabber loadImageIntoStream interface
272 /**
273 * \returns 0 on success
274 * \returns -1 on error
275 */
276 int loadImageIntoStream( void * dest /**< [in] */);
277
278 /// Implementation of the framegrabber reconfig interface
279 /**
280 * \returns 0 on success
281 * \returns -1 on error
282 */
283 int reconfig();
284
285 ///@}
286
287 pcf::IndiProperty m_indiP_nAverage;
288
289 pcf::IndiProperty m_indiP_avgTime;
290
291 pcf::IndiProperty m_indiP_nUpdate;
292
293 pcf::IndiProperty m_indiP_startAveraging;
294
299
300 pcf::IndiProperty m_indiP_fpsSource;
302
303 pcf::IndiProperty m_indiP_fps; ///< this integrator's FPS
304
305 pcf::IndiProperty m_indiP_stateSource;
307
308 pcf::IndiProperty m_indiP_imageValid;
309
310 /** \name Telemeter Interface
311 *
312 * @{
313 */
314 int checkRecordTimes();
315
316 int recordTelem( const telem_fgtimings * );
317
318 ///@}
319
320};
321
322inline
323shmimIntegrator::shmimIntegrator() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
324{
326 return;
327}
328
329inline
331{
335
338
339 config.add("integrator.nAverage", "", "integrator.nAverage", argType::Required, "integrator", "nAverage", false, "unsigned", "The default number of frames to average. Default 10. Can be changed via INDI.");
340 config.add("integrator.fpsSource", "", "integrator.fpsSource", argType::Required, "integrator", "fpsSource", false, "string", "Device name for getting fps if time-based averaging is used. This device should have *.fps.current.");
341
342 config.add("integrator.avgTime", "", "integrator.avgTime", argType::Required, "integrator", "avgTime", false, "float", "If non zero, then m_nAverage adjusts automatically to keep a constant averaging time [sec]. Default 0. Can be changed via INDI.");
343
344 config.add("integrator.nUpdate", "", "integrator.nUpdate", argType::Required, "integrator", "nUpdate", false, "unsigned", "The rate at which to update the average. If 0 < m_nUpdate < m_nAverage then this is a moving averager. Default 0. If 0, then it is a simple average.");
345
346 config.add("integrator.continuous", "", "integrator.continuous", argType::Required, "integrator", "continuous", false, "bool", "Flag controlling whether averaging is continuous or only when triggered. Default true.");
347 config.add("integrator.running", "", "integrator.running", argType::Required, "integrator", "running", false, "bool", "Flag controlling whether averaging is running at startup. Default true.");
348
349 config.add("integrator.stateSource", "", "integrator.stateSource", argType::Required, "integrator", "stateSource", false, "string", "///< Device name for getting the state string for file management. This device should have *.state_string.current.");
350 config.add("integrator.fileSaver", "", "integrator.fileSaver", argType::Required, "integrator", "fileSaver", false, "bool", "Flag controlling whether this saves and reloads files automatically. Default false.");
351
352
353}
354
355inline
356int shmimIntegrator::loadConfigImpl( mx::app::appConfigurator & _config )
357{
358
362
365
366 _config(m_nAverageDefault, "integrator.nAverage");
368 _config(m_fpsSource, "integrator.fpsSource");
369 _config(m_avgTime, "integrator.avgTime");
370 _config(m_nUpdate, "integrator.nUpdate");
371
372 _config(m_continuous, "integrator.continuous");
373
374 _config(m_running, "integrator.running");
375
376 _config(m_stateSource, "integrator.stateSource");
377 _config(m_fileSaver, "integrator.fileSaver");
378
379 return 0;
380}
381
382inline
384{
385 loadConfigImpl(config);
386}
387
388inline
390{
391
392 createStandardIndiNumber<unsigned>( m_indiP_nAverage, "nAverage", 1, std::numeric_limits<unsigned>::max(), 1, "%u");
393 m_indiP_nAverage["current"].set(m_nAverage);
394 m_indiP_nAverage["target"].set(m_nAverage);
395
397 {
399 return -1;
400 }
401
402 createStandardIndiNumber<float>( m_indiP_avgTime, "avgTime", 0, std::numeric_limits<float>::max(),0 , "%0.1f");
403 m_indiP_avgTime["current"].set(m_avgTime);
404 m_indiP_avgTime["target"].set(m_avgTime);
405
407 {
409 return -1;
410 }
411
412 createStandardIndiNumber<unsigned>( m_indiP_nUpdate, "nUpdate", 1, std::numeric_limits<unsigned>::max(), 1, "%u");
413 m_indiP_nUpdate["current"].set(m_nUpdate);
414 m_indiP_nUpdate["target"].set(m_nUpdate);
415
417 {
419 return -1;
420 }
421
424 {
426 return -1;
427 }
428
429 if(m_fpsSource != "")
430 {
431 REG_INDI_SETPROP(m_indiP_fpsSource, m_fpsSource, std::string("fps"));
432 }
433
435 m_indiP_fps.add(pcf::IndiElement("current"));
436 m_indiP_fps["current"].set(0);
437
438 if(m_fileSaver == true && m_stateSource != "")
439 {
440 REG_INDI_SETPROP(m_indiP_stateSource, m_stateSource, std::string("state_string"));
441
442 createROIndiText( m_indiP_imageValid, "image_valid", "flag", "Image Valid", "Image", "Valid");
443 if(!m_imageValid) //making sure we stay up with default
444 {
445 m_indiP_imageValid["flag"] = "no";
446 }
447 else
448 {
449 m_indiP_imageValid["flag"] = "yes";
450 }
451
453 {
455 return -1;
456 }
457
458
460
461 //Create save directory.
462 errno = 0;
463 if( mkdir(m_fileSaveDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 )
464 {
465 if( errno != EEXIST)
466 {
467 std::stringstream logss;
468 logss << "Failed to create image directory (" << m_fileSaveDir << "). Errno says: " << strerror(errno);
470
471 return -1;
472 }
473 }
474 }
475
476
477 if(sem_init(&m_smSemaphore, 0,0) < 0)
478 {
479 log<software_critical>({__FILE__, __LINE__, errno,0, "Initializing S.M. semaphore"});
480 return -1;
481 }
482
484 {
485 return log<software_error,-1>({__FILE__, __LINE__});
486 }
487
489 {
490 return log<software_error,-1>({__FILE__, __LINE__});
491 }
492
494 {
495 return log<software_error,-1>({__FILE__, __LINE__});
496 }
497
499 {
500 return log<software_error,-1>({__FILE__, __LINE__});
501 }
502
503 if(telemeterT::appStartup() < 0)
504 {
505 return log<software_error,-1>({__FILE__, __LINE__});
506 }
507
509
510 return 0;
511}
512
513inline
515{
516 if( shmimMonitorT::appLogic() < 0)
517 {
518 return log<software_error,-1>({__FILE__,__LINE__});
519 }
520
521 if( darkMonitorT::appLogic() < 0)
522 {
523 return log<software_error,-1>({__FILE__,__LINE__});
524 }
525
526 if( dark2MonitorT::appLogic() < 0)
527 {
528 return log<software_error,-1>({__FILE__,__LINE__});
529 }
530
531 if( frameGrabberT::appLogic() < 0)
532 {
533 return log<software_error,-1>({__FILE__,__LINE__});
534 }
535
536 if( telemeterT::appLogic() < 0)
537 {
538 return log<software_error,-1>({__FILE__,__LINE__});
539 }
540
541 std::unique_lock<std::mutex> lock(m_indiMutex);
542
544 {
546 }
547
549 {
551 }
552
554 {
556 }
557
559 {
561 }
562
563 if(m_running == false)
564 {
566 updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::Off, INDI_IDLE);
567
568 if(m_fileSaver)
569 {
570 if(m_stateStringChanged) //So if not running and the state has changed, we check
571 {
572 if(findMatchingDark() < 0)
573 {
575 }
576 }
577
578 if(!m_imageValid)
579 {
580 updateIfChanged(m_indiP_imageValid, "flag", "no");
581 }
582 else
583 {
584 updateIfChanged(m_indiP_imageValid, "flag", "yes");
585 }
586 }
587 }
588 else
589 {
591 updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::On, INDI_BUSY);
592 }
593
596
599
602
603 updateIfChanged(m_indiP_fps, "current", fps(), INDI_OK);
604
605 return 0;
606}
607
608inline
623
624inline
626{
627 static_cast<void>(dummy); //be unused
628
629 //This whole thing could invalidate the dark.
630 std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
631
632
633 if(m_avgTime > 0 && m_fps > 0)
634 {
636 if(m_nAverage <= 0)
637 {
638 m_nAverage = 1;
639 }
640 log<text_log>("set nAverage to " + std::to_string(m_nAverage) + " based on FPS", logPrio::LOG_NOTICE);
641 }
642 else if(m_avgTime > 0 && m_fps == 0) //Haven't gotten the update yet so we keep going for now
643 {
645 {
647 log<text_log>("set nAverage to default " + std::to_string(m_nAverage), logPrio::LOG_NOTICE);
648 }
649 }
650
651 if(m_nUpdate > 0)
652 {
654 m_accumImages.setZero();
655 }
656 else
657 {
658 m_accumImages.resize(1,1,1);
659 }
660
661 m_nprocessed = 0;
662 m_currImage = 0;
663 m_sinceUpdate = 0;
664
666 //m_avgImage.setZero();
667
669
670 if(pixget == nullptr)
671 {
672 log<software_error>({__FILE__, __LINE__, "bad data type"});
673 return -1;
674 }
675
678
681
684
685
687 {
688 m_darkValid = true;
689 }
690 else
691 {
692 m_darkValid = false;
693 }
694
696 {
697 m_dark2Valid = true;
698 }
699 else
700 {
701 m_dark2Valid = false;
702 }
703
704
705 m_reconfig = true;
706
707 return 0;
708}
709
710inline
711int shmimIntegrator::processImage( void * curr_src,
712 const dev::shmimT & dummy
713 )
714{
715 static_cast<void>(dummy); //be unused
716
717 if(!m_running) return 0;
718
719 if(m_nUpdate == 0)
720 {
721 if(m_updated) return 0;
722 if(m_sinceUpdate == 0) m_avgImage.setZero();
723
724 realT * data = m_avgImage.data();
725
727 {
728 data[nn] += pixget(curr_src, nn);
729 }
732 {
733 m_avgImage /= m_nAverage; ///\todo should this be /= m_sinceUpdate?
734
736 {
737 std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
739 }
740 else if(!(m_darkSet && m_darkValid) && (m_dark2Set && m_dark2Valid))
741 {
742 std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
744 }
745 else if((m_darkSet && m_darkValid) && (m_dark2Set && m_dark2Valid))
746 {
747 std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
749 }
750
751 m_updated = true;
752
753 //Now tell the f.g. to get going
754 if(sem_post(&m_smSemaphore) < 0)
755 {
756 log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
757 return -1;
758 }
759
760 m_sinceUpdate = 0;
761 if(!m_continuous)
762 {
763 m_running = false;
764 if(m_fileSaver)
765 {
767 {
768 m_imageValid = false;
769 log<text_log>("state changed during acquisition, not saving", logPrio::LOG_NOTICE);
770 }
771 else
772 {
773 m_imageValid = true;
775
776 ///\todo this should happen in a different less-real-time thread.
777 //Otherwise we save:
778 timespec fts;
780
781 tm uttime;//The broken down time.
782
783 if(gmtime_r(&fts.tv_sec, &uttime) == 0)
784 {
785 //Yell at operator but keep going
786 log<software_alert>({__FILE__,__LINE__,errno,0,"gmtime_r error. possible loss of timing information."});
787 }
788
789 char cts[] = "YYYYMMDDHHMMSSNNNNNNNNN";
790 int rv = snprintf(cts, sizeof(cts), "%04i%02i%02i%02i%02i%02i%09i", uttime.tm_year+1900,
791 uttime.tm_mon+1, uttime.tm_mday, uttime.tm_hour, uttime.tm_min, uttime.tm_sec, static_cast<int>(fts.tv_nsec));
792
793 if(rv != sizeof(cts)-1)
794 {
795 //Something is very wrong. Keep going to try to get it on disk.
796 log<software_alert>({__FILE__,__LINE__, errno, rv, "did not write enough chars to timestamp"});
797 }
798
799 std::string fname = m_fileSaveDir + "/" + m_configName + "_" + m_stateString + "__T" + cts + ".fits";
800
801 mx::fits::fitsFile<float> ff;
802 ff.write(fname, m_avgImage);
803 log<text_log>("Wrote " + fname);
804
805 }
806 }
807 }
808 }
809 }
810 else
811 {
812 realT * data = m_accumImages.image(m_currImage).data();
813
815 {
816 data[nn] = pixget(curr_src, nn);
817 }
818 ++m_nprocessed;
819 ++m_currImage;
821
822 if(m_nprocessed < m_nAverage) //Check that we are burned in on first pass through cube
823 {
824 return 0;
825 }
826
828
830 {
831 if(m_updated)
832 {
833 return 0; //In case f.g. thread is behind, we skip and come back.
834 }
835 //Don't use eigenCube functions to avoid any omp
836 m_avgImage.setZero();
837 for(size_t n =0; n < m_nAverage; ++n)
838 {
839 for(size_t ii=0; ii< shmimMonitorT::m_width; ++ii)
840 {
841 for(size_t jj=0; jj< shmimMonitorT::m_height; ++jj)
842 {
843 m_avgImage(ii,jj) += m_accumImages.image(n)(ii,jj);
844 }
845 }
846 }
848
850 {
851 std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
853 }
854
855 m_updated = true;
856
857 //Now tell the f.g. to get going
858 if(sem_post(&m_smSemaphore) < 0)
859 {
860 log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
861 return -1;
862 }
863
864 m_sinceUpdate = 0;
865 }
866 }
867 return 0;
868}
869
870inline
872{
873 static_cast<void>(dummy); //be unused
874
875 std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
876
878 m_darkImage.setZero();
879
881
882 if(dark_pixget == nullptr)
883 {
884 log<software_error>({__FILE__, __LINE__, "bad data type"});
885 m_darkSet = false;
886 m_darkValid = false;
887 return -1;
888 }
889
890
892 {
893 m_darkValid = true;
894 }
895 else
896 {
897 m_darkValid = false;
898 }
899
900 return 0;
901}
902
903inline
904int shmimIntegrator::processImage( void * curr_src,
905 const darkShmimT & dummy
906 )
907{
908 static_cast<void>(dummy); //be unused
909
910 realT * data = m_darkImage.data();
911
913 {
914 //data[nn] = *( (int16_t * ) (curr_src + nn*shmimMonitorT::m_typeSize));
915 data[nn] = dark_pixget(curr_src, nn);
916 }
917
918 m_darkSet = true; //There is a dark set and ready to use, but it may or may not be valid.
919
920 return 0;
921}
922
923inline
925{
926 static_cast<void>(dummy); //be unused
927
928 std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
929
931 m_dark2Image.setZero();
932
934
935 if(dark2_pixget == nullptr)
936 {
937 log<software_error>({__FILE__, __LINE__, "bad data type"});
938 m_dark2Set = false;
939 m_dark2Valid = false;
940 return -1;
941 }
942
944 {
945 m_dark2Valid = true;
946 }
947 else
948 {
949 m_dark2Valid = false;
950 }
951
952
953 return 0;
954}
955
956inline
957int shmimIntegrator::processImage( void * curr_src,
958 const dark2ShmimT & dummy
959 )
960{
961 static_cast<void>(dummy); //be unused
962
963 realT * data = m_dark2Image.data();
964
966 {
967 //data[nn] = *( (int16_t * ) (curr_src + nn*shmimMonitorT::m_typeSize));
968 data[nn] = dark2_pixget(curr_src, nn);
969 }
970
971 m_dark2Set = true; //There is a dark set and ready to use, but it may or may not be valid.
972
973 return 0;
974}
975
976inline
978{
979 std::vector<std::string> fnames = mx::ioutils::getFileNames(m_fileSaveDir, m_configName, "", ".fits");
980
981 //getFileNames sorts, so these will be in oldest to newest order by lexical timestamp sort
982 //So we search in reverse to always pick newest
983 long N = fnames.size();
984 for(long n = N-1; n >= 0; --n)
985 {
986 std::string fn = mx::ioutils::pathStem(fnames[n]);
987
988 if(fn.size() < m_configName.size()+1) continue;
989
990 size_t st = m_configName.size()+1;
991 size_t ed = fn.find("__T");
992 if(ed == std::string::npos || ed - st < 2) continue;
993 std::string stateStr = fn.substr(st,ed-st);
994
996 {
997 mx::fits::fitsFile<float> ff;
998 ff.read(m_avgImage, fnames[n]);
999
1001 {
1002 //Means the camera has changed but stream hasn't caught up
1003 //(This happens on startup before stream connection completes.)
1004
1005 // And possibly that we haven't turned the shmimMonitor on yet by switching to OPERATING
1007 {
1008 sleep(1); //wait for everything else to get initialized
1011 m_reconfig = true;
1012 }
1013 else
1014 {
1015 if(m_running) return 0;
1016 m_imageValid = false;
1017 m_stateStringChanged = true; //So we let appLogic try again next time around.
1018 return 0;
1019 }
1020 }
1021
1022 if(m_running) return 0;
1023
1024 m_updated = true;
1025 //Now tell the f.g. to get going
1026 if(sem_post(&m_smSemaphore) < 0)
1027 {
1028 log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1029 return -1;
1030 }
1031 m_imageValid = true;
1032 m_stateStringChanged = false;
1033 log<text_log>("loaded last matching dark from disk", logPrio::LOG_NOTICE);
1034 return 0;
1035 }
1036 }
1037
1038 if(m_running) return 0;
1039
1040 m_imageValid = false;
1041 m_stateStringChanged = false; //stop trying b/c who else is going to add a dark?
1042 log<text_log>("dark is not valid", logPrio::LOG_WARNING);
1043
1044 return 0;
1045}
1046
1047inline
1049{
1050 std::unique_lock<std::mutex> lock(m_indiMutex);
1051
1052 ///\todo potential but verrrrry unlikely bug: shmimMonitorT could change these before allocate sets the lock above. Should use a local set of w/h instead.
1054 {
1055 //This means we haven't connected to the stream to average. so wait.
1056 lock.unlock(); //don't hold the lock for a whole second.
1057 sleep(1);
1058 return -1;
1059 }
1060
1064
1065 return 0;
1066}
1067
1068inline
1070{
1071 return 0;
1072}
1073
1074inline
1076{
1077 timespec ts;
1078
1080 {
1081 log<software_critical>({__FILE__,__LINE__,errno,0,"clock_gettime"});
1082 return -1;
1083 }
1084
1085 ts.tv_sec += 1;
1086
1087 if(sem_timedwait(&m_smSemaphore, &ts) == 0)
1088 {
1089 if( m_updated )
1090 {
1092 return 0;
1093 }
1094 else
1095 {
1096 return 1;
1097 }
1098 }
1099 else
1100 {
1101 return 1;
1102 }
1103}
1104
1105inline
1112
1113inline
1115{
1116 return 0;
1117}
1118
1119INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_nAverage)(const pcf::IndiProperty &ipRecv)
1120{
1121 if(ipRecv.getName() != m_indiP_nAverage.getName())
1122 {
1123 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1124 return -1;
1125 }
1126
1127 unsigned target;
1128
1129 if( indiTargetUpdate( m_indiP_nAverage, target, ipRecv, true) < 0)
1130 {
1131 log<software_error>({__FILE__,__LINE__});
1132 return -1;
1133 }
1134
1135 m_nAverage = target;
1136
1137 if(m_avgTime > 0 && m_fps > 0)
1138 {
1139 m_avgTime = m_nAverage/m_fps;
1140 }
1141
1142 updateIfChanged(m_indiP_nAverage, "current", m_nAverage, INDI_IDLE);
1143 updateIfChanged(m_indiP_nAverage, "target", m_nAverage, INDI_IDLE);
1144
1145 updateIfChanged(m_indiP_avgTime, "current", m_avgTime, INDI_IDLE);
1146 updateIfChanged(m_indiP_avgTime, "target", m_avgTime, INDI_IDLE);
1147
1148 shmimMonitorT::m_restart = true;
1149
1150 log<text_log>("set nAverage to " + std::to_string(m_nAverage), logPrio::LOG_NOTICE);
1151
1152 return 0;
1153}
1154
1155INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_avgTime)(const pcf::IndiProperty &ipRecv)
1156{
1157 if(ipRecv.getName() != m_indiP_avgTime.getName())
1158 {
1159 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1160 return -1;
1161 }
1162
1163 float target;
1164
1165 if( indiTargetUpdate( m_indiP_avgTime, target, ipRecv, true) < 0)
1166 {
1167 log<software_error>({__FILE__,__LINE__});
1168 return -1;
1169 }
1170
1171 m_avgTime = target;
1172
1173 updateIfChanged(m_indiP_avgTime, "current", m_avgTime, INDI_IDLE);
1174 updateIfChanged(m_indiP_avgTime, "target", m_avgTime, INDI_IDLE);
1175
1176 shmimMonitorT::m_restart = true;
1177
1178 log<text_log>("set avgTime to " + std::to_string(m_avgTime), logPrio::LOG_NOTICE);
1179
1180 return 0;
1181}
1182
1183INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_nUpdate)(const pcf::IndiProperty &ipRecv)
1184{
1185 if(ipRecv.getName() != m_indiP_nUpdate.getName())
1186 {
1187 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1188 return -1;
1189 }
1190
1191 unsigned target;
1192
1193 if( indiTargetUpdate( m_indiP_nUpdate, target, ipRecv, true) < 0)
1194 {
1195 log<software_error>({__FILE__,__LINE__});
1196 return -1;
1197 }
1198
1199 m_nUpdate = target;
1200
1201 updateIfChanged(m_indiP_nUpdate, "current", m_nUpdate, INDI_IDLE);
1202 updateIfChanged(m_indiP_nUpdate, "target", m_nUpdate, INDI_IDLE);
1203
1204
1205 shmimMonitorT::m_restart = true;
1206
1207 log<text_log>("set nUpdate to " + std::to_string(m_nUpdate), logPrio::LOG_NOTICE);
1208
1209 return 0;
1210}
1211
1212INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_startAveraging)(const pcf::IndiProperty &ipRecv)
1213{
1214 if(ipRecv.getName() != m_indiP_startAveraging.getName())
1215 {
1216 log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1217 return -1;
1218 }
1219
1220 if(!ipRecv.find("toggle")) return 0;
1221
1222 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
1223 {
1224 std::unique_lock<std::mutex> lock(m_indiMutex);
1225
1226 m_running = false;
1227
1228 state(stateCodes::READY);
1229
1230 updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::Off, INDI_IDLE);
1231 }
1232
1233 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
1234 {
1235 std::unique_lock<std::mutex> lock(m_indiMutex);
1236
1237 if(m_fileSaver && !m_continuous) m_stateStringChanged = false; //We reset this here so we can detect a change at the end of the integration
1238
1239 m_stateStringValidOnStart = m_stateStringValid;
1240 m_running = true;
1241
1242 state(stateCodes::OPERATING);
1243
1244 updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::On, INDI_BUSY);
1245 }
1246 return 0;
1247}
1248
1249INDI_SETCALLBACK_DEFN( shmimIntegrator, m_indiP_fpsSource )(const pcf::IndiProperty &ipRecv)
1250{
1251 if( ipRecv.getName() != m_indiP_fpsSource.getName())
1252 {
1253 log<software_error>({__FILE__, __LINE__, "Invalid INDI property."});
1254 return -1;
1255 }
1256
1257 if( ipRecv.find("current") != true ) //this isn't valie
1258 {
1259 return 0;
1260 }
1261
1262 std::lock_guard<std::mutex> guard(m_indiMutex);
1263
1264 realT fps = ipRecv["current"].get<float>();
1265
1266 if(fps != m_fps)
1267 {
1268 m_fps = fps;
1269 std::cout << "Got fps: " << m_fps << "\n";
1270 shmimMonitorT::m_restart = true;
1271 }
1272
1273 return 0;
1274}
1275
1276INDI_SETCALLBACK_DEFN( shmimIntegrator, m_indiP_stateSource )(const pcf::IndiProperty &ipRecv)
1277{
1278 if( ipRecv.getName() != m_indiP_stateSource.getName())
1279 {
1280 log<software_error>({__FILE__, __LINE__, "Invalid INDI property."});
1281 return -1;
1282 }
1283
1284 if( ipRecv.find("valid") == true )
1285 {
1286 bool stateStringValid;
1287 if(ipRecv["valid"].get<std::string>() == "yes") stateStringValid = true;
1288 else stateStringValid = false;
1289
1290 if(stateStringValid != m_stateStringValid) m_stateStringChanged = true;
1291
1292 m_stateStringValid = stateStringValid;
1293 }
1294
1295 if( ipRecv.find("current") != true )
1296 {
1297 return 0;
1298 }
1299
1300
1301
1302 std::lock_guard<std::mutex> guard(m_indiMutex);
1303
1304 std::string ss = ipRecv["current"].get<std::string>();
1305
1306 if(ss != m_stateString)
1307 {
1308 m_stateString = ss;
1309 m_imageValid = false; //This will mark the current dark invalid
1310 updateIfChanged(m_indiP_imageValid, "flag", "no");
1311 m_stateStringChanged = true; //We declare it changed. This can have two effects:
1312 // 1) if we are not currently integrating, it will start a lookup in appLogic
1313 // 2) if we are integrating, after it finishes it will not be declared valid and then we'll lookup in appLogic
1314 }
1315
1316 return 0;
1317}
1318
1319inline
1324
1325inline
1327{
1328 return recordFGTimings(true);
1329}
1330
1331} //namespace app
1332} //namespace MagAOX
1333
1334#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
bool m_getExistingFirst
If set to true by derivedT, any existing image will be grabbed and sent to processImage before waitin...
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.
unsigned m_nUpdate
The rate at which to update the average. If 0 < m_nUpdate < m_nAverage then this is a moving averager...
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)
float m_avgTime
If non zero, then m_nAverage adjusts automatically to keep a constant averaging time [sec]....
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
std::string m_fpsSource
Device name for getting fps if time-based averaging is used. This device should have *....
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:28
#define INDI_BUSY
Definition indiUtils.hpp:30
#define INDI_OK
Definition indiUtils.hpp:29
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:24
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.