API
 
Loading...
Searching...
No Matches
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>
16using 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
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
50namespace MagAOX
51{
52namespace app
53{
54
55/// The MagAO-X DM Pupil Centering Application
56/**
57 * \ingroup dmPokeCenter
58 */
59class 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
72protected:
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
119
121
123
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
133
135
136 //Working memory for pupil fitting
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;
153
154 float m_pokeX {0};
155 float m_pokeY {0};
156
158public:
159 /// Default c'tor.
160 dmPokeCenter();
161
162 /// D'tor, declared and defined for noexcept.
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 */
216protected:
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 */
292protected:
293
294 pcf::IndiProperty m_indiP_poke_amp;
296
297 pcf::IndiProperty m_indiP_nPupilImages;
299
300 pcf::IndiProperty 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
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
341dmPokeCenter::dmPokeCenter() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
342{
343 return;
344}
345
347{
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
370int dmPokeCenter::loadConfigImpl( mx::app::appConfigurator & _config )
371{
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{
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
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
616int 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
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
638inline
640{
641 d->wfsThreadExec();
642}
643
644
645inline
647{
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
713inline
714int 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 {
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
845 {
848
849 //If here we got an image. m_rawImage will have been updated
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 {
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;
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 {
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;
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
985inline
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
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
1018
1019 return 0;
1020}
1021
1022inline
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
1034 int xmx, ymx;
1035 float mx;
1036
1037 float m_pupSmoothWidth=3;
1038 sm.resize(m_pupilImage().rows(), m_pupilImage().cols());
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 {
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
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
1165inline
1167{
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
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
1253INDI_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
1269INDI_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
1285INDI_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
1301INDI_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
1315INDI_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
1336INDI_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
1361INDI_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
1393INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_stop )(const pcf::IndiProperty &ipRecv)
1394{
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
1413inline
1418
1419inline
1421{
1422 return recordPokeCenter(true);
1423}
1424
1425inline
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 {
1449 {
1450 changed = true;
1451 break;
1452 }
1453 }
1454 }
1455 }
1456
1457 if(changed || force)
1458 {
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 XWCTk applications.
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.
int sendNewStandardIndiToggle(const std::string &device, const std::string &property, bool onoff)
Send a new property commmand for a standard toggle switch.
std::string m_configName
The name of the configuration file (minus .conf).
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
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.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
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.
uint32_t m_width
The width of the images in the stream.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
uint32_t m_height
The height of the images in the stream.
int appShutdown()
Shuts down the shmimMonitor thread.
int 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.
float m_wfsFps
Pointer to a function to extract the image data as float.
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 INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#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...
#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...
#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...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
#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...
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
#define INDI_IDLE
Definition indiUtils.hpp:27
#define INDI_OK
Definition indiUtils.hpp:28
const pcf::IndiProperty & ipRecv
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:19
#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:38
#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:53
#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:66
A device base class which saves telemetry.
Definition telemeter.hpp:75
int appShutdown()
Perform telemeter application shutdown.
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
int appLogic()
Perform telemeter application logic.
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
int appStartup()
Starts the telemetry log thread.
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
@ OPERATING
The device is operating, other than homing.
@ READY
The device is ready for operation, but is not operating.
Software CRITICAL log entry.
Software ERR log entry.
Log entry recording DM poke centering results.
static std::string configSection()
static std::string indiPrefix()