9 #ifndef dmPokeCenter_hpp
10 #define dmPokeCenter_hpp
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;
18 #include <mx/math/fit/fitGaussian.hpp>
20 #include "../../libMagAOX/libMagAOX.hpp"
21 #include "../../magaox_git_version.h"
62 friend class dmPokeCenter_test;
80 double m_wfsSemWait {1.5};
82 double m_imageSemWait {0.5};
84 unsigned m_nDarks {5};
86 unsigned m_nPupilImages {20};
88 unsigned m_nPokeImages {5};
95 float m_poke_amp {0.0};
97 float m_dmSleep {10000};
100 int m_pupilPixels {68600};
102 int m_pupilCutBuff {20};
104 float m_pupilMag {10};
106 float m_pupilMedThresh = {0.9};
108 int m_pokeBlockW {64};
110 int m_pokeFWHMGuess {2};
112 float m_smoothWidth {3};
126 float (*wfsPixget)(
void *, size_t) {
nullptr};
151 mx::math::fit::fitGaussian2Dsym<float>
m_gfit;
157 int32_t m_counter {0};
170 virtual void setupConfig();
175 int loadConfigImpl( mx::app::appConfigurator & _config );
177 virtual void loadConfig();
183 virtual int appStartup();
190 virtual int appLogic();
196 virtual int appShutdown();
206 int processImage(
void * curr_src,
218 int m_wfsThreadPrio {1};
224 bool m_wfsThreadInit {
true};
226 pid_t m_wfsThreadID {0};
234 void wfsThreadExec();
238 unsigned m_wfsSemWait_sec {1};
240 unsigned m_wfsSemWait_nsec {0};
246 bool m_continuous {
false};
248 bool m_stopMeasurement {
false};
254 unsigned m_imageSemWait_sec {1};
256 unsigned m_imageSemWait_nsec {0};
265 int runSensor(
bool firstRun );
333 int checkRecordTimes();
337 int recordPokeCenter(
bool force =
false );
341 dmPokeCenter::dmPokeCenter() :
MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
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");
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.");
389 _config(
m_dmChan,
"pokecen.dmChannel");
397 return log<
software_error,-1>({__FILE__, __LINE__,
"invalid poke specification"});
465 for(
size_t n = 0; n <
m_poke_x.size(); ++n)
467 std::string pstr =
"poke" + std::to_string(n) +
"_";
491 return log<
software_critical, -1>({__FILE__, __LINE__, errno,0,
"Initializing image semaphore"});
520 if(pthread_tryjoin_np(
m_wfsThread.native_handle(),0) == 0)
522 log<software_error>({__FILE__, __LINE__,
"WFS thread has exited"});
528 log<software_error>({__FILE__, __LINE__,
"WFS thread has exited"});
584 static_cast<void>(dummy);
601 catch(
const std::exception& e)
603 return log<
software_error,-1>({__FILE__, __LINE__, std::string(
"exception opening DM: ") + e.what()});
620 static_cast<void>(dummy);
632 return log<
software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
682 bool firstRun =
true;
688 log<software_error>({__FILE__, __LINE__,
"runSensor returned error"});
717 mx::fits::fitsFile<float> tmpFF;
726 mx::sys::milliSleep(10);
748 mx::sys::milliSleep(10);
800 return log<software_error>({__FILE__,__LINE__});
808 mx::sys::milliSleep(10);
861 for(
size_t nn = 0; nn <
m_poke_x.size(); ++nn)
919 for(
size_t nn = 0; nn <
m_poke_x.size(); ++nn)
1026 mx::fits::fitsFile<float> ff;
1033 eigenImage<float> sm;
1037 float m_pupSmoothWidth=3;
1039 medianSmooth(sm, xmx, ymx, mx,
m_pupilImage(), m_pupSmoothWidth);
1042 for(
int n =0; n < m_pupSmoothWidth; ++n)
1045 sm.row(sm.rows()-1-n) = 0;
1047 sm.col(sm.cols()-1-n) = 0;
1056 float x0, y0, avgr0, avgx, avgy, avgr;
1065 if(sm(rr,cc) < pupilThresh)
1091 return log<
software_error, -1>({__FILE__, __LINE__,
"pupilCutBuff is too big for pupil position"});
1096 return log<
software_error, -1>({__FILE__, __LINE__,
"pupilCutBuff is too big for pupil position"});
1099 if(cutx + cutw > sm.rows())
1101 return log<
software_error, -1>({__FILE__, __LINE__,
"pupilCutBuff is too big for pupil position"});
1104 if(cuty + cutw > sm.rows())
1106 return log<
software_error, -1>({__FILE__, __LINE__,
"pupilCutBuff is too big for pupil position"});
1109 m_pupilCut = sm.block( cutx, cuty, cutw, cutw);
1119 float dthresh = pupilThresh;
1138 ff.write(
"/tmp/magMask.fits",
m_magMask);
1145 ff.write(
"/tmp/magEdge.fits",
m_magEdge);
1168 eigenImage<float> sm, tim;
1183 sm.row(sm.rows()-1-n) = 0;
1185 sm.col(sm.cols()-1-n) = 0;
1188 mx::fits::fitsFile<float> ff;
1190 ff.write(
"/tmp/sm.fits", sm);
1194 for(
size_t nn = 0; nn <
m_poke_x.size(); ++nn)
1201 mx = sm.maxCoeff(&xmx, &ymx);
1220 int rc =
m_gfit.get_reason_code();
1221 if(rc != 1 && rc != 2)
1223 return log<
software_error, -1>({__FILE__,__LINE__,
"fit to poke " + std::to_string(nn) +
" failed: " +
m_gfit.get_reason_string()});
1259 if( indiTargetUpdate(m_indiP_nPupilImages, target,
ipRecv,
false) < 0)
1264 m_nPupilImages = target;
1275 if( indiTargetUpdate(m_indiP_nPokeImages, target,
ipRecv,
false) < 0)
1280 m_nPokeImages = target;
1291 if( indiTargetUpdate(m_indiP_poke_amp, target,
ipRecv,
false) < 0)
1296 m_poke_amp = target;
1305 if(
ipRecv.find(
"current") !=
true )
1310 m_wfsFps =
ipRecv[
"current"].get<
float>();
1319 if(
ipRecv.find(
"toggle") !=
true )
1324 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
1340 if(
ipRecv.find(
"toggle") !=
true )
1345 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1347 if(m_measuring == 0)
1351 if(sem_post(&m_wfsSemaphore) < 0)
1353 return log<
software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
1365 if(
ipRecv.find(
"toggle") !=
true )
1370 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1372 if(m_measuring == 0)
1376 if(sem_post(&m_wfsSemaphore) < 0)
1378 return log<
software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
1382 else if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
1384 if(m_measuring != 0)
1386 m_stopMeasurement =
true;
1397 if(
ipRecv.find(
"request") !=
true )
1402 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
1404 if(m_measuring != 0)
1406 m_stopMeasurement =
true;
1428 static int measuring = -1;
1429 static float pupilX = 0;
1430 static float pupilY = 0;
1431 static std::vector<float> pokePositions;
1438 bool changed =
false;
1442 else if(
m_pupilX != pupilX) changed =
true;
1443 else if(
m_pupilY != pupilY) changed =
true;
1457 if(changed || force)
The base-class for MagAO-X 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.
int appStartup()
Startup function.
uint32_t m_width
The width of the images in the stream.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
int appLogic()
Checks the shmimMonitor thread.
uint32_t m_height
The height of the images in the stream.
std::string m_shmimName
The name of the shared memory image, is used in /tmp/<shmimName>.im.shm. Derived classes should set a...
int appShutdown()
Shuts down the shmimMonitor thread.
uint8_t m_dataType
The ImageStreamIO type code.
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.
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.
virtual void loadConfig()
std::mutex m_wfsImageMutex
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.
virtual void setupConfig()
int recordPokeCenter(bool force=false)
INDI_NEWCALLBACK_DECL(dmPokeCenter, m_indiP_continuous)
std::thread m_wfsThread
A separate thread for the actual WFSing.
pcf::IndiProperty m_indiP_shutter
Property to get the status from the WFS camera.
pcf::IndiProperty m_indiP_nPupilImages
pcf::IndiProperty m_wfsThreadProp
The property to hold the WFS thread details.
int fitPupil()
Fit the pupil parameters.
INDI_SETCALLBACK_DECL(dmPokeCenter, m_indiP_wfsFps)
milkImage< float > m_pokeImage
pcf::IndiProperty m_indiP_poke_amp
pcf::IndiProperty m_indiP_stop
Property to request that measurement stop.
std::string m_wfsCpuset
The cpuset for the framegrabber thread. Ignored if empty (the default).
#define CREATE_REG_INDI_NEW_TOGGLESWITCH(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
#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 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...
@ OPERATING
The device is operating, other than homing.
@ READY
The device is ready for operation, but is not operating.
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
const pcf::IndiProperty & ipRecv
INDI_SETCALLBACK_DEFN(adcTracker, m_indiP_teldata)(const pcf
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
std::unique_lock< std::mutex > lock(m_indiMutex)
#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.
#define XWC_SEM_TIMEDWAIT_LOOP(sem, ts)
Perform a sem_timedwait in the context of a standard loop in MagAO-X code.
#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.
#define XWC_SEM_FLUSH(sem)
A device base class which saves telemetry.
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.
Software CRITICAL log entry.
Log entry recording DM poke centering results.
static std::string configSection()
static std::string indiPrefix()