API
photonCounter.hpp
Go to the documentation of this file.
1 /** \file photonCounter.hpp
2  * \brief The MagAO-X PWFS Slope Calculator
3  *
4  * \ingroup app_files
5  */
6 
7 #ifndef photonCounter_hpp
8 #define photonCounter_hpp
9 
10 #include <limits>
11 #include <algorithm>
12 #include <mx/improc/eigenCube.hpp>
13 #include <mx/improc/eigenImage.hpp>
14 using namespace mx::improc;
15 
16 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
17 #include "../../magaox_git_version.h"
18 
19 namespace MagAOX
20 {
21 namespace app
22 {
23 
24 /** \defgroup photonCounter PWFS Slope Calculator
25  * \brief Calculates slopes from a PWFS image.
26  *
27  * <a href="../handbook/operating/software/apps/photonCounter.html">Application Documentation</a>
28  *
29  * \ingroup apps
30  *
31  */
32 
33 /** \defgroup photonCounter_files PWFS Slope Calculator Files
34  * \ingroup photonCounter
35  */
36 
37 /** MagAO-X application to calculate slopes from PWFS images.
38  *
39  * \ingroup photonCounter
40  *
41  */
42 class photonCounter : public MagAOXApp<true>, public dev::shmimMonitor<photonCounter>, public dev::frameGrabber<photonCounter>
43 {
44 
45  //Give the test harness access.
46  friend class photonCounter_test;
47 
48  friend class dev::shmimMonitor<photonCounter>;
49  friend class dev::frameGrabber<photonCounter>;
50 
51  //The base shmimMonitor type
53 
54  //The base frameGrabber type
56 
57  ///Floating point type in which to do all calculations.
58  typedef float realT;
59 
60  static constexpr bool c_frameGrabber_flippable = false; ///< app:dev config to tell framegrabber these images can not be flipped
61 
62 protected:
63 
64  /** \name Configurable Parameters
65  *@{
66  */
67 
68  ///@}
69 
70  sem_t m_smSemaphore; ///< Semaphore used to synchronize the fg thread and the sm thread.
71  realT (*pixget)(void *, size_t) {nullptr}; ///< Pointer to a function to extract the image data as our desired type realT.
72  void * m_curr_src {nullptr};
73 
76 
77  mx::improc::eigenCube<realT> m_calibrationCube;
78  mx::improc::eigenImage<realT> m_dark_image;
79  mx::improc::eigenImage<realT> m_thresholdImage;
80  mx::improc::eigenImage<realT> m_photonCountedImage;
82 
83  //
86 
91 
92 
93 public:
94  /// Default c'tor.
95  photonCounter();
96 
97  /// D'tor, declared and defined for noexcept.
98  ~photonCounter() noexcept
99  {}
100 
101  virtual void setupConfig();
102 
103  /// Implementation of loadConfig logic, separated for testing.
104  /** This is called by loadConfig().
105  */
106  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
107 
108  virtual void loadConfig();
109 
110  /// Startup function
111  /**
112  *
113  */
114  virtual int appStartup();
115 
116  /// Implementation of the FSM for photonCounter.
117  /**
118  * \returns 0 on no critical error
119  * \returns -1 on an error requiring shutdown
120  */
121  virtual int appLogic();
122 
123  /// Shutdown the app.
124  /**
125  *
126  */
127  virtual int appShutdown();
128 
129  int allocate( const dev::shmimT & dummy /**< [in] tag to differentiate shmimMonitor parents.*/);
130 
131  int processImage( void * curr_src, ///< [in] pointer to start of current frame.
132  const dev::shmimT & dummy ///< [in] tag to differentiate shmimMonitor parents.
133  );
134 
135  float fps()
136  {
137  return 250;
138  }
139 
140 protected:
141 
142  /** \name dev::frameGrabber interface
143  *
144  * @{
145  */
146 
147  /// Implementation of the framegrabber configureAcquisition interface
148  /**
149  * \returns 0 on success
150  * \returns -1 on error
151  */
152  int configureAcquisition();
153 
154  /// Implementation of the framegrabber startAcquisition interface
155  /**
156  * \returns 0 on success
157  * \returns -1 on error
158  */
159  int startAcquisition();
160 
161  /// Implementation of the framegrabber acquireAndCheckValid interface
162  /**
163  * \returns 0 on success
164  * \returns -1 on error
165  */
166  int acquireAndCheckValid();
167 
168  /// Implementation of the framegrabber loadImageIntoStream interface
169  /**
170  * \returns 0 on success
171  * \returns -1 on error
172  */
173  int loadImageIntoStream( void * dest /**< [in] */);
174 
175  /// Implementation of the framegrabber reconfig interface
176  /**
177  * \returns 0 on success
178  * \returns -1 on error
179  */
180  int reconfig();
181 
182  ///@}
183  pcf::IndiProperty m_indiP_calibrateToggle;
184  pcf::IndiProperty m_indiP_calibrateSteps;
185  pcf::IndiProperty m_indiP_stackFrames;
186  pcf::IndiProperty m_indiP_quantileCut;
187 
188 public:
189 
190  // Control states
191  INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_calibrateToggle);
192  INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_calibrateSteps);
193  INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_stackFrames);
194  INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_quantileCut);
195 
196 };
197 
198 
199 inline
200 photonCounter::photonCounter() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
201 {
202  return;
203 }
204 
205 inline
207 {
210 
211  config.add("parameters.quantile", "", "parameters.quantile", argType::Required, "parameters", "quantile", false, "float", "The quantile of the threshold.");
212  config.add("parameters.Nstack", "", "parameters.Nstack", argType::Required, "parameters", "Nstack", false, "string", "The number of frames to stack.");
213  config.add("parameters.Ncalibrate", "", "parameters.Ncalibrate", argType::Required, "parameters", "Ncalibrate", false, "string", "The number of frames for calibration.");
214 }
215 
216 inline
217 int photonCounter::loadConfigImpl( mx::app::appConfigurator & _config )
218 {
219 
220  shmimMonitorT::loadConfig(_config);
221  frameGrabberT::loadConfig(_config);
222 
223  _config(m_stack_frames, "parameters.Nstack");
224  _config(m_quantile_cut, "parameters.quantile");
225  _config(calibration_steps, "parameters.Ncalibrate");
226 
227  return 0;
228 }
229 
230 inline
232 {
233  loadConfigImpl(config);
234 }
235 
236 inline
238 {
239  if(sem_init(&m_smSemaphore, 0,0) < 0)
240  {
241  log<software_critical>({__FILE__, __LINE__, errno,0, "Initializing S.M. semaphore"});
242  return -1;
243  }
244 
245  if(shmimMonitorT::appStartup() < 0)
246  {
247  return log<software_error,-1>({__FILE__, __LINE__});
248  }
249 
250  if(frameGrabberT::appStartup() < 0)
251  {
252  return log<software_error,-1>({__FILE__, __LINE__});
253  }
254 
255  // createStandardIndiToggleSw( m_indiP_calibrateToggle, "calibrate", "Control calibration state", "Calibration control");
256  // registerIndiPropertyNew( m_indiP_calibrateToggle, INDI_NEWCALLBACK(m_indiP_calibrateToggle) );
257 
258  createStandardIndiRequestSw( m_indiP_calibrateToggle, "calibrate", "Start calibration", "Calibration control");
260 
261  createStandardIndiNumber<int>( m_indiP_calibrateSteps, "nFrames", 0, 1000000, 1, "%d", "The calibration ", "Calibration control");
263 
264  createStandardIndiNumber<int>( m_indiP_stackFrames, "stackNframes", 0, 1000000, 1, "%d", "The calibration ", "Calibration control");
266 
267  createStandardIndiNumber<float>( m_indiP_quantileCut, "quantile", 0, 1.0, 0.0001, "%0.3f", "The quantile cut", "Calibration control");
269 
271 
272  return 0;
273 }
274 
275 inline
277 {
278  if( shmimMonitorT::appLogic() < 0)
279  {
280  return log<software_error,-1>({__FILE__,__LINE__});
281  }
282 
283  if( frameGrabberT::appLogic() < 0)
284  {
285  return log<software_error,-1>({__FILE__,__LINE__});
286  }
287 
288  std::unique_lock<std::mutex> lock(m_indiMutex);
289 
290  if(shmimMonitorT::updateINDI() < 0)
291  {
292  log<software_error>({__FILE__, __LINE__});
293  }
294 
295  if(frameGrabberT::updateINDI() < 0)
296  {
297  log<software_error>({__FILE__, __LINE__});
298  }
299 
303 
304  return 0;
305 }
306 
307 inline
309 {
311 
313 
314  return 0;
315 }
316 
317 inline
319 {
320  static_cast<void>(dummy); //be unused
321 
324 
326  m_calibrationCube.setZero();
327 
329  m_thresholdImage.setZero();
330 
332  m_dark_image.setZero();
333 
335  m_photonCountedImage.setZero();
336 
337  // m_stack_frames = 1;
338  // calibration_steps = 1000;
339 
340  m_calibrate = false;
341  m_calibrationSet = false;
344 
345  return 0;
346 }
347 
348 inline
349 int photonCounter::processImage( void * curr_src,
350  const dev::shmimT & dummy
351  )
352 {
353  static_cast<void>(dummy); //be unused
354 
355  // Set the internal pointer to the new data stream
356  Eigen::Map<eigenImage<unsigned short>> camera_image( static_cast<unsigned short *>(curr_src), m_image_height, m_image_width);
357 
358  if(m_calibrate){
359 
361  std::cout << "Start data collection" << std::endl;
362 
363  m_calibrationSet = false;
364 
365  // Copy the data into the stream
366  for(uint32_t col_i=0; col_i < m_image_width; ++col_i){
367  for(uint32_t row_i=0; row_i < m_image_height; ++row_i){
368 
369  m_calibrationCube.image(current_calibration_iteration)(row_i, col_i) = (float)camera_image(row_i, col_i);
370 
371  }
372  }
373 
375 
377  // We have collected enough data!
379  m_calibrate = false;
380  m_calibrationSet = true;
381 
382  // Measure the average dark frame
384 
385  // Copy the data into the stream
386  for(uint32_t col_i=0; col_i < m_image_width; ++col_i){
387  for(uint32_t row_i=0; row_i < m_image_height; ++row_i){
388 
389  if( col_i == 0){
390  std::cout << "The dark : " << m_dark_image(row_i, col_i) << " ";
391  }
392 
393  // Get the time-series of the pixel of interest
394  // auto pixelVec = m_calibrationCube.pixel(row_i, col_i);
395 
396  // Dark subtract the timeseries
397  // for(size_t p=0; p<pixelVec.size(); p++){
398  // pixelVec(p, 0) = pixelVec(p, 0) - m_dark_image(row_i, col_i);
399  // }
400  std::vector<float> tempVec;
401  for(int ti=0; ti < calibration_steps; ti++){
402  float val = m_calibrationCube.image(ti)(row_i, col_i);
403  tempVec.push_back(val);
404  }
405 
406  // Make it a standard library vector
407  // float* start = &pixelVec(0,0);
408  // float* end = &pixelVec(0,0) + pixelVec.size() * sizeof(float);
409  //
410  // Sort the array
411  std::sort(tempVec.begin(), tempVec.end());
412 
413  // Find the qth quantile
414  int q_index = (int)(m_quantile_cut * tempVec.size());
415  if( col_i == 0 )
416  std::cout <<"q0: [" << tempVec[0] << ", " << tempVec[q_index] << ", " << tempVec[tempVec.size()-1] << "]" << std::endl;
417 
418  m_thresholdImage(row_i, col_i) = tempVec[q_index];
419 
420  }
421  }
422 
423  std::cout << "Calibration done!" << std::endl;
424  }
425 
426 
427  }else{
428 
429  if(m_calibrationSet){
430  // if( m_stack_frames_index == 0 )
431  // std::cout << "Start collecting data for stacking..." << std::endl;
432 
433  // Apply photon counting
434  for(uint32_t col_i=0; col_i < m_image_width; ++col_i){
435  for(uint32_t row_i=0; row_i < m_image_height; ++row_i){
436  float dI = (float)camera_image(row_i, col_i); // - m_dark_image(row_i, col_i);
437 
438  if( dI > m_thresholdImage(row_i, col_i)){
439  m_photonCountedImage(row_i, col_i) += 1.0;
440  }
441 
442  }
443  }
444 
446 
448 
449  // std::cout << "Send stacked data..." << std::endl;
451 
452  //Now tell the f.g. to get going
453  if(sem_post(&m_smSemaphore) < 0)
454  {
455  log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
456  return -1;
457  }
458 
459  }
460 
461  }
462 
463  }
464 
465  return 0;
466 }
467 
468 inline
470 {
471  std::unique_lock<std::mutex> lock(m_indiMutex);
472 
474  {
475  //This means we haven't connected to the stream to average. so wait.
476  sleep(1);
477  return -1;
478  }
479 
480  // The frame grabber has the exact same size as the imagestream
483  frameGrabberT::m_dataType = _DATATYPE_FLOAT;
484 
485  return 0;
486 }
487 
488 inline
490 {
491  return 0;
492 }
493 
494 inline
496 {
497  timespec ts;
498 
499  if(clock_gettime(CLOCK_REALTIME, &ts) < 0)
500  {
501  log<software_critical>({__FILE__,__LINE__,errno,0,"clock_gettime"});
502  return -1;
503  }
504 
505  ts.tv_sec += 1;
506 
507  if(sem_timedwait(&m_smSemaphore, &ts) == 0)
508  {
509  clock_gettime(CLOCK_REALTIME, &m_currImageTimestamp);
510  return 0;
511  }
512  else
513  {
514  return 1;
515  }
516 }
517 
518 inline
520 {
521  //Here is where we do it.
522  Eigen::Map<eigenImage<float>> photon_counted_out(static_cast<uint16_t*>(dest), frameGrabberT::m_width, frameGrabberT::m_height );
523 
524  // Copy the data into the stream
525  for(uint32_t col_i=0; col_i < m_image_width; ++col_i){
526  for(uint32_t row_i=0; row_i < m_image_height; ++row_i){
527 
528  photon_counted_out(row_i, col_i) = m_photonCountedImage(row_i, col_i);
529 
530  m_photonCountedImage(row_i, col_i) = 0.0;
531 
532  }
533  }
534 
535  return 0;
536 }
537 
538 inline
540 {
541  return 0;
542 }
543 
544 
545 INDI_NEWCALLBACK_DEFN(photonCounter, m_indiP_calibrateToggle )(const pcf::IndiProperty &ipRecv)
546 {
547  if(ipRecv.getName() != m_indiP_calibrateToggle.getName())
548  {
549  log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
550  return -1;
551  }
552 
553  if(!ipRecv.find("request")) return 0;
554 
555  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
556  {
557 
558  if(!m_calibrate){
559  std::cout << "Request calibration" << std::endl;
560  m_calibrationSet = false;
561  m_calibrate = true;
562  }
563 
564  updateSwitchIfChanged(m_indiP_calibrateToggle, "request", pcf::IndiElement::Off, INDI_IDLE);
565  }
566 
567  return 0;
568 }
569 
570 INDI_NEWCALLBACK_DEFN(photonCounter, m_indiP_quantileCut )(const pcf::IndiProperty &ipRecv)
571 {
572  if(ipRecv.getName() != m_indiP_quantileCut.getName()){
573  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
574  return -1;
575  }
576 
577  float current = -1;
578  float target = -1;
579 
580  if(ipRecv.find("current"))
581  current = ipRecv["current"].get<float>();
582 
583  if(ipRecv.find("target"))
584  target = ipRecv["target"].get<float>();
585 
586  if(target == -1) target = current;
587 
588  if(target == -1)
589  return 0;
590 
591  std::lock_guard<std::mutex> guard(m_indiMutex);
592 
593  m_quantile_cut = target;
594  updateIfChanged(m_indiP_quantileCut, "target", m_quantile_cut);
595 
596  return 0;
597 }
598 
599 INDI_NEWCALLBACK_DEFN(photonCounter, m_indiP_calibrateSteps )(const pcf::IndiProperty &ipRecv)
600 {
601  if(ipRecv.getName() != m_indiP_calibrateSteps.getName())
602  {
603  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
604  return -1;
605  }
606 
607  int current = -1;
608  int target = -1;
609 
610  if(ipRecv.find("current"))
611  {
612  current = ipRecv["current"].get<int>();
613  }
614 
615  if(ipRecv.find("target"))
616  {
617  target = ipRecv["target"].get<int>();
618  }
619 
620  if(target == -1) target = current;
621 
622  if(target == -1)
623  {
624  return 0;
625  }
626 
627  std::lock_guard<std::mutex> guard(m_indiMutex);
628 
629  calibration_steps = target;
630  m_calibrationCube.resize(m_image_width, m_image_height, calibration_steps);
631  m_calibrationCube.setZero();
632 
633  updateIfChanged(m_indiP_calibrateSteps, "target", calibration_steps);
634 
635  return 0;
636 }
637 
638 INDI_NEWCALLBACK_DEFN(photonCounter, m_indiP_stackFrames )(const pcf::IndiProperty &ipRecv)
639 {
640  if(ipRecv.getName() != m_indiP_stackFrames.getName())
641  {
642  log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
643  return -1;
644  }
645 
646  int current = -1;
647  int target = -1;
648 
649  if(ipRecv.find("current"))
650  {
651  current = ipRecv["current"].get<int>();
652  }
653 
654  if(ipRecv.find("target"))
655  {
656  target = ipRecv["target"].get<int>();
657  }
658 
659  if(target == -1) target = current;
660 
661  if(target == -1)
662  {
663  return 0;
664  }
665 
666  std::lock_guard<std::mutex> guard(m_indiMutex);
667 
668  m_stack_frames = target;
669  updateIfChanged(m_indiP_stackFrames, "target", m_stack_frames);
670 
671  return 0;
672 }
673 
674 } //namespace app
675 } //namespace MagAOX
676 
677 #endif //photonCounter_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.
Definition: MagAOXApp.hpp:3120
int createStandardIndiRequestSw(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 request element.
Definition: MagAOXApp.hpp:2573
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
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.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1804
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:545
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
int updateINDI()
Update the INDI properties for this device controller.
uint8_t m_dataType
The ImageStreamIO type code.
int appLogic()
Checks the framegrabber thread.
uint32_t m_height
The height of the image, once deinterlaced etc.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
uint32_t m_width
The width of the images in the stream.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
int updateINDI()
Update the INDI properties for this device controller.
int appLogic()
Checks the shmimMonitor thread.
uint32_t m_height
The height of the images in the stream.
int appShutdown()
Shuts down the shmimMonitor thread.
uint8_t m_dataType
The ImageStreamIO type code.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
int loadImageIntoStream(void *dest)
Implementation of the framegrabber loadImageIntoStream interface.
int processImage(void *curr_src, const dev::shmimT &dummy)
~photonCounter() noexcept
D'tor, declared and defined for noexcept.
INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_calibrateSteps)
virtual int appStartup()
Startup function.
pcf::IndiProperty m_indiP_calibrateToggle
sem_t m_smSemaphore
Semaphore used to synchronize the fg thread and the sm thread.
INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_calibrateToggle)
virtual int appLogic()
Implementation of the FSM for photonCounter.
mx::improc::eigenImage< realT > m_photonCountedImage
pcf::IndiProperty m_indiP_stackFrames
float realT
Floating point type in which to do all calculations.
int acquireAndCheckValid()
Implementation of the framegrabber acquireAndCheckValid interface.
mx::improc::eigenImage< realT > m_thresholdImage
virtual int appShutdown()
Shutdown the app.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
int configureAcquisition()
Implementation of the framegrabber configureAcquisition interface.
dev::shmimMonitor< photonCounter > shmimMonitorT
int reconfig()
Implementation of the framegrabber reconfig interface.
dev::frameGrabber< photonCounter > frameGrabberT
pcf::IndiProperty m_indiP_calibrateSteps
mx::improc::eigenImage< realT > m_dark_image
INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_stackFrames)
mx::improc::eigenCube< realT > m_calibrationCube
INDI_NEWCALLBACK_DECL(photonCounter, m_indiP_quantileCut)
pcf::IndiProperty m_indiP_quantileCut
int allocate(const dev::shmimT &dummy)
int startAcquisition()
Implementation of the framegrabber startAcquisition interface.
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:208
@ OPERATING
The device is operating, other than homing.
Definition: stateCodes.hpp:55
#define INDI_IDLE
Definition: indiUtils.hpp:28
std::ostream & cout()
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:212
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
updateIfChanged(m_indiP_angle, "target", m_angle)
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition: dm.hpp:24
Software ERR log entry.