Line data Source code
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 0 : static std::string configSection()
36 : {
37 0 : return "wfscam";
38 : };
39 :
40 0 : static std::string indiPrefix()
41 : {
42 0 : 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 :
64 : friend class dev::shmimMonitor<dmPokeCenter, wfsShmimT>;
65 :
66 : friend class dev::telemeter<dmPokeCenter>;
67 :
68 : typedef dev::shmimMonitor<dmPokeCenter, wfsShmimT> shmimMonitorT;
69 :
70 : typedef dev::telemeter<dmPokeCenter> telemeterT;
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 0 : ~dmPokeCenter() noexcept
164 0 : {}
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;
295 0 : INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_poke_amp);
296 :
297 : pcf::IndiProperty m_indiP_nPupilImages;
298 0 : INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_nPupilImages);
299 :
300 : pcf::IndiProperty m_indiP_nPokeImages;
301 0 : INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_nPokeImages);
302 :
303 : pcf::IndiProperty m_indiP_wfsFps; ///< Property to get the FPS from the WFS camera
304 0 : INDI_SETCALLBACK_DECL(dmPokeCenter, m_indiP_wfsFps);
305 :
306 : pcf::IndiProperty m_indiP_shutter; ///< Property to get the status from the WFS camera
307 0 : INDI_SETCALLBACK_DECL(dmPokeCenter, m_indiP_shutter);
308 :
309 : pcf::IndiProperty m_indiP_single; ///< Property to start a single measurement
310 0 : INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_single);
311 :
312 : pcf::IndiProperty m_indiP_continuous; ///< Property to start continuous measurement
313 0 : INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_continuous);
314 :
315 : pcf::IndiProperty m_indiP_stop; ///< Property to request that measurement stop
316 0 : INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_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 0 : dmPokeCenter::dmPokeCenter() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
342 : {
343 0 : return;
344 0 : }
345 :
346 0 : void dmPokeCenter::setupConfig()
347 : {
348 0 : shmimMonitorT::setupConfig(config);
349 0 : telemeterT::setupConfig(config);
350 :
351 0 : config.add("wfscam.camDevName", "", "wfscam.camDevName", argType::Required, "wfs", "camDevName", false, "string", "INDI device name of the WFS camera. Default is wfscam.shmimName.");
352 0 : config.add("wfscam.loopSemWait", "", "wfscam.loopSemWait", argType::Required, "wfs", "loopSemWait", false, "float", "The semaphore wait time for the wfs loop start signal");
353 0 : config.add("wfscam.imageSemWait", "", "wfscam.imageSemWait", argType::Required, "wfs", "imageSemWait", false, "float", "The semaphore wait time for the image availability signal");
354 :
355 0 : config.add("pokecen.dmChannel", "", "pokecen.dmChannel", argType::Required, "pokecen", "dmChannel", false, "string", "The dm channel to use for pokes, e.g. dm01disp06.");
356 0 : config.add("pokecen.pokeX", "", "pokecen.pokeX", argType::Required, "pokecen", "pokeX", false, "vector<int>", "The x-coordinates of the actuators to poke. ");
357 0 : config.add("pokecen.pokeY", "", "pokecen.pokeY", argType::Required, "pokecen", "pokeY", false, "vector<int>", "The y-coordinates of the actuators to poke. ");
358 0 : config.add("pokecen.pokeAmp", "", "pokecen.pokeAmp", argType::Required, "pokecen", "pokeAmp", false, "float", "The poke amplitude, in DM command units. Default is 0.");
359 0 : 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 0 : config.add("pokecen.nPokeImages", "", "pokecen.nPokeImages", argType::Required, "pokecen", "nPokeImages", false, "int", "The number of poke images to average. Default 5.");
361 0 : config.add("pokecen.nPupilImages", "", "pokecen.nPupilImages", argType::Required, "pokecen", "nPupilImages", false, "int", "The number of pupil images to average. Default 20.");
362 0 : config.add("pokecen.pupilPixels", "", "pokecen.pupilPixels", argType::Required, "pokecen", "pupilPixels", false, "int", "The number of pixels in the pupil. Default is 68600.");
363 0 : 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 0 : config.add("pokecen.pupilMag", "", "pokecen.pupilMag", argType::Required, "pokecen", "pupilMag", false, "float", "The magnification to apply to the pupil image. >= 1, default 10.");
365 0 : 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 0 : config.add("pokecen.pokeBlockW", "", "pokecen.pokeBlockW", argType::Required, "pokecen", "pokeBlockW", false, "int", "The size of the sub-image for the poke analysis");
367 0 : 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 0 : }
369 :
370 0 : int dmPokeCenter::loadConfigImpl( mx::app::appConfigurator & _config )
371 : {
372 0 : shmimMonitorT::loadConfig(_config);
373 0 : telemeterT::loadConfig(_config);
374 :
375 0 : m_wfsCamDevName = shmimMonitorT::m_shmimName;
376 0 : _config(m_wfsCamDevName, "wfscam.camDevName");
377 :
378 : //configure the semaphore waits
379 0 : _config(m_wfsSemWait, "wfscam.loopSemWait");
380 :
381 0 : m_wfsSemWait_sec = floor(m_wfsSemWait);
382 0 : m_wfsSemWait_nsec = (m_wfsSemWait - m_wfsSemWait_sec) * 1e9;
383 :
384 0 : _config(m_imageSemWait, "wfscam.imageSemWait");
385 :
386 0 : m_imageSemWait_sec = floor(m_imageSemWait);
387 0 : m_imageSemWait_nsec = (m_imageSemWait - m_imageSemWait_sec) * 1e9;
388 :
389 0 : _config(m_dmChan, "pokecen.dmChannel");
390 :
391 0 : _config(m_poke_x, "pokecen.pokeX");
392 :
393 0 : _config(m_poke_y, "pokecen.pokeY");
394 :
395 0 : if(m_poke_x.size() == 0 || (m_poke_x.size() != m_poke_y.size()))
396 : {
397 0 : return log<software_error,-1>({__FILE__, __LINE__, "invalid poke specification"});
398 : }
399 :
400 0 : _config(m_poke_amp, "pokecen.pokeAmp");
401 :
402 0 : _config(m_dmSleep, "pokecen.dmSleep");
403 :
404 0 : _config(m_nPokeImages, "pokecen.nPokeImages");
405 0 : _config(m_nPupilImages, "pokecen.nPupilImages");
406 0 : _config(m_pupilPixels, "pokecen.pupilPixels");
407 0 : _config(m_pupilCutBuff, "pokecen.pupilCutBuff");
408 0 : _config(m_pupilMag, "pokecen.pupilMag");
409 0 : _config(m_pupilMedThresh, "pokecen.pupilMedThresh");
410 0 : _config(m_pokeBlockW, "pokecen.pokeBlockW");
411 0 : _config(m_pokeFWHMGuess, "pokecen.pokeFWHMGuess");
412 :
413 0 : return 0;
414 : }
415 :
416 0 : void dmPokeCenter::loadConfig()
417 : {
418 0 : if( loadConfigImpl(config)< 0)
419 : {
420 0 : m_shutdown = true;
421 : }
422 0 : }
423 :
424 0 : int dmPokeCenter::appStartup()
425 : {
426 0 : if( shmimMonitorT::appStartup() < 0)
427 : {
428 0 : return log<software_error, -1>({__FILE__,__LINE__});
429 : }
430 :
431 0 : if(telemeterT::appStartup() < 0)
432 : {
433 0 : return log<software_error,-1>({__FILE__, __LINE__});
434 : }
435 :
436 0 : CREATE_REG_INDI_NEW_NUMBERF(m_indiP_poke_amp, "poke_amp", -1, 1, 1e-1, "%0.01f", "", "");
437 0 : m_indiP_poke_amp["current"].setValue(m_poke_amp);
438 0 : m_indiP_poke_amp["target"].setValue(m_poke_amp);
439 :
440 0 : CREATE_REG_INDI_NEW_NUMBERI(m_indiP_nPupilImages, "nPupilImages", 1, 1000, 1, "%d", "", "");
441 0 : m_indiP_nPupilImages["current"].setValue(m_nPupilImages);
442 0 : m_indiP_nPupilImages["target"].setValue(m_nPupilImages);
443 :
444 0 : CREATE_REG_INDI_NEW_NUMBERI(m_indiP_nPokeImages, "nPokeImages", 1, 1000, 1, "%d", "", "");
445 0 : m_indiP_nPokeImages["current"].setValue(m_nPokeImages);
446 0 : m_indiP_nPokeImages["target"].setValue(m_nPokeImages);
447 :
448 0 : REG_INDI_SETPROP(m_indiP_wfsFps, m_wfsCamDevName, std::string("fps"));
449 :
450 0 : REG_INDI_SETPROP(m_indiP_shutter, m_wfsCamDevName, std::string("shutter"));
451 :
452 0 : CREATE_REG_INDI_NEW_TOGGLESWITCH( m_indiP_single, "single");
453 :
454 0 : CREATE_REG_INDI_NEW_TOGGLESWITCH( m_indiP_continuous, "continuous");
455 :
456 0 : CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_stop, "stop");
457 :
458 0 : registerIndiPropertyReadOnly( m_indiP_pupilPos, "pupil_position", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
459 0 : m_indiP_pupilPos.add({"x", 0.0});
460 0 : m_indiP_pupilPos.add({"y", 0.0});
461 :
462 0 : registerIndiPropertyReadOnly( m_indiP_pokePos, "poke_position", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
463 0 : m_indiP_pokePos.add({"avg_x", 0.0});
464 0 : m_indiP_pokePos.add({"avg_y", 0.0});
465 0 : for(size_t n = 0; n < m_poke_x.size(); ++n)
466 : {
467 0 : std::string pstr = "poke" + std::to_string(n) + "_";
468 0 : m_indiP_pokePos.add({pstr + "x", 0.0});
469 0 : m_indiP_pokePos.add({pstr + "y", 0.0});
470 0 : m_pokePosEls.push_back(pstr + "x");
471 0 : m_pokePosEls.push_back(pstr + "y");
472 0 : }
473 :
474 0 : m_pokePosEls.push_back("avg_x"); //keep vector of element names for UpdateIfChanged
475 0 : m_pokePosEls.push_back("avg_y");
476 :
477 0 : m_pokePositions.resize(m_pokePosEls.size());
478 :
479 0 : registerIndiPropertyReadOnly( m_indiP_deltaPos, "measurement", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
480 0 : m_indiP_deltaPos.add({"delta_x", 0.0});
481 0 : m_indiP_deltaPos.add({"delta_y", 0.0});
482 0 : m_indiP_deltaPos.add({"counter", 0});
483 :
484 0 : if(sem_init(&m_wfsSemaphore, 0,0) < 0)
485 : {
486 0 : return log<software_critical, -1>({__FILE__, __LINE__, errno,0, "Initializing wfs semaphore"});
487 : }
488 :
489 0 : if(sem_init(&m_imageSemaphore, 0,0) < 0)
490 : {
491 0 : return log<software_critical, -1>({__FILE__, __LINE__, errno,0, "Initializing image semaphore"});
492 : }
493 :
494 0 : if(threadStart( m_wfsThread, m_wfsThreadInit, m_wfsThreadID, m_wfsThreadProp, m_wfsThreadPrio, m_wfsCpuset, "wfs", this, wfsThreadStart) < 0)
495 : {
496 0 : return log<software_critical,-1>({__FILE__, __LINE__});
497 : }
498 :
499 0 : state(stateCodes::READY);
500 :
501 0 : return 0;
502 : }
503 :
504 0 : int dmPokeCenter::appLogic()
505 : {
506 0 : if( shmimMonitorT::appLogic() < 0)
507 : {
508 0 : return log<software_error, -1>({__FILE__,__LINE__});
509 : }
510 :
511 0 : if( telemeterT::appLogic() < 0)
512 : {
513 0 : 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 0 : if(pthread_tryjoin_np(m_wfsThread.native_handle(),0) == 0)
521 : {
522 0 : log<software_error>({__FILE__, __LINE__, "WFS thread has exited"});
523 0 : return -1;
524 : }
525 : }
526 0 : catch(...)
527 : {
528 0 : log<software_error>({__FILE__, __LINE__, "WFS thread has exited"});
529 0 : return -1;
530 0 : }
531 :
532 0 : if(m_measuring > 0)
533 : {
534 0 : if(m_continuous)
535 : {
536 0 : updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::On, INDI_OK);
537 : }
538 : else
539 : {
540 0 : updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
541 : }
542 :
543 0 : if(m_single)
544 : {
545 0 : updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::On, INDI_OK);
546 : }
547 : else
548 : {
549 0 : updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
550 : }
551 : }
552 : else
553 : {
554 0 : updateSwitchIfChanged(m_indiP_continuous, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
555 0 : updateSwitchIfChanged(m_indiP_single, "toggle", pcf::IndiElement::SwitchStateType::Off, INDI_IDLE);
556 : }
557 :
558 0 : updateIfChanged( m_indiP_nPupilImages, "current", m_nPupilImages);
559 0 : updateIfChanged( m_indiP_nPokeImages, "current", m_nPokeImages);
560 0 : updateIfChanged( m_indiP_poke_amp, "current", m_poke_amp);
561 :
562 0 : return 0;
563 : }
564 :
565 0 : int dmPokeCenter::appShutdown()
566 : {
567 0 : shmimMonitorT::appShutdown();
568 0 : telemeterT::appShutdown();
569 :
570 : try
571 : {
572 0 : if(m_wfsThread.joinable())
573 : {
574 0 : m_wfsThread.join();
575 : }
576 : }
577 0 : catch(...){}
578 :
579 0 : return 0;
580 : }
581 :
582 0 : int dmPokeCenter::allocate( const wfsShmimT & dummy)
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 0 : std::unique_lock<std::mutex> lock(m_wfsImageMutex);
588 :
589 0 : m_rawImage.create( m_configName + "_raw", shmimMonitorT::m_width, shmimMonitorT::m_height);
590 :
591 0 : m_wfsDark.create( m_configName + "_dark", shmimMonitorT::m_width, shmimMonitorT::m_height);
592 :
593 0 : m_pupilImage.create(m_configName + "_pupil", shmimMonitorT::m_width, shmimMonitorT::m_height);
594 :
595 0 : wfsPixget = getPixPointer<float>(shmimMonitorT::m_dataType);
596 :
597 : try
598 : {
599 0 : m_dmStream.open(m_dmChan);
600 : }
601 0 : catch(const std::exception& e) //this can check for invalid_argument and distinguish not existing
602 : {
603 0 : return log<software_error,-1>({__FILE__, __LINE__, std::string("exception opening DM: ") + e.what()});
604 0 : }
605 :
606 0 : m_dmImage.resize(m_dmStream.rows(), m_dmStream.cols());
607 :
608 : //end of call to pokeSensor::allocate
609 :
610 0 : m_pokeImage.create(m_configName + "_poke",shmimMonitorT::m_width, shmimMonitorT::m_height);
611 :
612 :
613 0 : return 0;
614 0 : }
615 :
616 0 : int dmPokeCenter::processImage( void * curr_src,
617 : const wfsShmimT & dummy
618 : )
619 : {
620 : static_cast<void>(dummy); //be unused
621 :
622 0 : float * data = m_rawImage().data();
623 :
624 : //Copy the data out as float no matter what type it is
625 0 : for(unsigned nn=0; nn < shmimMonitorT::m_width*shmimMonitorT::m_height; ++nn)
626 : {
627 0 : data[nn] = wfsPixget(curr_src, nn);
628 : }
629 :
630 0 : if(sem_post(&m_imageSemaphore) < 0)
631 : {
632 0 : return log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
633 : }
634 :
635 0 : return 0;
636 : }
637 :
638 : inline
639 0 : void dmPokeCenter::wfsThreadStart( dmPokeCenter * d)
640 : {
641 0 : d->wfsThreadExec();
642 0 : }
643 :
644 :
645 : inline
646 0 : void dmPokeCenter::wfsThreadExec()
647 : {
648 0 : m_wfsThreadID = syscall(SYS_gettid);
649 :
650 : //Wait fpr the thread starter to finish initializing this thread.
651 0 : while(m_wfsThreadInit == true && m_shutdown == 0)
652 : {
653 0 : sleep(1);
654 : }
655 :
656 0 : while(m_shutdown == 0)
657 : {
658 : timespec ts;
659 0 : XWC_SEM_WAIT_TS_RETVOID(ts, m_wfsSemWait_sec, m_wfsSemWait_nsec);
660 :
661 0 : XWC_SEM_TIMEDWAIT_LOOP( m_wfsSemaphore, ts )
662 :
663 : //Lock a mutex here
664 0 : if(m_single)
665 : {
666 0 : m_measuring = 1;
667 : }
668 0 : else if(m_continuous)
669 : {
670 0 : m_measuring = 2;
671 : }
672 : else
673 : {
674 0 : m_measuring = 0;
675 0 : return;
676 : }
677 :
678 0 : state(stateCodes::OPERATING);
679 :
680 0 : m_stopMeasurement = false;
681 :
682 0 : bool firstRun = true;
683 :
684 0 : while(!m_stopMeasurement && !m_shutdown)
685 : {
686 0 : if( runSensor(firstRun) < 0)
687 : {
688 0 : log<software_error>({__FILE__, __LINE__, "runSensor returned error"});
689 0 : break;
690 : }
691 :
692 0 : firstRun = false;
693 :
694 0 : if(m_measuring == 1)
695 : {
696 0 : break;
697 : }
698 : }
699 :
700 0 : m_measuring = 0;
701 0 : m_single = 0;
702 0 : m_continuous = 0;
703 :
704 0 : state(stateCodes::READY);
705 :
706 :
707 : } //outer loop, will exit if m_shutdown==true
708 :
709 0 : return;
710 :
711 : }
712 :
713 : inline
714 0 : int dmPokeCenter::runSensor(bool firstRun)
715 : {
716 :
717 0 : 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 0 : unsigned n = 0;
724 0 : while(!m_wfsDark.valid() && n < 200)
725 : {
726 0 : mx::sys::milliSleep(10);
727 0 : ++n;
728 : }
729 :
730 0 : if(!m_wfsDark.valid())
731 : {
732 0 : return log<software_error, -1>({__FILE__,__LINE__, "not allocated"});
733 : }
734 :
735 0 : if(firstRun)
736 : {
737 : //Shut the shutter
738 0 : if( sendNewStandardIndiToggle(m_wfsCamDevName, "shutter", true) < 0)
739 : {
740 0 : return log<software_error,-1>({__FILE__,__LINE__});
741 : }
742 :
743 : //Wait two seconds for it to shut
744 : ///\todo this should be configurable
745 0 : n = 0;
746 0 : while(m_shutter != 1 && n < 200)
747 : {
748 0 : mx::sys::milliSleep(10);
749 0 : ++n;
750 : }
751 :
752 0 : if(m_shutter != 1)
753 : {
754 0 : return log<software_error,-1>({__FILE__,__LINE__, "shutter did not shut"});
755 : }
756 :
757 0 : m_wfsDark().setZero();
758 0 : n = 0;
759 :
760 : //flush semaphore so we take the _next_ good image
761 0 : XWC_SEM_FLUSH(m_imageSemaphore);
762 :
763 : //** And wait one image **//
764 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
765 0 : bool ready = false;
766 0 : while(!ready)
767 : {
768 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
769 : else
770 : {
771 0 : ready = true;
772 : }
773 :
774 0 : if(m_stopMeasurement || m_shutdown)
775 : {
776 0 : m_dmImage.setZero();
777 0 : m_dmStream = m_dmImage;
778 0 : return 0;
779 : }
780 : }
781 :
782 0 : while(n < m_nDarks && !m_stopMeasurement && !m_shutdown)
783 : {
784 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
785 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
786 :
787 : //If here we got an image
788 0 : m_wfsDark() += m_rawImage();
789 0 : ++n;
790 : }
791 :
792 0 : m_wfsDark() /= m_nDarks;
793 :
794 : }
795 :
796 :
797 : //Open the shutter
798 0 : if( sendNewStandardIndiToggle(m_wfsCamDevName, "shutter", false) < 0)
799 : {
800 0 : return log<software_error>({__FILE__,__LINE__});
801 : }
802 :
803 : //Wait two seconds for it to open
804 : ///\todo this should be configurable
805 0 : n = 0;
806 0 : while(m_shutter != 0 && n < 200)
807 : {
808 0 : mx::sys::milliSleep(10);
809 0 : ++n;
810 : }
811 :
812 0 : if(m_shutter != 0)
813 : {
814 0 : return log<software_error,-1>({__FILE__,__LINE__, "shutter did not open"});
815 : }
816 :
817 : //** Now we record the pupil image **//
818 0 : m_pupilImage.setWrite();
819 0 : m_pupilImage().setZero();
820 0 : n = 0;
821 :
822 : //flush semaphore so we take the _next_ good image
823 0 : XWC_SEM_FLUSH(m_imageSemaphore);
824 :
825 : //** And wait one image **//
826 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
827 0 : bool ready = false;
828 0 : while(!ready)
829 : {
830 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
831 : else
832 : {
833 0 : ready = true;
834 : }
835 :
836 0 : if(m_stopMeasurement || m_shutdown)
837 : {
838 0 : m_dmImage.setZero();
839 0 : m_dmStream = m_dmImage;
840 0 : return 0;
841 : }
842 : }
843 :
844 0 : while(n < m_nPupilImages && !m_stopMeasurement && !m_shutdown)
845 : {
846 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
847 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
848 :
849 : //If here we got an image. m_rawImage will have been updated
850 0 : m_pupilImage() += m_rawImage();
851 0 : ++n;
852 : }
853 :
854 0 : m_pupilImage() = m_pupilImage()/m_nPupilImages - m_wfsDark();
855 0 : m_pupilImage.post();
856 :
857 0 : tmpFF.write("/tmp/pupilImage.fits", m_pupilImage());
858 :
859 0 : m_dmImage.setZero();
860 :
861 0 : for(size_t nn = 0; nn < m_poke_x.size(); ++nn)
862 : {
863 0 : m_dmImage( m_poke_x[nn], m_poke_y[nn]) = m_poke_amp;
864 : }
865 :
866 0 : m_pokeImage.setWrite();
867 0 : m_pokeImage().setZero();
868 :
869 0 : m_dmStream = m_dmImage;
870 :
871 0 : mx::sys::microSleep(m_dmSleep);
872 :
873 : //flush semaphore so we take the _next_ good image
874 0 : XWC_SEM_FLUSH(m_imageSemaphore);
875 :
876 : //** And wait one image **//
877 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
878 0 : ready = false;
879 0 : while(!ready)
880 : {
881 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
882 : else
883 : {
884 0 : ready = true;
885 : }
886 :
887 0 : if(m_stopMeasurement || m_shutdown)
888 : {
889 0 : m_dmImage.setZero();
890 0 : m_dmStream = m_dmImage;
891 0 : return 0;
892 : }
893 : }
894 :
895 0 : n = 0;
896 0 : while(n < m_nPokeImages && !m_stopMeasurement && !m_shutdown)
897 : {
898 : /* POSITIVE POKE */
899 :
900 : //** Now we record the poke image **//
901 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
902 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
903 :
904 : //If here, we got an image. m_rawImage will have been updated
905 0 : m_pokeImage() += m_rawImage();
906 :
907 0 : ++n;
908 : }
909 :
910 0 : if(m_stopMeasurement || m_shutdown)
911 : {
912 0 : m_dmImage.setZero();
913 0 : m_dmStream = m_dmImage;
914 0 : return 0;
915 : }
916 :
917 0 : m_dmImage.setZero();
918 :
919 0 : for(size_t nn = 0; nn < m_poke_x.size(); ++nn)
920 : {
921 0 : m_dmImage( m_poke_x[nn], m_poke_y[nn]) = -m_poke_amp;
922 : }
923 :
924 0 : m_dmStream = m_dmImage;
925 :
926 0 : mx::sys::microSleep(m_dmSleep);
927 :
928 : //flush semaphore so we take the _next_ good image
929 0 : XWC_SEM_FLUSH(m_imageSemaphore);
930 :
931 : //** And wait one image **//
932 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
933 0 : ready = false;
934 0 : while(!ready)
935 : {
936 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
937 : else
938 : {
939 0 : ready = true;
940 : }
941 :
942 0 : if(m_stopMeasurement || m_shutdown)
943 : {
944 0 : m_dmImage.setZero();
945 0 : m_dmStream = m_dmImage;
946 0 : return 0;
947 : }
948 : }
949 :
950 0 : n = 0;
951 0 : while(n < m_nPokeImages && !m_stopMeasurement && !m_shutdown)
952 : {
953 : /* NEGATIVE POKE */
954 :
955 : //** Now we record the poke image **//
956 0 : XWC_SEM_WAIT_TS(ts, m_imageSemWait_sec, m_imageSemWait_nsec);
957 0 : XWC_SEM_TIMEDWAIT_LOOP( m_imageSemaphore, ts )
958 :
959 : //If here, we got an image. m_rawImage will have been updated
960 0 : m_pokeImage() -= m_rawImage();
961 :
962 0 : ++n;
963 : }
964 :
965 0 : if(m_stopMeasurement || m_shutdown)
966 : {
967 0 : m_dmImage.setZero();
968 0 : m_dmStream = m_dmImage;
969 :
970 0 : return 0;
971 : }
972 :
973 0 : m_pokeImage() = m_pokeImage()/(2);
974 0 : m_pokeImage.post();
975 :
976 0 : tmpFF.write("/tmp/poke.fits", m_pokeImage());
977 :
978 :
979 0 : m_dmImage.setZero();
980 0 : m_dmStream = m_dmImage;
981 :
982 0 : return analyzeSensor();
983 0 : }
984 :
985 : inline
986 0 : int dmPokeCenter::analyzeSensor()
987 : {
988 0 : if(m_shutdown || m_stopMeasurement)
989 : {
990 0 : return 0;
991 : }
992 :
993 0 : if(fitPupil() < 0)
994 : {
995 0 : return log<software_error,-1>({__FILE__, __LINE__, "error from fitPupil"});
996 : }
997 :
998 0 : if(fitPokes() < 0)
999 : {
1000 0 : return log<software_error,-1>({__FILE__, __LINE__, "error from fitPupil"});
1001 : }
1002 :
1003 0 : if(m_stopMeasurement)
1004 : {
1005 0 : return 0;
1006 : }
1007 :
1008 0 : ++m_counter;
1009 :
1010 0 : std::cerr << m_counter << " " << m_pupilX << " " << m_pupilY << " " << m_pokeX << " " << m_pokeY << " | " << m_pupilX - m_pokeX << " " << m_pupilY - m_pokeY << "\n";
1011 :
1012 0 : m_indiP_deltaPos["delta_x"] = m_pupilX - m_pokeX;
1013 0 : m_indiP_deltaPos["delta_y"] = m_pupilY - m_pokeY;
1014 :
1015 0 : updateIfChanged(m_indiP_deltaPos, "counter", m_counter);
1016 :
1017 0 : recordPokeCenter();
1018 :
1019 0 : return 0;
1020 : }
1021 :
1022 : inline
1023 0 : int dmPokeCenter::fitPupil()
1024 : {
1025 :
1026 0 : mx::fits::fitsFile<float> ff;
1027 :
1028 0 : float threshPerc = (1.0*m_pupilPixels)/(m_pupilImage().rows() * m_pupilImage().cols());
1029 :
1030 : //Threshold to find the initial pupil mask geometrically
1031 0 : size_t pos = (1-threshPerc)*m_pupilImage().rows()*m_pupilImage().cols();
1032 :
1033 0 : eigenImage<float> sm;
1034 : int xmx, ymx;
1035 : float mx;
1036 :
1037 0 : float m_pupSmoothWidth=3;
1038 0 : sm.resize(m_pupilImage().rows(), m_pupilImage().cols());
1039 0 : medianSmooth(sm, xmx, ymx, mx, m_pupilImage(), m_pupSmoothWidth);
1040 :
1041 : //Above can put in wild vals within smooth width
1042 0 : for(int n =0; n < m_pupSmoothWidth; ++n)
1043 : {
1044 0 : sm.row(n) = 0;
1045 0 : sm.row(sm.rows()-1-n) = 0;
1046 0 : sm.col(n) = 0;
1047 0 : sm.col(sm.cols()-1-n) = 0;
1048 : }
1049 :
1050 0 : m_pupilCopy = sm;;
1051 :
1052 0 : std::nth_element(m_pupilCopy.data(), m_pupilCopy.data()+pos, m_pupilCopy.data()+m_pupilCopy.rows()*m_pupilCopy.cols());
1053 :
1054 0 : float pupilThresh = m_pupilCopy.data()[pos];
1055 :
1056 : float x0, y0, avgr0, avgx, avgy, avgr;
1057 :
1058 :
1059 0 : m_fullMask.resize(sm.rows(), sm.cols());
1060 :
1061 0 : for(int cc=0; cc < m_fullMask.cols(); ++cc)
1062 : {
1063 0 : for(int rr=0; rr < m_fullMask.rows(); ++rr)
1064 : {
1065 0 : if(sm(rr,cc) < pupilThresh)
1066 : {
1067 0 : m_fullMask(rr,cc) = 0;
1068 : }
1069 : else
1070 : {
1071 0 : m_fullMask(rr,cc) = 1;
1072 : }
1073 : }
1074 : }
1075 :
1076 0 : ff.write("/tmp/fullMask.fits", m_fullMask);
1077 :
1078 : //Now find the outer edge
1079 0 : if(circleOuterpix( x0, y0, avgr0, avgx, avgy, avgr, m_fullEdge, m_fullMask) < 0)
1080 : {
1081 0 : return log<software_error, -1>({__FILE__, __LINE__, "circle fit failed"});
1082 : }
1083 :
1084 : //And cut out the pupil plust buffer
1085 0 : float cutx = avgx - avgr-m_pupilCutBuff;
1086 0 : float cuty = avgy - avgr-m_pupilCutBuff;
1087 0 : float cutw = 2*avgr+2*m_pupilCutBuff;
1088 :
1089 0 : if(cutx < 0)
1090 : {
1091 0 : return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1092 : }
1093 :
1094 0 : if(cuty < 0)
1095 : {
1096 0 : return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1097 : }
1098 :
1099 0 : if(cutx + cutw > sm.rows())
1100 : {
1101 0 : return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1102 : }
1103 :
1104 0 : if(cuty + cutw > sm.rows())
1105 : {
1106 0 : return log<software_error, -1>({__FILE__, __LINE__, "pupilCutBuff is too big for pupil position"});
1107 : }
1108 :
1109 0 : m_pupilCut = sm.block( cutx, cuty, cutw, cutw);
1110 :
1111 0 : m_pupilMagnified.resize(m_pupilCut.rows()*m_pupilMag, m_pupilCut.cols()*m_pupilMag);
1112 :
1113 0 : imageMagnify(m_pupilMagnified, m_pupilCut, mx::improc::bilinearTransform<float>());
1114 :
1115 0 : ff.write("/tmp/pupilMagnified.fits", m_pupilMagnified);
1116 :
1117 0 : float med = imageMedian(m_pupilMagnified); /// \todo use work version
1118 :
1119 0 : float dthresh = pupilThresh; //med * m_pupilMedThresh;
1120 :
1121 0 : m_magMask.resize(m_pupilMagnified.rows(), m_pupilMagnified.cols()); //This is a different mask-- maskMag
1122 :
1123 0 : for(int cc=0; cc < m_pupilMagnified.cols(); ++cc)
1124 : {
1125 0 : for(int rr=0; rr < m_pupilMagnified.rows(); ++rr)
1126 : {
1127 0 : if(m_pupilMagnified(rr,cc) < dthresh)
1128 : {
1129 0 : m_magMask(rr,cc) = 0;
1130 : }
1131 : else
1132 : {
1133 0 : m_magMask(rr,cc) = 1;
1134 : }
1135 : }
1136 : }
1137 :
1138 0 : ff.write("/tmp/magMask.fits", m_magMask);
1139 :
1140 0 : if(circleOuterpix( x0, y0, avgr0, avgx, avgy, avgr, m_magEdge, m_magMask) < 0)
1141 : {
1142 0 : return log<software_error, -1>({__FILE__, __LINE__, "circle fit failed"});
1143 : }
1144 :
1145 0 : ff.write("/tmp/magEdge.fits", m_magEdge);
1146 :
1147 :
1148 0 : x0 = cutx + x0/m_pupilMag;
1149 0 : y0 = cuty + y0/m_pupilMag;
1150 0 : avgr0 /= m_pupilMag;
1151 :
1152 0 : avgx = cutx + avgx/m_pupilMag;
1153 0 : avgy = cuty + avgy/m_pupilMag;
1154 0 : avgr /= m_pupilMag;
1155 :
1156 0 : m_pupilX = avgx;
1157 0 : m_pupilY = avgy;
1158 :
1159 0 : updateIfChanged(m_indiP_pupilPos, std::vector<std::string>({"x", "y"}), std::vector<float>({m_pupilX, m_pupilY}));
1160 :
1161 0 : return 0;
1162 :
1163 0 : }
1164 :
1165 : inline
1166 0 : int dmPokeCenter::fitPokes()
1167 : {
1168 0 : eigenImage<float> sm, tim;
1169 :
1170 0 : sm.resize(m_pokeImage.rows(), m_pokeImage.cols());
1171 :
1172 0 : int xmx = 0;
1173 0 : int ymx = 0;
1174 :
1175 0 : float mx = 0;
1176 :
1177 0 : medianSmooth(sm, xmx, ymx, mx, m_pokeImage(), m_smoothWidth);
1178 :
1179 : //Above can put in wild vals within smooth width
1180 0 : for(int n =0; n < m_smoothWidth; ++n)
1181 : {
1182 0 : sm.row(n) = 0;
1183 0 : sm.row(sm.rows()-1-n) = 0;
1184 0 : sm.col(n) = 0;
1185 0 : sm.col(sm.cols()-1-n) = 0;
1186 : }
1187 :
1188 0 : mx::fits::fitsFile<float> ff;
1189 :
1190 0 : ff.write("/tmp/sm.fits", sm);
1191 0 : m_pokeX = 0;
1192 0 : m_pokeY = 0;
1193 :
1194 0 : 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 0 : mx = sm.maxCoeff(&xmx, &ymx);
1202 :
1203 0 : x0 = xmx - m_pokeBlockW/2;
1204 0 : 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 0 : m_pokeBlock = sm.block(x0, y0, m_pokeBlockW, m_pokeBlockW);
1212 :
1213 0 : sm.block(x0, y0, m_pokeBlockW, m_pokeBlockW) = 0;
1214 :
1215 0 : m_gfit.set_itmax(1000);
1216 0 : m_gfit.setArray(m_pokeBlock.data(), m_pokeBlock.rows(), m_pokeBlock.cols());
1217 0 : 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 0 : m_gfit.fit();
1219 :
1220 0 : int rc = m_gfit.get_reason_code();
1221 0 : if(rc != 1 && rc != 2)
1222 : {
1223 0 : return log<software_error, -1>({__FILE__,__LINE__, "fit to poke " + std::to_string(nn) + " failed: " + m_gfit.get_reason_string()});
1224 : }
1225 :
1226 0 : m_pokePositions[nn*2 + 0] = x0 + m_gfit.x0();
1227 0 : m_pokePositions[nn*2 + 1] = y0 + m_gfit.y0();
1228 :
1229 0 : m_pokeX += x0 + m_gfit.x0();
1230 0 : 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 0 : m_pokeX /= m_poke_x.size();
1242 0 : m_pokeY /= m_poke_x.size();
1243 :
1244 0 : m_pokePositions[m_poke_x.size()*2 + 0] = m_pokeX;
1245 0 : m_pokePositions[m_poke_x.size()*2 + 1] = m_pokeY;
1246 :
1247 0 : updateIfChanged(m_indiP_pokePos, m_pokePosEls, m_pokePositions);
1248 :
1249 :
1250 0 : return 0;
1251 0 : }
1252 :
1253 0 : INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_nPupilImages )(const pcf::IndiProperty &ipRecv)
1254 : {
1255 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_nPupilImages, ipRecv)
1256 :
1257 : float target;
1258 :
1259 0 : if( indiTargetUpdate(m_indiP_nPupilImages, target, ipRecv, false) < 0)
1260 : {
1261 0 : return log<software_error,-1>({__FILE__, __LINE__});
1262 : }
1263 :
1264 0 : m_nPupilImages = target;
1265 :
1266 0 : return 0;
1267 : }
1268 :
1269 0 : INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_nPokeImages )(const pcf::IndiProperty &ipRecv)
1270 : {
1271 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_nPokeImages, ipRecv)
1272 :
1273 : float target;
1274 :
1275 0 : if( indiTargetUpdate(m_indiP_nPokeImages, target, ipRecv, false) < 0)
1276 : {
1277 0 : return log<software_error,-1>({__FILE__, __LINE__});
1278 : }
1279 :
1280 0 : m_nPokeImages = target;
1281 :
1282 0 : return 0;
1283 : }
1284 :
1285 0 : INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_poke_amp )(const pcf::IndiProperty &ipRecv)
1286 : {
1287 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_poke_amp, ipRecv)
1288 :
1289 : float target;
1290 :
1291 0 : if( indiTargetUpdate(m_indiP_poke_amp, target, ipRecv, false) < 0)
1292 : {
1293 0 : return log<software_error,-1>({__FILE__, __LINE__});
1294 : }
1295 :
1296 0 : m_poke_amp = target;
1297 :
1298 0 : return 0;
1299 : }
1300 :
1301 0 : INDI_SETCALLBACK_DEFN( dmPokeCenter, m_indiP_wfsFps )(const pcf::IndiProperty &ipRecv)
1302 : {
1303 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_wfsFps, ipRecv)
1304 :
1305 0 : if( ipRecv.find("current") != true ) //this isn't valid
1306 : {
1307 0 : return 0;
1308 : }
1309 :
1310 0 : m_wfsFps = ipRecv["current"].get<float>();
1311 :
1312 0 : return 0;
1313 : }
1314 :
1315 0 : INDI_SETCALLBACK_DEFN( dmPokeCenter, m_indiP_shutter )(const pcf::IndiProperty &ipRecv)
1316 : {
1317 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_shutter, ipRecv)
1318 :
1319 0 : if( ipRecv.find("toggle") != true ) //this isn't valid
1320 : {
1321 0 : return -1;
1322 : }
1323 :
1324 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1325 : {
1326 0 : m_shutter = false; //open
1327 : }
1328 : else
1329 : {
1330 0 : m_shutter = true; //shut
1331 : }
1332 :
1333 0 : return 0;
1334 : }
1335 :
1336 0 : INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_single )(const pcf::IndiProperty &ipRecv)
1337 : {
1338 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_single, ipRecv)
1339 :
1340 0 : if( ipRecv.find("toggle") != true ) //this isn't valid
1341 : {
1342 0 : return -1;
1343 : }
1344 :
1345 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1346 : {
1347 0 : if(m_measuring == 0)
1348 : {
1349 0 : m_continuous = 0;
1350 0 : m_single = 1;
1351 0 : if(sem_post(&m_wfsSemaphore) < 0)
1352 : {
1353 0 : return log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1354 : }
1355 : }
1356 : }
1357 :
1358 0 : return 0;
1359 : }
1360 :
1361 0 : INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_continuous )(const pcf::IndiProperty &ipRecv)
1362 : {
1363 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_continuous, ipRecv)
1364 :
1365 0 : if( ipRecv.find("toggle") != true ) //this isn't valid
1366 : {
1367 0 : return -1;
1368 : }
1369 :
1370 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1371 : {
1372 0 : if(m_measuring == 0)
1373 : {
1374 0 : m_continuous = 1;
1375 0 : m_single = 0;
1376 0 : if(sem_post(&m_wfsSemaphore) < 0)
1377 : {
1378 0 : return log<software_critical, -1>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
1379 : }
1380 : }
1381 : }
1382 0 : else if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::Off )
1383 : {
1384 0 : if(m_measuring != 0)
1385 : {
1386 0 : m_stopMeasurement = true;
1387 : }
1388 : }
1389 :
1390 0 : return 0;
1391 : }
1392 :
1393 0 : INDI_NEWCALLBACK_DEFN( dmPokeCenter, m_indiP_stop )(const pcf::IndiProperty &ipRecv)
1394 : {
1395 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_stop, ipRecv)
1396 :
1397 0 : if( ipRecv.find("request") != true ) //this isn't valid
1398 : {
1399 0 : return -1;
1400 : }
1401 :
1402 0 : if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1403 : {
1404 0 : if(m_measuring != 0)
1405 : {
1406 0 : m_stopMeasurement = true;
1407 : }
1408 : }
1409 :
1410 0 : return 0;
1411 : }
1412 :
1413 : inline
1414 0 : int dmPokeCenter::checkRecordTimes()
1415 : {
1416 0 : return telemeterT::checkRecordTimes(telem_pokecenter());
1417 : }
1418 :
1419 : inline
1420 0 : int dmPokeCenter::recordTelem( const telem_pokecenter * )
1421 : {
1422 0 : return recordPokeCenter(true);
1423 : }
1424 :
1425 : inline
1426 0 : int dmPokeCenter::recordPokeCenter( bool force )
1427 : {
1428 : static int measuring = -1;
1429 : static float pupilX = 0;
1430 : static float pupilY = 0;
1431 0 : static std::vector<float> pokePositions;
1432 :
1433 0 : if(pokePositions.size() != m_pokePositions.size())
1434 : {
1435 0 : pokePositions.resize(m_pokePositions.size(), 0);
1436 : }
1437 :
1438 0 : bool changed = false;
1439 0 : if(!force)
1440 : {
1441 0 : if(m_measuring != measuring) changed = true;
1442 0 : else if(m_pupilX != pupilX) changed = true;
1443 0 : else if(m_pupilY != pupilY) changed = true;
1444 : else
1445 : {
1446 0 : for(size_t n = 0; n < m_pokePositions.size(); ++n)
1447 : {
1448 0 : if(m_pokePositions[n] != pokePositions[n])
1449 : {
1450 0 : changed = true;
1451 0 : break;
1452 : }
1453 : }
1454 : }
1455 : }
1456 :
1457 0 : if(changed || force)
1458 : {
1459 0 : uint8_t meas = m_measuring;
1460 0 : telem<telem_pokecenter>({meas, m_pupilX, m_pupilY, m_pokePositions});
1461 :
1462 0 : measuring = m_measuring;
1463 0 : pupilX = m_pupilX;
1464 0 : pupilY = m_pupilY;
1465 0 : pokePositions.assign(m_pokePositions.begin(), m_pokePositions.end());
1466 : }
1467 :
1468 0 : return 0;
1469 : }
1470 :
1471 : } //namespace app
1472 : } //namespace MagAOX
1473 :
1474 : #endif //dmPokeCenter_hpp
|