API
psfAcq.hpp
Go to the documentation of this file.
1 /** \file psfAcq.hpp
2  * \brief The MagAO-X PSF Fitter application header
3  *
4  * \ingroup psfAcq_files
5  */
6 
7 #ifndef psfAcq_hpp
8 #define psfAcq_hpp
9 
10 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
11 #include "../../magaox_git_version.h"
12 
13 #include <mx/math/fit/fitGaussian.hpp>
14 #include <mx/improc/imageFilters.hpp>
15 
16 /** \defgroup psfAcq
17  * \brief The MagAO-X PSF fitter.
18  *
19  * <a href="../handbook/operating/software/apps/psfAcq.html">Application Documentation</a>
20  *
21  * \ingroup apps
22  *
23  */
24 
25 /** \defgroup psfAcq_files
26  * \ingroup psfAcq
27  */
28 
29 namespace MagAOX
30 {
31 namespace app
32 {
33 
34 struct darkShmimT
35 {
36  static std::string configSection()
37  {
38  return "darkShmim";
39  };
40 
41  static std::string indiPrefix()
42  {
43  return "dark";
44  };
45 };
46 
47 struct Star
48 {
49 public:
50 
51  float x, y; // Star coordinates
52  float max; // Star brightness
53  float fwhm; // Star FWHM
54  float seeing; // Star's seeing
55 
56 private:
57 
58  pcf::IndiProperty * m_prop {nullptr};
59 
60 public:
61 
62  pcf::IndiProperty & prop()
63  {
64  if(m_prop == nullptr)
65  {
66  throw std::runtime_error("attempt to access nullptr prop");
67  }
68 
69  return *m_prop;
70  }
71 
72  void allocate()
73  {
74  m_prop = new pcf::IndiProperty;
75  }
76 
77  void deallocate()
78  {
79  pcf::IndiProperty * mp = m_prop;
80  m_prop = nullptr;
81  delete mp;
82  }
83 
84 
85 };
86 
87 /// The MagAO-X PSF Fitter
88 /**
89  * \ingroup psfAcq
90  */
91 class psfAcq : public MagAOXApp<true>,
92  public dev::shmimMonitor<psfAcq>,
93  public dev::shmimMonitor<psfAcq, darkShmimT>
94 {
95  // Give the test harness access.
96  friend class psfAcq_test;
97 
98  friend class dev::shmimMonitor<psfAcq>;
99  friend class dev::shmimMonitor<psfAcq, darkShmimT>;
100 
101  public:
102  // The base shmimMonitor type
104 
106 
107  /// Floating point type in which to do all calculations.
108  typedef float realT;
109 
110  /** \name app::dev Configurations
111  *@{
112  */
113  ///@}
114 
115  protected:
116  /** \name Configurable Parameters
117  *@{
118  */
119 
120  std::string m_fpsSource; ///< Device name for getting fps if time-based averaging is used. This device should have
121  ///< *.fps.current.
122 
123  uint16_t m_fitCircBuffMaxLength{ 3600 }; ///< Maximum length of the latency measurement circular buffers
124  float m_fitCircBuffMaxTime{ 5 }; ///< Maximum time of the latency meaurement circular buffers
125 
126  float m_fwhmGuess{ 4 };
127  ///@}
128 
129  mx::improc::eigenImage<float> m_image;
130  mx::improc::eigenImage<float> m_sm;
131 
132  mx::improc::eigenImage<float> m_dark;
133 
134  double m_acqQuitTime {0};
135  double m_acqPauseTime{2};
136 
138  int m_temp_acq_star {-1};
139  bool m_updated{ false };
140  float m_x{ 0 };
141  float m_y{ 0 };
142  int m_max_loops{ 5 }; // default to detecting a max of 5 stars
143  int m_zero_area{ 8 }; // default to zeroing an 8x8 pixel area around stars it finds
144  float m_threshold = { 7.0 }; // how many sigma away from the mean you want to classify a detection, default to
145  // 7sigma
146  float m_fwhm_threshold = { 4.0 }; // minumum fwhm to consider something a star
147  float m_max_fwhm = { 40.0 }; // max fwhm to consider a star
148 
149  std::vector<float> m_first_x_vals = {};
150  std::vector<float> m_first_y_vals = {};
151  std::vector<Star> m_detectedStars; // vector to store all the stars and properties
152 
153  float m_dx{ 0 };
154  float m_dy{ 0 };
155 
156  double m_plate_scale = .0795336;
157  int m_old_num_stars{ 0 };
158  int m_num_stars{ 0 };
159  float m_seeing{ 0 };
160  int m_acquire_star{ -1 }; // Testing for user to select star
161  int m_x_center{}; // 'center' of image or hot spot
162  int m_y_center{};
163 
164  float m_fps{ 0 };
165 
166  void resetAcq(); // class member for resetAcq function
167 
168  // Working memory for poke fitting
169  mx::math::fit::fitGaussian2Dsym<float> m_gfit;
170 
171  public:
172  /// Default c'tor.
173  psfAcq();
174 
175  /// D'tor, declared and defined for noexcept.
176  ~psfAcq() noexcept;
177 
178  virtual void setupConfig();
179 
180  /// Implementation of loadConfig logic, separated for testing.
181  /** This is called by loadConfig().
182  */
183  int loadConfigImpl(
184  mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
185 
186  virtual void loadConfig();
187 
188  /// Startup function
189  /**
190  *
191  */
192  virtual int appStartup();
193 
194  /// Implementation of the FSM for psfAcq.
195  /**
196  * \returns 0 on no critical error
197  * \returns -1 on an error requiring shutdown
198  */
199  virtual int appLogic();
200 
201  /// Shutdown the app.
202  /**
203  *
204  */
205  virtual int appShutdown();
206 
207  // shmimMonitor interface:
208  int allocate( const dev::shmimT & );
209 
210  int processImage( void *curr_src, const dev::shmimT & );
211 
212  // shmimMonitor interface for referenc:
213  int allocate( const darkShmimT & );
214 
215  int processImage( void *curr_src, const darkShmimT & );
216 
217  protected:
218  std::mutex m_imageMutex;
219 
220  protected:
221  /** \name INDI
222  * @{
223  */
224 
225  pcf::IndiProperty m_indiP_num_stars;
226 
227  pcf::IndiProperty m_indiP_seeing;
228 
229  pcf::IndiProperty m_indiP_fpsSource;
231 
232  // Testing for user to select star
233  pcf::IndiProperty m_indiP_acquire_star;
235 
236  // toggling
237  pcf::IndiProperty m_indiP_restartAcq;
239 
240  pcf::IndiProperty m_indiP_recordSeeing;
242 
243  ///@}
244 
245  /** \name Telemeter Interface
246  *
247  * @{
248  */
250 
251  int recordTelem( const telem_fgtimings * );
252 
253  ///@}
254 };
255 
256 inline psfAcq::psfAcq() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
257 {
259  return;
260 }
261 
262 inline psfAcq::~psfAcq() noexcept
263 {
264  for(size_t n = 0; n < m_detectedStars.size(); ++n)
265  {
266  m_detectedStars[n].deallocate();
267  }
268 }
269 
270 inline void psfAcq::setupConfig()
271 {
272  shmimMonitorT::setupConfig( config );
274 
275  config.add(
276  "fitter.fpsSource",
277  "",
278  "fitter.fpsSource",
279  argType::Required,
280  "fitter",
281  "fpsSource",
282  false,
283  "string",
284  "Device name for getting fps if time-based averaging is used. This device should have *.fps.current." );
285  config.add( "fitter.max_loops",
286  "",
287  "fitter.max_loops",
288  argType::Required,
289  "fitter",
290  "max_loops",
291  false,
292  "int",
293  "Setting the number of stars to detect in processImage function." );
294  config.add( "fitter.zero_area",
295  "",
296  "fitter.zero_area",
297  argType::Required,
298  "fitter",
299  "zero_area",
300  false,
301  "int",
302  "Setting the pixel area to zero out after detecting stars in processImage function." );
303  config.add( "fitter.threshold",
304  "",
305  "fitter.threshold",
306  argType::Required,
307  "fitter",
308  "threshold",
309  false,
310  "float",
311  "setting how many sigma away from the mean you want to classify a detection." );
312  config.add( "fitter.fwhm_threshold",
313  "",
314  "fitter.fwhm_threshold",
315  argType::Required,
316  "fitter",
317  "fwhm_threshold",
318  false,
319  "float",
320  "minumum fwhm to consider something a star." );
321  config.add( "acquisition.x_center",
322  "",
323  "acquisition.x_center",
324  argType::Required,
325  "acquisition",
326  "x_center",
327  false,
328  "int",
329  "X value for 'center' of image." );
330  config.add( "acquisition.y_center",
331  "",
332  "acquisition.y_center",
333  argType::Required,
334  "acquisition",
335  "y_center",
336  false,
337  "int",
338  "Y value for 'center' of image." );
339 }
340 
341 inline int psfAcq::loadConfigImpl( mx::app::appConfigurator &_config )
342 {
343  shmimMonitorT::loadConfig( _config );
345 
346  _config( m_fpsSource, "fitter.fpsSource" );
347  _config( m_max_loops, "fitter.max_loops" ); // Max number of stars to detect in processImage
348  _config( m_zero_area, "fitter.zero_area" ); // pixel area to zero out in processImage when a star is detected
349  _config( m_threshold, "fitter.threshold" ); // how many sigma away from the mean you want to classify a detection
350  _config( m_fwhm_threshold,
351  "fitter.fwhm_threshold" ); // how many sigma away from the mean you want to classify a detection
352 
353  _config( m_x_center, "acquisition.x_center" ); // star number to acquire
354  _config( m_y_center, "acquisition.y_center" ); // star number to acquire
355 
356  return 0;
357 }
358 
359 inline void psfAcq::loadConfig()
360 {
361  loadConfigImpl( config );
362 }
363 
364 inline int psfAcq::appStartup()
365 {
366  if( shmimMonitorT::appStartup() < 0 )
367  {
368  return log<software_error, -1>( { __FILE__, __LINE__ } );
369  }
370 
372  {
373  return log<software_error, -1>( { __FILE__, __LINE__ } );
374  }
375 
376  if( m_fpsSource != "" )
377  {
378  REG_INDI_SETPROP( m_indiP_fpsSource, m_fpsSource, std::string( "fps" ) );
379  }
380 
381  // creating toggling to restart the acquisition
382  createStandardIndiRequestSw( m_indiP_restartAcq, "restart_acq", "Restart Acquisition", "psfAcq");
384 
385  // INDI prop for seeing
387  m_indiP_seeing.add(pcf::IndiElement("current"));
388  m_indiP_seeing["current"].setValue( m_seeing ); //m_seeing gets assigned the seeing values of the star that is acquired
390 
391  // create toggling for recording seeing
392  createStandardIndiToggleSw( m_indiP_recordSeeing, "record_seeing", "Record Seeing");
393  m_indiP_recordSeeing["toggle"].set(pcf::IndiElement::Off);
395  {
396  log<software_error>({__FILE__,__LINE__});
397  return -1;
398  }
399 
400  // INDI prop for user to select star
401  CREATE_REG_INDI_NEW_NUMBERF( m_indiP_acquire_star, "acquire_star", 0, 20, 1, "%d", "", "" );
402  m_indiP_acquire_star["current"].setValue( m_acquire_star );
403  m_indiP_acquire_star["target"].setValue( m_acquire_star );
404 
405  // number of stars INDI prop
407  m_indiP_num_stars.add(pcf::IndiElement("current"));
408  m_indiP_num_stars["current"].setValue( m_num_stars );
410 
412 
413  return 0;
414 }
415 
416 inline int psfAcq::appLogic()
417 {
418  if( shmimMonitorT::appLogic() < 0 )
419  {
420  return log<software_error, -1>( { __FILE__, __LINE__ } );
421  }
422 
423  if( darkShmimMonitorT::appLogic() < 0 )
424  {
425  return log<software_error, -1>( { __FILE__, __LINE__ } );
426  }
427 
428  std::unique_lock<std::mutex> lock( m_indiMutex );
429 
432 
434  updateIfChanged( m_indiP_seeing, "current", m_seeing );
435 
436  for( size_t n = 0; n < m_detectedStars.size() ; ++n )
437  {
438 
439  updateIfChanged( m_detectedStars[n].prop(), "x", m_detectedStars[n].x );
440  updateIfChanged( m_detectedStars[n].prop(), "y", m_detectedStars[n].y );
441  updateIfChanged( m_detectedStars[n].prop(), "peak", m_detectedStars[n].max );
442  updateIfChanged( m_detectedStars[n].prop(), "fwhm", m_detectedStars[n].fwhm );
443  }
444  return 0;
445 }
446 
448 {
451 
452  return 0;
453 }
454 
455 inline int psfAcq::allocate( const dev::shmimT &dummy )
456 {
457  static_cast<void>( dummy );
458 
459  std::lock_guard<std::mutex> guard( m_imageMutex );
460 
462  m_image.setZero();
463 
464  m_sm.resize( m_image.rows(), m_image.cols() );
465 
466  m_updated = false;
467  return 0;
468 }
469 
470 // Function to calculate Euclidean distance between two stars
471 float calculateDistance( float x1, float y1, float x2, float y2 )
472 {
473  return sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) );
474 }
475 
476 inline int psfAcq::processImage( void *curr_src, const dev::shmimT &dummy )
477 {
478  if(mx::sys::get_curr_time() - m_acqQuitTime < m_acqPauseTime ) return 0; // Pausing while telescope moves to star
479  static_cast<void>( dummy );
480 
481  std::unique_lock<std::mutex> lock( m_imageMutex );
482 
483  if( m_dark.rows() == m_image.rows() && m_dark.cols() == m_image.cols() )
484  {
485  for( unsigned nn = 0; nn < shmimMonitorT::m_width * shmimMonitorT::m_height; ++nn )
486  {
487  m_image.data()[nn] = ( (uint16_t *)curr_src )[nn] - m_dark.data()[nn];
488  }
489  }
490  else
491  {
492  for( unsigned nn = 0; nn < shmimMonitorT::m_width * shmimMonitorT::m_height; ++nn )
493  {
494  m_image.data()[nn] = ( (uint16_t *)curr_src )[nn];
495  }
496  }
497 
498  lock.unlock();
499  float max;
500  float seeing = 0;
501  // int max_loops=5;
502  int x = 0;
503  int y = 0;
504  int N_loops = 0;
505  // 1. find brightest star
506  max = m_image.maxCoeff( &x, &y );
507  // mx::improc::medianSmooth(m_sm, x, y, max, m_image, 3);
508 
509  // mx::improc::imageCenterOfLight(m_x, m_y, m_image);
510  // std::cerr << __LINE__ << std::endl;
511 
512  /*if(fabs(m_x-x) > 2 || fabs(m_y-y) > 2)
513  {
514  std::cerr << "skip frame\n";
515  return 0;
516  }*/
517 
518  eigenImage<float> llcorn = m_image.block( 0, 0, 32, 32 ); // calc std dev of 32x32 block in lower left corner
519  float mean = llcorn.mean(); // Calculate the mean
520  float variance = ( llcorn.array() - mean ).square().sum() / ( llcorn.size() ); // calculate variance
521  float stddev = std::sqrt( variance ); // Calculate the standard deviation
522  float z_score = ( max - mean ) / stddev; // how many std dev away from mean
523  float fwhm = m_fwhm_threshold + 1; // getting intial fwhm before entering while loop
524  std::size_t numStars = m_detectedStars.size();
525  if( numStars == 0 )
526  { // This runs when the vector of stars is empty (usually the first time)
527  while( ( z_score > m_threshold ) && ( fwhm > m_fwhm_threshold ) && ( N_loops < m_max_loops ) )
528  { // m_max_loops, m_fwhm_threshold, and m_threshold are configurable variables
529  N_loops = N_loops + 1;
530  m_gfit.set_itmax( 1000 );
531  // m_zero_area is used to zero out the pixel array once a star is detected but can also be used to set up a
532  // sub image around the max pixel
533  if( x < m_zero_area )
534  {
535  x = m_zero_area;
536  }
537  if( x >= ( m_image.rows() - m_zero_area ) )
538  {
539  x = m_image.rows() - m_zero_area;
540  }
541  if( y < m_zero_area )
542  {
543  y = m_zero_area;
544  }
545  if( y >= ( m_image.cols() - m_zero_area ) )
546  {
547  y = m_image.cols() - m_zero_area;
548  }
549  eigenImage<float> subImage = m_image.block(
550  x - m_zero_area,
551  y - m_zero_area,
552  m_zero_area * 2,
553  m_zero_area * 2 ); // set m_image to subImage to speed up gaussian, x,y is position of max pixel
554  m_gfit.setArray( subImage.data(), subImage.rows(), subImage.cols() );
555  // m_gfit.setArray(m_image.data(), m_image.rows(), m_image.cols());
556  m_gfit.setGuess( 0, max, m_zero_area, m_zero_area, mx::math::func::fwhm2sigma( m_fwhmGuess ) );
557  m_gfit.fit();
558  m_x = ( x - m_zero_area ) + m_gfit.x0();
559  m_y = ( y - m_zero_area ) + m_gfit.y0();
560  m_first_x_vals.push_back( m_x ); // adding first detected x value to vector
561  m_first_y_vals.push_back( m_y ); // adding first detected y value to vector
562  fwhm = m_gfit.fwhm() ;
563  max = m_gfit.G();
564  seeing = fwhm * m_plate_scale;
565  int x_value = static_cast<int>(
566  m_x ); // convert m_x to an int so we can 0 out a rectangular area around the detected star
567  int y_value = static_cast<int>( m_y );
568  if (fwhm > m_fwhm_threshold && fwhm < m_max_fwhm ){
569  Star newStar;
570  newStar.x = m_x; // Adding attributes to the new star
571  newStar.y = m_y;
572  newStar.max = max;
573  newStar.fwhm = fwhm;
574  newStar.seeing = seeing;
575  std::unique_lock<std::mutex> lock( m_indiMutex );
576  newStar.allocate();
577  m_detectedStars.push_back( newStar );
578  int index = m_detectedStars.size() - 1;
579  std::string starPrefix = "star_" + std::to_string( index );
580  createROIndiNumber(m_detectedStars.back().prop(), starPrefix); //, "Star " + std::to_string( m_detectedStars.size() ) + " Properties", "Star Acq" );
581  m_detectedStars.back().prop().add( pcf::IndiElement( "x" ) );
582  m_detectedStars.back().prop()["x"].set( m_detectedStars.back().x );
583  m_detectedStars.back().prop().add( pcf::IndiElement( "y" ) );
584  m_detectedStars.back().prop()["y"].set( m_detectedStars.back().y );
585  m_detectedStars.back().prop().add( pcf::IndiElement( "peak" ) );
586  m_detectedStars.back().prop()["peak"].set( m_detectedStars.back().max );
587  m_detectedStars.back().prop().add( pcf::IndiElement( "fwhm" ) );
588  m_detectedStars.back().prop()["fwhm"].set( m_detectedStars.back().fwhm );
590  }
591  if( x_value < m_zero_area )
592  {
593  x_value = m_zero_area;
594  }
595  if( x_value >= ( m_image.rows() - m_zero_area ) )
596  {
597  x_value = m_image.rows() - m_zero_area;
598  }
599  if( y_value < m_zero_area )
600  {
601  y_value = m_zero_area;
602  }
603  if( y_value >= ( m_image.cols() - m_zero_area ) )
604  {
605  y_value = m_image.cols() - m_zero_area;
606  }
607  for( int i = x_value - m_zero_area; i < ( x_value + m_zero_area ); i++ )
608  { // zeroing out area around the star centered at m_x and m_y(8x8 pixel area)
609  for( int j = y_value - m_zero_area; j < ( y_value + m_zero_area ); j++ )
610  {
611  m_image( i, j ) = 0; // m_zero_area is defaulted to 20 to zero out a pixel array around the star
612  }
613  }
614  max = m_image.maxCoeff( &x, &y );
615  z_score = ( max - mean ) / stddev;
616  }
617  }
618 
619  else
620  {
621  // In here is where we track the stars using cross correlation between the first frame and subsequent frames
622  while( ( z_score > m_threshold ) && ( fwhm > m_fwhm_threshold ) && ( N_loops < m_max_loops ) )
623  {
624  N_loops = N_loops + 1;
625  m_gfit.set_itmax( 1000 );
626  // m_zero_area is used to zero out the pixel array once a star is detected but can also be used to set up a
627  // sub image around the max pixel
628  if( x < m_zero_area )
629  {
630  x = m_zero_area;
631  }
632  if( x >= ( m_image.rows() - m_zero_area ) )
633  {
634  x = m_image.rows() - m_zero_area;
635  }
636  if( y < m_zero_area )
637  {
638  y = m_zero_area;
639  }
640  if( y >= ( m_image.cols() - m_zero_area ) )
641  {
642  y = m_image.cols() - m_zero_area;
643  }
644  eigenImage<float> subImage = m_image.block(
645  x - m_zero_area,
646  y - m_zero_area,
647  m_zero_area * 2,
648  m_zero_area * 2 ); // set m_image to subImage to speed up gaussian, x,y is position of max pixel
649  // 2. fit it's position
650  m_gfit.setArray( subImage.data(), subImage.rows(), subImage.cols() );
651  m_gfit.setGuess( 0, max, m_zero_area, m_zero_area, mx::math::func::fwhm2sigma( m_fwhmGuess ) );
652  m_gfit.fit();
653  fwhm = m_gfit.fwhm() ;
654  max = m_gfit.G();
655  seeing = fwhm * m_plate_scale;
656  m_x = ( x - m_zero_area ) + m_gfit.x0();
657  m_y = ( y - m_zero_area ) + m_gfit.y0();
658  int x_value = static_cast<int>(
659  m_x ); // convert m_x to an int so we can 0 out a rectangular area around the detected star
660  int y_value = static_cast<int>( m_y );
661  if( x_value < m_zero_area )
662  {
663  x_value = m_zero_area;
664  }
665  if( x_value >= ( m_image.rows() - m_zero_area ) )
666  {
667  x_value = m_image.rows() - m_zero_area;
668  }
669  if( y_value < m_zero_area )
670  {
671  y_value = m_zero_area;
672  }
673  if( y_value >= ( m_image.cols() - m_zero_area ) )
674  {
675  y_value = m_image.cols() - m_zero_area;
676  }
677  for( int i = x_value - m_zero_area; i < ( x_value + m_zero_area ); i++ )
678  { // zeroing out area around the star centered at m_x and m_y(8x8 pixel area)
679  for( int j = y_value - m_zero_area; j < ( y_value + m_zero_area ); j++ )
680  {
681  m_image( i, j ) = 0; // m_zero_area is defaulted to 20 to zero out a pixel array around the star
682  }
683  }
684  // 3. search through all known stars to figure out which one it corresponds to. You can NOT assume it is in the order of the vector.
685  // This simple for loop calculate the distance from the detected star to the cloest star already in the list
686  // and updates the values
687  int threshold_distance = 20; // distance between new stars should be a small positive number so this updates
688  int tracker = 0; // tracks if the current star detected updated an already known star
689  if (fwhm > m_fwhm_threshold && fwhm < m_max_fwhm ){
690  for( Star &star : m_detectedStars )
691  {
692  float dist = calculateDistance( star.x, star.y, x_value, y_value );
693  // 4. if it is found, update that star's data in the vector
694  if( dist < threshold_distance )
695  {
696  star.x = m_x;
697  star.y = m_y;
698  star.max = max;
699  star.fwhm = fwhm;
700  star.seeing = seeing;
701  tracker = 1;
702  continue;
703  }
704  }
705  if (tracker == 0 && m_detectedStars.size() < m_max_loops) { // 5. if it is not found, add a star and corresponding INDI Property using push_back
706  Star newStar;
707  newStar.x = m_x; // Adding attributes to the new star
708  newStar.y = m_y;
709  newStar.max = max;
710  newStar.fwhm = fwhm;
711  newStar.seeing = seeing;
712  std::unique_lock<std::mutex> lock( m_indiMutex );
713  newStar.allocate();
714  m_detectedStars.push_back( newStar );
715  int index = m_detectedStars.size() - 1;
716  std::string starPrefix = "star_" + std::to_string( index );
717  createROIndiNumber(m_detectedStars.back().prop(), starPrefix, "Star " + std::to_string( index ) + " Properties", "Star Acq" );
718  m_detectedStars.back().prop().add( pcf::IndiElement( "x" ) );
719  m_detectedStars.back().prop()["x"].set( m_detectedStars.back().x );
720  m_detectedStars.back().prop().add( pcf::IndiElement( "y" ) );
721  m_detectedStars.back().prop()["y"].set( m_detectedStars.back().y );
722  m_detectedStars.back().prop().add( pcf::IndiElement( "peak" ) );
723  m_detectedStars.back().prop()["peak"].set( m_detectedStars.back().max );
724  m_detectedStars.back().prop().add( pcf::IndiElement( "fwhm" ) );
725  m_detectedStars.back().prop()["fwhm"].set( m_detectedStars.back().fwhm );
727  }
728  }
729  max = m_image.maxCoeff( &x, &y );
730  z_score = ( max - mean ) / stddev;
731  }
732  }
733 
734  m_num_stars = m_detectedStars.size();
735  // If statement that get the delta x and delta y from the 'center' of image
736  static int delta_x;
737  static int delta_y;
738  double plate_scale = .0795336; // plate scale factor: deltatheta/deltapixel, calculated in python, arcsec/pixel
739  //deltatheta -> Angular seperation between two stars in arcsec (from published data)
740  //deltapixel -> Pixel seperation between same two stars on our detector
741  if ( m_acquire_star != -1 && (m_acquire_star > m_detectedStars.size() - 1 || m_acquire_star < 0 )){
742  std::cout << "Please enter a star number between 0 and " << m_detectedStars.size() - 1 << "." << std::endl;
743  m_acquire_star = -1;
744  }
745 
746  if( m_acquire_star >= 0 && m_acquire_star < m_detectedStars.size())
747  {
748  m_acqQuitTime = mx::sys::get_curr_time();
752  std::cout << "delta_x = " << delta_x << " delta_y = " << delta_y << std::endl;
753 
754  // negative signs because we want to move scope opposite of how far it is from 'center'
755  double x_arcsec = -1*delta_y * plate_scale; //positive x_arcsec moves up, negetive moves down
756  double y_arcsec = -1*delta_x * plate_scale; //positive y_arcsec moves right, negetive moves left
757  std::cout << "x_arcsec=" << x_arcsec << " y_arcsec=" << y_arcsec << std::endl;
758 
759  // for moving telescope
760  pcf::IndiProperty ip( pcf::IndiProperty::Number );
761 
762  ip.setDevice( "tcsi" );
763  ip.setName( "pyrNudge" );
764  //send telescope x and y offsets in acrsec
765  ip.add( pcf::IndiElement( "y" ) );
766  ip["y"] = x_arcsec; //how far to move in y direction in arcsec?
767  ip.add( pcf::IndiElement( "x" ) );
768  ip["x"] = y_arcsec; //how far to move in x direction in arcsec?
769 
770  sendNewProperty( ip );
771  resetAcq();
772  m_acquire_star = -1;
773  }
774 
777  }
778 
779  m_updated = true;
780  return 0;
781 }
782 
783 inline int psfAcq::allocate( const darkShmimT &dummy )
784 {
785  static_cast<void>( dummy );
786 
787  std::lock_guard<std::mutex> guard( m_imageMutex );
788 
790  {
791  return log<software_error, -1>( { __FILE__, __LINE__, "dark is not float" } );
792  }
793 
795  m_dark.setZero();
796 
797  return 0;
798 }
799 
800 inline int psfAcq::processImage( void *curr_src, const darkShmimT &dummy )
801 {
802  static_cast<void>( dummy );
803 
804  std::unique_lock<std::mutex> lock( m_imageMutex );
805 
806  for( unsigned nn = 0; nn < darkShmimMonitorT::m_width * darkShmimMonitorT::m_height; ++nn )
807  {
808  m_dark.data()[nn] += ( (float *)curr_src )[nn];
809  }
810 
811  lock.unlock();
812 
813  log<text_log>( "dark updated", logPrio::LOG_INFO );
814 
815  return 0;
816 }
817 
818 //delete m_detectedStars Properties
820  for(size_t n=0; n < m_detectedStars.size(); ++n)
821  {
822  if(m_indiDriver) m_indiDriver->sendDelProperty(m_detectedStars[n].prop());
823  if(!m_indiNewCallBacks.erase(m_detectedStars[n].prop().createUniqueKey()))
824  {
825  log<software_error>({__FILE__, __LINE__, "failed to erase " + m_detectedStars[n].prop().createUniqueKey()});
826  }
827  }
828  std::cout << "size=" << m_detectedStars.size() << std::endl;
829  m_detectedStars.clear();
830 }
831 
832 //for toggling Restart Acquisition
834 {
836  if(!ipRecv.find("request")) return 0;
837  std::unique_lock<std::mutex> lock(m_indiMutex);
838 
839  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
840  {
841  std::cout << "size=" << m_detectedStars.size() << std::endl;
842  resetAcq();
843  std::cout << "size=" << m_detectedStars.size() << std::endl;
844  return 0;
845  }
846  else if( ipRecv["request"].getSwitchState() == pcf::IndiElement::Off)
847  {
848  return 0;
849  }
850 
851  log<software_error>({__FILE__,__LINE__, "switch state fall through."});
852  return -1;
853 }
854 
855 //for toggling Recording Seeing
857 {
859  if(!ipRecv.find("toggle")) return 0;
860  std::unique_lock<std::mutex> lock(m_indiMutex);
861 
862  if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
863  {
865  updateSwitchIfChanged(m_indiP_recordSeeing, "toggle", pcf::IndiElement::On, INDI_BUSY);
866  //m_seeing = m_detectedStars[m_current_acq_star].seeing;
867  return 0;
868  }
869  else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off)
870  {
871  m_current_acq_star = -1;
872  updateSwitchIfChanged(m_indiP_recordSeeing, "toggle", pcf::IndiElement::Off, INDI_IDLE);
873  return 0;
874  }
875 
876 
877  log<software_error>({__FILE__,__LINE__, "switch state fall through."});
878  return -1;
879 }
880 
881 // Testing for user to select star number
883 {
884  if( ipRecv.getName() != m_indiP_acquire_star.getName() )
885  {
886  log<software_error>( { __FILE__, __LINE__, "wrong INDI property received." } );
887  return -1;
888  }
889 
890  float target;
891 
892  if( indiTargetUpdate( m_indiP_acquire_star, target, ipRecv, true ) < 0 )
893  {
894  log<software_error>( { __FILE__, __LINE__ } );
895  return -1;
896  }
897 
898  m_acquire_star = target;
899 
900  log<text_log>( "set acquire_star = " + std::to_string( m_acquire_star ), logPrio::LOG_NOTICE );
901  return 0;
902 }
903 
904 INDI_SETCALLBACK_DEFN( psfAcq, m_indiP_fpsSource )( const pcf::IndiProperty &ipRecv )
905 {
906  if( ipRecv.getName() != m_indiP_fpsSource.getName() )
907  {
908  log<software_error>( { __FILE__, __LINE__, "Invalid INDI property." } );
909  return -1;
910  }
911 
912  if( ipRecv.find( "current" ) != true ) // this isn't valie
913  {
914  return 0;
915  }
916 
917  std::lock_guard<std::mutex> guard( m_indiMutex );
918 
919  realT fps = ipRecv["current"].get<float>();
920 
921  if( fps != m_fps )
922  {
923  m_fps = fps;
925  }
926 
927  return 0;
928 }
929 
930 } // namespace app
931 } // namespace MagAOX
932 
933 #endif // psfAcq_hpp
#define IMAGESTRUCT_FLOAT
Definition: ImageStruct.hpp:22
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.
int createStandardIndiToggleSw(pcf::IndiProperty &prop, const std::string &name, const std::string &label="", const std::string &group="")
Create a standard R/W INDI switch with a single toggle element.
Definition: MagAOXApp.hpp:2543
std::unordered_map< std::string, indiCallBack > m_indiNewCallBacks
Map to hold the NewProperty indiCallBacks for this App, with fast lookup by property name.
Definition: MagAOXApp.hpp:573
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
Definition: MagAOXApp.hpp:542
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:3144
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1804
int createROIndiNumber(pcf::IndiProperty &prop, const std::string &propName, const std::string &propLabel="", const std::string &propGroup="")
Create a ReadOnly INDI Number property.
Definition: MagAOXApp.hpp:2517
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
Definition: MagAOXApp.hpp:2655
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:545
int sendNewProperty(const pcf::IndiProperty &ipSend, const std::string &el, const T &newVal)
Send a newProperty command to another device (using the INDI Client interface)
Definition: MagAOXApp.hpp:3268
int indiTargetUpdate(pcf::IndiProperty &localProperty, T &localTarget, const pcf::IndiProperty &remoteProperty, bool setBusy=true)
Get the target element value from an new property.
Definition: MagAOXApp.hpp:3197
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.
bool m_restart
Flag indicating tha the shared memory should be reinitialized.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
bool m_getExistingFirst
If set to true by derivedT, any existing image will be grabbed and sent to processImage before waitin...
The MagAO-X PSF Fitter.
Definition: psfAcq.hpp:94
mx::improc::eigenImage< float > m_sm
Definition: psfAcq.hpp:130
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
Definition: psfAcq.hpp:341
virtual void loadConfig()
Definition: psfAcq.hpp:359
INDI_SETCALLBACK_DECL(psfAcq, m_indiP_fpsSource)
mx::improc::eigenImage< float > m_image
Definition: psfAcq.hpp:129
double m_acqQuitTime
Definition: psfAcq.hpp:134
std::string m_fpsSource
Definition: psfAcq.hpp:120
dev::shmimMonitor< psfAcq, darkShmimT > darkShmimMonitorT
Definition: psfAcq.hpp:105
float m_fwhm_threshold
Definition: psfAcq.hpp:146
psfAcq()
Default c'tor.
Definition: psfAcq.hpp:256
int processImage(void *curr_src, const dev::shmimT &)
Definition: psfAcq.hpp:476
pcf::IndiProperty m_indiP_num_stars
Definition: psfAcq.hpp:225
float realT
Floating point type in which to do all calculations.
Definition: psfAcq.hpp:108
virtual int appLogic()
Implementation of the FSM for psfAcq.
Definition: psfAcq.hpp:416
friend class psfAcq_test
Definition: psfAcq.hpp:96
std::vector< float > m_first_x_vals
Definition: psfAcq.hpp:149
double m_plate_scale
Definition: psfAcq.hpp:156
std::mutex m_imageMutex
Definition: psfAcq.hpp:218
int recordTelem(const telem_fgtimings *)
uint16_t m_fitCircBuffMaxLength
Maximum length of the latency measurement circular buffers.
Definition: psfAcq.hpp:123
virtual int appStartup()
Startup function.
Definition: psfAcq.hpp:364
~psfAcq() noexcept
D'tor, declared and defined for noexcept.
Definition: psfAcq.hpp:262
mx::improc::eigenImage< float > m_dark
Definition: psfAcq.hpp:132
double m_acqPauseTime
Definition: psfAcq.hpp:135
pcf::IndiProperty m_indiP_seeing
Definition: psfAcq.hpp:227
INDI_NEWCALLBACK_DECL(psfAcq, m_indiP_acquire_star)
pcf::IndiProperty m_indiP_recordSeeing
Definition: psfAcq.hpp:240
virtual void setupConfig()
Definition: psfAcq.hpp:270
int allocate(const dev::shmimT &)
Definition: psfAcq.hpp:455
virtual int appShutdown()
Shutdown the app.
Definition: psfAcq.hpp:447
mx::math::fit::fitGaussian2Dsym< float > m_gfit
Definition: psfAcq.hpp:169
dev::shmimMonitor< psfAcq > shmimMonitorT
Definition: psfAcq.hpp:103
float m_fitCircBuffMaxTime
Maximum time of the latency meaurement circular buffers.
Definition: psfAcq.hpp:124
pcf::IndiProperty m_indiP_fpsSource
Definition: psfAcq.hpp:229
std::vector< float > m_first_y_vals
Definition: psfAcq.hpp:150
pcf::IndiProperty m_indiP_acquire_star
Definition: psfAcq.hpp:233
std::vector< Star > m_detectedStars
Definition: psfAcq.hpp:151
pcf::IndiProperty m_indiP_restartAcq
Definition: psfAcq.hpp:237
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:208
#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:309
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
Definition: indiMacros.hpp:282
@ OPERATING
The device is operating, other than homing.
Definition: stateCodes.hpp:55
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_BUSY
Definition: indiUtils.hpp:30
std::ostream & cout()
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
float calculateDistance(float x1, float y1, float x2, float y2)
Definition: psfAcq.hpp:471
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
INDI_SETCALLBACK_DEFN(adcTracker, m_indiP_teldata)(const pcf
Definition: adcTracker.hpp:461
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
constexpr static logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
Definition: logPriority.hpp:49
constexpr static logPrioT LOG_NOTICE
A normal but significant condition.
Definition: logPriority.hpp:46
void deallocate()
Definition: psfAcq.hpp:77
void allocate()
Definition: psfAcq.hpp:72
pcf::IndiProperty * m_prop
Definition: psfAcq.hpp:58
pcf::IndiProperty & prop()
Definition: psfAcq.hpp:62
static std::string configSection()
Definition: psfAcq.hpp:36
static std::string indiPrefix()
Definition: psfAcq.hpp:41
Software ERR log entry.
Log entry recording framegrabber timings.