API
dmPokeCenter.hpp
Go to the documentation of this file.
1 /** \file dmPokeCenter.hpp
2  * \brief The MagAO-X DM Poke Centering header file
3  *
4  * \ingroup dmPokeCenter_files
5  */
6 
7 
8 
9 #ifndef dmPokeCenter_hpp
10 #define dmPokeCenter_hpp
11 
12 #include <mx/improc/eigenImage.hpp>
13 #include <mx/improc/milkImage.hpp>
14 #include <mx/improc/circleOuterpix.hpp>
15 #include <mx/improc/imageFilters.hpp>
16 using namespace mx::improc;
17 
18 #include <mx/math/fit/fitGaussian.hpp>
19 
20 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
21 #include "../../magaox_git_version.h"
22 
23 
24 /** \defgroup dmPokeCenter
25  * \brief The MagAO-X application to center a DM pupil by poking actuators
26  *
27  * <a href="../handbook/operating/software/apps/dmPokeCenter.html">Application Documentation</a>
28  *
29  * \ingroup apps
30  *
31  */
32 
33 struct wfsShmimT
34 {
35  static std::string configSection()
36  {
37  return "wfscam";
38  };
39 
40  static std::string indiPrefix()
41  {
42  return "wfscam";
43  };
44 };
45 
46 /** \defgroup dmPokeCenter_files
47  * \ingroup dmPokeCenter
48  */
49 
50 namespace MagAOX
51 {
52 namespace app
53 {
54 
55 /// The MagAO-X DM Pupil Centering Application
56 /**
57  * \ingroup dmPokeCenter
58  */
59 class dmPokeCenter : public MagAOXApp<true>, public dev::shmimMonitor<dmPokeCenter, wfsShmimT>, public dev::telemeter<dmPokeCenter>
60 {
61  //Give the test harness access.
62  friend class dmPokeCenter_test;
63 
65 
66  friend class dev::telemeter<dmPokeCenter>;
67 
69 
71 
72 protected:
73 
74  /** \name Configurable Parameters
75  *@{
76  */
77 
78  std::string m_wfsCamDevName; ///<INDI device name of the WFS camera. Default is wfscam.shmimName.
79 
80  double m_wfsSemWait {1.5}; ///< The time in sec to wait on the WFS semaphore. Default 0.5 sec.
81 
82  double m_imageSemWait {0.5}; ///< The time in sec to wait on the image semaphore. Default 0.5 sec.
83 
84  unsigned m_nDarks {5}; ///< The number of images to average for the dark. Default is 5.
85 
86  unsigned m_nPupilImages {20}; ///< The number of images to average for the pupil image. Default is 20.
87 
88  unsigned m_nPokeImages {5}; ///< The number of images to average for the poke images. Default is 5.
89 
90  std::string m_dmChan;
91 
92  std::vector<int> m_poke_x;
93  std::vector<int> m_poke_y;
94 
95  float m_poke_amp {0.0};
96 
97  float m_dmSleep {10000}; ///<The time to sleep for the DM command to be applied, in microseconds. Default is 10000.
98 
99  // Pupil fitting:
100  int m_pupilPixels {68600}; ///< The number of pixels in the pupil. Default is 68600.
101 
102  int m_pupilCutBuff {20}; ///< The buffer around the initial found-pupil to include in the cut image. />= 0, default 20.
103 
104  float m_pupilMag {10}; ///< The magnification to apply to the pupil image. />= 1, default 10.
105 
106  float m_pupilMedThresh = {0.9}; ///< Threshold in the magnified image as a fraction of the median. />0, /<=1, default 0.9.
107 
108  int m_pokeBlockW {64}; ///< The size of the sub-image for the poke analysis
109 
110  int m_pokeFWHMGuess {2}; ///< The initial guess for the FWHM of the Gaussian fit to the poke.
111 
112  float m_smoothWidth {3}; ///< Median smoothing kernal width
113 
114  ///@}
115 
116  std::mutex m_wfsImageMutex;
117 
118  milkImage<float> m_rawImage;
119 
120  milkImage<float> m_wfsDark;
121 
122  milkImage<float> m_pupilImage;
123 
124  milkImage<float> m_pokeImage;
125 
126  float (*wfsPixget)(void *, size_t) {nullptr}; ///< Pointer to a function to extract the image data as float
127 
128  float m_wfsFps {-1}; ///< The WFS camera FPS
129 
130  int m_shutter {-1}; ///< Shutter status. -1 is unknown, 0 open, 1 shut.
131 
132  milkImage<float> m_dmStream;
133 
134  eigenImage<float> m_dmImage;
135 
136  //Working memory for pupil fitting
137  eigenImage<float> m_pupilCopy;
138  eigenImage<float> m_fullEdge;
139  eigenImage<float> m_fullMask;
140  eigenImage<float> m_cutEdge;
141  eigenImage<float> m_cutMask;
142  eigenImage<float> m_pupilCut;
143  eigenImage<float> m_pupilMagnified;
144  eigenImage<float> m_magMask;
145  eigenImage<float> m_magEdge;
146 
147  float m_pupilX {0};
148  float m_pupilY {0};
149 
150  //Working memory for poke fitting
151  mx::math::fit::fitGaussian2Dsym<float> m_gfit;
152  eigenImage<float> m_pokeBlock;
153 
154  float m_pokeX {0};
155  float m_pokeY {0};
156 
157  int32_t m_counter {0};
158 public:
159  /// Default c'tor.
160  dmPokeCenter();
161 
162  /// D'tor, declared and defined for noexcept.
163  ~dmPokeCenter() noexcept
164  {}
165 
166  /**\name MagAOX Interface
167  *
168  * @{
169  */
170  virtual void setupConfig();
171 
172  /// Implementation of loadConfig logic, separated for testing.
173  /** This is called by loadConfig().
174  */
175  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
176 
177  virtual void loadConfig();
178 
179  /// Startup function
180  /**
181  *
182  */
183  virtual int appStartup();
184 
185  /// Implementation of the FSM for dmPokeCenter.
186  /**
187  * \returns 0 on no critical error
188  * \returns -1 on an error requiring shutdown
189  */
190  virtual int appLogic();
191 
192  /// Shutdown the app.
193  /**
194  *
195  */
196  virtual int appShutdown();
197 
198  ///@}
199 
200  /** \name shmimMonitor Interface
201  * @{
202  */
203 
204  int allocate( const wfsShmimT & /**< [in] tag to differentiate shmimMonitor parents.*/);
205 
206  int processImage( void * curr_src, ///< [in] pointer to the start of the current frame
207  const wfsShmimT & ///< [in] tag to differentiate shmimMonitor parents.
208  );
209  ///@}
210 
211  /** \name WFS Thread
212  * This thread coordinates the WFS process
213  *
214  * @{
215  */
216 protected:
217 
218  int m_wfsThreadPrio {1}; ///< Priority of the WFS thread, should normally be > 00.
219 
220  std::string m_wfsCpuset; ///< The cpuset for the framegrabber thread. Ignored if empty (the default).
221 
222  std::thread m_wfsThread; ///< A separate thread for the actual WFSing
223 
224  bool m_wfsThreadInit {true}; ///< Synchronizer to ensure wfs thread initializes before doing dangerous things.
225 
226  pid_t m_wfsThreadID {0}; ///< WFS thread PID.
227 
228  pcf::IndiProperty m_wfsThreadProp; ///< The property to hold the WFS thread details.
229 
230  ///Thread starter, called by wfsThreadStart on thread construction. Calls wfsThreadExec.
231  static void wfsThreadStart( dmPokeCenter * s /**< [in] a pointer to an streamWriter instance (normally this) */);
232 
233  /// Execute the frame grabber main loop.
234  void wfsThreadExec();
235 
236  sem_t m_wfsSemaphore; ///< Semaphore used to signal the WFS thread to start WFSing.
237 
238  unsigned m_wfsSemWait_sec {1}; ///< The timeout for the WFS semaphore, seconds component.
239 
240  unsigned m_wfsSemWait_nsec {0}; ///< The timeoutfor the WFS semaphore, nanoseconds component.
241 
242  int m_measuring {0}; ///< Status of measuring: 0 no, 1 single in progress, 2 continuous in progress.
243 
244  bool m_single {false}; ///< True a single measurement is in progress.
245 
246  bool m_continuous {false}; ///< True if continuous measurements are in progress.
247 
248  bool m_stopMeasurement {false}; ///< Used to request that the measurement in progress stop.
249 
250  ///@}
251 
252  sem_t m_imageSemaphore; ///< Semaphore used to signal that an image is ready
253 
254  unsigned m_imageSemWait_sec {1}; ///< The timeout for the image semaphore, seconds component.
255 
256  unsigned m_imageSemWait_nsec {0}; ///< The timeout for the image semaphore, nanoseconds component.
257 
258  /// Run the sensor steps
259  /** Coordinates the actions of poking and collecting images.
260  * Upon completion this calls runSensor. If \p firstRun == true, then this takes a dark.
261  *
262  * \returns 0 on success
263  * \returns < 0 on an error
264  */
265  int runSensor(bool firstRun /**< [in] flag indicating this is the first call. triggers taking a dark if true.*/);
266 
267  /// Analyze the images
268  /** Calls fitPupil and fitPokes and updates INDI.
269  *
270  * \returns 0 on success
271  * \returns < 0 on an error
272  */
273  int analyzeSensor();
274 
275  /// Fit the pupil parameters
276  /**
277  * \returns 0 on success
278  * \returns < 0 on an error
279  */
280  int fitPupil();
281 
282  /// Fit the poke parameters
283  /**
284  * \returns 0 on success
285  * \returns < 0 on an error
286  */
287  int fitPokes();
288 
289  /** \name INDI Interface
290  * @{
291  */
292 protected:
293 
294  pcf::IndiProperty m_indiP_poke_amp;
296 
297  pcf::IndiProperty m_indiP_nPupilImages;
298  INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_nPupilImages);
299 
300  pcf::IndiProperty m_indiP_nPokeImages;
301  INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_nPokeImages);
302 
303  pcf::IndiProperty m_indiP_wfsFps; ///< Property to get the FPS from the WFS camera
305 
306  pcf::IndiProperty m_indiP_shutter; ///< Property to get the status from the WFS camera
308 
309  pcf::IndiProperty m_indiP_single; ///< Property to start a single measurement
311 
312  pcf::IndiProperty m_indiP_continuous; ///< Property to start continuous measurement
313  INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_continuous);
314 
315  pcf::IndiProperty m_indiP_stop; ///< Property to request that measurement stop
317 
318 
319  pcf::IndiProperty m_indiP_pupilPos; ///< Property to report the pupil position
320 
321  pcf::IndiProperty m_indiP_pokePos; ///< Property to report the poke positions
322  std::vector<std::string> m_pokePosEls; ///< Vector of element names for easy calls to UpdateIfChanged. One per poke, plus last two are for the average.
323  std::vector<float> m_pokePositions; ///< Vector of positions for easy calls to UpdateIfChanged. One per poke, plus last two are for the average.
324 
325  pcf::IndiProperty m_indiP_deltaPos; ///< Property to report the difference in pupil and average poke position
326 
327  ///@}
328 
329  /** \name Telemeter Interface
330  *
331  * @{
332  */
333  int checkRecordTimes();
334 
335  int recordTelem( const telem_pokecenter * );
336 
337  int recordPokeCenter( bool force = false );
338  ///@}
339 };
340 
341 dmPokeCenter::dmPokeCenter() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
342 {
343  return;
344 }
345 
347 {
349  telemeterT::setupConfig(config);
350 
351  config.add("wfscam.camDevName", "", "wfscam.camDevName", argType::Required, "wfs", "camDevName", false, "string", "INDI device name of the WFS camera. Default is wfscam.shmimName.");
352  config.add("wfscam.loopSemWait", "", "wfscam.loopSemWait", argType::Required, "wfs", "loopSemWait", false, "float", "The semaphore wait time for the wfs loop start signal");
353  config.add("wfscam.imageSemWait", "", "wfscam.imageSemWait", argType::Required, "wfs", "imageSemWait", false, "float", "The semaphore wait time for the image availability signal");
354 
355  config.add("pokecen.dmChannel", "", "pokecen.dmChannel", argType::Required, "pokecen", "dmChannel", false, "string", "The dm channel to use for pokes, e.g. dm01disp06.");
356  config.add("pokecen.pokeX", "", "pokecen.pokeX", argType::Required, "pokecen", "pokeX", false, "vector<int>", "The x-coordinates of the actuators to poke. ");
357  config.add("pokecen.pokeY", "", "pokecen.pokeY", argType::Required, "pokecen", "pokeY", false, "vector<int>", "The y-coordinates of the actuators to poke. ");
358  config.add("pokecen.pokeAmp", "", "pokecen.pokeAmp", argType::Required, "pokecen", "pokeAmp", false, "float", "The poke amplitude, in DM command units. Default is 0.");
359  config.add("pokecen.dmSleep", "", "pokecen.dmSleep", argType::Required, "pokecen", "dmSleep", false, "float", "The time to sleep for the DM command to be applied, in microseconds. Default is 10000.");
360  config.add("pokecen.nPokeImages", "", "pokecen.nPokeImages", argType::Required, "pokecen", "nPokeImages", false, "int", "The number of poke images to average. Default 5.");
361  config.add("pokecen.nPupilImages", "", "pokecen.nPupilImages", argType::Required, "pokecen", "nPupilImages", false, "int", "The number of pupil images to average. Default 20.");
362  config.add("pokecen.pupilPixels", "", "pokecen.pupilPixels", argType::Required, "pokecen", "pupilPixels", false, "int", "The number of pixels in the pupil. Default is 68600.");
363  config.add("pokecen.pupilCutBuff", "", "pokecen.pupilCutBuff", argType::Required, "pokecen", "pupilCutBuff", false, "int", "The buffer around the initial found-pupil to include in the cut image. />= 0, default 20.");
364  config.add("pokecen.pupilMag", "", "pokecen.pupilMag", argType::Required, "pokecen", "pupilMag", false, "float", "The magnification to apply to the pupil image. >= 1, default 10.");
365  config.add("pokecen.pupilMedThresh", "", "pokecen.pupilMedThresh", argType::Required, "pokecen", "pupilMedThresh", false, "float", "Threshold in the magnified image as a fraction of the median. >0, <=1, default 0.9.");
366  config.add("pokecen.pokeBlockW", "", "pokecen.pokeBlockW", argType::Required, "pokecen", "pokeBlockW", false, "int", "The size of the sub-image for the poke analysis");
367  config.add("pokecen.pokeFWHMGuess", "", "pokecen.pokeFWHMGuess", argType::Required, "pokecen", "pokeFWHMGuess", false, "int", "The initial guess for the FWHM of the Gaussian fit to the poke.");
368 }
369 
370 int dmPokeCenter::loadConfigImpl( mx::app::appConfigurator & _config )
371 {
372  shmimMonitorT::loadConfig(_config);
373  telemeterT::loadConfig(_config);
374 
376  _config(m_wfsCamDevName, "wfscam.camDevName");
377 
378  //configure the semaphore waits
379  _config(m_wfsSemWait, "wfscam.loopSemWait");
380 
383 
384  _config(m_imageSemWait, "wfscam.imageSemWait");
385 
388 
389  _config(m_dmChan, "pokecen.dmChannel");
390 
391  _config(m_poke_x, "pokecen.pokeX");
392 
393  _config(m_poke_y, "pokecen.pokeY");
394 
395  if(m_poke_x.size() == 0 || (m_poke_x.size() != m_poke_y.size()))
396  {
397  return log<software_error,-1>({__FILE__, __LINE__, "invalid poke specification"});
398  }
399 
400  _config(m_poke_amp, "pokecen.pokeAmp");
401 
402  _config(m_dmSleep, "pokecen.dmSleep");
403 
404  _config(m_nPokeImages, "pokecen.nPokeImages");
405  _config(m_nPupilImages, "pokecen.nPupilImages");
406  _config(m_pupilPixels, "pokecen.pupilPixels");
407  _config(m_pupilCutBuff, "pokecen.pupilCutBuff");
408  _config(m_pupilMag, "pokecen.pupilMag");
409  _config(m_pupilMedThresh, "pokecen.pupilMedThresh");
410  _config(m_pokeBlockW, "pokecen.pokeBlockW");
411  _config(m_pokeFWHMGuess, "pokecen.pokeFWHMGuess");
412 
413  return 0;
414 }
415 
417 {
418  if( loadConfigImpl(config)< 0)
419  {
420  m_shutdown = true;
421  }
422 }
423 
425 {
426  if( shmimMonitorT::appStartup() < 0)
427  {
428  return log<software_error, -1>({__FILE__,__LINE__});
429  }
430 
431  if(telemeterT::appStartup() < 0)
432  {
433  return log<software_error,-1>({__FILE__, __LINE__});
434  }
435 
436  CREATE_REG_INDI_NEW_NUMBERF(m_indiP_poke_amp, "poke_amp", -1, 1, 1e-1, "%0.01f", "", "");
437  m_indiP_poke_amp["current"].setValue(m_poke_amp);
438  m_indiP_poke_amp["target"].setValue(m_poke_amp);
439 
440  CREATE_REG_INDI_NEW_NUMBERI(m_indiP_nPupilImages, "nPupilImages", 1, 1000, 1, "%d", "", "");
441  m_indiP_nPupilImages["current"].setValue(m_nPupilImages);
442  m_indiP_nPupilImages["target"].setValue(m_nPupilImages);
443 
444  CREATE_REG_INDI_NEW_NUMBERI(m_indiP_nPokeImages, "nPokeImages", 1, 1000, 1, "%d", "", "");
445  m_indiP_nPokeImages["current"].setValue(m_nPokeImages);
446  m_indiP_nPokeImages["target"].setValue(m_nPokeImages);
447 
448  REG_INDI_SETPROP(m_indiP_wfsFps, m_wfsCamDevName, std::string("fps"));
449 
450  REG_INDI_SETPROP(m_indiP_shutter, m_wfsCamDevName, std::string("shutter"));
451 
453 
455 
457 
458  registerIndiPropertyReadOnly( m_indiP_pupilPos, "pupil_position", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
459  m_indiP_pupilPos.add({"x", 0.0});
460  m_indiP_pupilPos.add({"y", 0.0});
461 
462  registerIndiPropertyReadOnly( m_indiP_pokePos, "poke_position", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
463  m_indiP_pokePos.add({"avg_x", 0.0});
464  m_indiP_pokePos.add({"avg_y", 0.0});
465  for(size_t n = 0; n < m_poke_x.size(); ++n)
466  {
467  std::string pstr = "poke" + std::to_string(n) + "_";
468  m_indiP_pokePos.add({pstr + "x", 0.0});
469  m_indiP_pokePos.add({pstr + "y", 0.0});
470  m_pokePosEls.push_back(pstr + "x");
471  m_pokePosEls.push_back(pstr + "y");
472  }
473 
474  m_pokePosEls.push_back("avg_x"); //keep vector of element names for UpdateIfChanged
475  m_pokePosEls.push_back("avg_y");
476 
477  m_pokePositions.resize(m_pokePosEls.size());
478 
479  registerIndiPropertyReadOnly( m_indiP_deltaPos, "measurement", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
480  m_indiP_deltaPos.add({"delta_x", 0.0});
481  m_indiP_deltaPos.add({"delta_y", 0.0});
482  m_indiP_deltaPos.add({"counter", 0});
483 
484  if(sem_init(&m_wfsSemaphore, 0,0) < 0)
485  {
486  return log<software_critical, -1>({__FILE__, __LINE__, errno,0, "Initializing wfs semaphore"});
487  }
488 
489  if(sem_init(&m_imageSemaphore, 0,0) < 0)
490  {
491  return log<software_critical, -1>({__FILE__, __LINE__, errno,0, "Initializing image semaphore"});
492  }
493 
495  {
496  return log<software_critical,-1>({__FILE__, __LINE__});
497  }
498 
500 
501  return 0;
502 }
503 
505 {
506  if( shmimMonitorT::appLogic() < 0)
507  {
508  return log<software_error, -1>({__FILE__,__LINE__});
509  }
510 
511  if( telemeterT::appLogic() < 0)
512  {
513  return log<software_error,-1>({__FILE__,__LINE__});
514  }
515 
516  //first do a join check to see if other threads have exited.
517  //these will throw if the threads are really gone
518  try
519  {
520  if(pthread_tryjoin_np(m_wfsThread.native_handle(),0) == 0)
521  {
522  log<software_error>({__FILE__, __LINE__, "WFS thread has exited"});
523  return -1;
524  }
525  }
526  catch(...)
527  {
528  log<software_error>({__FILE__, __LINE__, "WFS thread has exited"});
529  return -1;
530  }
531 
532  if(m_measuring > 0)
533  {
534  if(m_continuous)
535  {
536  updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::On, INDI_OK);
537  }
538  else
539  {
540  updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
541  }
542 
543  if(m_single)
544  {
545  updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::On, INDI_OK);
546  }
547  else
548  {
549  updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
550  }
551  }
552  else
553  {
554  updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
555  updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
556  }
557 
561 
562  return 0;
563 }
564 
566 {
569 
570  try
571  {
572  if(m_wfsThread.joinable())
573  {
574  m_wfsThread.join();
575  }
576  }
577  catch(...){}
578 
579  return 0;
580 }
581 
583 {
584  static_cast<void>(dummy); //be unused
585 
586  //This is a call to the pokeSensor::allocate, unless we can have dev::pokeSensor : public shmimMonitor<pokeSensor>
587  std::unique_lock<std::mutex> lock(m_wfsImageMutex);
588 
590 
592 
594 
595  wfsPixget = getPixPointer<float>(shmimMonitorT::m_dataType);
596 
597  try
598  {
599  m_dmStream.open(m_dmChan);
600  }
601  catch(const std::exception& e) //this can check for invalid_argument and distinguish not existing
602  {
603  return log<software_error,-1>({__FILE__, __LINE__, std::string("exception opening DM: ") + e.what()});
604  }
605 
606  m_dmImage.resize(m_dmStream.rows(), m_dmStream.cols());
607 
608  //end of call to pokeSensor::allocate
609 
611 
612 
613  return 0;
614 }
615 
616 int dmPokeCenter::processImage( void * curr_src,
617  const wfsShmimT & dummy
618  )
619 {
620  static_cast<void>(dummy); //be unused
621 
622  float * data = m_rawImage().data();
623 
624  //Copy the data out as float no matter what type it is
625  for(unsigned nn=0; nn < shmimMonitorT::m_width*shmimMonitorT::m_height; ++nn)
626  {
627  data[nn] = wfsPixget(curr_src, nn);
628  }
629 
630  if(sem_post(&m_imageSemaphore) < 0)
631  {
632  return log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
633  }
634 
635  return 0;
636 }
637 
638 inline
640 {
641  d->wfsThreadExec();
642 }
643 
644 
645 inline
647 {
648  m_wfsThreadID = syscall(SYS_gettid);
649 
650  //Wait fpr the thread starter to finish initializing this thread.
651  while(m_wfsThreadInit == true && m_shutdown == 0)
652  {
653  sleep(1);
654  }
655 
656  while(m_shutdown == 0)
657  {
658  timespec ts;
660 
662 
663  //Lock a mutex here
664  if(m_single)
665  {
666  m_measuring = 1;
667  }
668  else if(m_continuous)
669  {
670  m_measuring = 2;
671  }
672  else
673  {
674  m_measuring = 0;
675  return;
676  }
677 
679 
680  m_stopMeasurement = false;
681 
682  bool firstRun = true;
683 
684  while(!m_stopMeasurement && !m_shutdown)
685  {
686  if( runSensor(firstRun) < 0)
687  {
688  log<software_error>({__FILE__, __LINE__, "runSensor returned error"});
689  break;
690  }
691 
692  firstRun = false;
693 
694  if(m_measuring == 1)
695  {
696  break;
697  }
698  }
699 
700  m_measuring = 0;
701  m_single = 0;
702  m_continuous = 0;
703 
705 
706 
707  } //outer loop, will exit if m_shutdown==true
708 
709  return;
710 
711 }
712 
713 inline
714 int dmPokeCenter::runSensor(bool firstRun)
715 {
716 
717  mx::fits::fitsFile<float> tmpFF;
718 
719  timespec ts;
720 
721  //Wait two seconds for it to shut
722  ///\todo this should be configurable and based on fps
723  unsigned n = 0;
724  while(!m_wfsDark.valid() && n < 200)
725  {
726  mx::sys::milliSleep(10);
727  ++n;
728  }
729 
730  if(!m_wfsDark.valid())
731  {
732  return log<software_error, -1>({__FILE__,__LINE__, "not allocated"});
733  }
734 
735  if(firstRun)
736  {
737  //Shut the shutter
738  if( sendNewStandardIndiToggle(m_wfsCamDevName, "shutter", true) < 0)
739  {
740  return log<software_error,-1>({__FILE__,__LINE__});
741  }
742 
743  //Wait two seconds for it to shut
744  ///\todo this should be configurable
745  n = 0;
746  while(m_shutter != 1 && n < 200)
747  {
748  mx::sys::milliSleep(10);
749  ++n;
750  }
751 
752  if(m_shutter != 1)
753  {
754  return log<software_error,-1>({__FILE__,__LINE__, "shutter did not shut"});
755  }
756 
757  m_wfsDark().setZero();
758  n = 0;
759 
760  //flush semaphore so we take the _next_ good image
762 
763  //** And wait one image **//
765  bool ready = false;
766  while(!ready)
767  {
769  else
770  {
771  ready = true;
772  }
773 
775  {
776  m_dmImage.setZero();
778  return 0;
779  }
780  }
781 
782  while(n < m_nDarks && !m_stopMeasurement && !m_shutdown)
783  {
786 
787  //If here we got an image
788  m_wfsDark() += m_rawImage();
789  ++n;
790  }
791 
792  m_wfsDark() /= m_nDarks;
793 
794  }
795 
796 
797  //Open the shutter
798  if( sendNewStandardIndiToggle(m_wfsCamDevName, "shutter", false) < 0)
799  {
800  return log<software_error>({__FILE__,__LINE__});
801  }
802 
803  //Wait two seconds for it to open
804  ///\todo this should be configurable
805  n = 0;
806  while(m_shutter != 0 && n < 200)
807  {
808  mx::sys::milliSleep(10);
809  ++n;
810  }
811 
812  if(m_shutter != 0)
813  {
814  return log<software_error,-1>({__FILE__,__LINE__, "shutter did not open"});
815  }
816 
817  //** Now we record the pupil image **//
818  m_pupilImage.setWrite();
819  m_pupilImage().setZero();
820  n = 0;
821 
822  //flush semaphore so we take the _next_ good image
824 
825  //** And wait one image **//
827  bool ready = false;
828  while(!ready)
829  {
831  else
832  {
833  ready = true;
834  }
835 
837  {
838  m_dmImage.setZero();
840  return 0;
841  }
842  }
843 
844  while(n < m_nPupilImages && !m_stopMeasurement && !m_shutdown)
845  {
848 
849  //If here we got an image. m_rawImage will have been updated
850  m_pupilImage() += m_rawImage();
851  ++n;
852  }
853 
855  m_pupilImage.post();
856 
857  tmpFF.write("/tmp/pupilImage.fits", m_pupilImage());
858 
859  m_dmImage.setZero();
860 
861  for(size_t nn = 0; nn < m_poke_x.size(); ++nn)
862  {
863  m_dmImage( m_poke_x[nn], m_poke_y[nn]) = m_poke_amp;
864  }
865 
866  m_pokeImage.setWrite();
867  m_pokeImage().setZero();
868 
870 
871  mx::sys::microSleep(m_dmSleep);
872 
873  //flush semaphore so we take the _next_ good image
875 
876  //** And wait one image **//
878  ready = false;
879  while(!ready)
880  {
882  else
883  {
884  ready = true;
885  }
886 
888  {
889  m_dmImage.setZero();
891  return 0;
892  }
893  }
894 
895  n = 0;
896  while(n < m_nPokeImages && !m_stopMeasurement && !m_shutdown)
897  {
898  /* POSITIVE POKE */
899 
900  //** Now we record the poke image **//
903 
904  //If here, we got an image. m_rawImage will have been updated
905  m_pokeImage() += m_rawImage();
906 
907  ++n;
908  }
909 
911  {
912  m_dmImage.setZero();
914  return 0;
915  }
916 
917  m_dmImage.setZero();
918 
919  for(size_t nn = 0; nn < m_poke_x.size(); ++nn)
920  {
921  m_dmImage( m_poke_x[nn], m_poke_y[nn]) = -m_poke_amp;
922  }
923 
925 
926  mx::sys::microSleep(m_dmSleep);
927 
928  //flush semaphore so we take the _next_ good image
930 
931  //** And wait one image **//
933  ready = false;
934  while(!ready)
935  {
937  else
938  {
939  ready = true;
940  }
941 
943  {
944  m_dmImage.setZero();
946  return 0;
947  }
948  }
949 
950  n = 0;
951  while(n < m_nPokeImages && !m_stopMeasurement && !m_shutdown)
952  {
953  /* NEGATIVE POKE */
954 
955  //** Now we record the poke image **//
958 
959  //If here, we got an image. m_rawImage will have been updated
960  m_pokeImage() -= m_rawImage();
961 
962  ++n;
963  }
964 
966  {
967  m_dmImage.setZero();
969 
970  return 0;
971  }
972 
973  m_pokeImage() = m_pokeImage()/(2);
974  m_pokeImage.post();
975 
976  tmpFF.write("/tmp/poke.fits", m_pokeImage());
977 
978 
979  m_dmImage.setZero();
981 
982  return analyzeSensor();
983 }
984 
985 inline
987 {
989  {
990  return 0;
991  }
992 
993  if(fitPupil() < 0)
994  {
995  return log<software_error,-1>({__FILE__, __LINE__, "error from fitPupil"});
996  }
997 
998  if(fitPokes() < 0)
999  {
1000  return log<software_error,-1>({__FILE__, __LINE__, "error from fitPupil"});
1001  }
1002 
1003  if(m_stopMeasurement)
1004  {
1005  return 0;
1006  }
1007 
1008  ++m_counter;
1009 
1010  std::cerr << m_counter << " " << m_pupilX << " " << m_pupilY << " " << m_pokeX << " " << m_pokeY << " | " << m_pupilX - m_pokeX << " " << m_pupilY - m_pokeY << "\n";
1011 
1012  m_indiP_deltaPos["delta_x"] = m_pupilX - m_pokeX;
1013  m_indiP_deltaPos["delta_y"] = m_pupilY - m_pokeY;
1014 
1016 
1017  recordPokeCenter();
1018 
1019  return 0;
1020 }
1021 
1022 inline
1024 {
1025 
1026  mx::fits::fitsFile<float> ff;
1027 
1028  float threshPerc = (1.0*m_pupilPixels)/(m_pupilImage().rows() * m_pupilImage().cols());
1029 
1030  //Threshold to find the initial pupil mask geometrically
1031  size_t pos = (1-threshPerc)*m_pupilImage().rows()*m_pupilImage().cols();
1032 
1033  eigenImage<float> sm;
1034  int xmx, ymx;
1035  float mx;
1036 
1037  float m_pupSmoothWidth=3;
1038  sm.resize(m_pupilImage().rows(), m_pupilImage().cols());
1039  medianSmooth(sm, xmx, ymx, mx, m_pupilImage(), m_pupSmoothWidth);
1040 
1041  //Above can put in wild vals within smooth width
1042  for(int n =0; n < m_pupSmoothWidth; ++n)
1043  {
1044  sm.row(n) = 0;
1045  sm.row(sm.rows()-1-n) = 0;
1046  sm.col(n) = 0;
1047  sm.col(sm.cols()-1-n) = 0;
1048  }
1049 
1050  m_pupilCopy = sm;;
1051 
1052  std::nth_element(m_pupilCopy.data(), m_pupilCopy.data()+pos, m_pupilCopy.data()+m_pupilCopy.rows()*m_pupilCopy.cols());
1053 
1054  float pupilThresh = m_pupilCopy.data()[pos];
1055 
1056  float x0, y0, avgr0, avgx, avgy, avgr;
1057 
1058 
1059  m_fullMask.resize(sm.rows(), sm.cols());
1060 
1061  for(int cc=0; cc < m_fullMask.cols(); ++cc)
1062  {
1063  for(int rr=0; rr < m_fullMask.rows(); ++rr)
1064  {
1065  if(sm(rr,cc) < pupilThresh)
1066  {
1067  m_fullMask(rr,cc) = 0;
1068  }
1069  else
1070  {
1071  m_fullMask(rr,cc) = 1;
1072  }
1073  }
1074  }
1075 
1076  ff.write("/tmp/fullMask.fits", m_fullMask);
1077 
1078  //Now find the outer edge
1079  if(circleOuterpix( x0, y0, avgr0, avgx, avgy, avgr, m_fullEdge, m_fullMask) < 0)
1080  {
1081  return log<software_error, -1>({__FILE__, __LINE__, "circle fit failed"});
1082  }
1083 
1084  //And cut out the pupil plust buffer
1085  float cutx = avgx - avgr-m_pupilCutBuff;
1086  float cuty = avgy - avgr-m_pupilCutBuff;
1087  float cutw = 2*avgr+2*m_pupilCutBuff;
1088 
1089  if(cutx < 0)
1090  {
1091  return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1092  }
1093 
1094  if(cuty < 0)
1095  {
1096  return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1097  }
1098 
1099  if(cutx + cutw > sm.rows())
1100  {
1101  return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1102  }
1103 
1104  if(cuty + cutw > sm.rows())
1105  {
1106  return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1107  }
1108 
1109  m_pupilCut = sm.block( cutx, cuty, cutw, cutw);
1110 
1112 
1113  imageMagnify(m_pupilMagnified, m_pupilCut, mx::improc::bilinearTransform<float>());
1114 
1115  ff.write("/tmp/pupilMagnified.fits", m_pupilMagnified);
1116 
1117  float med = imageMedian(m_pupilMagnified); /// \todo use work version
1118 
1119  float dthresh = pupilThresh; //med * m_pupilMedThresh;
1120 
1121  m_magMask.resize(m_pupilMagnified.rows(), m_pupilMagnified.cols()); //This is a different mask-- maskMag
1122 
1123  for(int cc=0; cc < m_pupilMagnified.cols(); ++cc)
1124  {
1125  for(int rr=0; rr < m_pupilMagnified.rows(); ++rr)
1126  {
1127  if(m_pupilMagnified(rr,cc) < dthresh)
1128  {
1129  m_magMask(rr,cc) = 0;
1130  }
1131  else
1132  {
1133  m_magMask(rr,cc) = 1;
1134  }
1135  }
1136  }
1137 
1138  ff.write("/tmp/magMask.fits", m_magMask);
1139 
1140  if(circleOuterpix( x0, y0, avgr0, avgx, avgy, avgr, m_magEdge, m_magMask) < 0)
1141  {
1142  return log<software_error, -1>({__FILE__, __LINE__, "circle fit failed"});
1143  }
1144 
1145  ff.write("/tmp/magEdge.fits", m_magEdge);
1146 
1147 
1148  x0 = cutx + x0/m_pupilMag;
1149  y0 = cuty + y0/m_pupilMag;
1150  avgr0 /= m_pupilMag;
1151 
1152  avgx = cutx + avgx/m_pupilMag;
1153  avgy = cuty + avgy/m_pupilMag;
1154  avgr /= m_pupilMag;
1155 
1156  m_pupilX = avgx;
1157  m_pupilY = avgy;
1158 
1159  updateIfChanged(m_indiP_pupilPos, std::vector<std::string>({"x", "y"}), std::vector<float>({m_pupilX, m_pupilY}));
1160 
1161  return 0;
1162 
1163 }
1164 
1165 inline
1167 {
1168  eigenImage<float> sm, tim;
1169 
1170  sm.resize(m_pokeImage.rows(), m_pokeImage.cols());
1171 
1172  int xmx = 0;
1173  int ymx = 0;
1174 
1175  float mx = 0;
1176 
1177  medianSmooth(sm, xmx, ymx, mx, m_pokeImage(), m_smoothWidth);
1178 
1179  //Above can put in wild vals within smooth width
1180  for(int n =0; n < m_smoothWidth; ++n)
1181  {
1182  sm.row(n) = 0;
1183  sm.row(sm.rows()-1-n) = 0;
1184  sm.col(n) = 0;
1185  sm.col(sm.cols()-1-n) = 0;
1186  }
1187 
1188  mx::fits::fitsFile<float> ff;
1189 
1190  ff.write("/tmp/sm.fits", sm);
1191  m_pokeX = 0;
1192  m_pokeY = 0;
1193 
1194  for(size_t nn = 0; nn < m_poke_x.size(); ++nn)
1195  {
1196  //bool good = false;
1197 
1198  int x0, y0;
1199  /*while( good == false)
1200  {*/
1201  mx = sm.maxCoeff(&xmx, &ymx);
1202 
1203  x0 = xmx - m_pokeBlockW/2;
1204  y0 = ymx - m_pokeBlockW/2;
1205 
1206  /* if(x0 >= 0 && x0 < sm.rows() && y0 >= 0 && y0 < sm.cols())
1207  {
1208  good = true;
1209  }
1210  }*/
1211  m_pokeBlock = sm.block(x0, y0, m_pokeBlockW, m_pokeBlockW);
1212 
1213  sm.block(x0, y0, m_pokeBlockW, m_pokeBlockW) = 0;
1214 
1215  m_gfit.set_itmax(1000);
1216  m_gfit.setArray(m_pokeBlock.data(), m_pokeBlock.rows(), m_pokeBlock.cols());
1217  m_gfit.setGuess(0, mx, 0.5*(m_pokeBlock.rows()-1.0), 0.5*(m_pokeBlock.cols()-1.0), mx::math::func::sigma2fwhm(m_pokeFWHMGuess));
1218  m_gfit.fit();
1219 
1220  int rc = m_gfit.get_reason_code();
1221  if(rc != 1 && rc != 2)
1222  {
1223  return log<software_error, -1>({__FILE__,__LINE__, "fit to poke " + std::to_string(nn) + " failed: " + m_gfit.get_reason_string()});
1224  }
1225 
1226  m_pokePositions[nn*2 + 0] = x0 + m_gfit.x0();
1227  m_pokePositions[nn*2 + 1] = y0 + m_gfit.y0();
1228 
1229  m_pokeX += x0 + m_gfit.x0();
1230  m_pokeY += y0 + m_gfit.y0();
1231 
1232  /*m_pokePositions[nn*2 + 0] = xmx;
1233  m_pokePositions[nn*2 + 1] = ymx;
1234 
1235  m_pokeX += xmx;
1236  m_pokeY += ymx;*/
1237  }
1238 
1239 
1240 
1241  m_pokeX /= m_poke_x.size();
1242  m_pokeY /= m_poke_x.size();
1243 
1244  m_pokePositions[m_poke_x.size()*2 + 0] = m_pokeX;
1245  m_pokePositions[m_poke_x.size()*2 + 1] = m_pokeY;
1246 
1248 
1249 
1250  return 0;
1251 }
1252 
1253 INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_nPupilImages )(const pcf::IndiProperty &ipRecv)
1254 {
1255  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_nPupilImages, ipRecv)
1256 
1257  float target;
1258 
1259  if( indiTargetUpdate(m_indiP_nPupilImages, target, ipRecv, false) < 0)
1260  {
1261  return log<software_error,-1>({__FILE__, __LINE__});
1262  }
1263 
1264  m_nPupilImages = target;
1265 
1266  return 0;
1267 }
1268 
1269 INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_nPokeImages )(const pcf::IndiProperty &ipRecv)
1270 {
1271  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_nPokeImages, ipRecv)
1272 
1273  float target;
1274 
1275  if( indiTargetUpdate(m_indiP_nPokeImages, target, ipRecv, false) < 0)
1276  {
1277  return log<software_error,-1>({__FILE__, __LINE__});
1278  }
1279 
1280  m_nPokeImages = target;
1281 
1282  return 0;
1283 }
1284 
1285 INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_poke_amp )(const pcf::IndiProperty &ipRecv)
1286 {
1287  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_poke_amp, ipRecv)
1288 
1289  float target;
1290 
1291  if( indiTargetUpdate(m_indiP_poke_amp, target, ipRecv, false) < 0)
1292  {
1293  return log<software_error,-1>({__FILE__, __LINE__});
1294  }
1295 
1296  m_poke_amp = target;
1297 
1298  return 0;
1299 }
1300 
1301 INDI_SETCALLBACK_DEFN( dmPokeCenter, m_indiP_wfsFps )(const pcf::IndiProperty &ipRecv)
1302 {
1303  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_wfsFps, ipRecv)
1304 
1305  if( ipRecv.find("current") != true ) //this isn't valid
1306  {
1307  return 0;
1308  }
1309 
1310  m_wfsFps = ipRecv["current"].get<float>();
1311 
1312  return 0;
1313 }
1314 
1315 INDI_SETCALLBACK_DEFN( dmPokeCenter, m_indiP_shutter )(const pcf::IndiProperty &ipRecv)
1316 {
1317  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_shutter, ipRecv)
1318 
1319  if( ipRecv.find("toggle") != true ) //this isn't valid
1320  {
1321  return -1;
1322  }
1323 
1324  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1325  {
1326  m_shutter = false; //open
1327  }
1328  else
1329  {
1330  m_shutter = true; //shut
1331  }
1332 
1333  return 0;
1334 }
1335 
1336 INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_single )(const pcf::IndiProperty &ipRecv)
1337 {
1338  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_single, ipRecv)
1339 
1340  if( ipRecv.find("toggle") != true ) //this isn't valid
1341  {
1342  return -1;
1343  }
1344 
1345  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1346  {
1347  if(m_measuring == 0)
1348  {
1349  m_continuous = 0;
1350  m_single = 1;
1351  if(sem_post(&m_wfsSemaphore) < 0)
1352  {
1353  return log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1354  }
1355  }
1356  }
1357 
1358  return 0;
1359 }
1360 
1361 INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_continuous )(const pcf::IndiProperty &ipRecv)
1362 {
1363  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_continuous, ipRecv)
1364 
1365  if( ipRecv.find("toggle") != true ) //this isn't valid
1366  {
1367  return -1;
1368  }
1369 
1370  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1371  {
1372  if(m_measuring == 0)
1373  {
1374  m_continuous = 1;
1375  m_single = 0;
1376  if(sem_post(&m_wfsSemaphore) < 0)
1377  {
1378  return log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1379  }
1380  }
1381  }
1382  else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1383  {
1384  if(m_measuring != 0)
1385  {
1386  m_stopMeasurement = true;
1387  }
1388  }
1389 
1390  return 0;
1391 }
1392 
1393 INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_stop )(const pcf::IndiProperty &ipRecv)
1394 {
1395  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_stop, ipRecv)
1396 
1397  if( ipRecv.find("request") != true ) //this isn't valid
1398  {
1399  return -1;
1400  }
1401 
1402  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1403  {
1404  if(m_measuring != 0)
1405  {
1406  m_stopMeasurement = true;
1407  }
1408  }
1409 
1410  return 0;
1411 }
1412 
1413 inline
1415 {
1417 }
1418 
1419 inline
1421 {
1422  return recordPokeCenter(true);
1423 }
1424 
1425 inline
1427 {
1428  static int measuring = -1;
1429  static float pupilX = 0;
1430  static float pupilY = 0;
1431  static std::vector<float> pokePositions;
1432 
1433  if(pokePositions.size() != m_pokePositions.size())
1434  {
1435  pokePositions.resize(m_pokePositions.size(), 0);
1436  }
1437 
1438  bool changed = false;
1439  if(!force)
1440  {
1441  if(m_measuring != measuring) changed = true;
1442  else if(m_pupilX != pupilX) changed = true;
1443  else if(m_pupilY != pupilY) changed = true;
1444  else
1445  {
1446  for(size_t n = 0; n < m_pokePositions.size(); ++n)
1447  {
1448  if(m_pokePositions[n] != pokePositions[n])
1449  {
1450  changed = true;
1451  break;
1452  }
1453  }
1454  }
1455  }
1456 
1457  if(changed || force)
1458  {
1459  uint8_t meas = m_measuring;
1460  telem<telem_pokecenter>({meas, m_pupilX, m_pupilY, m_pokePositions});
1461 
1462  measuring = m_measuring;
1463  pupilX = m_pupilX;
1464  pupilY = m_pupilY;
1465  pokePositions.assign(m_pokePositions.begin(), m_pokePositions.end());
1466  }
1467 
1468  return 0;
1469 }
1470 
1471 } //namespace app
1472 } //namespace MagAOX
1473 
1474 #endif //dmPokeCenter_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
int sendNewStandardIndiToggle(const std::string &device, const std::string &property, bool onoff)
Send a new property commmand for a standard toggle switch.
Definition: MagAOXApp.hpp:3084
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 m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:102
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
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 threadStart(std::thread &thrd, bool &thrdInit, pid_t &tpid, pcf::IndiProperty &thProp, int thrdPrio, const std::string &cpuset, const std::string &thrdName, thisPtr *thrdThis, Function &&thrdStart)
Start a thread, using this class's privileges to set priority, etc.
Definition: MagAOXApp.hpp:1950
uint32_t m_width
The width of the images in the stream.
uint32_t m_height
The height of the images in the stream.
std::string m_shmimName
The name of the shared memory image, is used in /tmp/<shmimName>.im.shm. Derived classes should set a...
int appShutdown()
Shuts down the shmimMonitor thread.
void setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
void loadConfig(mx::app::appConfigurator &config)
load the configuration system results
The MagAO-X DM Pupil Centering Application.
double m_wfsSemWait
The time in sec to wait on the WFS semaphore. Default 0.5 sec.
std::vector< int > m_poke_y
unsigned m_nPupilImages
The number of images to average for the pupil image. Default is 20.
pid_t m_wfsThreadID
WFS thread PID.
int processImage(void *curr_src, const wfsShmimT &)
std::vector< int > m_poke_x
unsigned m_nDarks
The number of images to average for the dark. Default is 5.
milkImage< float > m_pupilImage
INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_stop)
unsigned m_nPokeImages
The number of images to average for the poke images. Default is 5.
eigenImage< float > m_fullEdge
bool m_single
True a single measurement is in progress.
pcf::IndiProperty m_indiP_pupilPos
Property to report the pupil position.
pcf::IndiProperty m_indiP_nPokeImages
unsigned m_wfsSemWait_sec
The timeout for the WFS semaphore, seconds component.
unsigned m_imageSemWait_nsec
The timeout for the image semaphore, nanoseconds component.
milkImage< float > m_wfsDark
INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_nPokeImages)
bool m_continuous
True if continuous measurements are in progress.
milkImage< float > m_rawImage
int fitPokes()
Fit the poke parameters.
int m_pupilPixels
The number of pixels in the pupil. Default is 68600.
pcf::IndiProperty m_indiP_pokePos
Property to report the poke positions.
int m_pupilCutBuff
The buffer around the initial found-pupil to include in the cut image. />= 0, default 20.
std::string m_wfsCamDevName
INDI device name of the WFS camera. Default is wfscam.shmimName.
INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_nPupilImages)
bool m_wfsThreadInit
Synchronizer to ensure wfs thread initializes before doing dangerous things.
eigenImage< float > m_pupilCopy
int analyzeSensor()
Analyze the images.
bool m_stopMeasurement
Used to request that the measurement in progress stop.
int m_measuring
Status of measuring: 0 no, 1 single in progress, 2 continuous in progress.
int m_wfsThreadPrio
Priority of the WFS thread, should normally be > 00.
int m_shutter
Shutter status. -1 is unknown, 0 open, 1 shut.
eigenImage< float > m_pupilMagnified
int allocate(const wfsShmimT &)
std::vector< float > m_pokePositions
Vector of positions for easy calls to UpdateIfChanged. One per poke, plus last two are for the averag...
pcf::IndiProperty m_indiP_continuous
Property to start continuous measurement.
eigenImage< float > m_cutMask
dev::telemeter< dmPokeCenter > telemeterT
float m_dmSleep
The time to sleep for the DM command to be applied, in microseconds. Default is 10000.
unsigned m_wfsSemWait_nsec
The timeoutfor the WFS semaphore, nanoseconds component.
mx::math::fit::fitGaussian2Dsym< float > m_gfit
eigenImage< float > m_magMask
INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_single)
void wfsThreadExec()
Execute the frame grabber main loop.
float m_pupilMag
The magnification to apply to the pupil image. />= 1, default 10.
sem_t m_wfsSemaphore
Semaphore used to signal the WFS thread to start WFSing.
INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_poke_amp)
eigenImage< float > m_pokeBlock
virtual int appShutdown()
Shutdown the app.
int m_pokeBlockW
The size of the sub-image for the poke analysis.
pcf::IndiProperty m_indiP_wfsFps
Property to get the FPS from the WFS camera.
milkImage< float > m_dmStream
eigenImage< float > m_dmImage
unsigned m_imageSemWait_sec
The timeout for the image semaphore, seconds component.
INDI_SETCALLBACK_DECL(dmPokeCenter, m_indiP_shutter)
std::vector< std::string > m_pokePosEls
Vector of element names for easy calls to UpdateIfChanged. One per poke, plus last two are for the av...
dev::shmimMonitor< dmPokeCenter, wfsShmimT > shmimMonitorT
pcf::IndiProperty m_indiP_deltaPos
Property to report the difference in pupil and average poke position.
pcf::IndiProperty m_indiP_single
Property to start a single measurement.
double m_imageSemWait
The time in sec to wait on the image semaphore. Default 0.5 sec.
eigenImage< float > m_fullMask
int m_pokeFWHMGuess
The initial guess for the FWHM of the Gaussian fit to the poke.
float(* wfsPixget)(void *, size_t)
eigenImage< float > m_pupilCut
int recordTelem(const telem_pokecenter *)
virtual int appLogic()
Implementation of the FSM for dmPokeCenter.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
virtual int appStartup()
Startup function.
eigenImage< float > m_magEdge
float m_pupilMedThresh
Threshold in the magnified image as a fraction of the median. />0, /<=1, default 0....
int runSensor(bool firstRun)
Run the sensor steps.
eigenImage< float > m_cutEdge
sem_t m_imageSemaphore
Semaphore used to signal that an image is ready.
float m_smoothWidth
Median smoothing kernal width.
~dmPokeCenter() noexcept
D'tor, declared and defined for noexcept.
static void wfsThreadStart(dmPokeCenter *s)
Thread starter, called by wfsThreadStart on thread construction. Calls wfsThreadExec.
int recordPokeCenter(bool force=false)
INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_continuous)
std::thread m_wfsThread
A separate thread for the actual WFSing.
pcf::IndiProperty m_indiP_shutter
Property to get the status from the WFS camera.
pcf::IndiProperty m_indiP_nPupilImages
pcf::IndiProperty m_wfsThreadProp
The property to hold the WFS thread details.
int fitPupil()
Fit the pupil parameters.
INDI_SETCALLBACK_DECL(dmPokeCenter, m_indiP_wfsFps)
milkImage< float > m_pokeImage
pcf::IndiProperty m_indiP_poke_amp
pcf::IndiProperty m_indiP_stop
Property to request that measurement stop.
std::string m_wfsCpuset
The cpuset for the framegrabber thread. Ignored if empty (the default).
#define CREATE_REG_INDI_NEW_TOGGLESWITCH(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
Definition: indiMacros.hpp:359
#define CREATE_REG_INDI_NEW_REQUESTSWITCH(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
Definition: indiMacros.hpp:399
#define CREATE_REG_INDI_NEW_NUMBERF(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as float, using the standard callback na...
Definition: indiMacros.hpp:285
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
Definition: indiMacros.hpp:264
#define CREATE_REG_INDI_NEW_NUMBERI(prop, name, min, max, step, format, label, group)
Create and register a NEW INDI property as a standard number as int, using the standard callback name...
Definition: indiMacros.hpp:312
@ 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_OK
Definition: indiUtils.hpp:29
std::ostream & cerr()
const pcf::IndiProperty & ipRecv
INDI_VALIDATE_CALLBACK_PROPS(function, 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
#define XWC_SEM_WAIT_TS(ts, sec, nsec)
Add the wait time to a timespec for a sem_timedwait call, with -1 returned on error.
Definition: semUtils.hpp:37
#define XWC_SEM_TIMEDWAIT_LOOP(sem, ts)
Perform a sem_timedwait in the context of a standard loop in MagAO-X code.
Definition: semUtils.hpp:52
#define XWC_SEM_WAIT_TS_RETVOID(ts, sec, nsec)
Add the wait time to a timespec for a sem_timedwait call, with no value returned on error.
Definition: semUtils.hpp:20
#define XWC_SEM_FLUSH(sem)
Definition: semUtils.hpp:65
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 CRITICAL log entry.
Software ERR log entry.
Log entry recording DM poke centering results.
static std::string configSection()
static std::string indiPrefix()