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