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 <limits>
11 :
12 : #include <mx/improc/eigenCube.hpp>
13 : #include <mx/improc/eigenImage.hpp>
14 :
15 : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
16 : #include "../../magaox_git_version.h"
17 :
18 : namespace MagAOX
19 : {
20 : namespace app
21 : {
22 :
23 : /** \defgroup w2tcsOffloader Woofer to TCS Offloading
24 : * \brief Monitors the averaged woofer shape, fits Zernikes, and sends it to INDI.
25 : *
26 : * <a href="../handbook/operating/software/apps/w2tcsOffloader.html">Application Documentation</a>
27 : *
28 : * \ingroup apps
29 : *
30 : */
31 :
32 : /** \defgroup w2tcsOffloader_files Woofer to TCS Offloading
33 : * \ingroup w2tcsOffloader
34 : */
35 :
36 : /** MagAO-X application to control offloading the woofer to the TCS.
37 : *
38 : * \ingroup w2tcsOffloader
39 : *
40 : */
41 : class w2tcsOffloader : public MagAOXApp<true>, public dev::shmimMonitor<w2tcsOffloader>
42 : {
43 :
44 : //Give the test harness access.
45 : friend class w2tcsOffloader_test;
46 :
47 : friend class dev::shmimMonitor<w2tcsOffloader>;
48 :
49 : //The base shmimMonitor type
50 : typedef dev::shmimMonitor<w2tcsOffloader> shmimMonitorT;
51 :
52 : ///Floating point type in which to do all calculations.
53 : typedef float realT;
54 :
55 : protected:
56 :
57 : /** \name Configurable Parameters
58 : *@{
59 : */
60 :
61 : std::string m_wZModesPath;
62 : std::string m_wMaskPath;
63 : std::vector<std::string> m_elNames;
64 : std::vector<realT> m_zCoeffs;
65 : float m_gain {0.1};
66 : int m_nModes {2};
67 : float m_norm {1.0};
68 :
69 : ///@}
70 :
71 : mx::improc::eigenCube<realT> m_wZModes;
72 : mx::improc::eigenImage<realT> m_woofer;
73 : mx::improc::eigenImage<realT> m_wMask;
74 :
75 : public:
76 : /// Default c'tor.
77 : w2tcsOffloader();
78 :
79 : /// D'tor, declared and defined for noexcept.
80 0 : ~w2tcsOffloader() noexcept
81 0 : {}
82 :
83 : virtual void setupConfig();
84 :
85 : /// Implementation of loadConfig logic, separated for testing.
86 : /** This is called by loadConfig().
87 : */
88 : int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
89 :
90 : virtual void loadConfig();
91 :
92 : /// Startup function
93 : /**
94 : *
95 : */
96 : virtual int appStartup();
97 :
98 : /// Implementation of the FSM for w2tcsOffloader.
99 : /**
100 : * \returns 0 on no critical error
101 : * \returns -1 on an error requiring shutdown
102 : */
103 : virtual int appLogic();
104 :
105 : /// Shutdown the app.
106 : /**
107 : *
108 : */
109 : virtual int appShutdown();
110 :
111 :
112 :
113 :
114 : int allocate( const dev::shmimT & dummy /**< [in] tag to differentiate shmimMonitor parents.*/);
115 :
116 : int processImage( void * curr_src, ///< [in] pointer to start of current frame.
117 : const dev::shmimT & dummy ///< [in] tag to differentiate shmimMonitor parents.
118 : );
119 :
120 :
121 : protected:
122 :
123 : pcf::IndiProperty m_indiP_gain;
124 : pcf::IndiProperty m_indiP_nModes;
125 : pcf::IndiProperty m_indiP_zCoeffs;
126 :
127 : pcf::IndiProperty m_indiP_zero;
128 :
129 0 : INDI_NEWCALLBACK_DECL(w2tcsOffloader, m_indiP_gain);
130 0 : INDI_NEWCALLBACK_DECL(w2tcsOffloader, m_indiP_nModes);
131 0 : INDI_NEWCALLBACK_DECL(w2tcsOffloader, m_indiP_zero);
132 0 : INDI_NEWCALLBACK_DECL(w2tcsOffloader, m_indiP_zCoeffs);
133 : };
134 :
135 : inline
136 : w2tcsOffloader::w2tcsOffloader() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
137 : {
138 : return;
139 : }
140 :
141 : inline
142 0 : void w2tcsOffloader::setupConfig()
143 : {
144 0 : shmimMonitorT::setupConfig(config);
145 :
146 0 : config.add("offload.wZModesPath", "", "offload.wZModesPath", argType::Required, "offload", "wZModesPath", false, "string", "The path to the woofer Zernike modes.");
147 0 : config.add("offload.wMaskPath", "", "offload.wMaskPath", argType::Required, "offload", "wMaskPath", false, "string", "Path to the woofer Zernike mode mask.");
148 0 : config.add("offload.gain", "", "offload.gain", argType::Required, "offload", "gain", false, "float", "The starting offload gain. Default is 0.1.");
149 0 : config.add("offload.nModes", "", "offload.nModes", argType::Required, "offload", "nModes", false, "int", "Number of modes to offload to the TCS.");
150 0 : }
151 :
152 : inline
153 0 : int w2tcsOffloader::loadConfigImpl( mx::app::appConfigurator & _config )
154 : {
155 :
156 0 : shmimMonitorT::loadConfig(_config);
157 :
158 0 : _config(m_wZModesPath, "offload.wZModesPath");
159 0 : _config(m_wMaskPath, "offload.wMaskPath");
160 0 : _config(m_gain, "offload.gain");
161 0 : _config(m_nModes, "offload.nModes");
162 :
163 0 : return 0;
164 : }
165 :
166 : inline
167 0 : void w2tcsOffloader::loadConfig()
168 : {
169 0 : loadConfigImpl(config);
170 0 : }
171 :
172 : inline
173 0 : int w2tcsOffloader::appStartup(){
174 :
175 0 : mx::fits::fitsFile<float> ff;
176 0 : mx::error_t errc = ff.read(m_wZModes, m_wZModesPath);
177 0 : if(errc != mx::error_t::noerror)
178 : {
179 0 : return log<text_log,-1>("Could not open mode cube file", logPrio::LOG_ERROR);
180 : }
181 :
182 0 : m_zCoeffs.resize(m_wZModes.planes(), 0);
183 :
184 0 : errc = ff.read(m_wMask, m_wMaskPath);
185 0 : if( errc != mx::error_t::noerror)
186 : {
187 0 : return log<text_log,-1>("Could not open mode mask file", logPrio::LOG_ERROR);
188 : }
189 :
190 0 : m_norm = m_wMask.sum();
191 :
192 0 : createStandardIndiNumber<unsigned>( m_indiP_gain, "gain", 0, 1, 0, "%0.2f");
193 0 : m_indiP_gain["current"] = m_gain;
194 0 : m_indiP_gain["target"] = m_gain;
195 :
196 0 : if( registerIndiPropertyNew( m_indiP_gain, INDI_NEWCALLBACK(m_indiP_gain)) < 0)
197 : {
198 0 : log<software_error>({__FILE__,__LINE__});
199 0 : return -1;
200 : }
201 :
202 0 : createStandardIndiNumber<unsigned>( m_indiP_nModes, "nModes", 1, std::numeric_limits<unsigned>::max(), 1, "%u");
203 0 : m_indiP_nModes["current"] = m_nModes;
204 :
205 0 : if( registerIndiPropertyNew( m_indiP_nModes, INDI_NEWCALLBACK(m_indiP_nModes)) < 0)
206 : {
207 0 : log<software_error>({__FILE__,__LINE__});
208 0 : return -1;
209 : }
210 :
211 0 : REG_INDI_NEWPROP(m_indiP_zCoeffs, "zCoeffs", pcf::IndiProperty::Number);
212 :
213 :
214 0 : m_elNames.resize(m_zCoeffs.size());
215 0 : for(size_t n=0; n < m_zCoeffs.size(); ++n)
216 : {
217 : //std::string el = std::to_string(n);
218 0 : m_elNames[n] = mx::ioutils::convertToString<size_t, 2, '0'>(n);
219 :
220 0 : m_indiP_zCoeffs.add( pcf::IndiElement(m_elNames[n]) );
221 0 : m_indiP_zCoeffs[m_elNames[n]].set(0);
222 : }
223 :
224 0 : if(shmimMonitorT::appStartup() < 0)
225 : {
226 0 : return log<software_error,-1>({__FILE__, __LINE__});
227 : }
228 :
229 :
230 0 : createStandardIndiRequestSw( m_indiP_zero, "zero", "zero loop");
231 0 : if( registerIndiPropertyNew( m_indiP_zero, INDI_NEWCALLBACK(m_indiP_zero)) < 0)
232 : {
233 0 : log<software_error>({__FILE__,__LINE__});
234 0 : return -1;
235 : }
236 :
237 0 : state(stateCodes::OPERATING);
238 :
239 0 : return 0;
240 0 : }
241 :
242 : inline
243 0 : int w2tcsOffloader::appLogic()
244 : {
245 0 : if( shmimMonitorT::appLogic() < 0)
246 : {
247 0 : return log<software_error,-1>({__FILE__,__LINE__});
248 : }
249 :
250 :
251 0 : std::unique_lock<std::mutex> lock(m_indiMutex);
252 :
253 0 : if(shmimMonitorT::updateINDI() < 0)
254 : {
255 0 : log<software_error>({__FILE__, __LINE__});
256 : }
257 :
258 :
259 0 : return 0;
260 0 : }
261 :
262 : inline
263 0 : int w2tcsOffloader::appShutdown()
264 : {
265 0 : shmimMonitorT::appShutdown();
266 :
267 :
268 0 : return 0;
269 : }
270 :
271 : inline
272 0 : int w2tcsOffloader::allocate(const dev::shmimT & dummy)
273 : {
274 : static_cast<void>(dummy); //be unused
275 :
276 : //std::unique_lock<std::mutex> lock(m_indiMutex);
277 :
278 0 : m_woofer.resize(shmimMonitorT::m_width, shmimMonitorT::m_height);
279 :
280 : //state(stateCodes::OPERATING);
281 :
282 0 : return 0;
283 : }
284 :
285 : inline
286 0 : int w2tcsOffloader::processImage( void * curr_src,
287 : const dev::shmimT & dummy
288 : )
289 : {
290 : static_cast<void>(dummy); //be unused (what is this?)
291 :
292 : // Replace this:
293 : // project zernikes onto avg image
294 : // update INDI properties with coeffs
295 :
296 0 : for(size_t i=0; i < m_zCoeffs.size(); ++i)
297 : {
298 : /* update requested nModes and explicitly zero out any
299 : modes that shouldn't be offloaded (but might have been
300 : previously set)
301 : */
302 0 : if(i < m_nModes)
303 : {
304 : float coeff;
305 0 : coeff = ( Eigen::Map<mx::improc::eigenImage<realT>>((float *)curr_src, shmimMonitorT::m_width, shmimMonitorT::m_height) * m_wZModes.image(i) * m_wMask).sum() / m_norm;
306 0 : m_indiP_zCoeffs[m_elNames[i]] = m_gain * coeff;
307 : }
308 : else
309 : {
310 0 : m_indiP_zCoeffs[m_elNames[i]] = 0.;
311 : }
312 : }
313 :
314 0 : m_indiP_zCoeffs.setState (pcf::IndiProperty::Ok);
315 0 : m_indiDriver->sendSetProperty (m_indiP_zCoeffs);
316 :
317 :
318 : // loop over something like this
319 : //z0 = (im * basis.image(0)*mask).sum()/norm;
320 :
321 :
322 0 : return 0;
323 : }
324 :
325 : // update this: mode coefficients (maybe they shouldn't be settable. How to handle?)
326 :
327 0 : INDI_NEWCALLBACK_DEFN(w2tcsOffloader, m_indiP_gain)(const pcf::IndiProperty &ipRecv)
328 : {
329 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_gain, ipRecv);
330 :
331 : float target;
332 :
333 0 : if( indiTargetUpdate( m_indiP_gain, target, ipRecv, true) < 0)
334 : {
335 0 : log<software_error>({__FILE__,__LINE__});
336 0 : return -1;
337 : }
338 :
339 0 : m_gain = target;
340 :
341 0 : updateIfChanged(m_indiP_gain, "current", m_gain);
342 0 : updateIfChanged(m_indiP_gain, "target", m_gain);
343 :
344 0 : log<text_log>("set gain to " + std::to_string(m_gain), logPrio::LOG_NOTICE);
345 :
346 0 : return 0;
347 : }
348 :
349 0 : INDI_NEWCALLBACK_DEFN(w2tcsOffloader, m_indiP_nModes)(const pcf::IndiProperty &ipRecv)
350 : {
351 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_nModes, ipRecv);
352 :
353 : unsigned target;
354 :
355 0 : if( indiTargetUpdate( m_indiP_nModes, target, ipRecv, true) < 0)
356 : {
357 0 : log<software_error>({__FILE__,__LINE__});
358 0 : return -1;
359 : }
360 :
361 0 : m_nModes = target;
362 :
363 0 : updateIfChanged(m_indiP_nModes, "current", m_nModes);
364 0 : updateIfChanged(m_indiP_nModes, "target", m_nModes);
365 :
366 0 : log<text_log>("set nModes to " + std::to_string(m_nModes), logPrio::LOG_NOTICE);
367 :
368 0 : return 0;
369 : }
370 :
371 0 : INDI_NEWCALLBACK_DEFN(w2tcsOffloader, m_indiP_zCoeffs)(const pcf::IndiProperty &ipRecv)
372 : {
373 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_zCoeffs, ipRecv);
374 :
375 :
376 0 : for(size_t n=0; n < m_zCoeffs.size(); ++n)
377 : {
378 0 : if(ipRecv.find(m_elNames[n]))
379 : {
380 0 : realT zcoeff = ipRecv[m_elNames[n]].get<realT>();
381 0 : m_zCoeffs[n] = zcoeff;
382 : }
383 : }
384 0 : return 0;
385 :
386 :
387 : return log<software_error,-1>({__FILE__,__LINE__, "invalid indi property name"});
388 : }
389 :
390 0 : INDI_NEWCALLBACK_DEFN(w2tcsOffloader, m_indiP_zero)(const pcf::IndiProperty &ipRecv)
391 : {
392 0 : INDI_VALIDATE_CALLBACK_PROPS(m_indiP_zero, ipRecv);
393 :
394 : float target;
395 :
396 0 : if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On)
397 : {
398 0 : m_woofer.setZero();
399 0 : log<text_log>("set zero", logPrio::LOG_NOTICE);
400 : }
401 0 : return 0;
402 : }
403 :
404 : } //namespace app
405 : } //namespace MagAOX
406 :
407 : #endif //w2tcsOffloader_hpp
|