LCOV - code coverage report
Current view: top level - apps/dmPokeCenter - dmPokeCenter.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 0.0 % 531 0
Test Date: 2026-01-03 21:03:39 Functions: 0.0 % 37 0

            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
        

Generated by: LCOV version 2.0-1