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
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 float m_cbFPS{ 0 }; ///< The FPS used to configure the circular buffers
148
149 mx::sigproc::circularBufferIndex<timespec, cbIndexT> m_atimes;
150 mx::sigproc::circularBufferIndex<timespec, cbIndexT> m_wtimes;
151
152 std::vector<double> m_atimesD;
153 std::vector<double> m_wtimesD;
154 std::vector<double> m_watimesD;
155
156 timespec m_dummy_ts{ 0, 0 };
157 uint64_t m_dummy_cnt{ 0 };
158 char m_dummy_c{ 0 };
159
160 double m_mna;
161 double m_vara;
162 double m_mina;
163 double m_maxa;
164
165 double m_mnw;
166 double m_varw;
167 double m_minw;
168 double m_maxw;
169
170 double m_mnwa;
171 double m_varwa;
172
173 public:
174 /// Setup the configuration system
175 /**
176 * This should be called in `derivedT::setupConfig` as
177 * \code
178 framegrabber<derivedT>::setupConfig(config);
179 \endcode
180 * with appropriate error checking.
181 */
182 int setupConfig( mx::app::appConfigurator &config /**< [out] the derived classes configurator*/ );
183
184 /// load the configuration system results
185 /**
186 * This should be called in `derivedT::loadConfig` as
187 * \code
188 framegrabber<derivedT>::loadConfig(config);
189 \endcode
190 * with appropriate error checking.
191 */
192 int loadConfig( mx::app::appConfigurator &config /**< [in] the derived classes configurator*/ );
193
194 /// Startup function
195 /** Starts the framegrabber thread
196 * This should be called in `derivedT::appStartup` as
197 * \code
198 framegrabber<derivedT>::appStartup();
199 \endcode
200 * with appropriate error checking.
201 *
202 * \returns 0 on success
203 * \returns -1 on error, which is logged.
204 */
206
207 /// Checks the framegrabber thread
208 /** This should be called in `derivedT::appLogic` as
209 * \code
210 framegrabber<derivedT>::appLogic();
211 \endcode
212 * with appropriate error checking.
213 *
214 * \returns 0 on success
215 * \returns -1 on error, which is logged.
216 */
217 int appLogic();
218
219 /// On power off, sets m_reconfig to true.
220 /** This should be called in `derivedT::onPowerOff` as
221 * \code
222 framegrabber<derivedT>::onPowerOff();
223 \endcode
224 * with appropriate error checking.
225 *
226 * \returns 0 on success
227 * \returns -1 on error, which is logged.
228 */
230
231 /// Shuts down the framegrabber thread
232 /** This should be called in `derivedT::appShutdown` as
233 * \code
234 framegrabber<derivedT>::appShutdown();
235 \endcode
236 * with appropriate error checking.
237 *
238 * \returns 0 on success
239 * \returns -1 on error, which is logged.
240 */
242
244
245 protected:
246 /** \name Framegrabber Thread
247 * This thread actually manages the framegrabbing hardware
248 * @{
249 */
250
251 bool m_fgThreadInit{ true }; ///< Synchronizer for thread startup, to allow priority setting to finish.
252
253 pid_t m_fgThreadID{ 0 }; ///< The ID of the framegrabber thread.
254
255 pcf::IndiProperty m_fgThreadProp; ///< The property to hold the f.g. thread details.
256
257 std::thread m_fgThread; ///< A separate thread for the actual framegrabbings
258
259 /// Thread starter, called by MagAOXApp::threadStart on thread construction. Calls fgThreadExec.
260 static void fgThreadStart( frameGrabber *o /**< [in] a pointer to a frameGrabber instance (normally this) */ );
261
262 /// Execute framegrabbing.
264
265 ///@}
266
267 void *loadImageIntoStreamCopy( void *dest, void *src, size_t width, size_t height, size_t szof );
268
269 /** \name INDI
270 *
271 *@{
272 */
273 protected:
274 // declare our properties
275
276 pcf::IndiProperty m_indiP_shmimName; ///< Property used to report the shmim buffer name
277
278 pcf::IndiProperty m_indiP_frameSize; ///< Property used to report the current frame size
279
280 pcf::IndiProperty m_indiP_timing;
281
282 public:
283 /// Update the INDI properties for this device controller
284 /** You should call this once per main loop.
285 * It is not called automatically.
286 *
287 * \returns 0 on success.
288 * \returns -1 on error.
289 */
291
292 ///@}
293
294 /** \name Telemeter Interface
295 * @{
296 */
297
298 int recordFGTimings( bool force = false );
299
300 /// @}
301
302 private:
303 derivedT &derived()
304 {
305 return *static_cast<derivedT *>( this );
306 }
307};
308
309template <class derivedT>
310int frameGrabber<derivedT>::setupConfig( mx::app::appConfigurator &config )
311{
312 config.add( "framegrabber.threadPrio",
313 "",
314 "framegrabber.threadPrio",
315 argType::Required,
316 "framegrabber",
317 "threadPrio",
318 false,
319 "int",
320 "The real-time priority of the framegrabber thread." );
321
322 config.add( "framegrabber.cpuset",
323 "",
324 "framegrabber.cpuset",
325 argType::Required,
326 "framegrabber",
327 "cpuset",
328 false,
329 "string",
330 "The cpuset to assign the framegrabber thread to." );
331
332 config.add( "framegrabber.shmimName",
333 "",
334 "framegrabber.shmimName",
335 argType::Required,
336 "framegrabber",
337 "shmimName",
338 false,
339 "string",
340 "The name of the ImageStreamIO shared memory image. Will be used as /milk/shm/<shmimName>.im.shm." );
341
342 config.add( "framegrabber.circBuffLength",
343 "",
344 "framegrabber.circBuffLength",
345 argType::Required,
346 "framegrabber",
347 "circBuffLength",
348 false,
349 "size_t",
350 "The length of the circular buffer. Sets m_circBuffLength, default is 1." );
351
352 config.add(
353 "framegrabber.latencyTime",
354 "",
355 "framegrabber.latencyTime",
356 argType::Required,
357 "framegrabber",
358 "latencyTime",
359 false,
360 "float",
361 "The maximum length of time to measure latency timings. Sets m_latencyCircBuffMaxTime, default is 5." );
362
363 config.add( "framegrabber.latencySize",
364 "",
365 "framegrabber.latencySize",
366 argType::Required,
367 "framegrabber",
368 "latencySize",
369 false,
370 "float",
371 "The maximum length of the buffer used to measure latency timings. Sets m_latencyCircBuffMaxLength, "
372 "default is 3600." );
373
374 if( derivedT::c_frameGrabber_flippable )
375 {
376 config.add( "framegrabber.defaultFlip",
377 "",
378 "framegrabber.defaultFlip",
379 argType::Required,
380 "framegrabber",
381 "defaultFlip",
382 false,
383 "string",
384 "The default flip of the image. Options are flipNone, flipUD, flipLR, flipUDLR. The default is "
385 "flipNone." );
386 }
387
388 return 0;
389}
390
391template <class derivedT>
392int frameGrabber<derivedT>::loadConfig( mx::app::appConfigurator &config )
393{
394 config( m_fgThreadPrio, "framegrabber.threadPrio" );
395 config( m_fgCpuset, "framegrabber.cpuset" );
396 if( m_shmimName == "" )
397 m_shmimName = derived().configName();
398 config( m_shmimName, "framegrabber.shmimName" );
399
400 config( m_circBuffLength, "framegrabber.circBuffLength" );
401
402 if( m_circBuffLength < 1 )
403 {
404 m_circBuffLength = 1;
405 derivedT::template log<text_log>( "circBuffLength set to 1" );
406 }
407
408 config( m_latencyCircBuffMaxTime, "framegrabber.latencyTime" );
409 if( m_latencyCircBuffMaxTime < 0 )
410 {
411 m_latencyCircBuffMaxTime = 0;
412 derivedT::template log<text_log>( "latencyTime set to 0 (off)" );
413 }
414
415 config( m_latencyCircBuffMaxLength, "framegrabber.latencySize" );
416
417 if( derivedT::c_frameGrabber_flippable )
418 {
419 std::string flip = "flipNone";
420 config( flip, "framegrabber.defaultFlip" );
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>(
440 { std::string( "invalid framegrabber flip specification (" ) + flip + "), setting flipNone" },
441 logPrio::LOG_ERROR );
442 m_defaultFlip = fgFlipNone;
443 }
444 }
445
446 return 0;
447}
448
449template <class derivedT>
451{
452 // Register the shmimName INDI property
453 m_indiP_shmimName = pcf::IndiProperty( pcf::IndiProperty::Text );
454 m_indiP_shmimName.setDevice( derived().configName() );
455 m_indiP_shmimName.setName( "fg_shmimName" );
456 m_indiP_shmimName.setPerm( pcf::IndiProperty::ReadOnly );
457 m_indiP_shmimName.setState( pcf::IndiProperty::Idle );
458 m_indiP_shmimName.add( pcf::IndiElement( "name" ) );
459 m_indiP_shmimName["name"] = m_shmimName;
460
461 if( derived().registerIndiPropertyNew( m_indiP_shmimName, nullptr ) < 0 )
462 {
463#ifndef FRAMEGRABBER_TEST_NOLOG
464 derivedT::template log<software_error>( { __FILE__, __LINE__ } );
465#endif
466 return -1;
467 }
468
469 // Register the frameSize INDI property
470 m_indiP_frameSize = pcf::IndiProperty( pcf::IndiProperty::Number );
471 m_indiP_frameSize.setDevice( derived().configName() );
472 m_indiP_frameSize.setName( "fg_frameSize" );
473 m_indiP_frameSize.setPerm( pcf::IndiProperty::ReadOnly );
474 m_indiP_frameSize.setState( pcf::IndiProperty::Idle );
475 m_indiP_frameSize.add( pcf::IndiElement( "width" ) );
476 m_indiP_frameSize["width"] = 0;
477 m_indiP_frameSize.add( pcf::IndiElement( "height" ) );
478 m_indiP_frameSize["height"] = 0;
479
480 if( derived().registerIndiPropertyNew( m_indiP_frameSize, nullptr ) < 0 )
481 {
482#ifndef FRAMEGRABBER_TEST_NOLOG
483 derivedT::template log<software_error>( { __FILE__, __LINE__ } );
484#endif
485 return -1;
486 }
487
488 // Register the timing INDI property
489 derived().createROIndiNumber( m_indiP_timing, "fg_timing" );
490 m_indiP_timing.add( pcf::IndiElement( "acq_fps" ) );
491 m_indiP_timing.add( pcf::IndiElement( "acq_min" ) );
492 m_indiP_timing.add( pcf::IndiElement( "acq_max" ) );
493 m_indiP_timing.add( pcf::IndiElement( "acq_jitter" ) );
494 m_indiP_timing.add( pcf::IndiElement( "write_fps" ) );
495 m_indiP_timing.add( pcf::IndiElement( "write_min" ) );
496 m_indiP_timing.add( pcf::IndiElement( "write_max" ) );
497 m_indiP_timing.add( pcf::IndiElement( "write_jitter" ) );
498 m_indiP_timing.add( pcf::IndiElement( "delta_aw" ) );
499 m_indiP_timing.add( pcf::IndiElement( "delta_aw_jitter" ) );
500
501 if( derived().registerIndiPropertyReadOnly( m_indiP_timing ) < 0 )
502 {
503#ifndef STDCAMERA_TEST_NOLOG
504 derivedT::template log<software_error>( { __FILE__, __LINE__ } );
505#endif
506 return -1;
507 }
508
509 // Start the f.g. thread
510 if( derived().threadStart( m_fgThread,
511 m_fgThreadInit,
512 m_fgThreadID,
513 m_fgThreadProp,
514 m_fgThreadPrio,
515 m_fgCpuset,
516 "framegrabber",
517 this,
518 fgThreadStart ) < 0 )
519 {
520 derivedT::template log<software_error, -1>( { __FILE__, __LINE__ } );
521 return -1;
522 }
523
524 return 0;
525}
526
527template <class derivedT>
529{
530 // do a join check to see if other threads have exited.
531 if( pthread_tryjoin_np( m_fgThread.native_handle(), 0 ) == 0 )
532 {
533 derivedT::template log<software_error>( { __FILE__, __LINE__, "framegrabber thread has exited" } );
534
535 return -1;
536 }
537
538 try
539 {
540 if( derived().state() == stateCodes::OPERATING && m_atimes.size() > 0 && derived().fps() > 0 )
541 {
542 if( m_atimes.size() >= m_atimes.maxEntries() )
543 {
544
545 cbIndexT latTime = m_latencyCircBuffMaxTime * m_cbFPS;
546 if( latTime >= m_atimes.maxEntries() )
547 {
548 latTime = m_atimes.maxEntries() - 1;
549 }
550
551 m_atimesD.resize( latTime - 1 );
552 m_wtimesD.resize( latTime - 1 );
553 m_watimesD.resize( latTime - 1 );
554
555 cbIndexT refEntry = m_atimes.latest();
556
557 if( refEntry >= latTime )
558 {
559 refEntry -= latTime;
560 }
561 else
562 {
563 refEntry = m_atimes.maxEntries() + refEntry - latTime;
564 }
565
566 timespec ts = m_atimes.at( refEntry, 0 );
567 double a0 = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
568
569 ts = m_wtimes.at( refEntry, 0 );
570 double w0 = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
571
572 double mina = 1e9;
573 double maxa = -1e9;
574 double minw = 1e9;
575 double maxw = -1e9;
576
577 for( size_t n = 1; n <= m_atimesD.size(); ++n )
578 {
579 ts = m_atimes.at( refEntry, n );
580 double a = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
581
582 ts = m_wtimes.at( refEntry, n );
583 double w = ts.tv_sec + ( (double)ts.tv_nsec ) / 1e9;
584
585 m_atimesD[n - 1] = a - a0;
586 m_wtimesD[n - 1] = w - w0;
587 m_watimesD[n - 1] = w - a;
588 a0 = a;
589 w0 = w;
590
591 if( m_atimesD[n - 1] < mina )
592 {
593 mina = m_atimesD[n - 1];
594 }
595
596 if( m_atimesD[n - 1] > maxa )
597 {
598 maxa = m_atimesD[n - 1];
599 }
600
601 if( m_wtimesD[n - 1] < minw )
602 {
603 minw = m_wtimesD[n - 1];
604 }
605
606 if( m_wtimesD[n - 1] > maxw )
607 {
608 maxw = m_wtimesD[n - 1];
609 }
610
611 if( m_wtimesD[n - 1] < 0 )
612 {
613 std::cerr << "negative wtime: " << m_wtimesD[n - 1] << ' ' << n << ' ' << m_atimesD.size()
614 << ' ' << refEntry << ' ' << m_atimes.maxEntries() << ' ' << latTime << '\n';
615 return derivedT::template log<software_error, 0>(
616 { __FILE__, __LINE__, "negative write time. latency circ buff is not long enought" } );
617 }
618 }
619
620 m_mna = mx::math::vectorMean( m_atimesD );
621 m_vara = mx::math::vectorVariance( m_atimesD, m_mna );
622 m_mina = mina;
623 m_maxa = maxa;
624
625 m_mnw = mx::math::vectorMean( m_wtimesD );
626 m_varw = mx::math::vectorVariance( m_wtimesD, m_mnw );
627 m_minw = minw;
628 m_maxw = maxw;
629
630 m_mnwa = mx::math::vectorMean( m_watimesD );
631 m_varwa = mx::math::vectorVariance( m_watimesD, m_mnwa );
632
633 recordFGTimings();
634 }
635 else
636 {
637 m_mna = 0;
638 m_vara = 0;
639 m_mina = 0;
640 m_maxa = 0;
641 m_mnw = 0;
642 m_varw = 0;
643 m_minw = 0;
644 m_maxw = 0;
645 m_mnwa = 0;
646 m_varwa = 0;
647 }
648 }
649 else
650 {
651 m_mna = 0;
652 m_vara = 0;
653 m_mina = 0;
654 m_maxa = 0;
655 m_mnw = 0;
656 m_varw = 0;
657 m_minw = 0;
658 m_maxw = 0;
659 m_mnwa = 0;
660 m_varwa = 0;
661 }
662 }
663 catch( const std::exception &e )
664 {
665 std::cerr << e.what() << '\n';
666 }
667
668 return 0;
669}
670
671template <class derivedT>
673{
674 m_mna = 0;
675 m_vara = 0;
676 m_mina = 0;
677 m_maxa = 0;
678 m_mnw = 0;
679 m_varw = 0;
680 m_minw = 0;
681 m_maxw = 0;
682 m_mnwa = 0;
683 m_varwa = 0;
684
685 m_width = 0;
686 m_height = 0;
687
688 updateINDI();
689
690 m_reconfig = true;
691
692 return 0;
693}
694
695template <class derivedT>
697{
698 if( m_fgThread.joinable() )
699 {
700 try
701 {
702 m_fgThread.join(); // this will throw if it was already joined
703 }
704 catch( ... )
705 {
706 }
707 }
708
709 return 0;
710}
711
712template <class derivedT>
714{
715 m_cbFPS = derived().fps();
716
717 if( m_latencyCircBuffMaxLength == 0 || m_latencyCircBuffMaxTime == 0 || m_cbFPS <= 0 )
718 {
719 m_atimes.maxEntries( 0 );
720 m_wtimes.maxEntries( 0 );
721
722 if( m_cbFPS < 0 )
723 {
724 return -1;
725 }
726 }
727 else
728 {
729 // Set up the latency circ. buffs
730 std::cerr << "Circ Buff setup: " << m_latencyCircBuffMaxTime << ' ' << m_cbFPS << ' ';
731 cbIndexT cbSz = 2 * m_latencyCircBuffMaxTime * m_cbFPS;
732 if( cbSz > m_latencyCircBuffMaxLength )
733 {
734 cbSz = m_latencyCircBuffMaxLength;
735 }
736 if( cbSz < 3 )
737 {
738 cbSz = 3; // Make variance meaningful
739 }
740
741 std::cerr << cbSz << '\n';
742
743 m_atimes.maxEntries( cbSz );
744 m_wtimes.maxEntries( cbSz );
745 }
746
747 return 0;
748}
749
750template <class derivedT>
755
756template <class derivedT>
758{
759 // Get the thread PID immediately so the caller can return.
760 m_fgThreadID = syscall( SYS_gettid );
761
762 // timespec writestart;
763
764 // Wait fpr the thread starter to finish initializing this thread.
765 while( m_fgThreadInit == true && derived().shutdown() == 0 )
766 {
767 sleep( 1 );
768 }
769
770 uint32_t imsize[3] = { 0, 0, 0 };
771 std::string shmimName;
772
773 while( derived().shutdown() == 0 )
774 {
775 ///\todo this ought to wait until OPERATING, using READY as a sign of "not integrating"
776 while( !derived().shutdown() &&
777 ( !( derived().state() == stateCodes::READY || derived().state() == stateCodes::OPERATING ) ||
778 derived().powerState() <= 0 ) )
779 {
780 sleep( 1 );
781 }
782
783 if( derived().shutdown() )
784 {
785 break;
786 }
787
788 // At the end of this, must have m_width, m_height, m_dataType set, and derived()->fps must be valid.
789 if( derived().configureAcquisition() < 0 )
790 {
791 continue;
792 }
793
794 m_typeSize = ImageStreamIO_typesize( m_dataType );
795
796 // Here we resolve currentFlip somehow.
797 m_currentFlip = m_defaultFlip;
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_currentFlip )
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.
@ 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:26