LCOV - code coverage report
Current view: top level - apps/w2tcsOffloader - w2tcsOffloader.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 4.4 % 114 5
Test Date: 2026-04-15 19:34:29 Functions: 15.4 % 13 2

            Line data    Source code
       1              : /** \file w2tcsOffloader.hpp
       2              :  * \brief The MagAO-X Woofer To Telescope Control System (TCS) offloading manager.
       3              :  *
       4              :  * \ingroup app_files
       5              :  */
       6              : 
       7              : #ifndef w2tcsOffloader_hpp
       8              : #define w2tcsOffloader_hpp
       9              : 
      10              : #include <format>
      11              : #include <limits>
      12              : 
      13              : #include <mx/improc/eigenCube.hpp>
      14              : #include <mx/improc/eigenImage.hpp>
      15              : 
      16              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      17              : #include "../../magaox_git_version.h"
      18              : 
      19              : namespace MagAOX
      20              : {
      21              : namespace app
      22              : {
      23              : 
      24              : /** \defgroup w2tcsOffloader Woofer to TCS Offloading
      25              :  * \brief Monitors the averaged woofer shape, fits Zernikes, and sends it to INDI.
      26              :  *
      27              :  * <a href="../handbook/operating/software/apps/w2tcsOffloader.html">Application Documentation</a>
      28              :  *
      29              :  * \ingroup apps
      30              :  *
      31              :  */
      32              : 
      33              : /** \defgroup w2tcsOffloader_files Woofer to TCS Offloading
      34              :  * \ingroup w2tcsOffloader
      35              :  */
      36              : 
      37              : /** MagAO-X application to control offloading the woofer to the TCS.
      38              :  *
      39              :  * \ingroup w2tcsOffloader
      40              :  *
      41              :  */
      42              : class w2tcsOffloader : public MagAOXApp<true>,
      43              :                        public dev::shmimMonitor<w2tcsOffloader>,
      44              :                        public dev::telemeter<w2tcsOffloader>
      45              : {
      46              : 
      47              :     // Give the test harness access.
      48              :     friend class w2tcsOffloader_test;
      49              : 
      50              :     friend class dev::shmimMonitor<w2tcsOffloader>;
      51              :     friend class dev::telemeter<w2tcsOffloader>;
      52              : 
      53              :     // The base helper types.
      54              :     typedef dev::shmimMonitor<w2tcsOffloader> shmimMonitorT;
      55              :     typedef dev::telemeter<w2tcsOffloader>    telemeterT;
      56              : 
      57              :     /// Floating point type in which to do all calculations.
      58              :     typedef float realT;
      59              : 
      60              :   protected:
      61              :     /** \name Configurable Parameters - Data
      62              :      *@{
      63              :      */
      64              : 
      65              :     std::string m_wZModesPath; ///< Filesystem path to the woofer Zernike basis cube.
      66              : 
      67              :     std::string m_wMaskPath; ///< Filesystem path to the mask used for coefficient projection.
      68              : 
      69              :     std::vector<std::string>
      70              :         m_elNames; ///< INDI element names corresponding to the coefficient vector, formatted as `00` through `99`.
      71              : 
      72              :     std::vector<realT> m_zCoeffs; ///< Current coefficient vector sent to INDI and telemetry.
      73              : 
      74              :     unsigned m_nModes{
      75              :         5 }; ///< Number of low-order modes to retain when offloading, clamped to the loaded cube size at startup.
      76              : 
      77              :     float m_norm{ 1.0 }; ///< Mask normalization applied to each coefficient measurement.
      78              : 
      79              :     ///@}
      80              : 
      81              :     /** \name Offloading State - Data
      82              :      * @{
      83              :      */
      84              :     mx::improc::eigenCube<realT> m_wZModes; ///< Basis cube used to project the incoming woofer image.
      85              : 
      86              :     mx::improc::eigenImage<realT> m_woofer; ///< Copy of the most recently processed woofer image.
      87              : 
      88              :     mx::improc::eigenImage<realT> m_wMask; ///< Mask selecting valid pixels for the coefficient projection.
      89              : 
      90              :     std::vector<realT> m_lastZCoeffs; ///< Last coefficient vector recorded to telemetry.
      91              :                                       ///@}
      92              : 
      93              :   public:
      94              :     /// Default constructor.
      95              :     w2tcsOffloader();
      96              : 
      97              :     /// Destructor, declared and defined for noexcept.
      98            1 :     ~w2tcsOffloader() noexcept
      99            1 :     {
     100            1 :     }
     101              : 
     102              :     /// Set up the application configuration.
     103              :     virtual void setupConfig();
     104              : 
     105              :     /// Implementation of loadConfig logic, separated for testing.
     106              :     /** This is called by loadConfig().
     107              :      */
     108              :     int loadConfigImpl(
     109              :         mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
     110              : 
     111              :     /// Load the application configuration.
     112              :     virtual void loadConfig();
     113              : 
     114              :     /// Start the application and validate the loaded mode cube against the configured mode count.
     115              :     virtual int appStartup();
     116              : 
     117              :     /// Implementation of the FSM for w2tcsOffloader.
     118              :     /**
     119              :      * \returns 0 on no critical error
     120              :      * \returns -1 on an error requiring shutdown
     121              :      */
     122              :     virtual int appLogic();
     123              : 
     124              :     /// Shut down the application.
     125              :     virtual int appShutdown();
     126              : 
     127              :     /// Allocate image buffers for a new shared-memory image stream.
     128              :     int allocate( const dev::shmimT &dummy /**< [in] tag to differentiate shmimMonitor parents.*/ );
     129              : 
     130              :     /// Process a new woofer image and update offload outputs using the currently allowed mode count.
     131              :     int processImage( void              *curr_src, ///< [in] pointer to start of current frame.
     132              :                       const dev::shmimT &dummy     ///< [in] tag to differentiate shmimMonitor parents.
     133              :     );
     134              : 
     135              :     /** \name Telemeter Interface
     136              :      * @{
     137              :      */
     138              :     /// Check whether the telemetry max-interval requires a record.
     139              :     int checkRecordTimes();
     140              : 
     141              :     /// Record the current coefficient vector for telemetry when requested by the telemeter.
     142              :     int recordTelem( const logger::telem_w2tcsoffloader * /**< [in] telemetry tag used for overload resolution */ );
     143              : 
     144              :     /// Record the current coefficient vector when it changes or when forced.
     145              :     int recordZCoeffs( bool force = false /**< [in] set true to record even if unchanged */ );
     146              :     ///@}
     147              : 
     148              :   protected:
     149              :     /** \name Offloading State
     150              :      * @{
     151              :      */
     152              :     pcf::IndiProperty m_indiP_nModes; ///< INDI property publishing the number of active offload modes.
     153              : 
     154              :     pcf::IndiProperty m_indiP_zCoeffs; ///< INDI property publishing the current offload coefficients.
     155              :     ///@}
     156              : };
     157              : 
     158            3 : inline w2tcsOffloader::w2tcsOffloader() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
     159              : {
     160            1 :     return;
     161            0 : }
     162              : 
     163            0 : inline void w2tcsOffloader::setupConfig()
     164              : {
     165            0 :     shmimMonitorT::setupConfig( config );
     166            0 :     TELEMETER_SETUP_CONFIG( config );
     167              : 
     168            0 :     config.add( "offload.wZModesPath",
     169              :                 "",
     170              :                 "offload.wZModesPath",
     171              :                 argType::Required,
     172              :                 "offload",
     173              :                 "wZModesPath",
     174              :                 false,
     175              :                 "string",
     176              :                 "The path to the woofer Zernike modes." );
     177            0 :     config.add( "offload.wMaskPath",
     178              :                 "",
     179              :                 "offload.wMaskPath",
     180              :                 argType::Required,
     181              :                 "offload",
     182              :                 "wMaskPath",
     183              :                 false,
     184              :                 "string",
     185              :                 "Path to the woofer Zernike mode mask." );
     186            0 :     config.add( "offload.nModes",
     187              :                 "",
     188              :                 "offload.nModes",
     189              :                 argType::Required,
     190              :                 "offload",
     191              :                 "nModes",
     192              :                 false,
     193              :                 "int",
     194              :                 "Number of modes to offload to the TCS." );
     195            0 : }
     196              : 
     197            0 : inline int w2tcsOffloader::loadConfigImpl( mx::app::appConfigurator &_config )
     198              : {
     199              : 
     200            0 :     shmimMonitorT::loadConfig( _config );
     201            0 :     TELEMETER_LOAD_CONFIG( _config );
     202              : 
     203            0 :     _config( m_wZModesPath, "offload.wZModesPath" );
     204            0 :     _config( m_wMaskPath, "offload.wMaskPath" );
     205            0 :     _config( m_nModes, "offload.nModes" );
     206              : 
     207            0 :     return 0;
     208              : }
     209              : 
     210            0 : inline void w2tcsOffloader::loadConfig()
     211              : {
     212            0 :     loadConfigImpl( config );
     213            0 : }
     214              : 
     215            0 : inline int w2tcsOffloader::appStartup()
     216              : {
     217              : 
     218            0 :     mx::fits::fitsFile<float> ff;
     219            0 :     mx::error_t               errc = ff.read( m_wZModes, m_wZModesPath );
     220            0 :     if( errc != mx::error_t::noerror )
     221              :     {
     222            0 :         return log<text_log, -1>( "Could not open mode cube file", logPrio::LOG_ERROR );
     223              :     }
     224              : 
     225            0 :     m_zCoeffs.resize( m_wZModes.planes(), 0 );
     226            0 :     m_lastZCoeffs.resize( m_zCoeffs.size(), std::numeric_limits<realT>::max() );
     227              : 
     228            0 :     if( m_nModes > m_zCoeffs.size() )
     229              :     {
     230            0 :         m_nModes = m_zCoeffs.size();
     231              :     }
     232              : 
     233            0 :     if( m_zCoeffs.size() > 100 )
     234              :     {
     235            0 :         m_shutdown = true;
     236            0 :         return log<text_log, -1>( "w2tcsOffloader supports at most 100 offload modes because INDI element names are "
     237              :                                   "formatted with two digits.",
     238            0 :                                   logPrio::LOG_CRITICAL );
     239              :     }
     240              : 
     241            0 :     errc = ff.read( m_wMask, m_wMaskPath );
     242            0 :     if( errc != mx::error_t::noerror )
     243              :     {
     244            0 :         return log<text_log, -1>( "Could not open mode mask file", logPrio::LOG_ERROR );
     245              :     }
     246              : 
     247            0 :     m_norm = m_wMask.sum();
     248              : 
     249            0 :     createROIndiNumber( m_indiP_nModes, "nModes", "number of modes calculated" );
     250            0 :     indi::addNumberElement<unsigned>( m_indiP_nModes, "current", 0, m_zCoeffs.size(), 1, "%d" );
     251            0 :     m_indiP_nModes["current"] = m_nModes;
     252              : 
     253            0 :     registerIndiPropertyReadOnly( m_indiP_nModes );
     254              : 
     255            0 :     createROIndiNumber( m_indiP_zCoeffs, "zCoeffs", "offload coefficients" );
     256              : 
     257            0 :     m_elNames.resize( m_zCoeffs.size() );
     258            0 :     for( size_t n = 0; n < m_zCoeffs.size(); ++n )
     259              :     {
     260            0 :         m_elNames[n] = std::format( "{:02}", n );
     261              : 
     262            0 :         indi::addNumberElement<realT>( m_indiP_zCoeffs,
     263            0 :                                        m_elNames[n],
     264            0 :                                        -std::numeric_limits<realT>::max(),
     265            0 :                                        std::numeric_limits<realT>::max(),
     266              :                                        0,
     267              :                                        "%0.6f" );
     268            0 :         m_indiP_zCoeffs[m_elNames[n]] = 0;
     269              :     }
     270              : 
     271            0 :     registerIndiPropertyReadOnly( m_indiP_zCoeffs );
     272              : 
     273            0 :     TELEMETER_APP_STARTUP;
     274              : 
     275            0 :     if( shmimMonitorT::appStartup() < 0 )
     276              :     {
     277            0 :         return log<software_error, -1>( { __FILE__, __LINE__ } );
     278              :     }
     279              : 
     280            0 :     state( stateCodes::OPERATING );
     281              : 
     282            0 :     return 0;
     283            0 : }
     284              : 
     285            0 : inline int w2tcsOffloader::appLogic()
     286              : {
     287            0 :     if( shmimMonitorT::appLogic() < 0 )
     288              :     {
     289            0 :         return log<software_error, -1>( { __FILE__, __LINE__ } );
     290              :     }
     291              : 
     292              :     {
     293            0 :         std::unique_lock<std::mutex> lock( m_indiMutex ); // mutex scope
     294              : 
     295            0 :         if( shmimMonitorT::updateINDI() < 0 )
     296              :         {
     297            0 :             log<software_error>( { __FILE__, __LINE__ } );
     298              :         }
     299            0 :     }
     300              : 
     301            0 :     TELEMETER_APP_LOGIC;
     302              : 
     303            0 :     return 0;
     304              : }
     305              : 
     306            0 : inline int w2tcsOffloader::appShutdown()
     307              : {
     308            0 :     shmimMonitorT::appShutdown();
     309            0 :     TELEMETER_APP_SHUTDOWN;
     310              : 
     311            0 :     return 0;
     312              : }
     313              : 
     314            0 : inline int w2tcsOffloader::allocate( const dev::shmimT &dummy )
     315              : {
     316              :     static_cast<void>( dummy ); // be unused
     317              : 
     318            0 :     m_woofer.resize( shmimMonitorT::m_width, shmimMonitorT::m_height );
     319              : 
     320            0 :     return 0;
     321              : }
     322              : 
     323            0 : inline int w2tcsOffloader::processImage( void *curr_src, const dev::shmimT &dummy )
     324              : {
     325              :     static_cast<void>( dummy ); // be unused
     326              : 
     327              :     Eigen::Map<mx::improc::eigenImage<realT>> wooferImage(
     328            0 :         static_cast<realT *>( curr_src ), shmimMonitorT::m_width, shmimMonitorT::m_height );
     329              : 
     330              :     {
     331            0 :         std::unique_lock<std::mutex> lock( m_indiMutex ); // mutex scope
     332              : 
     333            0 :         m_woofer = wooferImage;
     334              : 
     335            0 :         for( size_t i = 0; i < m_zCoeffs.size(); ++i )
     336              :         {
     337            0 :             if( i < m_nModes )
     338              :             {
     339            0 :                 m_zCoeffs[i] = ( wooferImage * m_wZModes.image( i ) * m_wMask ).sum() / m_norm;
     340              :             }
     341              :             else
     342              :             {
     343            0 :                 m_zCoeffs[i] = 0;
     344              :             }
     345              : 
     346            0 :             m_indiP_zCoeffs[m_elNames[i]] = m_zCoeffs[i];
     347              :         }
     348              : 
     349            0 :         m_indiP_zCoeffs.setState( pcf::IndiProperty::Ok );
     350              : 
     351            0 :         if( m_indiDriver )
     352              :         {
     353            0 :             m_indiDriver->sendSetProperty( m_indiP_zCoeffs );
     354              :         }
     355            0 :     }
     356              : 
     357            0 :     recordZCoeffs();
     358              : 
     359            0 :     return 0;
     360              : }
     361              : 
     362            0 : inline int w2tcsOffloader::checkRecordTimes()
     363              : {
     364            0 :     return telemeterT::checkRecordTimes( logger::telem_w2tcsoffloader() );
     365              : }
     366              : 
     367            0 : inline int w2tcsOffloader::recordTelem( const logger::telem_w2tcsoffloader * )
     368              : {
     369            0 :     return recordZCoeffs( true );
     370              : }
     371              : 
     372            0 : inline int w2tcsOffloader::recordZCoeffs( bool force )
     373              : {
     374            0 :     std::vector<float> coeffs;
     375            0 :     bool               changed{ false };
     376              : 
     377              :     {
     378            0 :         std::unique_lock<std::mutex> lock( m_indiMutex ); // mutex scope
     379              : 
     380            0 :         if( m_lastZCoeffs.size() != m_zCoeffs.size() )
     381              :         {
     382            0 :             m_lastZCoeffs.resize( m_zCoeffs.size(), std::numeric_limits<realT>::max() );
     383              :         }
     384              : 
     385            0 :         coeffs.resize( m_zCoeffs.size() );
     386              : 
     387            0 :         for( size_t n = 0; n < m_zCoeffs.size(); ++n )
     388              :         {
     389            0 :             coeffs[n] = m_zCoeffs[n];
     390              : 
     391            0 :             if( m_lastZCoeffs[n] != m_zCoeffs[n] )
     392              :             {
     393            0 :                 changed = true;
     394              :             }
     395              :         }
     396              : 
     397            0 :         if( force || changed )
     398              :         {
     399            0 :             for( size_t n = 0; n < m_lastZCoeffs.size(); ++n )
     400              :             {
     401            0 :                 m_lastZCoeffs[n] = m_zCoeffs[n];
     402              :             }
     403              :         }
     404            0 :     }
     405              : 
     406            0 :     if( force || changed )
     407              :     {
     408            0 :         telem<logger::telem_w2tcsoffloader>( logger::telem_w2tcsoffloader::messageT( coeffs ) );
     409              :     }
     410              : 
     411            0 :     return 0;
     412            0 : }
     413              : 
     414              : } // namespace app
     415              : } // namespace MagAOX
     416              : 
     417              : #endif // w2tcsOffloader_hpp
        

Generated by: LCOV version 2.0-1