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