API
 
Loading...
Searching...
No Matches
w2tcsOffloader.hpp
Go to the documentation of this file.
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
19namespace MagAOX
20{
21namespace 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 */
42class 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
51 friend class dev::telemeter<w2tcsOffloader>;
52
53 // The base helper types.
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.
96
97 /// Destructor, declared and defined for noexcept.
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
158inline w2tcsOffloader::w2tcsOffloader() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
159{
160 return;
161}
162
164{
166 TELEMETER_SETUP_CONFIG( config );
167
168 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 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 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}
196
197inline int w2tcsOffloader::loadConfigImpl( mx::app::appConfigurator &_config )
198{
199
202
203 _config( m_wZModesPath, "offload.wZModesPath" );
204 _config( m_wMaskPath, "offload.wMaskPath" );
205 _config( m_nModes, "offload.nModes" );
206
207 return 0;
208}
209
211{
212 loadConfigImpl( config );
213}
214
216{
217
218 mx::fits::fitsFile<float> ff;
219 mx::error_t errc = ff.read( m_wZModes, m_wZModesPath );
220 if( errc != mx::error_t::noerror )
221 {
222 return log<text_log, -1>( "Could not open mode cube file", logPrio::LOG_ERROR );
223 }
224
225 m_zCoeffs.resize( m_wZModes.planes(), 0 );
226 m_lastZCoeffs.resize( m_zCoeffs.size(), std::numeric_limits<realT>::max() );
227
228 if( m_nModes > m_zCoeffs.size() )
229 {
230 m_nModes = m_zCoeffs.size();
231 }
232
233 if( m_zCoeffs.size() > 100 )
234 {
235 m_shutdown = true;
236 return log<text_log, -1>( "w2tcsOffloader supports at most 100 offload modes because INDI element names are "
237 "formatted with two digits.",
239 }
240
241 errc = ff.read( m_wMask, m_wMaskPath );
242 if( errc != mx::error_t::noerror )
243 {
244 return log<text_log, -1>( "Could not open mode mask file", logPrio::LOG_ERROR );
245 }
246
247 m_norm = m_wMask.sum();
248
249 createROIndiNumber( m_indiP_nModes, "nModes", "number of modes calculated" );
250 indi::addNumberElement<unsigned>( m_indiP_nModes, "current", 0, m_zCoeffs.size(), 1, "%d" );
251 m_indiP_nModes["current"] = m_nModes;
252
254
255 createROIndiNumber( m_indiP_zCoeffs, "zCoeffs", "offload coefficients" );
256
257 m_elNames.resize( m_zCoeffs.size() );
258 for( size_t n = 0; n < m_zCoeffs.size(); ++n )
259 {
260 m_elNames[n] = std::format( "{:02}", n );
261
262 indi::addNumberElement<realT>( m_indiP_zCoeffs,
263 m_elNames[n],
264 -std::numeric_limits<realT>::max(),
265 std::numeric_limits<realT>::max(),
266 0,
267 "%0.6f" );
269 }
270
272
274
275 if( shmimMonitorT::appStartup() < 0 )
276 {
277 return log<software_error, -1>( { __FILE__, __LINE__ } );
278 }
279
281
282 return 0;
283}
284
286{
287 if( shmimMonitorT::appLogic() < 0 )
288 {
289 return log<software_error, -1>( { __FILE__, __LINE__ } );
290 }
291
292 {
293 std::unique_lock<std::mutex> lock( m_indiMutex ); // mutex scope
294
295 if( shmimMonitorT::updateINDI() < 0 )
296 {
298 }
299 }
300
302
303 return 0;
304}
305
307{
310
311 return 0;
312}
313
314inline int w2tcsOffloader::allocate( const dev::shmimT &dummy )
315{
316 static_cast<void>( dummy ); // be unused
317
319
320 return 0;
321}
322
323inline 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(
329
330 {
331 std::unique_lock<std::mutex> lock( m_indiMutex ); // mutex scope
332
334
335 for( size_t i = 0; i < m_zCoeffs.size(); ++i )
336 {
337 if( i < m_nModes )
338 {
339 m_zCoeffs[i] = ( wooferImage * m_wZModes.image( i ) * m_wMask ).sum() / m_norm;
340 }
341 else
342 {
343 m_zCoeffs[i] = 0;
344 }
345
347 }
348
349 m_indiP_zCoeffs.setState( pcf::IndiProperty::Ok );
350
351 if( m_indiDriver )
352 {
353 m_indiDriver->sendSetProperty( m_indiP_zCoeffs );
354 }
355 }
356
358
359 return 0;
360}
361
366
368{
369 return recordZCoeffs( true );
370}
371
372inline int w2tcsOffloader::recordZCoeffs( bool force )
373{
374 std::vector<float> coeffs;
375 bool changed{ false };
376
377 {
378 std::unique_lock<std::mutex> lock( m_indiMutex ); // mutex scope
379
380 if( m_lastZCoeffs.size() != m_zCoeffs.size() )
381 {
382 m_lastZCoeffs.resize( m_zCoeffs.size(), std::numeric_limits<realT>::max() );
383 }
384
385 coeffs.resize( m_zCoeffs.size() );
386
387 for( size_t n = 0; n < m_zCoeffs.size(); ++n )
388 {
389 coeffs[n] = m_zCoeffs[n];
390
391 if( m_lastZCoeffs[n] != m_zCoeffs[n] )
392 {
393 changed = true;
394 }
395 }
396
397 if( force || changed )
398 {
399 for( size_t n = 0; n < m_lastZCoeffs.size(); ++n )
400 {
402 }
403 }
404 }
405
406 if( force || changed )
407 {
409 }
410
411 return 0;
412}
413
414} // namespace app
415} // namespace MagAOX
416
417#endif // w2tcsOffloader_hpp
The base-class for XWCTk applications.
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
int createROIndiNumber(pcf::IndiProperty &prop, const std::string &propName, const std::string &propLabel="", const std::string &propGroup="")
Create a ReadOnly INDI Number property.
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
std::mutex m_indiMutex
Mutex for locking INDI communications.
uint32_t m_width
The width of the images in the stream.
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
int updateINDI()
Update the INDI properties for this device controller.
int appLogic()
Checks the shmimMonitor thread.
uint32_t m_height
The height of the images in the stream.
int appShutdown()
Shuts down the shmimMonitor thread.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
virtual int appLogic()
Implementation of the FSM for w2tcsOffloader.
mx::improc::eigenImage< realT > m_wMask
Mask selecting valid pixels for the coefficient projection.
std::vector< realT > m_zCoeffs
Current coefficient vector sent to INDI and telemetry.
mx::improc::eigenCube< realT > m_wZModes
Basis cube used to project the incoming woofer image.
int allocate(const dev::shmimT &dummy)
Allocate image buffers for a new shared-memory image stream.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
dev::shmimMonitor< w2tcsOffloader > shmimMonitorT
std::string m_wMaskPath
Filesystem path to the mask used for coefficient projection.
pcf::IndiProperty m_indiP_nModes
INDI property publishing the number of active offload modes.
dev::telemeter< w2tcsOffloader > telemeterT
mx::improc::eigenImage< realT > m_woofer
Copy of the most recently processed woofer image.
std::vector< realT > m_lastZCoeffs
Last coefficient vector recorded to telemetry.
std::string m_wZModesPath
Filesystem path to the woofer Zernike basis cube.
virtual int appStartup()
Start the application and validate the loaded mode cube against the configured mode count.
int recordTelem(const logger::telem_w2tcsoffloader *)
Record the current coefficient vector for telemetry when requested by the telemeter.
float m_norm
Mask normalization applied to each coefficient measurement.
unsigned m_nModes
Number of low-order modes to retain when offloading, clamped to the loaded cube size at startup.
~w2tcsOffloader() noexcept
Destructor, declared and defined for noexcept.
int checkRecordTimes()
Check whether the telemetry max-interval requires a record.
float realT
Floating point type in which to do all calculations.
int recordZCoeffs(bool force=false)
Record the current coefficient vector when it changes or when forced.
w2tcsOffloader()
Default constructor.
pcf::IndiProperty m_indiP_zCoeffs
INDI property publishing the current offload coefficients.
virtual void loadConfig()
Load the application configuration.
std::vector< std::string > m_elNames
INDI element names corresponding to the coefficient vector, formatted as 00 through 99.
virtual void setupConfig()
Set up the application configuration.
virtual int appShutdown()
Shut down the application.
int processImage(void *curr_src, const dev::shmimT &dummy)
Process a new woofer image and update offload outputs using the currently allowed mode count.
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:19
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
A device base class which saves telemetry.
Definition telemeter.hpp:75
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
@ OPERATING
The device is operating, other than homing.
Software ERR log entry.
Log entry recording the woofer-to-TCS Zernike coefficient vector.
A simple text log, a string-type log.
Definition text_log.hpp:24
#define TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
#define TELEMETER_LOAD_CONFIG(cfig)
Call telemeter::loadConfig with error checking.
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
#define TELEMETER_SETUP_CONFIG(cfig)
Call telemeter::setupConfig with error checking.
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.