10 #include <mx/improc/eigenImage.hpp>
11 #include <mx/improc/milkImage.hpp>
12 #include <mx/improc/eigenCube.hpp>
13 using namespace mx::improc;
15 #include "../../ImageStreamIO/pixaccess.hpp"
135 template<
class derivedT>
175 double m_wfsSemWait {1.5};
177 double m_imageSemWait {0.5};
179 unsigned m_nPokeImages {5};
181 unsigned m_nPokeAverage {10};
188 float m_poke_amp {0.0};
190 float m_dmSleep {10000};
201 float (*wfsPixget)(
void *, size_t) {
nullptr};
207 bool m_darkValid {
false};
209 float (*darkPixget)(
void *, size_t) {
nullptr};
217 uint64_t m_counter {0};
315 int m_wfsThreadPrio {1};
321 bool m_wfsThreadInit {
true};
323 pid_t m_wfsThreadID {0};
335 unsigned m_wfsSemWait_sec {1};
337 unsigned m_wfsSemWait_nsec {0};
343 bool m_continuous {
false};
345 bool m_stopMeasurement {
false};
351 unsigned m_imageSemWait_sec {1};
353 unsigned m_imageSemWait_nsec {0};
428 return *
static_cast<derivedT *
>(
this);
433 template<
class derivedT>
438 derivedT::template log<software_error>({__FILE__, __LINE__,
"shmimMonitorT::setupConfig"});
442 config.add(
"wfscam.camDevName",
"",
"wfscam.camDevName", argType::Required,
"wfscam",
"camDevName",
false,
"string",
"INDI device name of the WFS camera. Default is wfscam.shmimName.");
443 config.add(
"wfscam.loopSemWait",
"",
"wfscam.loopSemWait", argType::Required,
"wfscam",
"loopSemWait",
false,
"float",
"The semaphore wait time for the wfs loop start signal");
444 config.add(
"wfscam.imageSemWait",
"",
"wfscam.imageSemWait", argType::Required,
"wfscam",
"imageSemWait",
false,
"float",
"The semaphore wait time for the image availability signal");
446 if(derived().darkShmimMonitor().setupConfig(config) < 0)
448 derivedT::template log<software_error>({__FILE__, __LINE__,
"darkShmimMonitorT::setupConfig"});
452 config.add(
"pokecen.dmChannel",
"",
"pokecen.dmChannel", argType::Required,
"pokecen",
"dmChannel",
false,
"string",
"The dm channel to use for pokes, e.g. dm01disp06.");
453 config.add(
"pokecen.pokeX",
"",
"pokecen.pokeX", argType::Required,
"pokecen",
"pokeX",
false,
"vector<int>",
"The x-coordinates of the actuators to poke. ");
454 config.add(
"pokecen.pokeY",
"",
"pokecen.pokeY", argType::Required,
"pokecen",
"pokeY",
false,
"vector<int>",
"The y-coordinates of the actuators to poke. ");
455 config.add(
"pokecen.pokeAmp",
"",
"pokecen.pokeAmp", argType::Required,
"pokecen",
"pokeAmp",
false,
"float",
"The poke amplitude, in DM command units. Default is 0.");
456 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.");
457 config.add(
"pokecen.nPokeImages",
"",
"pokecen.nPokeImages", argType::Required,
"pokecen",
"nPokeImages",
false,
"int",
"The number of poke images to average. Default 5.");
458 config.add(
"pokecen.nPokeAverage",
"",
"pokecen.nPokeAverage", argType::Required,
"pokecen",
"nPokeAverage",
false,
"int",
"The number of poke sequences to average. Default 10.");
465 template<
class derivedT>
470 return derivedT::template
log<software_error, -1>({__FILE__, __LINE__,
"shmimMonitorT::loadConfig"});
473 m_wfsCamDevName = derived().shmimMonitor().shmimName();
474 config(m_wfsCamDevName,
"wfscam.camDevName");
477 config(m_wfsSemWait,
"wfscam.loopSemWait");
479 m_wfsSemWait_sec = floor(m_wfsSemWait);
480 m_wfsSemWait_nsec = (m_wfsSemWait - m_wfsSemWait_sec) * 1e9;
482 config(m_imageSemWait,
"wfscam.imageSemWait");
484 m_imageSemWait_sec = floor(m_imageSemWait);
485 m_imageSemWait_nsec = (m_imageSemWait - m_imageSemWait_sec) * 1e9;
487 if(derived().darkShmimMonitor().loadConfig(config) < 0)
489 return derivedT::template
log<software_error, -1>({__FILE__, __LINE__,
"darkShmimMonitorT::loadConfig"});
492 config(m_dmChan,
"pokecen.dmChannel");
494 config(m_poke_x,
"pokecen.pokeX");
496 config(m_poke_y,
"pokecen.pokeY");
498 if(m_poke_x.size() == 0 || (m_poke_x.size() != m_poke_y.size()))
500 return derivedT::template
log<software_error,-1>({__FILE__, __LINE__,
"invalid poke specification"});
503 config(m_poke_amp,
"pokecen.pokeAmp");
505 config(m_dmSleep,
"pokecen.dmSleep");
507 config(m_nPokeImages,
"pokecen.nPokeImages");
509 config(m_nPokeAverage,
"pokecen.nPokeAverage");
514 template<
class derivedT>
519 return derivedT::template
log<software_error, -1>({__FILE__,__LINE__});
522 if( derived().darkShmimMonitor().appStartup() < 0)
524 return derivedT::template
log<software_error, -1>({__FILE__,__LINE__});
528 m_indiP_poke_amp[
"current"].setValue(m_poke_amp);
529 m_indiP_poke_amp[
"target"].setValue(m_poke_amp);
532 m_indiP_nPokeImages[
"current"].setValue(m_nPokeImages);
533 m_indiP_nPokeImages[
"target"].setValue(m_nPokeImages);
536 m_indiP_nPokeAverage[
"current"].setValue(m_nPokeAverage);
537 m_indiP_nPokeAverage[
"target"].setValue(m_nPokeAverage);
547 derived().template registerIndiPropertyReadOnly( m_indiP_measurement,
"measurement", pcf::IndiProperty::Number, pcf::IndiProperty::ReadOnly, pcf::IndiProperty::Idle);
548 m_indiP_measurement.add({
"delta_x", 0.0});
549 m_indiP_measurement.add({
"delta_y", 0.0});
550 m_indiP_measurement.add({
"counter", 0});
552 if(sem_init(&m_wfsSemaphore, 0,0) < 0)
554 return derivedT::template
log<software_critical, -1>({__FILE__, __LINE__, errno,0,
"Initializing wfs semaphore"});
557 if(sem_init(&m_imageSemaphore, 0,0) < 0)
559 return derivedT::template
log<software_critical, -1>({__FILE__, __LINE__, errno,0,
"Initializing image semaphore"});
562 if(derived().template threadStart( m_wfsThread, m_wfsThreadInit, m_wfsThreadID, m_wfsThreadProp, m_wfsThreadPrio, m_wfsCpuset,
"wfs",
this, wfsThreadStart) < 0)
564 return derivedT::template
log<software_critical,-1>({__FILE__, __LINE__});
570 template<
class derivedT>
576 return derivedT::template
log<software_error, -1>({__FILE__,__LINE__});
579 if( derived().darkShmimMonitor().appLogic() < 0)
581 return derivedT::template
log<software_error, -1>({__FILE__,__LINE__});
588 if(pthread_tryjoin_np(m_wfsThread.native_handle(),0) == 0)
590 derivedT::template log<software_error>({__FILE__, __LINE__,
"WFS thread has exited"});
596 derivedT::template log<software_error>({__FILE__, __LINE__,
"WFS thread has exited"});
626 derived().template
updateIfChanged( m_indiP_nPokeImages,
"current", m_nPokeImages);
627 derived().template
updateIfChanged( m_indiP_nPokeAverage,
"current", m_nPokeAverage);
628 derived().template
updateIfChanged( m_indiP_poke_amp,
"current", m_poke_amp);
633 template<
class derivedT>
638 derivedT::template log<software_error>({__FILE__, __LINE__,
"error from shmimMonitorT::appShutdown"});
641 if(derived().darkShmimMonitor().appShutdown() < 0)
643 derivedT::template log<software_error>({__FILE__, __LINE__,
"error from darkShmimMonitorT::appShutdown"});
646 if (m_wfsThread.joinable())
648 pthread_kill(m_wfsThread.native_handle(), SIGUSR1);
661 template<
class derivedT>
664 static_cast<void>(dummy);
666 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
670 wfsPixget = getPixPointer<float>(derived().
shmimMonitor().dataType());
674 m_dmStream.open(m_dmChan);
676 catch(
const std::exception& e)
678 return derivedT::template
log<software_error,-1>({__FILE__, __LINE__, std::string(
"exception opening DM: ") + e.what()});
681 m_dmImage.resize(m_dmStream.rows(), m_dmStream.cols());
683 if(derived().darkShmimMonitor().width() == derived().shmimMonitor().width() &&
684 derived().darkShmimMonitor().height() == derived().shmimMonitor().height() )
693 if(m_pokeImage.rows() != derived().shmimMonitor().width() || m_pokeImage.cols() != derived().shmimMonitor().height())
695 m_pokeImage.create(derived().m_configName +
"_poke", derived().shmimMonitor().width(), derived().shmimMonitor().height());
698 m_pokeLocal.resize(derived().shmimMonitor().width(), derived().shmimMonitor().height());
703 template<
class derivedT>
708 static_cast<void>(dummy);
710 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
712 float * data = m_rawImage().data();
713 float * darkData = m_darkImage.data();
716 uint64_t Npix = derived().shmimMonitor().width()*derived().shmimMonitor().height();
720 for(
unsigned nn=0; nn < Npix; ++nn)
722 data[nn] = wfsPixget(curr_src, nn) - darkData[nn];
727 for(
unsigned nn=0; nn < Npix; ++nn)
729 data[nn] = wfsPixget(curr_src, nn);
733 if(sem_post(&m_imageSemaphore) < 0)
735 return derivedT::template
log<software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
743 template<
class derivedT>
746 static_cast<void>(dummy);
748 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
750 m_darkImage.resize(derived().darkShmimMonitor().
width(), derived().darkShmimMonitor().height());
752 darkPixget = getPixPointer<float>(derived().darkShmimMonitor().dataType());
755 derived().darkShmimMonitor().height() == derived().
shmimMonitor().height() )
757 std::cerr <<
"dark is valid " << derived().darkShmimMonitor().width() <<
" " << derived().shmimMonitor().width() <<
" ";
758 std::cerr << derived().darkShmimMonitor().height() <<
" " << derived().shmimMonitor().height() <<
"\n";
769 template<
class derivedT>
774 static_cast<void>(dummy);
776 std::unique_lock<std::mutex>
lock(m_wfsImageMutex);
778 float * darkData = m_darkImage.data();
781 uint64_t nPix = derived().darkShmimMonitor().width()*derived().darkShmimMonitor().height();
782 for(
unsigned nn=0; nn < nPix; ++nn)
784 darkData[nn] = darkPixget(curr_src, nn);
790 template<
class derivedT>
796 template<
class derivedT>
799 m_wfsThreadID = syscall(SYS_gettid);
802 while(m_wfsThreadInit ==
true && derived().m_shutdown == 0)
807 while(derived().m_shutdown == 0)
819 else if(m_continuous)
829 derived().template state(stateCodes::OPERATING);
831 while(!m_pokeImage.valid())
833 mx::sys::milliSleep(10);
836 m_stopMeasurement =
false;
838 bool firstRun =
true;
840 while(!m_stopMeasurement && !derived().m_shutdown)
842 if( derived().runSensor(firstRun) < 0)
844 derivedT::template log<software_error>({__FILE__, __LINE__,
"runSensor returned error"});
848 if(m_stopMeasurement || derived().m_shutdown)
853 if( derived().analyzeSensor() < 0)
855 derivedT::template log<software_error>({__FILE__, __LINE__,
"runSensor returned error"});
860 derived().updateIfChanged(m_indiP_measurement,
"counter", m_counter);
861 derived().recordPokeLoop();
875 derived().template state(stateCodes::READY);
884 template<
class derivedT>
890 if(pokeSign < 0) sign = -1;
895 for(
size_t nn = 0; nn < m_poke_x.size(); ++nn)
897 m_dmImage( m_poke_x[nn], m_poke_y[nn]) = pokeSign*m_poke_amp;
901 m_dmStream = m_dmImage;
903 mx::sys::microSleep(m_dmSleep);
911 while(!ready && !(m_stopMeasurement || derived().m_shutdown))
921 while(n < m_nPokeImages && !(m_stopMeasurement || derived().m_shutdown))
928 m_pokeLocal += sign*m_rawImage();
933 if(m_stopMeasurement || derived().m_shutdown)
936 m_dmStream = m_dmImage;
943 template<
class derivedT>
948 if(!m_pokeImage.valid())
950 return derivedT::template
log<software_error,-1>({__FILE__, __LINE__,
"poke image is not allocated"});
953 m_pokeLocal.setZero();
955 for(
unsigned nseq = 0; nseq < m_nPokeAverage; ++nseq)
960 rv = basicTimedPoke(+1);
964 derivedT::template log<software_error>({__FILE__, __LINE__});
972 if(m_stopMeasurement || derived().m_shutdown)
979 rv = basicTimedPoke(-1);
983 derivedT::template log<software_error>({__FILE__, __LINE__});
991 if(m_stopMeasurement || derived().m_shutdown)
999 m_pokeImage = m_pokeLocal/(2.0*m_nPokeImages*m_nPokeAverage);
1001 catch(
const std::exception& e)
1003 return derivedT::template
log<software_error,-1>({__FILE__, __LINE__, e.what()});
1008 m_dmImage.setZero();
1009 m_dmStream = m_dmImage;
1014 template<
class derivedT>
1021 m_indiP_measurement[
"delta_x"] = deltaX;
1022 m_indiP_measurement[
"delta_y"] = deltaY;
1027 template<
class derivedT>
1034 if( derived().
template indiTargetUpdate(m_indiP_nPokeImages, target,
ipRecv,
false) < 0)
1036 return derivedT::template
log<software_error,-1>({__FILE__, __LINE__});
1039 m_nPokeImages = target;
1044 template<
class derivedT>
1051 if( derived().
template indiTargetUpdate(m_indiP_nPokeAverage, target,
ipRecv,
false) < 0)
1053 return derivedT::template
log<software_error,-1>({__FILE__, __LINE__});
1056 m_nPokeAverage = target;
1061 template<
class derivedT>
1068 if( derived().
template indiTargetUpdate(m_indiP_poke_amp, target,
ipRecv,
false) < 0)
1070 return derivedT::template
log<software_error,-1>({__FILE__, __LINE__});
1073 m_poke_amp = target;
1078 template<
class derivedT>
1083 if(
ipRecv.find(
"current") !=
true )
1088 m_wfsFps =
ipRecv[
"current"].get<
float>();
1093 template<
class derivedT>
1098 if(
ipRecv.find(
"toggle") !=
true )
1103 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1105 if(m_measuring == 0)
1109 if(sem_post(&m_wfsSemaphore) < 0)
1111 return derivedT::template
log<software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
1119 template<
class derivedT>
1124 if(
ipRecv.find(
"toggle") !=
true )
1129 if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::On )
1131 if(m_measuring == 0)
1135 if(sem_post(&m_wfsSemaphore) < 0)
1137 return derivedT::template
log<software_critical, -1>({__FILE__, __LINE__, errno, 0,
"Error posting to semaphore"});
1141 else if(
ipRecv[
"toggle"].getSwitchState() == pcf::IndiElement::Off )
1143 if(m_measuring != 0)
1145 m_stopMeasurement =
true;
1152 template<
class derivedT>
1157 if(
ipRecv.find(
"request") !=
true )
1162 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
1164 if(m_measuring != 0)
1166 m_stopMeasurement =
true;
1173 template<
class derivedT>
1176 return recordPokeLoop(
true);
1179 template<
class derivedT>
1182 static int measuring = -1;
1183 static float deltaX = std::numeric_limits<float>::max();
1184 static float deltaY = std::numeric_limits<float>::max();
1185 static uint64_t counter = std::numeric_limits<uint64_t>::max();
1187 if(force || (m_counter != counter) || (m_deltaX != deltaX) || (m_deltaY != deltaY) || (m_measuring != measuring))
1189 uint8_t meas = m_measuring;
1190 derived().template telem<telem_pokeloop>({meas, m_deltaX, m_deltaY, m_counter});
1192 measuring = m_measuring;
1195 counter = m_counter;
1205 #define DMPOKEWFS_SETUP_CONFIG( cfig ) \
1206 if(dmPokeWFST::setupConfig(cfig) < 0) \
1208 log<software_error>({__FILE__, __LINE__, "Error from dmPokeWFST::setupConfig"}); \
1209 m_shutdown = true; \
1217 #define DMPOKEWFS_LOAD_CONFIG( cfig ) \
1218 if(dmPokeWFST::loadConfig(cfig) < 0) \
1220 return log<software_error,-1>({__FILE__, __LINE__, "Error from dmPokeWFST::loadConfig"}); \
1224 #define DMPOKEWFS_APP_STARTUP \
1225 if( dmPokeWFST::appStartup() < 0) \
1227 return log<software_error, -1>({__FILE__,__LINE__}); \
1231 #define DMPOKEWFS_APP_LOGIC \
1232 if( dmPokeWFST::appLogic() < 0) \
1234 return log<software_error, -1>({__FILE__,__LINE__}); \
1238 #define DMPOKEWFS_APP_SHUTDOWN \
1239 if(dmPokeWFST::appShutdown() < 0) \
1241 log<software_error>({__FILE__, __LINE__, "error from dmPokeWFST::appShutdown"}); \
A base class to coordinate poking a deformable mirror's actuators and synchronizedreads of a camera i...
pcf::IndiProperty m_indiP_poke_amp
mx::improc::eigenImage< float > m_pokeLocal
mx::improc::milkImage< float > m_rawImage
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_nPokeAverage)
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_continuous)
std::string m_wfsCamDevName
INDI device name of the WFS camera. Default is wfscam.shmimName.
void wfsThreadExec()
Execute the frame grabber main loop.
int updateMeasurement(float deltaX, float deltaY)
sem_t m_imageSemaphore
Semaphore used to signal that an image is ready.
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_single)
static void wfsThreadStart(dmPokeWFS *s)
Thread starter, called by wfsThreadStart on thread construction. Calls wfsThreadExec.
sem_t m_wfsSemaphore
Semaphore used to signal the WFS thread to start WFSing.
pcf::IndiProperty m_indiP_nPokeAverage
std::vector< int > m_poke_y
int processImage(void *curr_src, const darkShmimT &)
mx::improc::eigenImage< float > m_dmImage
int appShutdown()
dmPokeWFS shutdown
pcf::IndiProperty m_wfsThreadProp
The property to hold the WFS thread details.
mx::improc::milkImage< float > m_pokeImage
pcf::IndiProperty m_indiP_single
Switch to start a single measurement.
pcf::IndiProperty m_indiP_measurement
Property to report the delta measurement, including the loop counter.
int basicTimedPoke(float pokeSign)
Apply a single DM poke pattern and record the results.
int allocate(const wfsShmimT &)
mx::improc::milkImage< float > m_dmStream
Pointer to a function to extract the dark image data as float.
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_nPokeImages)
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
pcf::IndiProperty m_indiP_wfsFps
Property to get the FPS from the WFS camera.
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_poke_amp)
pcf::IndiProperty m_indiP_stop
Switch to request that measurement stop.
pcf::IndiProperty m_indiP_nPokeImages
INDI_NEWCALLBACK_DECL(derivedT, m_indiP_stop)
int basicRunSensor()
Run the basic +/- poke sensor steps.
int allocate(const darkShmimT &)
std::mutex m_wfsImageMutex
int recordPokeLoop(bool force=false)
std::thread m_wfsThread
A separate thread for the actual WFSing.
int appStartup()
Startup function.
INDI_SETCALLBACK_DECL(derivedT, m_indiP_wfsFps)
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
std::string m_wfsCpuset
The cpuset for the framegrabber thread. Ignored if empty (the default).
int appLogic()
dmPokeWFS application logic
int recordTelem(const telem_pokeloop *)
mx::improc::eigenImage< float > m_darkImage
The dark image.
int processImage(void *curr_src, const wfsShmimT &)
pcf::IndiProperty m_indiP_continuous
Switch to start continuous measurement.
std::vector< int > m_poke_x
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define CREATE_REG_INDI_NEW_NUMBERI_DERIVED(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 CREATE_REG_INDI_NEW_REQUESTSWITCH_DERIVED(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define CREATE_REG_INDI_NEW_TOGGLESWITCH_DERIVED(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
#define CREATE_REG_INDI_NEW_NUMBERF_DERIVED(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_VALIDATE_CALLBACK_PROPS_DERIVED(prop1, prop2)
Standard check for matching INDI properties in a callback in a CRTP base class.
#define REG_INDI_SETPROP_DERIVED(prop, devName, propName)
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
const pcf::IndiProperty & ipRecv
std::unique_lock< std::mutex > lock(m_indiMutex)
#define XWC_SEM_FLUSH_DERIVED(sem)
#define XWC_SEM_WAIT_TS_RETVOID_DERIVED(ts, sec, nsec)
Add the wait time to a timespec for a sem_timedwait call, with no value returned on error,...
#define XWC_SEM_TIMEDWAIT_LOOP_DERIVED(sem, ts)
Perform a sem_timedwait in the context of a standard loop in MagAO-X code using the derived class.
#define XWC_SEM_WAIT_TS_DERIVED(ts, sec, nsec)
Add the wait time to a timespec for a sem_timedwait call, with -1 returned on error.
static std::string indiPrefix()
static std::string configSection()
static std::string configSection()
static std::string indiPrefix()