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
|