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