API
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 
18 namespace MagAOX
19 {
20 namespace app
21 {
22 
23 struct 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 
36 struct dark2ShmimT
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  */
67 class 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.
72  friend class shmimIntegrator_test;
73 
74  friend class dev::shmimMonitor<shmimIntegrator>;
77  friend class dev::frameGrabber<shmimIntegrator>;
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 
97 public:
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 
106 protected:
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};
147  bool m_stateStringChanged {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 
169 public:
170  /// Default c'tor.
171  shmimIntegrator();
172 
173  /// D'tor, declared and defined for noexcept.
174  ~shmimIntegrator() noexcept
175  {}
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 
205 protected:
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  */
238  int configureAcquisition();
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  return 1.0;
247  }
248 
249  /// Implementation of the framegrabber startAcquisition interface
250  /**
251  * \returns 0 on success
252  * \returns -1 on error
253  */
254  int startAcquisition();
255 
256  /// Implementation of the framegrabber acquireAndCheckValid interface
257  /**
258  * \returns 0 on success
259  * \returns -1 on error
260  */
261  int acquireAndCheckValid();
262 
263  /// Implementation of the framegrabber loadImageIntoStream interface
264  /**
265  * \returns 0 on success
266  * \returns -1 on error
267  */
268  int loadImageIntoStream( void * dest /**< [in] */);
269 
270  /// Implementation of the framegrabber reconfig interface
271  /**
272  * \returns 0 on success
273  * \returns -1 on error
274  */
275  int reconfig();
276 
277  ///@}
278 
279  pcf::IndiProperty m_indiP_nAverage;
280 
281  pcf::IndiProperty m_indiP_avgTime;
282 
283  pcf::IndiProperty m_indiP_nUpdate;
284 
285  pcf::IndiProperty m_indiP_startAveraging;
286 
291 
292  pcf::IndiProperty m_indiP_fpsSource;
294 
295  pcf::IndiProperty m_indiP_stateSource;
297 
298  pcf::IndiProperty m_indiP_imageValid;
299 
300  /** \name Telemeter Interface
301  *
302  * @{
303  */
304  int checkRecordTimes();
305 
306  int recordTelem( const telem_fgtimings * );
307 
308  ///@}
309 
310 };
311 
312 inline
313 shmimIntegrator::shmimIntegrator() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
314 {
316  return;
317 }
318 
319 inline
321 {
325 
327  telemeterT::setupConfig(config);
328 
329  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.");
330  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.");
331 
332  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.");
333 
334  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.");
335 
336  config.add("integrator.continuous", "", "integrator.continuous", argType::Required, "integrator", "continuous", false, "bool", "Flag controlling whether averaging is continuous or only when triggered. Default true.");
337  config.add("integrator.running", "", "integrator.running", argType::Required, "integrator", "running", false, "bool", "Flag controlling whether averaging is running at startup. Default true.");
338 
339  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.");
340  config.add("integrator.fileSaver", "", "integrator.fileSaver", argType::Required, "integrator", "fileSaver", false, "bool", "Flag controlling whether this saves and reloads files automatically. Default false.");
341 
342 
343 }
344 
345 inline
346 int shmimIntegrator::loadConfigImpl( mx::app::appConfigurator & _config )
347 {
348 
350  darkMonitorT::loadConfig(config);
352 
354  telemeterT::loadConfig(config);
355 
356  _config(m_nAverageDefault, "integrator.nAverage");
358  _config(m_fpsSource, "integrator.fpsSource");
359  _config(m_avgTime, "integrator.avgTime");
360  _config(m_nUpdate, "integrator.nUpdate");
361 
362  _config(m_continuous, "integrator.continuous");
363 
364  _config(m_running, "integrator.running");
365 
366  _config(m_stateSource, "integrator.stateSource");
367  _config(m_fileSaver, "integrator.fileSaver");
368 
369  return 0;
370 }
371 
372 inline
374 {
375  loadConfigImpl(config);
376 }
377 
378 inline
380 {
381 
382  createStandardIndiNumber<unsigned>( m_indiP_nAverage, "nAverage", 1, std::numeric_limits<unsigned>::max(), 1, "%u");
383  m_indiP_nAverage["current"].set(m_nAverage);
384  m_indiP_nAverage["target"].set(m_nAverage);
385 
387  {
388  log<software_error>({__FILE__,__LINE__});
389  return -1;
390  }
391 
392  createStandardIndiNumber<float>( m_indiP_avgTime, "avgTime", 0, std::numeric_limits<float>::max(),0 , "%0.1f");
393  m_indiP_avgTime["current"].set(m_avgTime);
394  m_indiP_avgTime["target"].set(m_avgTime);
395 
397  {
398  log<software_error>({__FILE__,__LINE__});
399  return -1;
400  }
401 
402  createStandardIndiNumber<unsigned>( m_indiP_nUpdate, "nUpdate", 1, std::numeric_limits<unsigned>::max(), 1, "%u");
403  m_indiP_nUpdate["current"].set(m_nUpdate);
404  m_indiP_nUpdate["target"].set(m_nUpdate);
405 
407  {
408  log<software_error>({__FILE__,__LINE__});
409  return -1;
410  }
411 
414  {
415  log<software_error>({__FILE__,__LINE__});
416  return -1;
417  }
418 
419  if(m_fpsSource != "")
420  {
421  REG_INDI_SETPROP(m_indiP_fpsSource, m_fpsSource, std::string("fps"));
422  }
423 
424  if(m_fileSaver == true && m_stateSource != "")
425  {
426  REG_INDI_SETPROP(m_indiP_stateSource, m_stateSource, std::string("state_string"));
427 
428  createROIndiText( m_indiP_imageValid, "image_valid", "flag", "Image Valid", "Image", "Valid");
429  if(!m_imageValid) //making sure we stay up with default
430  {
431  m_indiP_imageValid["flag"] = "no";
432  }
433  else
434  {
435  m_indiP_imageValid["flag"] = "yes";
436  }
437 
439  {
440  log<software_error>({__FILE__,__LINE__});
441  return -1;
442  }
443 
444 
446 
447  //Create save directory.
448  errno = 0;
449  if( mkdir(m_fileSaveDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 )
450  {
451  if( errno != EEXIST)
452  {
453  std::stringstream logss;
454  logss << "Failed to create image directory (" << m_fileSaveDir << "). Errno says: " << strerror(errno);
455  log<software_critical>({__FILE__, __LINE__, errno, 0, logss.str()});
456 
457  return -1;
458  }
459  }
460  }
461 
462 
463  if(sem_init(&m_smSemaphore, 0,0) < 0)
464  {
465  log<software_critical>({__FILE__, __LINE__, errno,0, "Initializing S.M. semaphore"});
466  return -1;
467  }
468 
469  if(shmimMonitorT::appStartup() < 0)
470  {
471  return log<software_error,-1>({__FILE__, __LINE__});
472  }
473 
474  if(darkMonitorT::appStartup() < 0)
475  {
476  return log<software_error,-1>({__FILE__, __LINE__});
477  }
478 
479  if(dark2MonitorT::appStartup() < 0)
480  {
481  return log<software_error,-1>({__FILE__, __LINE__});
482  }
483 
484  if(frameGrabberT::appStartup() < 0)
485  {
486  return log<software_error,-1>({__FILE__, __LINE__});
487  }
488 
489  if(telemeterT::appStartup() < 0)
490  {
491  return log<software_error,-1>({__FILE__, __LINE__});
492  }
493 
495 
496  return 0;
497 }
498 
499 inline
501 {
502  if( shmimMonitorT::appLogic() < 0)
503  {
504  return log<software_error,-1>({__FILE__,__LINE__});
505  }
506 
507  if( darkMonitorT::appLogic() < 0)
508  {
509  return log<software_error,-1>({__FILE__,__LINE__});
510  }
511 
512  if( dark2MonitorT::appLogic() < 0)
513  {
514  return log<software_error,-1>({__FILE__,__LINE__});
515  }
516 
517  if( frameGrabberT::appLogic() < 0)
518  {
519  return log<software_error,-1>({__FILE__,__LINE__});
520  }
521 
522  if( telemeterT::appLogic() < 0)
523  {
524  return log<software_error,-1>({__FILE__,__LINE__});
525  }
526 
527  std::unique_lock<std::mutex> lock(m_indiMutex);
528 
529  if(shmimMonitorT::updateINDI() < 0)
530  {
531  log<software_error>({__FILE__, __LINE__});
532  }
533 
534  if(darkMonitorT::updateINDI() < 0)
535  {
536  log<software_error>({__FILE__, __LINE__});
537  }
538 
539  if(dark2MonitorT::updateINDI() < 0)
540  {
541  log<software_error>({__FILE__, __LINE__});
542  }
543 
544  if(frameGrabberT::updateINDI() < 0)
545  {
546  log<software_error>({__FILE__, __LINE__});
547  }
548 
549  if(m_running == false)
550  {
552  updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::Off, INDI_IDLE);
553 
554  if(m_fileSaver)
555  {
556  if(m_stateStringChanged) //So if not running and the state has changed, we check
557  {
558  if(findMatchingDark() < 0)
559  {
560  log<software_error>({__FILE__, __LINE__});
561  }
562  }
563 
564  if(!m_imageValid)
565  {
566  updateIfChanged(m_indiP_imageValid, "flag", "no");
567  }
568  else
569  {
570  updateIfChanged(m_indiP_imageValid, "flag", "yes");
571  }
572  }
573  }
574  else
575  {
577  updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::On, INDI_BUSY);
578  }
579 
582 
585 
588 
589  return 0;
590 }
591 
592 inline
594 {
596 
598 
600 
602 
604 
605  return 0;
606 }
607 
608 inline
610 {
611  static_cast<void>(dummy); //be unused
612 
613  //This whole thing could invalidate the dark.
614  std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
615 
616 
617  if(m_avgTime > 0 && m_fps > 0)
618  {
620  if(m_nAverage <= 0)
621  {
622  m_nAverage = 1;
623  }
624  log<text_log>("set nAverage to " + std::to_string(m_nAverage) + " based on FPS", logPrio::LOG_NOTICE);
625  }
626  else if(m_avgTime > 0 && m_fps == 0) //Haven't gotten the update yet so we keep going for now
627  {
629  {
631  log<text_log>("set nAverage to default " + std::to_string(m_nAverage), logPrio::LOG_NOTICE);
632  }
633  }
634 
635  if(m_nUpdate > 0)
636  {
638  m_accumImages.setZero();
639  }
640  else
641  {
642  m_accumImages.resize(1,1,1);
643  }
644 
645  m_nprocessed = 0;
646  m_currImage = 0;
647  m_sinceUpdate = 0;
648 
650  //m_avgImage.setZero();
651 
652  pixget = getPixPointer<realT>(shmimMonitorT::m_dataType);
653 
654  if(pixget == nullptr)
655  {
656  log<software_error>({__FILE__, __LINE__, "bad data type"});
657  return -1;
658  }
659 
662 
665 
668 
669 
671  {
672  m_darkValid = true;
673  }
674  else
675  {
676  m_darkValid = false;
677  }
678 
680  {
681  m_dark2Valid = true;
682  }
683  else
684  {
685  m_dark2Valid = false;
686  }
687 
688 
689  m_reconfig = true;
690 
691  return 0;
692 }
693 
694 inline
695 int shmimIntegrator::processImage( void * curr_src,
696  const dev::shmimT & dummy
697  )
698 {
699  static_cast<void>(dummy); //be unused
700 
701  if(!m_running) return 0;
702 
703  if(m_nUpdate == 0)
704  {
705  if(m_updated) return 0;
706  if(m_sinceUpdate == 0) m_avgImage.setZero();
707 
708  realT * data = m_avgImage.data();
709 
710  for(unsigned nn=0; nn < shmimMonitorT::m_width*shmimMonitorT::m_height; ++nn)
711  {
712  data[nn] += pixget(curr_src, nn);
713  }
714  ++m_sinceUpdate;
716  {
717  m_avgImage /= m_nAverage; ///\todo should this be /= m_sinceUpdate?
718 
719  if((m_darkSet && m_darkValid) && !(m_dark2Set && m_dark2Valid))
720  {
721  std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
723  }
724  else if(!(m_darkSet && m_darkValid) && (m_dark2Set && m_dark2Valid))
725  {
726  std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
728  }
729  else if((m_darkSet && m_darkValid) && (m_dark2Set && m_dark2Valid))
730  {
731  std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
733  }
734 
735  m_updated = true;
736 
737  //Now tell the f.g. to get going
738  if(sem_post(&m_smSemaphore) < 0)
739  {
740  log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
741  return -1;
742  }
743 
744  m_sinceUpdate = 0;
745  if(!m_continuous)
746  {
747  m_running = false;
748  if(m_fileSaver)
749  {
751  {
752  m_imageValid = false;
753  log<text_log>("state changed during acquisition, not saving", logPrio::LOG_NOTICE);
754  }
755  else
756  {
757  m_imageValid = true;
758  m_stateStringChanged=false;
759 
760  ///\todo this should happen in a different less-real-time thread.
761  //Otherwise we save:
762  timespec fts;
763  clock_gettime(CLOCK_REALTIME, &fts);
764 
765  tm uttime;//The broken down time.
766 
767  if(gmtime_r(&fts.tv_sec, &uttime) == 0)
768  {
769  //Yell at operator but keep going
770  log<software_alert>({__FILE__,__LINE__,errno,0,"gmtime_r error. possible loss of timing information."});
771  }
772 
773  char cts[] = "YYYYMMDDHHMMSSNNNNNNNNN";
774  int rv = snprintf(cts, sizeof(cts), "%04i%02i%02i%02i%02i%02i%09i", uttime.tm_year+1900,
775  uttime.tm_mon+1, uttime.tm_mday, uttime.tm_hour, uttime.tm_min, uttime.tm_sec, static_cast<int>(fts.tv_nsec));
776 
777  if(rv != sizeof(cts)-1)
778  {
779  //Something is very wrong. Keep going to try to get it on disk.
780  log<software_alert>({__FILE__,__LINE__, errno, rv, "did not write enough chars to timestamp"});
781  }
782 
783  std::string fname = m_fileSaveDir + "/" + m_configName + "_" + m_stateString + "__T" + cts + ".fits";
784 
785  mx::fits::fitsFile<float> ff;
786  ff.write(fname, m_avgImage);
787  log<text_log>("Wrote " + fname);
788 
789  }
790  }
791  }
792  }
793  }
794  else
795  {
796  realT * data = m_accumImages.image(m_currImage).data();
797 
798  for(unsigned nn=0; nn < shmimMonitorT::m_width*shmimMonitorT::m_height; ++nn)
799  {
800  data[nn] = pixget(curr_src, nn);
801  }
802  ++m_nprocessed;
803  ++m_currImage;
805 
806  if(m_nprocessed < m_nAverage) //Check that we are burned in on first pass through cube
807  {
808  return 0;
809  }
810 
811  ++m_sinceUpdate;
812 
813  if(m_sinceUpdate >= m_nUpdate)
814  {
815  if(m_updated)
816  {
817  return 0; //In case f.g. thread is behind, we skip and come back.
818  }
819  //Don't use eigenCube functions to avoid any omp
820  m_avgImage.setZero();
821  for(size_t n =0; n < m_nAverage; ++n)
822  {
823  for(size_t ii=0; ii< shmimMonitorT::m_width; ++ii)
824  {
825  for(size_t jj=0; jj< shmimMonitorT::m_height; ++jj)
826  {
827  m_avgImage(ii,jj) += m_accumImages.image(n)(ii,jj);
828  }
829  }
830  }
832 
833  if(m_darkValid && m_darkSet)
834  {
835  std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
837  }
838 
839  m_updated = true;
840 
841  //Now tell the f.g. to get going
842  if(sem_post(&m_smSemaphore) < 0)
843  {
844  log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
845  return -1;
846  }
847 
848  m_sinceUpdate = 0;
849  }
850  }
851  return 0;
852 }
853 
854 inline
856 {
857  static_cast<void>(dummy); //be unused
858 
859  std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
860 
862  m_darkImage.setZero();
863 
864  dark_pixget = getPixPointer<realT>(darkMonitorT::m_dataType);
865 
866  if(dark_pixget == nullptr)
867  {
868  log<software_error>({__FILE__, __LINE__, "bad data type"});
869  m_darkSet = false;
870  m_darkValid = false;
871  return -1;
872  }
873 
874 
876  {
877  m_darkValid = true;
878  }
879  else
880  {
881  m_darkValid = false;
882  }
883 
884  return 0;
885 }
886 
887 inline
888 int shmimIntegrator::processImage( void * curr_src,
889  const darkShmimT & dummy
890  )
891 {
892  static_cast<void>(dummy); //be unused
893 
894  realT * data = m_darkImage.data();
895 
896  for(unsigned nn=0; nn < darkMonitorT::m_width*darkMonitorT::m_height; ++nn)
897  {
898  //data[nn] = *( (int16_t * ) (curr_src + nn*shmimMonitorT::m_typeSize));
899  data[nn] = dark_pixget(curr_src, nn);
900  }
901 
902  m_darkSet = true; //There is a dark set and ready to use, but it may or may not be valid.
903 
904  return 0;
905 }
906 
907 inline
909 {
910  static_cast<void>(dummy); //be unused
911 
912  std::unique_lock<std::mutex> lock(m_darkMutex); //Lock the mutex before messing with the dark.
913 
915  m_dark2Image.setZero();
916 
917  dark2_pixget = getPixPointer<realT>(dark2MonitorT::m_dataType);
918 
919  if(dark2_pixget == nullptr)
920  {
921  log<software_error>({__FILE__, __LINE__, "bad data type"});
922  m_dark2Set = false;
923  m_dark2Valid = false;
924  return -1;
925  }
926 
928  {
929  m_dark2Valid = true;
930  }
931  else
932  {
933  m_dark2Valid = false;
934  }
935 
936 
937  return 0;
938 }
939 
940 inline
941 int shmimIntegrator::processImage( void * curr_src,
942  const dark2ShmimT & dummy
943  )
944 {
945  static_cast<void>(dummy); //be unused
946 
947  realT * data = m_dark2Image.data();
948 
949  for(unsigned nn=0; nn < dark2MonitorT::m_width*dark2MonitorT::m_height; ++nn)
950  {
951  //data[nn] = *( (int16_t * ) (curr_src + nn*shmimMonitorT::m_typeSize));
952  data[nn] = dark2_pixget(curr_src, nn);
953  }
954 
955  m_dark2Set = true; //There is a dark set and ready to use, but it may or may not be valid.
956 
957  return 0;
958 }
959 
960 inline
962 {
963  std::vector<std::string> fnames = mx::ioutils::getFileNames(m_fileSaveDir, m_configName, "", ".fits");
964 
965  //getFileNames sorts, so these will be in oldest to newest order by lexical timestamp sort
966  //So we search in reverse to always pick newest
967  long N = fnames.size();
968  for(long n = N-1; n >= 0; --n)
969  {
970  std::string fn = mx::ioutils::pathStem(fnames[n]);
971 
972  if(fn.size() < m_configName.size()+1) continue;
973 
974  size_t st = m_configName.size()+1;
975  size_t ed = fn.find("__T");
976  if(ed == std::string::npos || ed - st < 2) continue;
977  std::string stateStr = fn.substr(st,ed-st);
978 
979  if(stateStr == m_stateString)
980  {
981  mx::fits::fitsFile<float> ff;
982  ff.read(m_avgImage, fnames[n]);
983 
985  {
986  //Means the camera has changed but stream hasn't caught up
987  //(This happens on startup before stream connection completes.)
988 
989  // And possibly that we haven't turned the shmimMonitor on yet by switching to OPERATING
991  {
992  sleep(1); //wait for everything else to get initialized
995  m_reconfig = true;
996  }
997  else
998  {
999  if(m_running) return 0;
1000  m_imageValid = false;
1001  m_stateStringChanged = true; //So we let appLogic try again next time around.
1002  return 0;
1003  }
1004  }
1005 
1006  if(m_running) return 0;
1007 
1008  m_updated = true;
1009  //Now tell the f.g. to get going
1010  if(sem_post(&m_smSemaphore) < 0)
1011  {
1012  log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1013  return -1;
1014  }
1015  m_imageValid = true;
1016  m_stateStringChanged = false;
1017  log<text_log>("loaded last matching dark from disk", logPrio::LOG_NOTICE);
1018  return 0;
1019  }
1020  }
1021 
1022  if(m_running) return 0;
1023 
1024  m_imageValid = false;
1025  m_stateStringChanged = false; //stop trying b/c who else is going to add a dark?
1026  log<text_log>("dark is not valid", logPrio::LOG_WARNING);
1027 
1028  return 0;
1029 }
1030 
1031 inline
1033 {
1034  std::unique_lock<std::mutex> lock(m_indiMutex);
1035 
1036  ///\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.
1038  {
1039  //This means we haven't connected to the stream to average. so wait.
1040  lock.unlock(); //don't hold the lock for a whole second.
1041  sleep(1);
1042  return -1;
1043  }
1044 
1047  frameGrabberT::m_dataType = _DATATYPE_FLOAT;
1048 
1049  return 0;
1050 }
1051 
1052 inline
1054 {
1055  return 0;
1056 }
1057 
1058 inline
1060 {
1061  timespec ts;
1062 
1063  if(clock_gettime(CLOCK_REALTIME, &ts) < 0)
1064  {
1065  log<software_critical>({__FILE__,__LINE__,errno,0,"clock_gettime"});
1066  return -1;
1067  }
1068 
1069  ts.tv_sec += 1;
1070 
1071  if(sem_timedwait(&m_smSemaphore, &ts) == 0)
1072  {
1073  if( m_updated )
1074  {
1075  clock_gettime(CLOCK_REALTIME, &m_currImageTimestamp);
1076  return 0;
1077  }
1078  else
1079  {
1080  return 1;
1081  }
1082  }
1083  else
1084  {
1085  return 1;
1086  }
1087 }
1088 
1089 inline
1091 {
1093  m_updated = false;
1094  return 0;
1095 }
1096 
1097 inline
1099 {
1100  return 0;
1101 }
1102 
1103 INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_nAverage)(const pcf::IndiProperty &ipRecv)
1104 {
1105  if(ipRecv.getName() != m_indiP_nAverage.getName())
1106  {
1107  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1108  return -1;
1109  }
1110 
1111  unsigned target;
1112 
1113  if( indiTargetUpdate( m_indiP_nAverage, target, ipRecv, true) < 0)
1114  {
1115  log<software_error>({__FILE__,__LINE__});
1116  return -1;
1117  }
1118 
1119  m_nAverage = target;
1120 
1121  if(m_avgTime > 0 && m_fps > 0)
1122  {
1123  m_avgTime = m_nAverage/m_fps;
1124  }
1125 
1126  updateIfChanged(m_indiP_nAverage, "current", m_nAverage, INDI_IDLE);
1127  updateIfChanged(m_indiP_nAverage, "target", m_nAverage, INDI_IDLE);
1128 
1129  updateIfChanged(m_indiP_avgTime, "current", m_avgTime, INDI_IDLE);
1130  updateIfChanged(m_indiP_avgTime, "target", m_avgTime, INDI_IDLE);
1131 
1132  shmimMonitorT::m_restart = true;
1133 
1134  log<text_log>("set nAverage to " + std::to_string(m_nAverage), logPrio::LOG_NOTICE);
1135 
1136  return 0;
1137 }
1138 
1139 INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_avgTime)(const pcf::IndiProperty &ipRecv)
1140 {
1141  if(ipRecv.getName() != m_indiP_avgTime.getName())
1142  {
1143  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1144  return -1;
1145  }
1146 
1147  float target;
1148 
1149  if( indiTargetUpdate( m_indiP_avgTime, target, ipRecv, true) < 0)
1150  {
1151  log<software_error>({__FILE__,__LINE__});
1152  return -1;
1153  }
1154 
1155  m_avgTime = target;
1156 
1157  updateIfChanged(m_indiP_avgTime, "current", m_avgTime, INDI_IDLE);
1158  updateIfChanged(m_indiP_avgTime, "target", m_avgTime, INDI_IDLE);
1159 
1160  shmimMonitorT::m_restart = true;
1161 
1162  log<text_log>("set avgTime to " + std::to_string(m_avgTime), logPrio::LOG_NOTICE);
1163 
1164  return 0;
1165 }
1166 
1167 INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_nUpdate)(const pcf::IndiProperty &ipRecv)
1168 {
1169  if(ipRecv.getName() != m_indiP_nUpdate.getName())
1170  {
1171  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1172  return -1;
1173  }
1174 
1175  unsigned target;
1176 
1177  if( indiTargetUpdate( m_indiP_nUpdate, target, ipRecv, true) < 0)
1178  {
1179  log<software_error>({__FILE__,__LINE__});
1180  return -1;
1181  }
1182 
1183  m_nUpdate = target;
1184 
1185  updateIfChanged(m_indiP_nUpdate, "current", m_nUpdate, INDI_IDLE);
1186  updateIfChanged(m_indiP_nUpdate, "target", m_nUpdate, INDI_IDLE);
1187 
1188 
1189  shmimMonitorT::m_restart = true;
1190 
1191  log<text_log>("set nUpdate to " + std::to_string(m_nUpdate), logPrio::LOG_NOTICE);
1192 
1193  return 0;
1194 }
1195 
1196 INDI_NEWCALLBACK_DEFN(shmimIntegrator, m_indiP_startAveraging)(const pcf::IndiProperty &ipRecv)
1197 {
1198  if(ipRecv.getName() != m_indiP_startAveraging.getName())
1199  {
1200  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
1201  return -1;
1202  }
1203 
1204  if(!ipRecv.find("toggle")) return 0;
1205 
1206  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
1207  {
1208  std::unique_lock<std::mutex> lock(m_indiMutex);
1209 
1210  m_running = false;
1211 
1212  state(stateCodes::READY);
1213 
1214  updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::Off, INDI_IDLE);
1215  }
1216 
1217  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
1218  {
1219  std::unique_lock<std::mutex> lock(m_indiMutex);
1220 
1221  if(m_fileSaver && !m_continuous) m_stateStringChanged = false; //We reset this here so we can detect a change at the end of the integration
1222 
1223  m_stateStringValidOnStart = m_stateStringValid;
1224  m_running = true;
1225 
1226  state(stateCodes::OPERATING);
1227 
1228  updateSwitchIfChanged(m_indiP_startAveraging, "toggle", pcf::IndiElement::On, INDI_BUSY);
1229  }
1230  return 0;
1231 }
1232 
1233 INDI_SETCALLBACK_DEFN( shmimIntegrator, m_indiP_fpsSource )(const pcf::IndiProperty &ipRecv)
1234 {
1235  if( ipRecv.getName() != m_indiP_fpsSource.getName())
1236  {
1237  log<software_error>({__FILE__, __LINE__, "Invalid INDI property."});
1238  return -1;
1239  }
1240 
1241  if( ipRecv.find("current") != true ) //this isn't valie
1242  {
1243  return 0;
1244  }
1245 
1246  std::lock_guard<std::mutex> guard(m_indiMutex);
1247 
1248  realT fps = ipRecv["current"].get<float>();
1249 
1250  if(fps != m_fps)
1251  {
1252  m_fps = fps;
1253  std::cout << "Got fps: " << m_fps << "\n";
1254  shmimMonitorT::m_restart = true;
1255  }
1256 
1257  return 0;
1258 }
1259 
1260 INDI_SETCALLBACK_DEFN( shmimIntegrator, m_indiP_stateSource )(const pcf::IndiProperty &ipRecv)
1261 {
1262  if( ipRecv.getName() != m_indiP_stateSource.getName())
1263  {
1264  log<software_error>({__FILE__, __LINE__, "Invalid INDI property."});
1265  return -1;
1266  }
1267 
1268  if( ipRecv.find("valid") == true )
1269  {
1270  bool stateStringValid;
1271  if(ipRecv["valid"].get<std::string>() == "yes") stateStringValid = true;
1272  else stateStringValid = false;
1273 
1274  if(stateStringValid != m_stateStringValid) m_stateStringChanged = true;
1275 
1276  m_stateStringValid = stateStringValid;
1277  }
1278 
1279  if( ipRecv.find("current") != true )
1280  {
1281  return 0;
1282  }
1283 
1284 
1285 
1286  std::lock_guard<std::mutex> guard(m_indiMutex);
1287 
1288  std::string ss = ipRecv["current"].get<std::string>();
1289 
1290  if(ss != m_stateString)
1291  {
1292  m_stateString = ss;
1293  m_imageValid = false; //This will mark the current dark invalid
1294  updateIfChanged(m_indiP_imageValid, "flag", "no");
1295  m_stateStringChanged = true; //We declare it changed. This can have two effects:
1296  // 1) if we are not currently integrating, it will start a lookup in appLogic
1297  // 2) if we are integrating, after it finishes it will not be declared valid and then we'll lookup in appLogic
1298  }
1299 
1300  return 0;
1301 }
1302 
1303 inline
1305 {
1307 }
1308 
1309 inline
1311 {
1312  return recordFGTimings(true);
1313 }
1314 
1315 } //namespace app
1316 } //namespace MagAOX
1317 
1318 #endif //shmimIntegrator_hpp
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:75
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.
Definition: MagAOXApp.hpp:2877
std::string m_configName
The name of the configuration file (minus .conf).
Definition: MagAOXApp.hpp:88
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2082
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.
Definition: MagAOXApp.hpp:2321
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.
Definition: MagAOXApp.hpp:2901
std::string m_calibDir
The path to calibration files for MagAOX.
Definition: MagAOXApp.hpp:94
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1590
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
Definition: MagAOXApp.hpp:2437
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.
Definition: MagAOXApp.hpp:2209
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:540
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.
void loadConfig(mx::app::appConfigurator &config)
load the configuration system results
void setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
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.
uint32_t m_width
The width of the images in the stream.
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.
void setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
bool m_getExistingFirst
If set to true by derivedT, any existing image will be grabbed and sent to processImage before waitin...
void loadConfig(mx::app::appConfigurator &config)
load the configuration system results
pcf::IndiProperty m_indiP_imageValid
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.
pcf::IndiProperty m_indiP_nAverage
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.
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.
pcf::IndiProperty m_indiP_fpsSource
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(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:207
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
Definition: indiMacros.hpp:264
@ OPERATING
The device is operating, other than homing.
Definition: stateCodes.hpp:50
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:51
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_BUSY
Definition: indiUtils.hpp:30
std::ostream & cout()
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:95
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:206
const pcf::IndiProperty & ipRecv
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
INDI_SETCALLBACK_DEFN(MagAOXApp< _useINDI >, m_indiP_powerChannel)(const pcf
Definition: MagAOXApp.hpp:3195
Definition: dm.hpp:24
constexpr static logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
Definition: logPriority.hpp:43
constexpr static logPrioT LOG_NOTICE
A normal but significant condition.
Definition: logPriority.hpp:46
static std::string indiPrefix()
static std::string configSection()
static std::string configSection()
static std::string indiPrefix()
A device which saves telemetry.
Definition: telemeter.hpp:52
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:259
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:208
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:253
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Definition: telemeter.hpp:195
int appStartup()
Starts the telemetry log thread.
Definition: telemeter.hpp:226
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Definition: telemeter.hpp:266
Software ERR log entry.
Log entry recording framegrabber timings.