API
frameGrabber.hpp
Go to the documentation of this file.
1 /** \file frameGrabber.hpp
2  * \brief The MagAO-X generic frame grabber.
3  *
4  * \author Jared R. Males (jaredmales@gmail.com)
5  *
6  * \ingroup app_files
7  */
8 
9 #ifndef frameGrabber_hpp
10 #define frameGrabber_hpp
11 
12 #include <sys/syscall.h>
13 
14 #include <mx/sigproc/circularBuffer.hpp>
15 #include <mx/math/vectorUtils.hpp>
16 #include <mx/improc/imageUtils.hpp>
17 
18 #include <ImageStreamIO/ImageStruct.h>
19 #include <ImageStreamIO/ImageStreamIO.h>
20 
21 #include "../../common/paths.hpp"
22 
23 
24 namespace MagAOX
25 {
26 namespace app
27 {
28 namespace dev
29 {
30 
31 
32 
33 
34 /** MagAO-X generic frame grabber
35  *
36  *
37  * The derived class `derivedT` has the following requirements:
38  *
39  * - Must be derived from MagAOXApp<true>
40  *
41  * - Must contain the following friend declaration:
42  * \code
43  * friend class dev::frameGrabber<derivedT>; //replace derivedT
44  * \endcode
45  *
46  * - Must declare the following typedef:
47  * \code
48  * typedef dev::frameGrabber<derivedT> frameGrabberT; //replace derivedT
49  * \endcode
50  *
51  * - must expose the following interface
52  * \code
53  //Configures the camera for acquistion, must also set m_width, m_height, and m_dataType
54  //so that the shared memory can be allocated
55  int derivedT::configureAcquisition();
56 
57  //Gets the frames-per-second readout rate
58  //used for the latency statistics
59  float derivedT::fps();
60 
61  //Start acquisition.
62  int derivedT::startAcquisition();
63 
64  //Acquires the data, and checks if it is valid.
65  //This should set m_currImageTimestamp to the image timestamp.
66  // returns 0 if valid, < 0 on error, > 0 on no data.
67  int derivedT::acquireAndCheckValid()
68 
69  //Loads the acquired image into the stream, copying it to the appropriate member of m_imageStream->array.
70  //This could simply be a memcpy.
71  int derivedT::loadImageIntoStream(void * dest);
72 
73  //Take any actions needed to reconfigure the system. Called if m_reconfig is set to true.
74  int derivedT::reconfig()
75  * \endcode
76  * Each of the above functions should return 0 on success, and -1 on an error (except fps).
77  * For `acquireAndCheckValid` >0 will indicate no data but not an error. In most cases,
78  * an appropriate state code, such as NOTCONNECTED, should be set as well.
79  *
80  * A static configuration variable must be defined in derivedT as
81  * \code
82  * static constexpr bool c_frameGrabber_flippable =true; //or: false
83  * \endcode
84  * which determines whether or not the images can be flipped programatically.
85  *
86  * Calls to this class's `setupConfig`, `loadConfig`, `appStartup`, `appLogic`, `updateINDI`, and `appShutdown`
87  * functions must be placed in the derived class's functions of the same name. For convenience the
88  * following macros are defined to provide error checking:
89  * \code
90  * FRAMEGRABBER_SETUP_CONFIG( cfig )
91  * FRAMEGRABBER_LOAD_CONFIG( cfig )
92  * FRAMEGRABBER_APP_STARTUP
93  * FRAMEGRABBER_APP_LOGIC
94  * FRAMEGRABBER_UPDATE_INDI
95  * FRAMEGRABBER_APP_SHUTDOWN
96  * \endcode
97  *
98  * \ingroup appdev
99  */
100 template<class derivedT>
102 {
103 public:
105 
106 protected:
107 
108  /** \name Configurable Parameters
109  * @{
110  */
111  std::string m_shmimName {""}; ///< The name of the shared memory image, is used in `/tmp/<shmimName>.im.shm`. Derived classes should set a default.
112 
113  int m_fgThreadPrio {2}; ///< Priority of the framegrabber thread, should normally be > 00.
114  std::string m_fgCpuset; ///< The cpuset to assign the framegrabber thread to. Not used if empty, the default.
115 
116  uint32_t m_circBuffLength {1}; ///< Length of the circular buffer, in frames
117 
118  uint16_t m_latencyCircBuffMaxLength {3600}; ///< Maximum length of the latency measurement circular buffers
119  float m_latencyCircBuffMaxTime {5}; ///< Maximum time of the latency meaurement circular buffers
120 
122 
123  ///@}
124 
126 
127  uint32_t m_width {0}; ///< The width of the image, once deinterlaced etc.
128  uint32_t m_height {0}; ///< The height of the image, once deinterlaced etc.
129 
130  uint8_t m_dataType{0}; ///< The ImageStreamIO type code.
131  size_t m_typeSize {0}; ///< The size of the type, in bytes. Result of sizeof.
132 
133  int m_xbinning {0}; ///< The x-binning according to the framegrabber
134  int m_ybinning {0}; ///< The y-binning according to the framegrabber
135 
136 
137  timespec m_currImageTimestamp {0,0}; ///< The timestamp of the current image.
138 
139  bool m_reconfig {false}; ///< Flag to set if a camera reconfiguration requires a framegrabber reset.
140 
141  IMAGE * m_imageStream {nullptr}; ///< The ImageStreamIO shared memory buffer.
142 
143  typedef uint16_t cbIndexT;
144 
145  mx::sigproc::circularBufferIndex<timespec, cbIndexT> m_atimes;
146  mx::sigproc::circularBufferIndex<timespec, cbIndexT> m_wtimes;
147 
148  std::vector<double> m_atimesD;
149  std::vector<double> m_wtimesD;
150  std::vector<double> m_watimesD;
151 
152  timespec m_dummy_ts {0,0};
153  uint64_t m_dummy_cnt {0};
154  char m_dummy_c {0};
155 
156  double m_mna;
157  double m_vara;
158 
159  double m_mnw;
160  double m_varw;
161 
162  double m_mnwa;
163  double m_varwa;
164 
165 
166 
167 
168 public:
169 
170  /// Setup the configuration system
171  /**
172  * This should be called in `derivedT::setupConfig` as
173  * \code
174  framegrabber<derivedT>::setupConfig(config);
175  \endcode
176  * with appropriate error checking.
177  */
178  int setupConfig(mx::app::appConfigurator & config /**< [out] the derived classes configurator*/);
179 
180  /// load the configuration system results
181  /**
182  * This should be called in `derivedT::loadConfig` as
183  * \code
184  framegrabber<derivedT>::loadConfig(config);
185  \endcode
186  * with appropriate error checking.
187  */
188  int loadConfig(mx::app::appConfigurator & config /**< [in] the derived classes configurator*/);
189 
190  /// Startup function
191  /** Starts the framegrabber thread
192  * This should be called in `derivedT::appStartup` as
193  * \code
194  framegrabber<derivedT>::appStartup();
195  \endcode
196  * with appropriate error checking.
197  *
198  * \returns 0 on success
199  * \returns -1 on error, which is logged.
200  */
201  int appStartup();
202 
203  /// Checks the framegrabber thread
204  /** This should be called in `derivedT::appLogic` as
205  * \code
206  framegrabber<derivedT>::appLogic();
207  \endcode
208  * with appropriate error checking.
209  *
210  * \returns 0 on success
211  * \returns -1 on error, which is logged.
212  */
213  int appLogic();
214 
215  /// On power off, sets m_reconfig to true.
216  /** This should be called in `derivedT::onPowerOff` as
217  * \code
218  framegrabber<derivedT>::onPowerOff();
219  \endcode
220  * with appropriate error checking.
221  *
222  * \returns 0 on success
223  * \returns -1 on error, which is logged.
224  */
225  int onPowerOff();
226 
227  /// Shuts down the framegrabber thread
228  /** This should be called in `derivedT::appShutdown` as
229  * \code
230  framegrabber<derivedT>::appShutdown();
231  \endcode
232  * with appropriate error checking.
233  *
234  * \returns 0 on success
235  * \returns -1 on error, which is logged.
236  */
237  int appShutdown();
238 
239 protected:
240 
241 
242  /** \name Framegrabber Thread
243  * This thread actually manages the framegrabbing hardware
244  * @{
245  */
246 
247  bool m_fgThreadInit {true}; ///< Synchronizer for thread startup, to allow priority setting to finish.
248 
249  pid_t m_fgThreadID {0}; ///< The ID of the framegrabber thread.
250 
251  pcf::IndiProperty m_fgThreadProp; ///< The property to hold the f.g. thread details.
252 
253  std::thread m_fgThread; ///< A separate thread for the actual framegrabbings
254 
255  ///Thread starter, called by MagAOXApp::threadStart on thread construction. Calls fgThreadExec.
256  static void fgThreadStart( frameGrabber * o /**< [in] a pointer to a frameGrabber instance (normally this) */);
257 
258  /// Execute framegrabbing.
259  void fgThreadExec();
260 
261 
262  ///@}
263 
264  void * loadImageIntoStreamCopy( void * dest,
265  void * src,
266  size_t width,
267  size_t height,
268  size_t szof
269  );
270 
271 
272  /** \name INDI
273  *
274  *@{
275  */
276 protected:
277  //declare our properties
278 
279  pcf::IndiProperty m_indiP_shmimName; ///< Property used to report the shmim buffer name
280 
281  pcf::IndiProperty m_indiP_frameSize; ///< Property used to report the current frame size
282 
283  pcf::IndiProperty m_indiP_timing;
284 public:
285 
286  /// Update the INDI properties for this device controller
287  /** You should call this once per main loop.
288  * It is not called automatically.
289  *
290  * \returns 0 on success.
291  * \returns -1 on error.
292  */
293  int updateINDI();
294 
295  ///@}
296 
297  /** \name Telemeter Interface
298  * @{
299  */
300 
301  int recordFGTimings( bool force = false );
302 
303  /// @}
304 
305 private:
306  derivedT & derived()
307  {
308  return *static_cast<derivedT *>(this);
309  }
310 };
311 
312 template<class derivedT>
313 int frameGrabber<derivedT>::setupConfig(mx::app::appConfigurator & config)
314 {
315  config.add("framegrabber.threadPrio", "", "framegrabber.threadPrio", argType::Required, "framegrabber", "threadPrio", false, "int", "The real-time priority of the framegrabber thread.");
316 
317  config.add("framegrabber.cpuset", "", "framegrabber.cpuset", argType::Required, "framegrabber", "cpuset", false, "string", "The cpuset to assign the framegrabber thread to.");
318 
319  config.add("framegrabber.shmimName", "", "framegrabber.shmimName", argType::Required, "framegrabber", "shmimName", false, "string", "The name of the ImageStreamIO shared memory image. Will be used as /milk/shm/<shmimName>.im.shm.");
320 
321  config.add("framegrabber.circBuffLength", "", "framegrabber.circBuffLength", argType::Required, "framegrabber", "circBuffLength", false, "size_t", "The length of the circular buffer. Sets m_circBuffLength, default is 1.");
322 
323  config.add("framegrabber.latencyTime", "", "framegrabber.latencyTime", argType::Required, "framegrabber", "latencyTime", false, "float", "The maximum length of time to measure latency timings. Sets m_latencyCircBuffMaxTime, default is 5.");
324 
325  config.add("framegrabber.latencySize", "", "framegrabber.latencySize", argType::Required, "framegrabber", "latencySize", false, "float", "The maximum length of the buffer used to measure latency timings. Sets m_latencyCircBuffMaxLength, default is 3600.");
326 
327 
328  if(derivedT::c_frameGrabber_flippable)
329  {
330  config.add("framegrabber.defaultFlip", "", "framegrabber.defaultFlip", argType::Required, "framegrabber", "defaultFlip", false, "string", "The default flip of the image. Options are flipNone, flipUD, flipLR, flipUDLR. The default is flipNone.");
331  }
332 
333  return 0;
334 }
335 
336 template<class derivedT>
337 int frameGrabber<derivedT>::loadConfig(mx::app::appConfigurator & config)
338 {
339  config(m_fgThreadPrio, "framegrabber.threadPrio");
340  config(m_fgCpuset, "framegrabber.cpuset");
341  if(m_shmimName == "") m_shmimName = derived().configName();
342  config(m_shmimName, "framegrabber.shmimName");
343 
344  config(m_circBuffLength, "framegrabber.circBuffLength");
345 
346  if(m_circBuffLength < 1)
347  {
348  m_circBuffLength = 1;
349  derivedT::template log<text_log>("circBuffLength set to 1");
350  }
351 
352  config(m_latencyCircBuffMaxTime, "framegrabber.latencyTime");
353  if(m_latencyCircBuffMaxTime < 0)
354  {
355  m_latencyCircBuffMaxTime = 0;
356  derivedT::template log<text_log>("latencyTime set to 0 (off)");
357  }
358 
359  config(m_latencyCircBuffMaxLength, "framegrabber.latencySize");
360 
361 
362  if(derivedT::c_frameGrabber_flippable)
363  {
364  std::string flip = "flipNone";
365  config(flip, "framegrabber.defaultFlip");
366  if(flip == "flipNone")
367  {
368  m_defaultFlip = fgFlipNone;
369  }
370  else if(flip == "flipUD")
371  {
372  m_defaultFlip = fgFlipUD;
373  }
374  else if(flip == "flipLR")
375  {
376  m_defaultFlip = fgFlipLR;
377  }
378  else if(flip == "flipUDLR")
379  {
380  m_defaultFlip = fgFlipUDLR;
381  }
382  else
383  {
384  derivedT::template log<text_log>({std::string("invalid framegrabber flip specification (") + flip + "), setting flipNone"}, logPrio::LOG_ERROR);
385  m_defaultFlip = fgFlipNone;
386  }
387  }
388 
389  return 0;
390 }
391 
392 
393 template<class derivedT>
395 {
396  //Register the shmimName INDI property
397  m_indiP_shmimName = pcf::IndiProperty(pcf::IndiProperty::Text);
398  m_indiP_shmimName.setDevice(derived().configName());
399  m_indiP_shmimName.setName("fg_shmimName");
400  m_indiP_shmimName.setPerm(pcf::IndiProperty::ReadOnly);
401  m_indiP_shmimName.setState(pcf::IndiProperty::Idle);
402  m_indiP_shmimName.add(pcf::IndiElement("name"));
403  m_indiP_shmimName["name"] = m_shmimName;
404 
405  if( derived().registerIndiPropertyNew( m_indiP_shmimName, nullptr) < 0)
406  {
407  #ifndef FRAMEGRABBER_TEST_NOLOG
408  derivedT::template log<software_error>({__FILE__,__LINE__});
409  #endif
410  return -1;
411  }
412 
413  //Register the frameSize INDI property
414  m_indiP_frameSize = pcf::IndiProperty(pcf::IndiProperty::Number);
415  m_indiP_frameSize.setDevice(derived().configName());
416  m_indiP_frameSize.setName("fg_frameSize");
417  m_indiP_frameSize.setPerm(pcf::IndiProperty::ReadOnly);
418  m_indiP_frameSize.setState(pcf::IndiProperty::Idle);
419  m_indiP_frameSize.add(pcf::IndiElement("width"));
420  m_indiP_frameSize["width"] = 0;
421  m_indiP_frameSize.add(pcf::IndiElement("height"));
422  m_indiP_frameSize["height"] = 0;
423 
424  if( derived().registerIndiPropertyNew( m_indiP_frameSize, nullptr) < 0)
425  {
426  #ifndef FRAMEGRABBER_TEST_NOLOG
427  derivedT::template log<software_error>({__FILE__,__LINE__});
428  #endif
429  return -1;
430  }
431 
432  //Register the timing INDI property
433  derived().createROIndiNumber( m_indiP_timing, "fg_timing");
434  m_indiP_timing.add(pcf::IndiElement("acq_fps"));
435  m_indiP_timing.add(pcf::IndiElement("acq_jitter"));
436  m_indiP_timing.add(pcf::IndiElement("write_fps"));
437  m_indiP_timing.add(pcf::IndiElement("write_jitter"));
438  m_indiP_timing.add(pcf::IndiElement("delta_aw"));
439  m_indiP_timing.add(pcf::IndiElement("delta_aw_jitter"));
440 
441  if( derived().registerIndiPropertyReadOnly( m_indiP_timing ) < 0)
442  {
443  #ifndef STDCAMERA_TEST_NOLOG
444  derivedT::template log<software_error>({__FILE__,__LINE__});
445  #endif
446  return -1;
447  }
448 
449  //Start the f.g. thread
450  if(derived().threadStart( m_fgThread, m_fgThreadInit, m_fgThreadID, m_fgThreadProp, m_fgThreadPrio, m_fgCpuset, "framegrabber", this, fgThreadStart) < 0)
451  {
452  derivedT::template log<software_error, -1>({__FILE__, __LINE__});
453  return -1;
454  }
455 
456  return 0;
457 
458 }
459 
460 template<class derivedT>
462 {
463  //do a join check to see if other threads have exited.
464  if(pthread_tryjoin_np(m_fgThread.native_handle(),0) == 0)
465  {
466  derivedT::template log<software_error>({__FILE__, __LINE__, "framegrabber thread has exited"});
467 
468  return -1;
469  }
470 
471  if( derived().state() == stateCodes::OPERATING && m_atimes.size() > 0 )
472  {
473  if(m_atimes.size() >= m_atimes.maxEntries())
474  {
475  cbIndexT refEntry = m_atimes.earliest();
476 
477  m_atimesD.resize(m_atimes.maxEntries()-1);
478  m_wtimesD.resize(m_wtimes.maxEntries()-1);
479  m_watimesD.resize(m_wtimes.maxEntries()-1);
480 
481  double a0 = m_atimes.at(refEntry, 0).tv_sec + ((double) m_atimes.at(refEntry, 0).tv_nsec)/1e9;
482  double w0 = m_wtimes.at(refEntry, 0).tv_sec + ((double) m_wtimes.at(refEntry, 0).tv_nsec)/1e9;
483  for(size_t n=1; n <= m_atimesD.size(); ++n)
484  {
485  double a = m_atimes.at(refEntry, n).tv_sec + ((double) m_atimes.at(refEntry, n).tv_nsec)/1e9;
486  double w = m_wtimes.at(refEntry, n).tv_sec + ((double) m_wtimes.at(refEntry, n).tv_nsec)/1e9;
487  m_atimesD[n-1] = a - a0;
488  m_wtimesD[n-1] = w - w0;
489  m_watimesD[n-1] = w - a;
490  a0 = a;
491  w0 = w;
492  }
493 
494  m_mna = mx::math::vectorMean(m_atimesD);
495  m_vara = mx::math::vectorVariance(m_atimesD, m_mna);
496 
497  m_mnw = mx::math::vectorMean(m_wtimesD);
498  m_varw = mx::math::vectorVariance(m_wtimesD, m_mnw);
499 
500  m_mnwa = mx::math::vectorMean(m_watimesD);
501  m_varwa = mx::math::vectorVariance(m_watimesD, m_mnwa);
502 
503  recordFGTimings();
504  }
505  else
506  {
507  m_mna = 0;
508  m_vara = 0;
509  m_mnw = 0;
510  m_varw = 0;
511  m_mnwa = 0;
512  m_varwa = 0;
513  }
514  }
515  else
516  {
517  m_mna = 0;
518  m_vara = 0;
519  m_mnw = 0;
520  m_varw = 0;
521  m_mnwa = 0;
522  m_varwa = 0;
523  }
524 
525  return 0;
526 
527 }
528 
529 template<class derivedT>
531 {
532  m_mna = 0;
533  m_vara = 0;
534  m_mnw = 0;
535  m_varw = 0;
536  m_mnwa = 0;
537  m_varwa = 0;
538 
539  m_width = 0;
540  m_height = 0;
541 
542  updateINDI();
543 
544  m_reconfig = true;
545 
546 
547  return 0;
548 }
549 
550 
551 template<class derivedT>
553 {
554  if(m_fgThread.joinable())
555  {
556  try
557  {
558  m_fgThread.join(); //this will throw if it was already joined
559  }
560  catch(...)
561  {
562  }
563  }
564 
565 
566 
567  return 0;
568 }
569 
570 
571 
572 template<class derivedT>
574 {
575  o->fgThreadExec();
576 }
577 
578 
579 template<class derivedT>
581 {
582  //Get the thread PID immediately so the caller can return.
583  m_fgThreadID = syscall(SYS_gettid);
584 
585  timespec writestart;
586 
587  //Wait fpr the thread starter to finish initializing this thread.
588  while(m_fgThreadInit == true && derived().shutdown() == 0)
589  {
590  sleep(1);
591  }
592 
593  uint32_t imsize[3] = {0,0,0};
594  std::string shmimName;
595 
596  while(derived().shutdown() == 0)
597  {
598  ///\todo this ought to wait until OPERATING, using READY as a sign of "not integrating"
599  while(!derived().shutdown() && (!( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING) || derived().powerState() <= 0 ) )
600  {
601  sleep(1);
602  }
603 
604  if(derived().shutdown()) continue;
605  else
606  {
607  //At the end of this, must have m_width, m_height, m_dataType set, and derived()->fps must be valid.
608  if(derived().configureAcquisition() < 0) continue;
609 
610  if(m_latencyCircBuffMaxLength == 0 || m_latencyCircBuffMaxTime == 0)
611  {
612  m_atimes.maxEntries(0);
613  m_wtimes.maxEntries(0);
614  }
615  else
616  {
617  //Set up the latency circ. buffs
618  cbIndexT cbSz = m_latencyCircBuffMaxTime * derived().fps();
619  if(cbSz > m_latencyCircBuffMaxLength) cbSz = m_latencyCircBuffMaxLength;
620  if(cbSz < 3) cbSz = 3; //Make variance meaningful
621  m_atimes.maxEntries(cbSz);
622  m_wtimes.maxEntries(cbSz);
623  }
624 
625  m_typeSize = ImageStreamIO_typesize(m_dataType);
626 
627 
628  //Here we resolve currentFlip somehow.
629  m_currentFlip = m_defaultFlip;
630  }
631 
632  /* Initialize ImageStreamIO
633  */
634  if(m_shmimName == "") m_shmimName = derived().configName();
635 
636  if(m_width != imsize[0] || m_height != imsize[1] || m_circBuffLength != imsize[2] || m_shmimName != shmimName || m_imageStream == nullptr)
637  {
638  if(m_imageStream != nullptr)
639  {
640  ImageStreamIO_destroyIm(m_imageStream);
641  free(m_imageStream);
642  }
643 
644  m_imageStream = (IMAGE *) malloc(sizeof(IMAGE));
645 
646  imsize[0] = m_width;
647  imsize[1] = m_height;
648  imsize[2] = m_circBuffLength;
649  shmimName = m_shmimName;
650 
651  std::cerr << "Creating: " << m_shmimName << " " << m_width << " " << m_height << " " << m_circBuffLength << "\n";
652 
653  ImageStreamIO_createIm_gpu(m_imageStream, m_shmimName.c_str(), 3, imsize, m_dataType, -1, 1, IMAGE_NB_SEMAPHORE, 0, CIRCULAR_BUFFER | ZAXIS_TEMPORAL, 0);
654 
655  m_imageStream->md->cnt1 = m_circBuffLength - 1;
656  }
657  else
658  {
659  std::cerr << "Not creating . . .\n";
660  }
661 
662  //This completes the reconfiguration.
663  m_reconfig = false;
664 
665  if(derived().startAcquisition() < 0) continue;
666 
667  uint64_t next_cnt1 = 0;
668  char * next_dest = (char *) m_imageStream->array.raw;
669  timespec * next_wtimearr = &m_imageStream->writetimearray[0];
670  timespec * next_atimearr = &m_imageStream->atimearray[0];
671  uint64_t * next_cntarr = &m_imageStream->cntarray[0];
672 
673  //This is the main image grabbing loop.
674  while(!derived().shutdown() && !m_reconfig && derived().powerState() > 0)
675  {
676  //==================
677  //Get next image, process validity.
678  //====================
679  int isValid = derived().acquireAndCheckValid();
680  if(isValid != 0)
681  {
682  if( isValid < 0)
683  {
684  break;
685  }
686  else if( isValid > 0)
687  {
688  continue;
689  }
690  }
691 
692  //Ok, no timeout, so we process the image and publish it.
693  m_imageStream->md->write=1;
694 
695  //Set the time of last write
696  clock_gettime(CLOCK_REALTIME, &writestart);
697 
698  if(derived().loadImageIntoStream(next_dest) < 0)
699  {
700  break;
701  }
702 
703  //Set the time of last write
704  clock_gettime(CLOCK_REALTIME, &m_imageStream->md->writetime);
705 
706  //Set the image acquisition timestamp
707  m_imageStream->md->atime = m_currImageTimestamp;
708 
709  //Update cnt1
710  m_imageStream->md->cnt1 = next_cnt1;
711 
712  //Update cnt0
713  m_imageStream->md->cnt0++;
714 
715  *next_wtimearr = m_imageStream->md->writetime;
716  *next_atimearr = m_currImageTimestamp;
717  *next_cntarr = m_imageStream->md->cnt0;
718 
719  //And post
720  m_imageStream->md->write=0;
721  ImageStreamIO_sempost(m_imageStream,-1);
722 
723  //Update the latency circ. buffs
724  if(m_atimes.maxEntries() > 0)
725  {
726  m_atimes.nextEntry(m_imageStream->md->atime);
727  m_wtimes.nextEntry(m_imageStream->md->writetime);
728  }
729 
730  //Now we increment pointers outside the time-critical part of the loop.
731  next_cnt1 = m_imageStream->md->cnt1+1;
732  if(next_cnt1 >= m_circBuffLength) next_cnt1 = 0;
733 
734  next_dest = (char *) m_imageStream->array.raw + next_cnt1*m_width*m_height*m_typeSize;
735  next_wtimearr = &m_imageStream->writetimearray[next_cnt1];
736  next_atimearr = &m_imageStream->atimearray[next_cnt1];
737  next_cntarr = &m_imageStream->cntarray[next_cnt1];
738 
739  //Touch them to make sure we move
740  m_dummy_c = next_dest[0];
741  m_dummy_ts.tv_sec = next_wtimearr[0].tv_sec + next_atimearr[0].tv_sec;
742  m_dummy_cnt = next_cntarr[0];
743  }
744 
745  if(m_reconfig && !derived().shutdown())
746  {
747  derived().reconfig();
748  }
749 
750  } //outer loop, will exit if m_shutdown==true
751 
752  if(m_imageStream != nullptr)
753  {
754  ImageStreamIO_destroyIm( m_imageStream );
755  free(m_imageStream);
756  m_imageStream = nullptr;
757  }
758 }
759 
760 template<class derivedT>
762  void * src,
763  size_t width,
764  size_t height,
765  size_t szof
766  )
767 {
768  if(!derivedT::c_frameGrabber_flippable)
769  {
770  return memcpy(dest, src, width*height*szof);
771  }
772  else
773  {
774  switch(m_currentFlip)
775  {
776  case fgFlipNone:
777  return mx::improc::imcpy(dest, src, width, height, szof);
778  case fgFlipUD:
779  return mx::improc::imcpy_flipUD(dest, src, width, height, szof);
780  case fgFlipLR:
781  return mx::improc::imcpy_flipLR(dest, src, width, height, szof);
782  case fgFlipUDLR:
783  return mx::improc::imcpy_flipUDLR(dest, src, width, height, szof);
784  default:
785  return nullptr;
786  }
787  }
788 }
789 
790 
791 
792 template<class derivedT>
794 {
795  if( !derived().m_indiDriver ) return 0;
796 
797  indi::updateIfChanged(m_indiP_shmimName, "name", m_shmimName, derived().m_indiDriver);
798  indi::updateIfChanged(m_indiP_frameSize, "width", m_width, derived().m_indiDriver);
799  indi::updateIfChanged(m_indiP_frameSize, "height", m_height, derived().m_indiDriver);
800 
801  double fpsa = 0;
802  double fpsw = 0;
803  if(m_mna != 0 ) fpsa = 1.0/m_mna;
804  if(m_mnw != 0 ) fpsw = 1.0/m_mnw;
805 
806  indi::updateIfChanged<double>(m_indiP_timing, {"acq_fps","acq_jitter","write_fps","write_jitter","delta_aw","delta_aw_jitter"},
807  {fpsa, sqrt(m_vara), fpsw, sqrt(m_varw), m_mnwa, sqrt(m_varwa)},derived().m_indiDriver);
808 
809  return 0;
810 }
811 
812 template<class derivedT>
814 {
815  static double last_mna = 0;
816  static double last_vara = 0;
817 
818  static double last_mnw = 0;
819  static double last_varw = 0;
820 
821  static double last_mnwa = 0;
822  static double last_varwa = 0;
823 
824  if(force || m_mna != last_mna || m_vara != last_vara ||
825  m_mnw != last_mnw || m_varw != last_varw ||
826  m_mnwa != last_mnwa || m_varwa != last_varwa )
827  {
828  derived().template telem<telem_fgtimings>({m_mna, sqrt(m_vara), m_mnw, sqrt(m_varw), m_mnwa, sqrt(m_varwa)});
829 
830  last_mna = m_mna;
831  last_vara = m_vara;
832  last_mnw = m_mnw;
833  last_varw = m_varw;
834  last_mnwa = m_mnwa;
835  last_varwa = m_varwa;
836  }
837 
838  return 0;
839 
840 }
841 
842 /// Call frameGrabberT::setupConfig with error checking for frameGrabber
843 /**
844  * \param cfig the application configurator
845  */
846 #define FRAMEGRABBER_SETUP_CONFIG( cfig ) \
847  if(frameGrabberT::setupConfig(cfig) < 0) \
848  { \
849  log<software_error>({__FILE__, __LINE__, "Error from frameGrabberT::setupConfig"}); \
850  m_shutdown = true; \
851  return; \
852  }
853 
854 /// Call frameGrabberT::loadConfig with error checking for frameGrabber
855 /** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
856  * \param cfig the application configurator
857  */
858 #define FRAMEGRABBER_LOAD_CONFIG( cfig ) \
859  if(frameGrabberT::loadConfig(cfig) < 0) \
860  { \
861  return log<software_error,-1>({__FILE__, __LINE__, "Error from frameGrabberT::loadConfig"}); \
862  }
863 
864 /// Call frameGrabberT::appStartup with error checking for frameGrabber
865 #define FRAMEGRABBER_APP_STARTUP \
866  if(frameGrabberT::appStartup() < 0) \
867  { \
868  return log<software_error,-1>({__FILE__, __LINE__, "Error from frameGrabberT::appStartup"}); \
869  }
870 
871 /// Call frameGrabberT::appLogic with error checking for frameGrabber
872 #define FRAMEGRABBER_APP_LOGIC \
873  if(frameGrabberT::appLogic() < 0) \
874  { \
875  return log<software_error,-1>({__FILE__, __LINE__, "Error from frameGrabberT::appLogic"}); \
876  }
877 
878 /// Call frameGrabberT::updateINDI with error checking for frameGrabber
879 #define FRAMEGRABBER_UPDATE_INDI \
880  if(frameGrabberT::updateINDI() < 0) \
881  { \
882  return log<software_error,-1>({__FILE__, __LINE__, "Error from frameGrabberT::updateINDI"}); \
883  }
884 
885 /// Call frameGrabberT::appShutdown with error checking for frameGrabber
886 #define FRAMEGRABBER_APP_SHUTDOWN \
887  if(frameGrabberT::appShutdown() < 0) \
888  { \
889  return log<software_error,-1>({__FILE__, __LINE__, "Error from frameGrabberT::appShutdown"}); \
890  }
891 
892 } //namespace dev
893 } //namespace app
894 } //namespace MagAOX
895 #endif
uint16_t m_latencyCircBuffMaxLength
Maximum length of the latency measurement circular buffers.
int m_xbinning
The x-binning according to the framegrabber.
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.
mx::sigproc::circularBufferIndex< timespec, cbIndexT > m_wtimes
void * loadImageIntoStreamCopy(void *dest, void *src, size_t width, size_t height, size_t szof)
int recordFGTimings(bool force=false)
pcf::IndiProperty m_fgThreadProp
The property to hold the f.g. thread details.
void fgThreadExec()
Execute framegrabbing.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
bool m_fgThreadInit
Synchronizer for thread startup, to allow priority setting to finish.
std::string m_fgCpuset
The cpuset to assign the framegrabber thread to. Not used if empty, the default.
static void fgThreadStart(frameGrabber *o)
Thread starter, called by MagAOXApp::threadStart on thread construction. Calls fgThreadExec.
int m_ybinning
The y-binning according to the framegrabber.
std::vector< double > m_watimesD
size_t m_typeSize
The size of the type, in bytes. Result of sizeof.
int updateINDI()
Update the INDI properties for this device controller.
std::vector< double > m_wtimesD
int onPowerOff()
On power off, sets m_reconfig to true.
pcf::IndiProperty m_indiP_shmimName
Property used to report the shmim buffer name.
std::string m_shmimName
The name of the shared memory image, is used in /tmp/<shmimName>.im.shm. Derived classes should set a...
uint8_t m_dataType
The ImageStreamIO type code.
bool m_reconfig
Flag to set if a camera reconfiguration requires a framegrabber reset.
std::thread m_fgThread
A separate thread for the actual framegrabbings.
pid_t m_fgThreadID
The ID of the framegrabber thread.
int appLogic()
Checks the framegrabber thread.
int m_fgThreadPrio
Priority of the framegrabber thread, should normally be > 00.
mx::sigproc::circularBufferIndex< timespec, cbIndexT > m_atimes
std::vector< double > m_atimesD
IMAGE * m_imageStream
The ImageStreamIO shared memory buffer.
pcf::IndiProperty m_indiP_frameSize
Property used to report the current frame size.
int appStartup()
Startup function.
pcf::IndiProperty m_indiP_timing
float m_latencyCircBuffMaxTime
Maximum time of the latency meaurement circular buffers.
uint32_t m_circBuffLength
Length of the circular buffer, in frames.
uint32_t m_height
The height of the image, once deinterlaced etc.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
@ OPERATING
The device is operating, other than homing.
Definition: stateCodes.hpp:55
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:56
std::ostream & cerr()
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
Definition: dm.hpp:24
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Definition: logPriority.hpp:40