Line data Source code
1 : /** \file cred2Utils.hpp
2 : * \brief Utilities for the C-RED 2 camera controller.
3 : *
4 : * \author Jared R. Males (jaredmales@gmail.com)
5 : *
6 : * \ingroup cred2Ctrl_files
7 : */
8 :
9 : #ifndef cred2Utils_hpp
10 : #define cred2Utils_hpp
11 :
12 : #include <algorithm>
13 : #include <cctype>
14 : #include <cmath>
15 : #include <cstdlib>
16 : #include <string>
17 : #include <vector>
18 :
19 : #include <mx/ioutils/stringUtils.hpp>
20 :
21 : namespace MagAOX
22 : {
23 : namespace app
24 : {
25 :
26 : /// Structure holding the temperature values reported by the C-RED 2.
27 : /**
28 : * \ingroup cred2Ctrl
29 : */
30 : struct cred2Temps
31 : {
32 : float motherboard{ 0 }; ///< Motherboard temperature [C].
33 : float frontend{ 0 }; ///< Front-end temperature [C].
34 : float powerboard{ 0 }; ///< Power-board temperature [C].
35 : float snake{ 0 }; ///< Detector temperature [C].
36 : float setpoint{ 0 }; ///< Detector temperature setpoint [C].
37 : float peltier{ 0 }; ///< External TEC temperature [C].
38 : float heatsink{ 0 }; ///< Heatsink temperature [C].
39 :
40 : /// Compare two cached temperature sets.
41 18 : bool operator==( const cred2Temps &t /**< [in] the values to compare against */ ) const
42 : {
43 15 : return motherboard == t.motherboard && frontend == t.frontend && powerboard == t.powerboard &&
44 33 : snake == t.snake && setpoint == t.setpoint && peltier == t.peltier && heatsink == t.heatsink;
45 : }
46 :
47 : /// Mark all temperature values invalid.
48 118 : int setInvalid()
49 : {
50 118 : motherboard = -999;
51 118 : frontend = -999;
52 118 : powerboard = -999;
53 118 : snake = -999;
54 118 : setpoint = -999;
55 118 : peltier = -999;
56 118 : heatsink = -999;
57 :
58 118 : return 0;
59 : }
60 : };
61 :
62 : /// C-RED 2 ROI expressed as 0-based inclusive column and row limits.
63 : /**
64 : * \ingroup cred2Ctrl
65 : */
66 : struct cred2Roi
67 : {
68 : int startColumn{ 0 }; ///< First included column.
69 : int endColumn{ 0 }; ///< Last included column.
70 : int startRow{ 0 }; ///< First included row.
71 : int endRow{ 0 }; ///< Last included row.
72 : bool fullFrame{ true }; ///< True when the ROI spans the full detector.
73 : };
74 :
75 : /// Strip an optional prompt and surrounding whitespace from a C-RED 2 CLI response.
76 511 : inline std::string cred2CleanResponse( const std::string &response /**< [in] raw CLI response */ )
77 : {
78 511 : std::string clean = response;
79 :
80 511 : size_t promptPos = clean.find( "fli-cli>" );
81 511 : if( promptPos != std::string::npos )
82 : {
83 7 : clean.erase( promptPos );
84 : }
85 :
86 511 : size_t first = 0;
87 512 : while( first < clean.size() && std::isspace( static_cast<unsigned char>( clean[first] ) ) )
88 : {
89 1 : ++first;
90 : }
91 :
92 511 : size_t last = clean.size();
93 704 : while( last > first && std::isspace( static_cast<unsigned char>( clean[last - 1] ) ) )
94 : {
95 193 : --last;
96 : }
97 :
98 1022 : return clean.substr( first, last - first );
99 511 : }
100 :
101 : /// Parse a raw numeric response into a float.
102 172 : inline int cred2ParseFloat( float &value, ///< [out] parsed floating-point value
103 : const std::string &response /**< [in] raw or cleaned CLI response */
104 : )
105 : {
106 172 : std::string clean = cred2CleanResponse( response );
107 172 : if( clean.empty() )
108 : {
109 2 : return -1;
110 : }
111 :
112 170 : char *end = nullptr;
113 170 : value = std::strtof( clean.c_str(), &end );
114 :
115 170 : if( end == clean.c_str() )
116 : {
117 14 : return -1;
118 : }
119 :
120 157 : while( end != nullptr && *end != '\0' && std::isspace( static_cast<unsigned char>( *end ) ) )
121 : {
122 1 : ++end;
123 : }
124 :
125 156 : if( end != nullptr && *end != '\0' )
126 : {
127 1 : return -1;
128 : }
129 :
130 155 : return 0;
131 172 : }
132 :
133 : /// Parse a delimited list of raw numeric responses into a float vector.
134 21 : inline int cred2ParseFloatVector( std::vector<float> &values, ///< [out] parsed floating-point values
135 : const std::string &response, /**< [in] raw or cleaned CLI response */
136 : size_t expectedValues /**< [in] expected number of parsed values, or 0 */
137 : )
138 : {
139 21 : std::string clean = cred2CleanResponse( response );
140 21 : std::vector<std::string> tokens;
141 :
142 21 : mx::ioutils::parseStringVector( tokens, clean, ":, \t\r\n" );
143 :
144 21 : if( tokens.empty() )
145 : {
146 0 : return -1;
147 : }
148 :
149 21 : if( expectedValues > 0 && tokens.size() != expectedValues )
150 : {
151 2 : return -1;
152 : }
153 :
154 19 : values.clear();
155 19 : values.reserve( tokens.size() );
156 :
157 119 : for( const auto &token : tokens )
158 : {
159 102 : float value = 0;
160 :
161 102 : if( cred2ParseFloat( value, token ) < 0 )
162 : {
163 2 : values.clear();
164 2 : return -1;
165 : }
166 :
167 100 : values.push_back( value );
168 : }
169 :
170 17 : return 0;
171 21 : }
172 :
173 : /// Parse a raw on/off response into a boolean.
174 38 : inline int cred2ParseBool( bool &value, ///< [out] parsed boolean value
175 : const std::string &response /**< [in] raw or cleaned CLI response */
176 : )
177 : {
178 38 : std::string clean = cred2CleanResponse( response );
179 38 : std::transform( clean.begin(),
180 : clean.end(),
181 : clean.begin(),
182 148 : []( unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
183 :
184 38 : if( clean == "on" || clean == "true" || clean == "1" )
185 : {
186 14 : value = true;
187 14 : return 0;
188 : }
189 :
190 24 : if( clean == "off" || clean == "false" || clean == "0" )
191 : {
192 11 : value = false;
193 11 : return 0;
194 : }
195 :
196 13 : return -1;
197 38 : }
198 :
199 : /// Parse a raw range response such as `0-639`.
200 : inline int cred2ParseRange( int &firstValue, ///< [out] first parsed range value
201 : int &secondValue, ///< [out] second parsed range value
202 : const std::string &response /**< [in] raw or cleaned CLI response */
203 : );
204 :
205 : /// Parse a raw cropping status response such as `on` or `on:192-447:128-383`.
206 23 : inline int cred2ParseCropState( bool &enabled, ///< [out] parsed cropping-enabled flag
207 : int &startColumn, ///< [out] parsed first included column
208 : int &endColumn, ///< [out] parsed last included column
209 : int &startRow, ///< [out] parsed first included row
210 : int &endRow, ///< [out] parsed last included row
211 : const std::string &response /**< [in] raw or cleaned CLI response */
212 : )
213 : {
214 23 : std::string clean = cred2CleanResponse( response );
215 23 : std::vector<std::string> tokens;
216 :
217 23 : mx::ioutils::parseStringVector( tokens, clean, ":" );
218 :
219 23 : if( tokens.empty() )
220 : {
221 0 : return -1;
222 : }
223 :
224 23 : if( cred2ParseBool( enabled, tokens[0] ) < 0 )
225 : {
226 5 : return -1;
227 : }
228 :
229 18 : if( tokens.size() == 1 )
230 : {
231 13 : startColumn = 0;
232 13 : endColumn = 0;
233 13 : startRow = 0;
234 13 : endRow = 0;
235 13 : return 0;
236 : }
237 :
238 5 : if( tokens.size() != 3 )
239 : {
240 1 : return -1;
241 : }
242 :
243 4 : if( cred2ParseRange( startColumn, endColumn, tokens[1] ) < 0 || cred2ParseRange( startRow, endRow, tokens[2] ) < 0 )
244 : {
245 1 : return -1;
246 : }
247 :
248 3 : return 0;
249 23 : }
250 :
251 : /// Parse a raw range response such as `0-639`.
252 15 : inline int cred2ParseRange( int &firstValue, ///< [out] first parsed range value
253 : int &secondValue, ///< [out] second parsed range value
254 : const std::string &response /**< [in] raw or cleaned CLI response */
255 : )
256 : {
257 15 : std::string clean = cred2CleanResponse( response );
258 15 : std::vector<std::string> tokens;
259 15 : mx::ioutils::parseStringVector( tokens, clean, "-, \t\r\n" );
260 :
261 15 : if( tokens.size() != 2 )
262 : {
263 4 : return -1;
264 : }
265 :
266 : try
267 : {
268 11 : firstValue = mx::ioutils::stoT<int>( tokens[0] );
269 11 : secondValue = mx::ioutils::stoT<int>( tokens[1] );
270 : }
271 0 : catch( ... )
272 : {
273 0 : return -1;
274 0 : }
275 :
276 11 : return 0;
277 15 : }
278 :
279 : /// Check whether a command response looks successful.
280 23 : inline bool cred2ResponseOK( const std::string &response /**< [in] raw or cleaned CLI response */ )
281 : {
282 23 : std::string clean = cred2CleanResponse( response );
283 23 : std::string lower = clean;
284 23 : std::transform( lower.begin(),
285 : lower.end(),
286 : lower.begin(),
287 85 : []( unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
288 :
289 23 : if( lower.find( "error" ) != std::string::npos || lower.find( "fail" ) != std::string::npos )
290 : {
291 11 : return false;
292 : }
293 :
294 12 : return true;
295 23 : }
296 :
297 : /// Convert a MagAO-X ROI center/size description into C-RED 2 corners.
298 10 : inline int cred2RoiFromCenter( cred2Roi &roi, ///< [out] the corresponding C-RED 2 ROI
299 : float centerX, /**< [in] ROI x center coordinate */
300 : float centerY, /**< [in] ROI y center coordinate */
301 : int width, /**< [in] ROI width in pixels */
302 : int height, /**< [in] ROI height in pixels */
303 : int fullWidth, /**< [in] detector full-frame width */
304 : int fullHeight /**< [in] detector full-frame height */
305 : )
306 : {
307 10 : if( width < 1 || height < 1 || fullWidth < 1 || fullHeight < 1 )
308 : {
309 1 : return -1;
310 : }
311 :
312 9 : roi.startColumn = static_cast<int>( std::lround( centerX - 0.5f * ( static_cast<float>( width ) - 1.0f ) ) );
313 9 : roi.endColumn = roi.startColumn + width - 1;
314 9 : roi.startRow = static_cast<int>( std::lround( centerY - 0.5f * ( static_cast<float>( height ) - 1.0f ) ) );
315 9 : roi.endRow = roi.startRow + height - 1;
316 :
317 9 : if( roi.startColumn < 0 || roi.startRow < 0 || roi.endColumn >= fullWidth || roi.endRow >= fullHeight )
318 : {
319 1 : return -1;
320 : }
321 :
322 8 : roi.fullFrame =
323 8 : roi.startColumn == 0 && roi.endColumn == fullWidth - 1 && roi.startRow == 0 && roi.endRow == fullHeight - 1;
324 :
325 8 : return 0;
326 : }
327 :
328 : /// Convert C-RED 2 ROI corners into a MagAO-X ROI center/size description.
329 6 : inline int cred2RoiToCenter( float ¢erX, ///< [out] ROI x center coordinate
330 : float ¢erY, ///< [out] ROI y center coordinate
331 : int &width, ///< [out] ROI width in pixels
332 : int &height, ///< [out] ROI height in pixels
333 : const cred2Roi &roi, /**< [in] the C-RED 2 ROI to convert */
334 : int fullWidth, /**< [in] detector full-frame width */
335 : int fullHeight /**< [in] detector full-frame height */
336 : )
337 : {
338 6 : if( roi.startColumn < 0 || roi.startRow < 0 || roi.endColumn < roi.startColumn || roi.endRow < roi.startRow ||
339 4 : roi.endColumn >= fullWidth || roi.endRow >= fullHeight )
340 : {
341 2 : return -1;
342 : }
343 :
344 4 : width = roi.endColumn - roi.startColumn + 1;
345 4 : height = roi.endRow - roi.startRow + 1;
346 :
347 4 : centerX = roi.startColumn + 0.5f * ( static_cast<float>( width ) - 1.0f );
348 4 : centerY = roi.startRow + 0.5f * ( static_cast<float>( height ) - 1.0f );
349 :
350 4 : return 0;
351 : }
352 :
353 : /// Format the column command payload for `set cropping columns`.
354 3 : inline std::string cred2ColumnsSpec( const cred2Roi &roi /**< [in] the ROI to format */ )
355 : {
356 3 : return std::to_string( roi.startColumn ) + "-" + std::to_string( roi.endColumn );
357 : }
358 :
359 : /// Format the row command payload for `set cropping rows`.
360 3 : inline std::string cred2RowsSpec( const cred2Roi &roi /**< [in] the ROI to format */ )
361 : {
362 3 : return std::to_string( roi.startRow ) + "-" + std::to_string( roi.endRow );
363 : }
364 :
365 : } // namespace app
366 : } // namespace MagAOX
367 :
368 : #endif // cred2Utils_hpp
|