Line data Source code
1 : /** \file frameGrabber.hpp
2 : * \brief The MagAO-X generic frame grabber.
3 : *
4 : * \author Jared R. Males (jaredmales@gmail.com)
5 : *
6 : * \ingroup app_files
7 : */
8 :
9 : #ifndef frameGrabber_hpp
10 : #define frameGrabber_hpp
11 :
12 : #include <sys/syscall.h>
13 :
14 : #include <mx/sigproc/circularBuffer.hpp>
15 : #include <mx/math/vectorUtils.hpp>
16 : #include <mx/improc/imageUtils.hpp>
17 :
18 : #include "../../ImageStreamIO/ImageStruct.hpp"
19 : #include <ImageStreamIO/ImageStreamIO.h>
20 :
21 : #include "../../common/paths.hpp"
22 :
23 : namespace MagAOX
24 : {
25 : namespace app
26 : {
27 : namespace dev
28 : {
29 :
30 : /** MagAO-X generic frame grabber
31 : *
32 : *
33 : * The derived class `derivedT` has the following requirements:
34 : *
35 : * - Must be derived from MagAOXApp<true>
36 : *
37 : * - Must be derived from telemeter<derivedT> (see below for required interface)
38 : *
39 : * - Must contain the following friend declaration:
40 : * \code
41 : * friend class dev::frameGrabber<derivedT>; //replace derivedT
42 : * \endcode
43 : *
44 : * - Must declare the following typedef:
45 : * \code
46 : * typedef dev::frameGrabber<derivedT> frameGrabberT; //replace derivedT
47 : * \endcode
48 : *
49 : * - must expose the following interface
50 : * \code
51 : * //Configures the camera for acquistion, must also set m_width, m_height, and m_dataType
52 : * //so that the shared memory can be allocated
53 : * int derivedT::configureAcquisition();
54 : *
55 : * //Gets the frames-per-second readout rate
56 : * //used for the latency statistics
57 : * float derivedT::fps();
58 : *
59 : * //Start acquisition.
60 : * int derivedT::startAcquisition();
61 : *
62 : * //Acquires the data, and checks if it is valid.
63 : * //This should set m_currImageTimestamp to the image timestamp.
64 : * // returns 0 if valid, < 0 on error, > 0 on no data.
65 : * int derivedT::acquireAndCheckValid()
66 : *
67 : * //Loads the acquired image into the stream, copying it to the appropriate member of m_imageStream->array.
68 : * //This could simply be a memcpy.
69 : * int derivedT::loadImageIntoStream(void * dest);
70 : *
71 : * //Take any actions needed to reconfigure the system. Called if m_reconfig is set to true.
72 : * int derivedT::reconfig()
73 : * \endcode
74 : * Each of the above functions should return 0 on success, and -1 on an error (except fps).
75 : * For `acquireAndCheckValid` >0 will indicate no data but not an error. In most cases,
76 : * an appropriate state code, such as NOTCONNECTED, should be set as well.
77 : *
78 : * - A static configuration variable must be defined in derivedT as
79 : * \code
80 : * static constexpr bool c_frameGrabber_flippable =true; //or: false
81 : * \endcode
82 : * which determines whether or not the images can be flipped programmatically.
83 : *
84 : * - Calls to this class's `setupConfig`, `loadConfig`, `appStartup`, `appLogic`, `updateINDI`, and `appShutdown`
85 : * functions must be placed in the derived class's functions of the same name. For convenience the
86 : * following macros are defined to provide error checking:
87 : * \code
88 : * FRAMEGRABBER_SETUP_CONFIG( cfig )
89 : * FRAMEGRABBER_LOAD_CONFIG( cfig )
90 : * FRAMEGRABBER_APP_STARTUP
91 : * FRAMEGRABBER_APP_LOGIC
92 : * FRAMEGRABBER_UPDATE_INDI
93 : * FRAMEGRABBER_APP_SHUTDOWN
94 : * \endcode
95 : *
96 : * \ingroup appdev
97 : */
98 : template <class derivedT>
99 : class frameGrabber
100 : {
101 : public:
102 : enum fgFlip
103 : {
104 : fgFlipNone,
105 : fgFlipUD,
106 : fgFlipLR,
107 : fgFlipUDLR
108 : };
109 :
110 : typedef int32_t cbIndexT;
111 :
112 : protected:
113 : /** \name Configurable Parameters
114 : * @{
115 : */
116 : std::string m_shmimName{ "" }; ///< The name of the shared memory image, is used in `/tmp/<shmimName>.im.shm`.
117 : ///< Derived classes should set a default.
118 :
119 : int m_fgThreadPrio{ 2 }; ///< Priority of the framegrabber thread, should normally be > 00.
120 : std::string m_fgCpuset; ///< The cpuset to assign the framegrabber thread to. Not used if empty, the default.
121 :
122 : uint32_t m_circBuffLength{ 1 }; ///< Length of the circular buffer, in frames
123 :
124 : cbIndexT m_latencyCircBuffMaxLength{ 100000 }; ///< Maximum length of the latency measurement circular buffers
125 :
126 : float m_latencyCircBuffMaxTime{ 5 }; ///< Maximum time of the latency meaurement circular buffers
127 :
128 : int m_defaultFlip{ fgFlipNone };
129 :
130 : ///@}
131 :
132 : uint32_t m_width{ 0 }; ///< The width of the image, once deinterlaced etc.
133 : uint32_t m_height{ 0 }; ///< The height of the image, once deinterlaced etc.
134 :
135 : uint8_t m_dataType{ 0 }; ///< The ImageStreamIO type code.
136 : size_t m_typeSize{ 0 }; ///< The size of the type, in bytes. Result of sizeof.
137 :
138 : int m_xbinning{ 0 }; ///< The x-binning according to the framegrabber
139 : int m_ybinning{ 0 }; ///< The y-binning according to the framegrabber
140 :
141 : timespec m_currImageTimestamp{ 0, 0 }; ///< The timestamp of the current image.
142 :
143 : bool m_reconfig{ false }; ///< Flag to set if a camera reconfiguration requires a framegrabber reset.
144 :
145 : IMAGE *m_imageStream{ nullptr }; ///< The ImageStreamIO shared memory buffer.
146 :
147 : bool m_ownShmim{ true }; ///< Flag controlling if the shmim is owned. If true it will be destroyed as needed.
148 :
149 : ino_t m_inode{ 0 }; ///< The inode of the image stream file
150 :
151 : float m_cbFPS{ 0 }; ///< The FPS used to configure the circular buffers
152 :
153 : mx::sigproc::circularBufferIndex<timespec, cbIndexT> m_atimes;
154 : mx::sigproc::circularBufferIndex<timespec, cbIndexT> m_wtimes;
155 :
156 : std::vector<double> m_atimesD;
157 : std::vector<double> m_wtimesD;
158 : std::vector<double> m_watimesD;
159 :
160 : timespec m_dummy_ts{ 0, 0 };
161 : uint64_t m_dummy_cnt{ 0 };
162 : char m_dummy_c{ 0 };
163 :
164 : double m_mna;
165 : double m_vara;
166 : double m_mina;
167 : double m_maxa;
168 :
169 : double m_mnw;
170 : double m_varw;
171 : double m_minw;
172 : double m_maxw;
173 :
174 : double m_mnwa;
175 : double m_varwa;
176 :
177 : public:
178 : /// Setup the configuration system
179 : /**
180 : * This should be called in `derivedT::setupConfig` as
181 : * \code
182 : framegrabber<derivedT>::setupConfig(config);
183 : \endcode
184 : * with appropriate error checking.
185 : */
186 : int setupConfig( mx::app::appConfigurator &config /**< [out] the derived classes configurator*/ );
187 :
188 : /// load the configuration system results
189 : /**
190 : * This should be called in `derivedT::loadConfig` as
191 : * \code
192 : framegrabber<derivedT>::loadConfig(config);
193 : \endcode
194 : * with appropriate error checking.
195 : */
196 : int loadConfig( mx::app::appConfigurator &config /**< [in] the derived classes configurator*/ );
197 :
198 : /// Startup function
199 : /** Starts the framegrabber thread
200 : * This should be called in `derivedT::appStartup` as
201 : * \code
202 : framegrabber<derivedT>::appStartup();
203 : \endcode
204 : * with appropriate error checking.
205 : *
206 : * \returns 0 on success
207 : * \returns -1 on error, which is logged.
208 : */
209 : int appStartup();
210 :
211 : /// Checks the framegrabber thread
212 : /** This should be called in `derivedT::appLogic` as
213 : * \code
214 : framegrabber<derivedT>::appLogic();
215 : \endcode
216 : * with appropriate error checking.
217 : *
218 : * \returns 0 on success
219 : * \returns -1 on error, which is logged.
220 : */
221 : int appLogic();
222 :
223 : /// On power off, sets m_reconfig to true.
224 : /** This should be called in `derivedT::onPowerOff` as
225 : * \code
226 : framegrabber<derivedT>::onPowerOff();
227 : \endcode
228 : * with appropriate error checking.
229 : *
230 : * \returns 0 on success
231 : * \returns -1 on error, which is logged.
232 : */
233 : int onPowerOff();
234 :
235 : /// Shuts down the framegrabber thread
236 : /** This should be called in `derivedT::appShutdown` as
237 : * \code
238 : framegrabber<derivedT>::appShutdown();
239 : \endcode
240 : * with appropriate error checking.
241 : *
242 : * \returns 0 on success
243 : * \returns -1 on error, which is logged.
244 : */
245 : int appShutdown();
246 :
247 : int configCircBuffs();
248 :
249 : protected:
250 : /** \name Framegrabber Thread
251 : * This thread actually manages the framegrabbing hardware
252 : * @{
253 : */
254 :
255 : bool m_fgThreadInit{ true }; ///< Synchronizer for thread startup, to allow priority setting to finish.
256 :
257 : pid_t m_fgThreadID{ 0 }; ///< The ID of the framegrabber thread.
258 :
259 : pcf::IndiProperty m_fgThreadProp; ///< The property to hold the f.g. thread details.
260 :
261 : std::thread m_fgThread; ///< A separate thread for the actual framegrabbings
262 :
263 : /// Thread starter, called by MagAOXApp::threadStart on thread construction. Calls fgThreadExec.
264 : static void fgThreadStart( frameGrabber *o /**< [in] a pointer to a frameGrabber instance (normally this) */ );
265 :
266 : /// Execute framegrabbing.
267 : void fgThreadExec();
268 :
269 : ///@}
270 :
271 : void *loadImageIntoStreamCopy( void *dest, void *src, size_t width, size_t height, size_t szof );
272 :
273 : int openShmim();
274 :
275 : /** \name INDI
276 : *
277 : *@{
278 : */
279 : protected:
280 : // declare our properties
281 :
282 : pcf::IndiProperty m_indiP_shmimName; ///< Property used to report the shmim buffer name
283 :
284 : pcf::IndiProperty m_indiP_frameSize; ///< Property used to report the current frame size
285 :
286 : pcf::IndiProperty m_indiP_timing;
287 :
288 : public:
289 : /// Update the INDI properties for this device controller
290 : /** You should call this once per main loop.
291 : * It is not called automatically.
292 : *
293 : * \returns 0 on success.
294 : * \returns -1 on error.
295 : */
296 : int updateINDI();
297 :
298 : ///@}
299 :
300 : /** \name Telemeter Interface
301 : * @{
302 : */
303 :
304 : int recordFGTimings( bool force = false );
305 :
306 : /// @}
307 :
308 : private:
309 0 : derivedT &derived()
310 : {
311 0 : return *static_cast<derivedT *>( this );
312 : }
313 : };
314 :
315 : template <class derivedT>
316 0 : int frameGrabber<derivedT>::setupConfig( mx::app::appConfigurator &config )
317 : {
318 0 : config.add( "framegrabber.threadPrio",
319 : "",
320 : "framegrabber.threadPrio",
321 : argType::Required,
322 : "framegrabber",
323 : "threadPrio",
324 : false,
325 : "int",
326 : "The real-time priority of the framegrabber thread." );
327 :
328 0 : config.add( "framegrabber.cpuset",
329 : "",
330 : "framegrabber.cpuset",
331 : argType::Required,
332 : "framegrabber",
333 : "cpuset",
334 : false,
335 : "string",
336 : "The cpuset to assign the framegrabber thread to." );
337 :
338 0 : config.add( "framegrabber.shmimName",
339 : "",
340 : "framegrabber.shmimName",
341 : argType::Required,
342 : "framegrabber",
343 : "shmimName",
344 : false,
345 : "string",
346 : "The name of the ImageStreamIO shared memory image. Will be used as /milk/shm/<shmimName>.im.shm." );
347 :
348 0 : config.add( "framegrabber.circBuffLength",
349 : "",
350 : "framegrabber.circBuffLength",
351 : argType::Required,
352 : "framegrabber",
353 : "circBuffLength",
354 : false,
355 : "size_t",
356 : "The length of the circular buffer. Sets m_circBuffLength, default is 1." );
357 :
358 0 : config.add( "framegrabber.latencyTime",
359 : "",
360 : "framegrabber.latencyTime",
361 : argType::Required,
362 : "framegrabber",
363 : "latencyTime",
364 : false,
365 : "float",
366 : "The maximum length of time to measure latency timings. "
367 : "Sets m_latencyCircBuffMaxTime, default is 5." );
368 :
369 0 : config.add( "framegrabber.latencySize",
370 : "",
371 : "framegrabber.latencySize",
372 : argType::Required,
373 : "framegrabber",
374 : "latencySize",
375 : false,
376 : "float",
377 : "The maximum length of the buffer used to measure latency timings. Sets m_latencyCircBuffMaxLength, "
378 : "default is 3600." );
379 :
380 : if( derivedT::c_frameGrabber_flippable )
381 : {
382 : config.add( "framegrabber.defaultFlip",
383 : "",
384 : "framegrabber.defaultFlip",
385 : argType::Required,
386 : "framegrabber",
387 : "defaultFlip",
388 : false,
389 : "string",
390 : "The default flip of the image. Options are flipNone, flipUD, flipLR, flipUDLR. The default is "
391 : "flipNone." );
392 : }
393 :
394 0 : return 0;
395 : }
396 :
397 : template <class derivedT>
398 0 : int frameGrabber<derivedT>::loadConfig( mx::app::appConfigurator &config )
399 : {
400 0 : config( m_fgThreadPrio, "framegrabber.threadPrio" );
401 0 : config( m_fgCpuset, "framegrabber.cpuset" );
402 0 : if( m_shmimName == "" )
403 : {
404 0 : m_shmimName = derived().configName();
405 : }
406 :
407 0 : config( m_shmimName, "framegrabber.shmimName" );
408 :
409 0 : if( m_shmimName == "" )
410 : {
411 0 : return derivedT::template log<software_critical, -1>( { "framegrabber.shmimName can't be empty" } );
412 : }
413 :
414 0 : config( m_circBuffLength, "framegrabber.circBuffLength" );
415 :
416 0 : if( m_circBuffLength < 1 )
417 : {
418 0 : m_circBuffLength = 1;
419 0 : derivedT::template log<text_log>( "circBuffLength set to 1" );
420 : }
421 :
422 0 : config( m_latencyCircBuffMaxTime, "framegrabber.latencyTime" );
423 0 : if( m_latencyCircBuffMaxTime < 0 )
424 : {
425 0 : m_latencyCircBuffMaxTime = 0;
426 0 : derivedT::template log<text_log>( "latencyTime set to 0 (off)" );
427 : }
428 :
429 0 : config( m_latencyCircBuffMaxLength, "framegrabber.latencySize" );
430 :
431 : if( derivedT::c_frameGrabber_flippable )
432 : {
433 : std::string flip = "flipNone";
434 :
435 : config( flip, "framegrabber.defaultFlip" );
436 :
437 : if( flip == "flipNone" )
438 : {
439 : m_defaultFlip = fgFlipNone;
440 : }
441 : else if( flip == "flipUD" )
442 : {
443 : m_defaultFlip = fgFlipUD;
444 : }
445 : else if( flip == "flipLR" )
446 : {
447 : m_defaultFlip = fgFlipLR;
448 : }
449 : else if( flip == "flipUDLR" )
450 : {
451 : m_defaultFlip = fgFlipUDLR;
452 : }
453 : else
454 : {
455 : derivedT::template log<text_log>( { std::string( "invalid framegrabber flip"
456 : "specification (" ) +
457 : flip + "), setting flipNone" },
458 : logPrio::LOG_ERROR );
459 :
460 : m_defaultFlip = fgFlipNone;
461 : }
462 : }
463 :
464 0 : return 0;
465 : }
466 :
467 : template <class derivedT>
468 0 : int frameGrabber<derivedT>::appStartup()
469 : {
470 : // Register the shmimName INDI property
471 0 : m_indiP_shmimName = pcf::IndiProperty( pcf::IndiProperty::Text );
472 0 : m_indiP_shmimName.setDevice( derived().configName() );
473 0 : m_indiP_shmimName.setName( "fg_shmimName" );
474 0 : m_indiP_shmimName.setPerm( pcf::IndiProperty::ReadOnly );
475 0 : m_indiP_shmimName.setState( pcf::IndiProperty::Idle );
476 0 : m_indiP_shmimName.add( pcf::IndiElement( "name" ) );
477 0 : m_indiP_shmimName["name"] = m_shmimName;
478 :
479 0 : if( derived().registerIndiPropertyNew( m_indiP_shmimName, nullptr ) < 0 )
480 : {
481 : #ifndef FRAMEGRABBER_TEST_NOLOG
482 0 : derivedT::template log<software_error>( { std::source_location::current() } );
483 : #endif
484 :
485 0 : return -1;
486 : }
487 :
488 : // Register the frameSize INDI property
489 0 : m_indiP_frameSize = pcf::IndiProperty( pcf::IndiProperty::Number );
490 0 : m_indiP_frameSize.setDevice( derived().configName() );
491 0 : m_indiP_frameSize.setName( "fg_frameSize" );
492 0 : m_indiP_frameSize.setPerm( pcf::IndiProperty::ReadOnly );
493 0 : m_indiP_frameSize.setState( pcf::IndiProperty::Idle );
494 0 : m_indiP_frameSize.add( pcf::IndiElement( "width" ) );
495 0 : m_indiP_frameSize["width"] = 0;
496 0 : m_indiP_frameSize.add( pcf::IndiElement( "height" ) );
497 0 : m_indiP_frameSize["height"] = 0;
498 0 : m_indiP_frameSize.add( pcf::IndiElement( "depth" ) );
499 0 : m_indiP_frameSize["depth"] = 0;
500 :
501 0 : if( derived().registerIndiPropertyNew( m_indiP_frameSize, nullptr ) < 0 )
502 : {
503 : #ifndef FRAMEGRABBER_TEST_NOLOG
504 0 : derivedT::template log<software_error>( { std::source_location::current() } );
505 : #endif
506 :
507 0 : return -1;
508 : }
509 :
510 : // Register the timing INDI property
511 0 : derived().createROIndiNumber( m_indiP_timing, "fg_timing" );
512 0 : m_indiP_timing.add( pcf::IndiElement( "acq_fps" ) );
513 0 : m_indiP_timing.add( pcf::IndiElement( "acq_min" ) );
514 0 : m_indiP_timing.add( pcf::IndiElement( "acq_max" ) );
515 0 : m_indiP_timing.add( pcf::IndiElement( "acq_jitter" ) );
516 0 : m_indiP_timing.add( pcf::IndiElement( "write_fps" ) );
517 0 : m_indiP_timing.add( pcf::IndiElement( "write_min" ) );
518 0 : m_indiP_timing.add( pcf::IndiElement( "write_max" ) );
519 0 : m_indiP_timing.add( pcf::IndiElement( "write_jitter" ) );
520 0 : m_indiP_timing.add( pcf::IndiElement( "delta_aw" ) );
521 0 : m_indiP_timing.add( pcf::IndiElement( "delta_aw_jitter" ) );
522 :
523 0 : if( derived().registerIndiPropertyReadOnly( m_indiP_timing ) < 0 )
524 : {
525 : #ifndef FRAMEGRABBER_TEST_NOLOG
526 0 : derivedT::template log<software_error>( { std::source_location::current() } );
527 : #endif
528 :
529 0 : return -1;
530 : }
531 :
532 : // Start the f.g. thread
533 0 : if( derived().threadStart( m_fgThread,
534 0 : m_fgThreadInit,
535 0 : m_fgThreadID,
536 0 : m_fgThreadProp,
537 : m_fgThreadPrio,
538 0 : m_fgCpuset,
539 : "framegrabber",
540 : this,
541 0 : fgThreadStart ) < 0 )
542 : {
543 0 : derivedT::template log<software_error, -1>( { std::source_location::current() } );
544 0 : return -1;
545 : }
546 :
547 0 : return 0;
548 : }
549 :
550 : template <class derivedT>
551 0 : int frameGrabber<derivedT>::appLogic()
552 : {
553 : // do a join check to see if other threads have exited.
554 0 : if( pthread_tryjoin_np( m_fgThread.native_handle(), 0 ) == 0 )
555 : {
556 0 : derivedT::template log<software_error>( { "framegrabber thread has exited" } );
557 :
558 0 : return -1;
559 : }
560 :
561 : try
562 : {
563 0 : if( derived().state() == stateCodes::OPERATING && m_atimes.size() > 0 && derived().fps() > 0 )
564 : {
565 0 : if( m_atimes.size() >= m_atimes.maxEntries() )
566 : {
567 0 : cbIndexT latTime = m_latencyCircBuffMaxTime * m_cbFPS;
568 0 : if( latTime >= m_atimes.maxEntries() )
569 : {
570 0 : latTime = m_atimes.maxEntries() - 1;
571 : }
572 :
573 0 : m_atimesD.resize( latTime - 1 );
574 0 : m_wtimesD.resize( latTime - 1 );
575 0 : m_watimesD.resize( latTime - 1 );
576 :
577 0 : cbIndexT refEntry = m_atimes.latest();
578 :
579 0 : if( refEntry >= latTime )
580 : {
581 0 : refEntry -= latTime;
582 : }
583 : else
584 : {
585 0 : refEntry = m_atimes.maxEntries() + refEntry - latTime;
586 : }
587 :
588 0 : timespec ts = m_atimes.at( refEntry, 0 );
589 0 : double a0 = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
590 :
591 0 : ts = m_wtimes.at( refEntry, 0 );
592 0 : double w0 = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
593 :
594 0 : double mina = 1e9;
595 0 : double maxa = -1e9;
596 0 : double minw = 1e9;
597 0 : double maxw = -1e9;
598 :
599 0 : for( size_t n = 1; n <= m_atimesD.size(); ++n )
600 : {
601 0 : ts = m_atimes.at( refEntry, n );
602 :
603 0 : double a = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
604 :
605 0 : ts = m_wtimes.at( refEntry, n );
606 :
607 0 : double w = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
608 :
609 0 : m_atimesD[n - 1] = a - a0;
610 0 : m_wtimesD[n - 1] = w - w0;
611 0 : m_watimesD[n - 1] = w - a;
612 0 : a0 = a;
613 0 : w0 = w;
614 :
615 0 : if( m_atimesD[n - 1] < mina )
616 : {
617 0 : mina = m_atimesD[n - 1];
618 : }
619 :
620 0 : if( m_atimesD[n - 1] > maxa )
621 : {
622 0 : maxa = m_atimesD[n - 1];
623 : }
624 :
625 0 : if( m_wtimesD[n - 1] < minw )
626 : {
627 0 : minw = m_wtimesD[n - 1];
628 : }
629 :
630 0 : if( m_wtimesD[n - 1] > maxw )
631 : {
632 0 : maxw = m_wtimesD[n - 1];
633 : }
634 :
635 0 : if( m_wtimesD[n - 1] < 0 )
636 : {
637 0 : std::cerr << "negative wtime: " << m_wtimesD[n - 1] << ' ' << n << ' ' << m_atimesD.size()
638 0 : << ' ' << refEntry << ' ' << m_atimes.maxEntries() << ' ' << latTime << '\n';
639 :
640 0 : return derivedT::template log<software_error, 0>( { "negative write time. latency "
641 0 : "circ buff is not long enought" } );
642 : }
643 : }
644 :
645 0 : m_mna = mx::math::vectorMean( m_atimesD );
646 0 : m_vara = mx::math::vectorVariance( m_atimesD, m_mna );
647 0 : m_mina = mina;
648 0 : m_maxa = maxa;
649 :
650 0 : m_mnw = mx::math::vectorMean( m_wtimesD );
651 0 : m_varw = mx::math::vectorVariance( m_wtimesD, m_mnw );
652 0 : m_minw = minw;
653 0 : m_maxw = maxw;
654 :
655 0 : m_mnwa = mx::math::vectorMean( m_watimesD );
656 0 : m_varwa = mx::math::vectorVariance( m_watimesD, m_mnwa );
657 :
658 0 : recordFGTimings();
659 : }
660 : else
661 : {
662 0 : m_mna = 0;
663 0 : m_vara = 0;
664 0 : m_mina = 0;
665 0 : m_maxa = 0;
666 0 : m_mnw = 0;
667 0 : m_varw = 0;
668 0 : m_minw = 0;
669 0 : m_maxw = 0;
670 0 : m_mnwa = 0;
671 0 : m_varwa = 0;
672 : }
673 : }
674 : else
675 : {
676 0 : m_mna = 0;
677 0 : m_vara = 0;
678 0 : m_mina = 0;
679 0 : m_maxa = 0;
680 0 : m_mnw = 0;
681 0 : m_varw = 0;
682 0 : m_minw = 0;
683 0 : m_maxw = 0;
684 0 : m_mnwa = 0;
685 0 : m_varwa = 0;
686 : }
687 : }
688 0 : catch( const std::exception &e )
689 : {
690 0 : std::cerr << e.what() << '\n';
691 : }
692 :
693 0 : return 0;
694 : }
695 :
696 : template <class derivedT>
697 : int frameGrabber<derivedT>::onPowerOff()
698 : {
699 : m_mna = 0;
700 : m_vara = 0;
701 : m_mina = 0;
702 : m_maxa = 0;
703 : m_mnw = 0;
704 : m_varw = 0;
705 : m_minw = 0;
706 : m_maxw = 0;
707 : m_mnwa = 0;
708 : m_varwa = 0;
709 :
710 : m_width = 0;
711 : m_height = 0;
712 : m_circBuffLength = 1;
713 :
714 : updateINDI();
715 :
716 : m_reconfig = true;
717 :
718 : return 0;
719 : }
720 :
721 : template <class derivedT>
722 0 : int frameGrabber<derivedT>::appShutdown()
723 : {
724 0 : if( m_fgThread.joinable() )
725 : {
726 : try
727 : {
728 0 : m_fgThread.join(); // this will throw if it was already joined
729 : }
730 0 : catch( ... )
731 : {
732 : }
733 : }
734 :
735 0 : return 0;
736 : }
737 :
738 : template <class derivedT>
739 0 : int frameGrabber<derivedT>::configCircBuffs()
740 : {
741 0 : m_cbFPS = derived().fps();
742 :
743 0 : if( m_latencyCircBuffMaxLength == 0 || m_latencyCircBuffMaxTime == 0 || m_cbFPS <= 0 )
744 : {
745 0 : m_atimes.maxEntries( 0 );
746 0 : m_wtimes.maxEntries( 0 );
747 :
748 0 : if( m_cbFPS < 0 )
749 : {
750 0 : return -1;
751 : }
752 : }
753 : else
754 : {
755 : // Set up the latency circ. buffs
756 0 : cbIndexT cbSz = 2 * m_latencyCircBuffMaxTime * m_cbFPS;
757 0 : if( cbSz > m_latencyCircBuffMaxLength )
758 : {
759 0 : cbSz = m_latencyCircBuffMaxLength;
760 : }
761 0 : if( cbSz < 3 )
762 : {
763 0 : cbSz = 3; // Make variance meaningful
764 : }
765 :
766 0 : m_atimes.maxEntries( cbSz );
767 0 : m_wtimes.maxEntries( cbSz );
768 : }
769 :
770 0 : return 0;
771 : }
772 :
773 : template <class derivedT>
774 0 : void frameGrabber<derivedT>::fgThreadStart( frameGrabber *o )
775 : {
776 0 : o->fgThreadExec();
777 0 : }
778 :
779 : template <class derivedT>
780 0 : void frameGrabber<derivedT>::fgThreadExec()
781 : {
782 : // Get the thread PID immediately so the caller can return.
783 0 : m_fgThreadID = syscall( SYS_gettid );
784 :
785 : // timespec writestart;
786 :
787 : // Wait fpr the thread starter to finish initializing this thread.
788 0 : while( m_fgThreadInit == true && derived().shutdown() == 0 )
789 : {
790 0 : sleep( 1 );
791 : }
792 :
793 0 : uint32_t imsize[3] = { 0, 0, 0 };
794 0 : bool cbuff = false;
795 0 : std::string shmimName;
796 :
797 : static bool logged_wrong_size = false;
798 :
799 0 : while( derived().shutdown() == 0 )
800 : {
801 : ///\todo this ought to wait until OPERATING, using READY as a sign of "not integrating"
802 0 : while( !derived().shutdown() &&
803 0 : ( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) ||
804 0 : derived().powerState() <= 0 ) )
805 : {
806 0 : sleep( 1 );
807 : }
808 :
809 0 : if( derived().shutdown() )
810 : {
811 0 : break;
812 : }
813 :
814 : // At the end of this, must have m_width, m_height, m_dataType set, and derived()->fps must be valid.
815 0 : if( derived().configureAcquisition() != 0 )
816 : {
817 0 : sleep( 1 );
818 0 : continue;
819 : }
820 :
821 0 : m_typeSize = ImageStreamIO_typesize( m_dataType );
822 :
823 0 : if( configCircBuffs() < 0 )
824 : {
825 0 : derivedT::template log<software_error>( { "error configuring latency circ. buffs" } );
826 : }
827 :
828 : /* Initialize ImageStreamIO
829 : */
830 :
831 : // Check if we are already connected by configureAcquisition
832 0 : if( m_imageStream != nullptr )
833 : {
834 0 : imsize[0] = m_imageStream->md[0].size[0];
835 :
836 0 : if( m_imageStream->md[0].naxis > 1 )
837 : {
838 0 : imsize[1] = m_imageStream->md[0].size[1];
839 : }
840 :
841 0 : if( m_imageStream->md[0].naxis > 2 )
842 : {
843 0 : imsize[2] = m_imageStream->md[0].size[2];
844 0 : cbuff = true;
845 : }
846 : else
847 : {
848 0 : imsize[2] = 1;
849 0 : cbuff = false;
850 : }
851 : }
852 :
853 0 : if( m_width != imsize[0] || m_height != imsize[1] || m_circBuffLength != imsize[2] || m_imageStream == nullptr )
854 : {
855 0 : if( m_imageStream != nullptr && m_ownShmim )
856 : {
857 0 : ImageStreamIO_destroyIm( m_imageStream );
858 0 : free( m_imageStream );
859 : }
860 0 : else if( m_imageStream != nullptr && !m_ownShmim )
861 : {
862 0 : if( !logged_wrong_size )
863 : {
864 0 : derivedT::template log<text_log>(
865 0 : std::format( "image stream {} is not expected size", m_shmimName ), logPrio::LOG_WARNING );
866 0 : logged_wrong_size = true;
867 : }
868 :
869 0 : sleep( 1 );
870 0 : continue; // we go around and try again, waiting for the shmim to get resized to our expectations
871 : }
872 :
873 0 : logged_wrong_size = false;
874 :
875 0 : m_imageStream = reinterpret_cast<IMAGE *>( malloc( sizeof( IMAGE ) ) );
876 :
877 0 : imsize[0] = m_width;
878 0 : imsize[1] = m_height;
879 0 : imsize[2] = m_circBuffLength;
880 :
881 0 : std::cerr << "Creating: " << m_shmimName << " " << m_width << " " << m_height << " " << m_circBuffLength
882 0 : << "\n";
883 :
884 0 : ImageStreamIO_createIm_gpu( m_imageStream,
885 : m_shmimName.c_str(),
886 : 3,
887 : imsize,
888 0 : m_dataType,
889 : -1,
890 : 1,
891 : IMAGE_NB_SEMAPHORE,
892 : 0,
893 : CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
894 : 0 );
895 :
896 0 : m_imageStream->md->cnt1 = m_circBuffLength - 1;
897 :
898 0 : cbuff = true; // because we created it!
899 : }
900 :
901 : // This completes the reconfiguration.
902 0 : m_reconfig = false;
903 :
904 0 : if( derived().startAcquisition() < 0 )
905 : {
906 0 : continue;
907 : }
908 :
909 0 : uint64_t next_cnt1 = 0;
910 0 : char *next_dest = reinterpret_cast<char *>( m_imageStream->array.raw );
911 :
912 0 : timespec *next_wtimearr = nullptr;
913 0 : timespec *next_atimearr = nullptr;
914 0 : uint64_t *next_cntarr = nullptr;
915 :
916 0 : if( cbuff )
917 : {
918 0 : next_wtimearr = &m_imageStream->writetimearray[0];
919 0 : next_atimearr = &m_imageStream->atimearray[0];
920 0 : next_cntarr = &m_imageStream->cntarray[0];
921 : }
922 :
923 : // This is the main image grabbing loop.
924 0 : while( !derived().shutdown() && !m_reconfig && derived().powerState() > 0 )
925 : {
926 : //==================
927 : // Get next image, process validity.
928 : //====================
929 0 : int isValid = derived().acquireAndCheckValid();
930 0 : if( isValid != 0 )
931 : {
932 0 : if( isValid < 0 )
933 : {
934 0 : break;
935 : }
936 : else
937 : {
938 0 : continue;
939 : }
940 : }
941 :
942 : // Ok, no timeout, so we process the image and publish it.
943 0 : m_imageStream->md->write = 1;
944 :
945 0 : if( derived().loadImageIntoStream( next_dest ) < 0 )
946 : {
947 0 : break;
948 : }
949 :
950 : // Set the time of last write
951 : // clock_gettime(CLOCK_REALTIME, &m_imageStream->md->writetime);
952 0 : if( clock_gettime( CLOCK_REALTIME, &m_imageStream->md->writetime ) < 0 )
953 : {
954 0 : derivedT::template log<software_critical>( { errno, "clock_gettime" } );
955 : }
956 :
957 : // Set the image acquisition timestamp
958 0 : m_imageStream->md->atime = m_currImageTimestamp;
959 :
960 : // Update cnt1
961 0 : m_imageStream->md->cnt1 = next_cnt1;
962 :
963 : // Update cnt0
964 0 : m_imageStream->md->cnt0++;
965 :
966 0 : if( cbuff )
967 : {
968 0 : *next_wtimearr = m_imageStream->md->writetime;
969 0 : *next_atimearr = m_currImageTimestamp;
970 0 : *next_cntarr = m_imageStream->md->cnt0;
971 : }
972 :
973 : // And post
974 0 : m_imageStream->md->write = 0;
975 0 : ImageStreamIO_sempost( m_imageStream, -1 );
976 :
977 : // Update the latency circ. buffs
978 0 : if( m_atimes.maxEntries() > 0 )
979 : {
980 0 : m_atimes.nextEntry( m_imageStream->md->atime );
981 0 : m_wtimes.nextEntry( m_imageStream->md->writetime );
982 : }
983 :
984 : // Now we increment pointers outside the time-critical part of the loop.
985 0 : next_cnt1 = m_imageStream->md->cnt1 + 1;
986 0 : if( next_cnt1 >= m_circBuffLength )
987 : {
988 0 : next_cnt1 = 0;
989 : }
990 :
991 0 : next_dest =
992 0 : reinterpret_cast<char *>( m_imageStream->array.raw ) + next_cnt1 * m_width * m_height * m_typeSize;
993 :
994 0 : if( cbuff )
995 : {
996 0 : next_wtimearr = &m_imageStream->writetimearray[next_cnt1];
997 0 : next_atimearr = &m_imageStream->atimearray[next_cnt1];
998 0 : next_cntarr = &m_imageStream->cntarray[next_cnt1];
999 : }
1000 :
1001 : // Touch them to make sure we move
1002 0 : m_dummy_c = next_dest[0];
1003 :
1004 0 : if( cbuff )
1005 : {
1006 0 : m_dummy_ts.tv_sec = next_wtimearr[0].tv_sec + next_atimearr[0].tv_sec;
1007 0 : m_dummy_cnt = next_cntarr[0];
1008 : }
1009 :
1010 0 : if( m_cbFPS != derived().fps() )
1011 : {
1012 0 : std::cerr << "configuring due to mismatch m_cbFPS\n";
1013 0 : if( configCircBuffs() < 0 )
1014 : {
1015 0 : derivedT::template log<software_error>( { "error configuring latency circ. buffs" } );
1016 : }
1017 : }
1018 : }
1019 :
1020 0 : if( m_reconfig && !derived().shutdown() )
1021 : {
1022 0 : derived().reconfig();
1023 : }
1024 :
1025 : } // outer loop, will exit if m_shutdown==true
1026 :
1027 0 : if( m_imageStream != nullptr )
1028 : {
1029 0 : if( m_ownShmim )
1030 : {
1031 0 : ImageStreamIO_destroyIm( m_imageStream );
1032 : }
1033 : else
1034 : {
1035 0 : ImageStreamIO_closeIm( m_imageStream );
1036 : }
1037 :
1038 0 : free( m_imageStream );
1039 0 : m_imageStream = nullptr;
1040 : }
1041 0 : }
1042 :
1043 : template <class derivedT>
1044 : void *frameGrabber<derivedT>::loadImageIntoStreamCopy( void *dest, void *src, size_t width, size_t height, size_t szof )
1045 : {
1046 : if( !derivedT::c_frameGrabber_flippable )
1047 : {
1048 : return memcpy( dest, src, width * height * szof );
1049 : }
1050 : else
1051 : {
1052 : switch( m_defaultFlip )
1053 : {
1054 : case fgFlipNone:
1055 : return mx::improc::imcpy( dest, src, width, height, szof );
1056 : case fgFlipUD:
1057 : return mx::improc::imcpy_flipUD( dest, src, width, height, szof );
1058 : case fgFlipLR:
1059 : return mx::improc::imcpy_flipLR( dest, src, width, height, szof );
1060 : case fgFlipUDLR:
1061 : return mx::improc::imcpy_flipUDLR( dest, src, width, height, szof );
1062 : default:
1063 : return nullptr;
1064 : }
1065 : }
1066 : }
1067 :
1068 : template <class derivedT>
1069 : int frameGrabber<derivedT>::openShmim()
1070 : {
1071 : static bool logged = false;
1072 :
1073 : if( m_imageStream != nullptr )
1074 : {
1075 : ImageStreamIO_closeIm( m_imageStream );
1076 : free( m_imageStream );
1077 : m_imageStream = nullptr;
1078 : }
1079 :
1080 : // b/c ImageStreamIO prints every single time, and latest version don't support stopping it yet, and that
1081 : // isn't thread-safe-able anyway we do our own checks. This is the same code in ImageStreamIO_openIm...
1082 : int SM_fd;
1083 : char SM_fname[1024];
1084 : ImageStreamIO_filename( SM_fname, sizeof( SM_fname ), m_shmimName.c_str() );
1085 : SM_fd = open( SM_fname, O_RDWR );
1086 :
1087 : if( SM_fd == -1 )
1088 : {
1089 : if( !logged )
1090 : {
1091 : derivedT::template log<text_log>( "ImageStream " + m_shmimName + " not found (yet). Retrying . . .",
1092 : logPrio::LOG_NOTICE );
1093 : logged = true;
1094 : }
1095 :
1096 : return 1;
1097 : }
1098 :
1099 : // Found and opened, close it and then use ImageStreamIO
1100 : logged = false;
1101 : close( SM_fd );
1102 :
1103 : m_imageStream = reinterpret_cast<IMAGE *>( malloc( sizeof( IMAGE ) ) );
1104 :
1105 : if( ImageStreamIO_openIm( m_imageStream, m_shmimName.c_str() ) == 0 )
1106 : {
1107 : if( m_imageStream->md[0].sem < SEMAPHORE_MAXVAL )
1108 : {
1109 : ImageStreamIO_closeIm( m_imageStream );
1110 : free( m_imageStream );
1111 : m_imageStream = nullptr;
1112 :
1113 : return 1; // We just need to wait for the server process to finish startup.
1114 : }
1115 : else
1116 : {
1117 : char SM_fname[1024];
1118 : ImageStreamIO_filename( SM_fname, sizeof( SM_fname ), m_shmimName.c_str() );
1119 :
1120 : struct stat buffer;
1121 :
1122 : int rv = stat( SM_fname, &buffer );
1123 :
1124 : if( rv != 0 )
1125 : {
1126 : derivedT::template log<software_critical>( { errno,
1127 : "Could not get inode for " + m_shmimName +
1128 : ". Source process will need to be restarted." } );
1129 :
1130 : ImageStreamIO_closeIm( m_imageStream );
1131 :
1132 : free( m_imageStream );
1133 :
1134 : m_imageStream = nullptr;
1135 :
1136 : derived().m_shutdown = true;
1137 :
1138 : return -1;
1139 : }
1140 :
1141 : m_inode = buffer.st_ino;
1142 :
1143 : m_width = m_imageStream->md->size[0];
1144 :
1145 : if( m_imageStream->md->naxis == 2 )
1146 : {
1147 : m_height = m_imageStream->md->size[1];
1148 : m_circBuffLength = 1;
1149 : }
1150 : else if( m_imageStream->md->naxis == 3 )
1151 : {
1152 : m_height = m_imageStream->md->size[1];
1153 : m_circBuffLength = m_imageStream->md->size[2];
1154 : }
1155 : else
1156 : {
1157 : m_height = 1;
1158 : m_circBuffLength = 1;
1159 : }
1160 :
1161 : m_dataType = m_imageStream->md->datatype;
1162 : m_typeSize = ImageStreamIO_typesize( m_dataType );
1163 :
1164 : return 0;
1165 : }
1166 : }
1167 : else
1168 : {
1169 : free( m_imageStream );
1170 : m_imageStream = nullptr;
1171 :
1172 : return 1; // be patient
1173 : }
1174 : }
1175 :
1176 : template <class derivedT>
1177 0 : int frameGrabber<derivedT>::updateINDI()
1178 : {
1179 0 : if( !derived().m_indiDriver )
1180 0 : return 0;
1181 :
1182 0 : indi::updateIfChanged( m_indiP_shmimName, "name", m_shmimName, derived().m_indiDriver );
1183 0 : indi::updateIfChanged( m_indiP_frameSize, "width", m_width, derived().m_indiDriver );
1184 0 : indi::updateIfChanged( m_indiP_frameSize, "height", m_height, derived().m_indiDriver );
1185 0 : indi::updateIfChanged( m_indiP_frameSize, "depth", m_circBuffLength, derived().m_indiDriver );
1186 :
1187 0 : double fpsa = 0;
1188 0 : double fpsw = 0;
1189 0 : if( m_mna != 0 )
1190 0 : fpsa = 1.0 / m_mna;
1191 0 : if( m_mnw != 0 )
1192 0 : fpsw = 1.0 / m_mnw;
1193 :
1194 0 : indi::updateIfChanged<double>(
1195 0 : m_indiP_timing,
1196 : { "acq_fps",
1197 : "acq_min",
1198 : "acq_max",
1199 : "acq_jitter",
1200 : "write_fps",
1201 : "write_min",
1202 : "write_max",
1203 : "write_jitter",
1204 : "delta_aw",
1205 : "delta_aw_jitter" },
1206 0 : { fpsa, m_mina, m_maxa, sqrt( m_vara ), fpsw, m_minw, m_maxw, sqrt( m_varw ), m_mnwa, sqrt( m_varwa ) },
1207 0 : derived().m_indiDriver );
1208 :
1209 0 : return 0;
1210 : }
1211 :
1212 : template <class derivedT>
1213 0 : int frameGrabber<derivedT>::recordFGTimings( bool force )
1214 : {
1215 : static double last_mna = 0;
1216 : static double last_vara = 0;
1217 :
1218 : static double last_mnw = 0;
1219 : static double last_varw = 0;
1220 :
1221 : static double last_mnwa = 0;
1222 : static double last_varwa = 0;
1223 :
1224 0 : if( force || m_mna != last_mna || m_vara != last_vara || m_mnw != last_mnw || m_varw != last_varw ||
1225 0 : m_mnwa != last_mnwa || m_varwa != last_varwa )
1226 : {
1227 0 : derived().template telem<telem_fgtimings>(
1228 0 : { m_mna, sqrt( m_vara ), m_mnw, sqrt( m_varw ), m_mnwa, sqrt( m_varwa ) } );
1229 :
1230 0 : last_mna = m_mna;
1231 0 : last_vara = m_vara;
1232 0 : last_mnw = m_mnw;
1233 0 : last_varw = m_varw;
1234 0 : last_mnwa = m_mnwa;
1235 0 : last_varwa = m_varwa;
1236 : }
1237 :
1238 0 : return 0;
1239 : }
1240 :
1241 : /// Call frameGrabberT::setupConfig with error checking for frameGrabber
1242 : /**
1243 : * \param cfig the application configurator
1244 : */
1245 : #define FRAMEGRABBER_SETUP_CONFIG( cfig ) \
1246 : if( frameGrabberT::setupConfig( cfig ) < 0 ) \
1247 : { \
1248 : log<software_error>( { "Error from frameGrabberT::setupConfig" } ); \
1249 : m_shutdown = true; \
1250 : return; \
1251 : }
1252 :
1253 : /// Call frameGrabberT::loadConfig with error checking for frameGrabber
1254 : /** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
1255 : * \param cfig the application configurator
1256 : */
1257 : #define FRAMEGRABBER_LOAD_CONFIG( cfig ) \
1258 : if( frameGrabberT::loadConfig( cfig ) < 0 ) \
1259 : { \
1260 : return log<software_error, -1>( { "Error from frameGrabberT::loadConfig" } ); \
1261 : }
1262 :
1263 : /// Call frameGrabberT::appStartup with error checking for frameGrabber
1264 : #define FRAMEGRABBER_APP_STARTUP \
1265 : if( frameGrabberT::appStartup() < 0 ) \
1266 : { \
1267 : return log<software_error, -1>( { "Error from frameGrabberT::appStartup" } ); \
1268 : }
1269 :
1270 : /// Call frameGrabberT::appLogic with error checking for frameGrabber
1271 : #define FRAMEGRABBER_APP_LOGIC \
1272 : if( frameGrabberT::appLogic() < 0 ) \
1273 : { \
1274 : return log<software_error, -1>( { "Error from frameGrabberT::appLogic" } ); \
1275 : }
1276 :
1277 : /// Call frameGrabberT::updateINDI with error checking for frameGrabber
1278 : #define FRAMEGRABBER_UPDATE_INDI \
1279 : if( frameGrabberT::updateINDI() < 0 ) \
1280 : { \
1281 : return log<software_error, -1>( { "Error from frameGrabberT::updateINDI" } ); \
1282 : }
1283 :
1284 : /// Call frameGrabberT::appShutdown with error checking for frameGrabber
1285 : #define FRAMEGRABBER_APP_SHUTDOWN \
1286 : if( frameGrabberT::appShutdown() < 0 ) \
1287 : { \
1288 : return log<software_error, -1>( { "Error from frameGrabberT::appShutdown" } ); \
1289 : }
1290 :
1291 : } // namespace dev
1292 : } // namespace app
1293 : } // namespace MagAOX
1294 : #endif
|