Line data Source code
1 : /** \file shmimPlot.hpp
2 : * \brief The shmimPlot class declaration and definition.
3 : *
4 : * \ingroup xrif2hmim_files
5 : */
6 :
7 : #ifndef shmimPlot_hpp
8 : #define shmimPlot_hpp
9 :
10 : #include <ImageStreamIO/ImageStruct.h>
11 : #include <ImageStreamIO/ImageStreamIO.h>
12 :
13 : #include <xrif/xrif.h>
14 :
15 : #include <mx/ioutils/fileUtils.hpp>
16 : #include <mx/improc/eigenCube.hpp>
17 :
18 : #include <mx/sys/timeUtils.hpp>
19 : #include <mx/math/plot/gnuPlot.hpp>
20 :
21 : #include "../../libMagAOX/libMagAOX.hpp"
22 :
23 : /** \defgroup shmimPlot shmimPlot: plot data from a shmim in gnuplot
24 : * \brief Monitor a shmim and update a gnuplot plot
25 : *
26 : * <a href="../handbook/utils/shmimPlot.html">Utility Documentation</a>
27 : *
28 : * \ingroup utils
29 : *
30 : */
31 :
32 : /** \defgroup shmimPlot_files shmimPlot Files
33 : * \ingroup shmimPlot
34 : */
35 :
36 : bool g_timeToDie = false;
37 :
38 0 : void sigTermHandler( int signum, siginfo_t *siginf, void *ucont )
39 : {
40 : // Suppress those warnings . . .
41 : static_cast<void>( signum );
42 : static_cast<void>( siginf );
43 : static_cast<void>( ucont );
44 :
45 0 : std::cerr << "\n"; // clear out the ^C char
46 :
47 0 : g_timeToDie = true;
48 0 : }
49 :
50 : /// A utility to stream MagaO-X images from xrif compressed archives to an ImageStreamIO stream.
51 : /**
52 : * \todo finish md doc for shmimPlot
53 : *
54 : * \ingroup shmimPlot
55 : */
56 : class shmimPlot : public mx::app::application
57 : {
58 : protected:
59 : /** \name Configurable Parameters
60 : * @{
61 : */
62 :
63 : double m_fps{ 2 }; ///< The rate, in plots per second, at which to plot. Default is 2 Hz.
64 :
65 : bool m_logx{ false };
66 : bool m_logy{ false };
67 :
68 : float m_x0{ 0 };
69 : float m_x1{ 0 };
70 : bool m_xr_set{ false };
71 :
72 : float m_y0{ 0 };
73 : float m_y1{ 0 };
74 : bool m_yr_set{ false };
75 :
76 : std::vector<int> m_cols;
77 :
78 : std::vector<int> m_rows;
79 :
80 : ///@}
81 :
82 : /// Structure to manage the image threads, including startup.
83 : struct s_imageThread
84 : {
85 : shmimPlot *m_sp; ///< a pointer to a shmimPlot instance (normally this)
86 :
87 : std::thread *m_thread{ nullptr }; ///< Thread for receiving image slice updates. A pointer to allow copying,
88 : ///< but must be deleted in d'tor of parent.
89 :
90 : std::string m_shmimName; ///< the name of the image to subscribe from this thread
91 :
92 : std::vector<std::vector<float>> m_y;
93 :
94 : /// C'tor to create the thread object
95 0 : s_imageThread()
96 0 : {
97 0 : m_thread = new std::thread;
98 0 : }
99 : };
100 :
101 : std::vector<s_imageThread> m_imageThreads; ///< The image threads, one per shared memory stream.
102 :
103 : mx::math::gnuPlot m_gp;
104 : ///@}
105 :
106 : public:
107 : ~shmimPlot();
108 :
109 : virtual void setupConfig();
110 :
111 : virtual void loadConfig();
112 :
113 : virtual int execute();
114 :
115 0 : void doPlot()
116 : {
117 : // m_gp.clear();
118 :
119 0 : for( size_t i = 0; i < m_imageThreads.size(); ++i )
120 : {
121 0 : for( size_t n = 0; n < m_imageThreads[i].m_y.size(); ++n )
122 : {
123 0 : std::string tit = m_imageThreads[i].m_shmimName;
124 0 : if( m_imageThreads[i].m_y.size() > 0 )
125 : {
126 0 : if( m_rows.size() > 0 )
127 : {
128 0 : tit += " " + std::to_string( m_rows[n] );
129 : }
130 : else
131 : {
132 0 : tit += " " + std::to_string( m_cols[n] );
133 : }
134 : }
135 :
136 0 : m_gp.plot( m_imageThreads[i].m_y[n], "w l", tit, tit );
137 0 : }
138 : }
139 0 : }
140 :
141 : private:
142 : /// Thread starter, called by imageThreadStart on thread construction. Calls imageThreadExec.
143 : static void internal_imageThreadStart( s_imageThread *mit /**< [in] a pointer to an s_imageThread structure */ );
144 :
145 : public:
146 : /// Start the image thread.
147 : int imageThreadStart( size_t thno /**< [in] the thread to start */ );
148 :
149 : /// Execute the image thread.
150 : void imageThreadExec( s_imageThread *mit /**< [in] a pointer to an s_imageThread structure */ );
151 : };
152 :
153 0 : inline shmimPlot::~shmimPlot()
154 : {
155 0 : }
156 :
157 0 : inline void shmimPlot::setupConfig()
158 : {
159 0 : config.add( "shmimName",
160 : "n",
161 : "shmimName",
162 : argType::Required,
163 : "",
164 : "shmimName",
165 : false,
166 : "vector<string>",
167 : "The names of the shared memory buffer to stream to. Default is \"shmimPlot\"" );
168 :
169 0 : config.add( "circBuffLength",
170 : "L",
171 : "circBuffLength",
172 : argType::Required,
173 : "",
174 : "circBuffLength",
175 : false,
176 : "int",
177 : "The length of the shared memory circular buffer. Default is 1." );
178 :
179 0 : config.add( "fps",
180 : "F",
181 : "fps",
182 : argType::Required,
183 : "",
184 : "fps",
185 : false,
186 : "float",
187 : "The rate, in frames per second, at which to stream images. Default is 10 fps." );
188 :
189 0 : config.add( "logx", "", "logx", argType::True, "", "logx", false, "bool", "Set the x-axis to log scale" );
190 :
191 0 : config.add( "logy", "", "logy", argType::True, "", "logy", false, "bool", "Set the y-axis to log scale" );
192 :
193 0 : config.add( "x0", "", "x0", argType::Required, "", "x0", false, "float", "The x-axis minimum value" );
194 :
195 0 : config.add( "x1", "", "x1", argType::Required, "", "x1", false, "float", "The x-axis maximum value" );
196 :
197 0 : config.add( "y0", "", "y0", argType::Required, "", "y0", false, "float", "The y-axis minimum value" );
198 :
199 0 : config.add( "y1", "", "y1", argType::Required, "", "y1", false, "float", "The y-axis maximum value" );
200 :
201 0 : config.add( "rows", "r", "rows", argType::Required, "", "rows", false, "vector<int>", "The row(s) to plot." );
202 :
203 0 : config.add( "cols",
204 : "c",
205 : "cols",
206 : argType::Required,
207 : "",
208 : "cols",
209 : false,
210 : "vector<int>",
211 : "The column(s) to plot. Default is column 0." );
212 0 : }
213 :
214 0 : inline void shmimPlot::loadConfig()
215 : {
216 0 : std::vector<std::string> shmimNames;
217 0 : config( shmimNames, "shmimName" );
218 :
219 0 : if( shmimNames.size() == 0 )
220 : {
221 0 : std::cerr << "shmim names not specified with -n\n";
222 0 : doHelp = true;
223 0 : return;
224 : }
225 :
226 0 : for( auto &s : shmimNames )
227 : {
228 0 : s_imageThread nt;
229 0 : nt.m_sp = this;
230 0 : nt.m_shmimName = s;
231 0 : m_imageThreads.push_back( nt );
232 0 : }
233 :
234 0 : config( m_fps, "fps" );
235 :
236 0 : if( config.isSet( "logx" ) )
237 : {
238 0 : m_logx = true;
239 : }
240 :
241 0 : if( config.isSet( "logy" ) )
242 : {
243 0 : m_logy = true;
244 : }
245 :
246 0 : if( config.isSet( "x0" ) && config.isSet( "x1" ) )
247 : {
248 0 : m_xr_set = true;
249 0 : config( m_x0, "x0" );
250 0 : config( m_x1, "x1" );
251 : }
252 :
253 0 : if( config.isSet( "y0" ) && config.isSet( "y1" ) )
254 : {
255 0 : m_yr_set = true;
256 0 : config( m_y0, "y0" );
257 0 : config( m_y1, "y1" );
258 : }
259 :
260 0 : if( config.isSet( "rows" ) )
261 : {
262 0 : m_rows.push_back( 0 );
263 0 : config( m_rows, "rows" );
264 : }
265 : else
266 : {
267 0 : m_cols.push_back( 0 );
268 0 : config( m_cols, "cols" );
269 : }
270 :
271 0 : for( size_t n = 0; n < m_imageThreads.size(); ++n )
272 : {
273 0 : if( m_rows.size() > 0 )
274 : {
275 0 : m_imageThreads[n].m_y.resize( m_rows.size() );
276 : }
277 : else
278 : {
279 0 : m_imageThreads[n].m_y.resize( m_cols.size() );
280 : }
281 : }
282 :
283 : // Setup gnuplot
284 0 : if( m_logx )
285 : {
286 0 : m_gp.logx();
287 : }
288 :
289 0 : if( m_logy )
290 : {
291 0 : m_gp.logy();
292 : }
293 :
294 0 : if( m_xr_set )
295 : {
296 0 : m_gp.xrange( m_x0, m_x1 );
297 : }
298 :
299 0 : if( m_yr_set )
300 : {
301 0 : m_gp.yrange( m_y0, m_y1 );
302 : }
303 :
304 : // m_gp.command( "set term qt font \"Arial,14\" title \"" + m_shmimName + "\" noraise" );
305 0 : m_gp.command( "set object 1 rectangle from screen 0,0 to screen 1,1 fillcolor rgb \"#23262a\" behind" );
306 0 : m_gp.command( "set border lc rgb \"#eff0f1\"" );
307 0 : m_gp.command( "set key font \"Arial,14\" textcolor \"#eff0f1\" noenhanced" );
308 0 : }
309 :
310 0 : inline int shmimPlot::execute()
311 : {
312 :
313 : // Install signal handling
314 : struct sigaction act;
315 : sigset_t set;
316 :
317 0 : act.sa_sigaction = sigTermHandler;
318 0 : act.sa_flags = SA_SIGINFO;
319 0 : sigemptyset( &set );
320 0 : act.sa_mask = set;
321 :
322 0 : errno = 0;
323 0 : if( sigaction( SIGTERM, &act, 0 ) < 0 )
324 : {
325 0 : std::cerr << " (" << invokedName << "): error setting SIGTERM handler: " << strerror( errno ) << "\n";
326 0 : return -1;
327 : }
328 :
329 0 : errno = 0;
330 0 : if( sigaction( SIGQUIT, &act, 0 ) < 0 )
331 : {
332 0 : std::cerr << " (" << invokedName << "): error setting SIGQUIT handler: " << strerror( errno ) << "\n";
333 0 : return -1;
334 : }
335 :
336 0 : errno = 0;
337 0 : if( sigaction( SIGINT, &act, 0 ) < 0 )
338 : {
339 0 : std::cerr << " (" << invokedName << "): error setting SIGINT handler: " << strerror( errno ) << "\n";
340 0 : return -1;
341 : }
342 :
343 0 : for( size_t n = 0; n < m_imageThreads.size(); ++n )
344 : {
345 0 : std::cerr << "starting " << m_imageThreads[n].m_shmimName << '\n';
346 0 : imageThreadStart( n );
347 : }
348 :
349 0 : while( !g_timeToDie )
350 : {
351 0 : sleep( 1 );
352 0 : doPlot();
353 : }
354 :
355 0 : m_gp.command( "set term qt close" );
356 :
357 0 : return 0;
358 : }
359 :
360 0 : inline void shmimPlot::internal_imageThreadStart( s_imageThread *mit )
361 : {
362 0 : mit->m_sp->imageThreadExec( mit );
363 0 : }
364 :
365 0 : inline int shmimPlot::imageThreadStart( size_t thno )
366 : {
367 : try
368 : {
369 0 : *m_imageThreads[thno].m_thread = std::thread( internal_imageThreadStart, &m_imageThreads[thno] );
370 : }
371 0 : catch( const std::exception &e )
372 : {
373 0 : std::cerr << std::string( "exception in image thread startup: " ) + e.what() << ' ' << __FILE__ << ' '
374 0 : << __LINE__ << '\n';
375 0 : return -1;
376 0 : }
377 0 : catch( ... )
378 : {
379 0 : std::cerr << "unknown exception in image thread startup: " << __FILE__ << ' ' << __LINE__ << '\n';
380 0 : return -1;
381 0 : }
382 :
383 0 : if( !m_imageThreads[thno].m_thread->joinable() )
384 : {
385 0 : std::cerr << "image thread did not start: " << __FILE__ << ' ' << __LINE__ << '\n';
386 0 : return -1;
387 : }
388 :
389 0 : return 0;
390 : }
391 :
392 0 : inline void shmimPlot::imageThreadExec( s_imageThread *mit )
393 : {
394 0 : bool opened = false;
395 : bool restart;
396 0 : ino_t inode = 0; ///< The inode of the image stream file
397 0 : int semaphoreNumber = 9; ///< The image structure semaphore index.
398 :
399 : uint32_t width;
400 : uint32_t height;
401 : uint32_t depth;
402 : uint8_t dataType;
403 : size_t typeSize;
404 : IMAGE imageStream;
405 :
406 0 : mx::improc::eigenImage<float> im;
407 :
408 0 : float ( *pixget )( void *, size_t ){ nullptr }; ///< Pointer to a function to extract the image data as float
409 :
410 0 : while( !g_timeToDie )
411 : {
412 : /* Initialize ImageStreamIO
413 : */
414 0 : opened = false;
415 0 : restart = false; // Set this up front, since we're about to restart.
416 :
417 0 : int logged = 0;
418 0 : while( !opened && !g_timeToDie && !restart )
419 : {
420 : // b/c ImageStreamIO prints every single time, and latest version don't support stopping it yet, and that
421 : // isn't thread-safe-able anyway we do our own checks. This is the same code in ImageStreamIO_openIm...
422 : int SM_fd;
423 : char SM_fname[200];
424 0 : ImageStreamIO_filename( SM_fname, sizeof( SM_fname ), mit->m_shmimName.c_str() );
425 0 : SM_fd = open( SM_fname, O_RDWR );
426 0 : if( SM_fd == -1 )
427 : {
428 0 : if( !logged )
429 : {
430 0 : std::cerr << "ImageStream " + mit->m_shmimName + " not found (yet). Retrying . . .\n";
431 : }
432 0 : logged = 1;
433 0 : sleep( 1 ); // be patient
434 0 : continue;
435 : }
436 :
437 : // Found and opened, close it and then use ImageStreamIO
438 0 : logged = 0;
439 0 : close( SM_fd );
440 :
441 0 : if( ImageStreamIO_openIm( &imageStream, mit->m_shmimName.c_str() ) == 0 )
442 : {
443 : /// \todo this isn't right--> isn't there a define in cacao to use?
444 0 : if( imageStream.md[0].sem <= semaphoreNumber )
445 :
446 : {
447 0 : ImageStreamIO_closeIm( &imageStream );
448 0 : mx::sys::sleep( 1 ); // We just need to wait for the server process to finish startup.
449 : }
450 : else
451 : {
452 0 : opened = true;
453 : char SM_fname[200];
454 0 : ImageStreamIO_filename( SM_fname, sizeof( SM_fname ), mit->m_shmimName.c_str() );
455 :
456 : struct stat buffer;
457 0 : int rv = stat( SM_fname, &buffer );
458 :
459 0 : if( rv != 0 )
460 : {
461 0 : std::cerr << "Could not get inode for " + mit->m_shmimName +
462 0 : ". Source process will need to be restarted.\n";
463 0 : ImageStreamIO_closeIm( &imageStream );
464 0 : return;
465 : }
466 0 : inode = buffer.st_ino;
467 : }
468 : }
469 : else
470 : {
471 0 : mx::sys::sleep( 1 ); // be patient
472 : }
473 : }
474 :
475 0 : if( restart )
476 : {
477 0 : continue; // this is kinda dumb. we just go around on restart, so why test in the while loop at all?
478 : }
479 :
480 0 : if( g_timeToDie )
481 : {
482 0 : if( !opened )
483 : {
484 0 : return;
485 : }
486 :
487 0 : ImageStreamIO_closeIm( &imageStream );
488 0 : return;
489 : }
490 :
491 : semaphoreNumber =
492 0 : ImageStreamIO_getsemwaitindex( &imageStream, semaphoreNumber ); // ask for semaphore we had before
493 :
494 0 : if( semaphoreNumber < 0 )
495 : {
496 0 : std::cerr << "No valid semaphore found for " + mit->m_shmimName +
497 0 : ". Source process will need to be restarted.\n";
498 0 : return;
499 : }
500 :
501 0 : ImageStreamIO_semflush( &imageStream, semaphoreNumber );
502 :
503 0 : sem_t *sem = imageStream.semptr[semaphoreNumber]; ///< The semaphore to monitor for new image data
504 :
505 0 : dataType = imageStream.md[0].datatype;
506 0 : typeSize = ImageStreamIO_typesize( dataType );
507 0 : width = imageStream.md[0].size[0];
508 0 : int dim = 1;
509 0 : if( imageStream.md[0].naxis > 1 )
510 : {
511 0 : height = imageStream.md[0].size[1];
512 :
513 0 : if( imageStream.md[0].naxis > 2 )
514 : {
515 0 : dim = 3;
516 0 : depth = imageStream.md[0].size[2];
517 : }
518 : else
519 : {
520 0 : dim = 2;
521 0 : depth = 1;
522 : }
523 : }
524 : else
525 : {
526 0 : height = 1;
527 0 : depth = 1;
528 : }
529 :
530 : uint8_t atype;
531 : size_t snx, sny, snz;
532 : uint64_t curr_image; // The current cnt1 index
533 :
534 0 : if( imageStream.md[0].size[2] > 0 ) ///\todo change to naxis?
535 : {
536 0 : curr_image = imageStream.md[0].cnt1;
537 : }
538 : else
539 : {
540 0 : curr_image = 0;
541 : }
542 :
543 0 : atype = imageStream.md[0].datatype;
544 0 : snx = imageStream.md[0].size[0];
545 :
546 0 : if( dim == 2 )
547 : {
548 0 : sny = imageStream.md[0].size[1];
549 0 : snz = 1;
550 : }
551 0 : else if( dim == 3 )
552 : {
553 0 : sny = imageStream.md[0].size[1];
554 0 : snz = imageStream.md[0].size[2];
555 : }
556 : else
557 : {
558 0 : sny = 1;
559 0 : snz = 1;
560 : }
561 :
562 0 : if( atype != dataType || snx != width || sny != height || snz != depth )
563 : {
564 0 : continue; // exit the nearest while loop and get the new image setup.
565 : }
566 :
567 0 : std::cerr << mit->m_shmimName << ' ' << width << " x " << height << '\n';
568 :
569 0 : im.resize( width, height );
570 0 : pixget = getPixPointer<float>( dataType );
571 :
572 0 : char *raw = reinterpret_cast<char *>( imageStream.array.raw ) + curr_image * width * height * typeSize;
573 :
574 0 : for( size_t idx = 0; idx < width * height; ++idx )
575 : {
576 0 : im( idx ) = pixget( raw, idx );
577 : }
578 :
579 0 : for( size_t n = 0; n < mit->m_y.size(); ++n )
580 : {
581 0 : if( m_rows.size() > 0 )
582 : {
583 0 : mit->m_y[n].resize( height );
584 :
585 0 : for( uint32_t c = 0; c < height; ++c )
586 : {
587 0 : mit->m_y[n][c] = im( m_rows[n], c );
588 : }
589 : }
590 : else
591 : {
592 0 : mit->m_y[n].resize( width );
593 :
594 0 : for( uint32_t r = 0; r < width; ++r )
595 : {
596 0 : mit->m_y[n][r] = im( r, m_cols[n] );
597 : }
598 : }
599 : }
600 :
601 : // This is the main image grabbing loop.
602 0 : while( !g_timeToDie && !restart )
603 : {
604 : timespec ts;
605 :
606 0 : if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
607 : {
608 0 : std::cerr << "error from clock_gettime\n";
609 0 : return;
610 : }
611 :
612 0 : ts.tv_sec += 1;
613 :
614 0 : if( sem_timedwait( sem, &ts ) == 0 )
615 : {
616 0 : if( imageStream.md[0].size[2] > 0 ) ///\todo change to naxis?
617 : {
618 0 : curr_image = imageStream.md[0].cnt1;
619 : }
620 : else
621 : {
622 0 : curr_image = 0;
623 : }
624 :
625 0 : atype = imageStream.md[0].datatype;
626 0 : snx = imageStream.md[0].size[0];
627 :
628 0 : if( dim == 2 )
629 : {
630 0 : sny = imageStream.md[0].size[1];
631 0 : snz = 1;
632 : }
633 0 : else if( dim == 3 )
634 : {
635 0 : sny = imageStream.md[0].size[1];
636 0 : snz = imageStream.md[0].size[2];
637 : }
638 : else
639 : {
640 0 : sny = 1;
641 0 : snz = 1;
642 : }
643 :
644 0 : if( atype != dataType || snx != width || sny != height || snz != depth )
645 : {
646 : break; // exit the nearest while loop and get the new image setup.
647 : }
648 :
649 0 : if( g_timeToDie || restart )
650 : {
651 : break; // Check for exit signals
652 : }
653 :
654 0 : char *raw = reinterpret_cast<char *>( imageStream.array.raw ) + curr_image * width * height * typeSize;
655 0 : for( size_t idx = 0; idx < width * height; ++idx )
656 : {
657 0 : im( idx ) = pixget( raw, idx );
658 : }
659 :
660 0 : for( size_t n = 0; n < mit->m_y.size(); ++n )
661 : {
662 0 : if( m_rows.size() > 0 )
663 : {
664 0 : for( uint32_t c = 0; c < height; ++c )
665 : {
666 0 : mit->m_y[n][c] = im( m_rows[n], c );
667 : }
668 : }
669 : else
670 : {
671 0 : for( uint32_t r = 0; r < width; ++r )
672 : {
673 0 : mit->m_y[n][r] = im( r, m_cols[n] );
674 : }
675 : }
676 : }
677 : }
678 : else
679 : {
680 0 : if( imageStream.md[0].sem <= 0 )
681 0 : break; // Indicates that the server has cleaned up.
682 :
683 : // Check for why we timed out
684 0 : if( errno == EINTR )
685 0 : break; // This indicates signal interrupted us, time to restart or shutdown, loop will exit normally
686 : // if flags set.
687 :
688 : // ETIMEDOUT means we should check for deletion, and then wait more.
689 : // Otherwise, report an error.
690 0 : if( errno != ETIMEDOUT )
691 : {
692 0 : std::cerr << "error from sem_timedwait\n";
693 0 : break;
694 : }
695 :
696 : // Check if the file has disappeared.
697 : int SM_fd;
698 : char SM_fname[200];
699 0 : ImageStreamIO_filename( SM_fname, sizeof( SM_fname ), mit->m_shmimName.c_str() );
700 0 : SM_fd = open( SM_fname, O_RDWR );
701 0 : if( SM_fd == -1 )
702 : {
703 0 : restart = true;
704 : }
705 0 : close( SM_fd );
706 :
707 : // Check if the inode changed
708 : struct stat buffer;
709 0 : int rv = stat( SM_fname, &buffer );
710 0 : if( rv != 0 )
711 : {
712 0 : restart = true;
713 : }
714 :
715 0 : if( buffer.st_ino != inode )
716 : {
717 0 : restart = true;
718 : }
719 : }
720 : }
721 :
722 : // opened == true if we can get to this
723 0 : if( semaphoreNumber >= 0 )
724 0 : imageStream.semReadPID[semaphoreNumber] = 0; // release semaphore
725 0 : ImageStreamIO_closeIm( &imageStream );
726 0 : opened = false;
727 :
728 : } // outer loop, will exit if m_shutdown==true
729 :
730 0 : if( opened )
731 : {
732 0 : ImageStreamIO_closeIm( &imageStream );
733 : }
734 0 : }
735 :
736 : #endif // shmimPlot_hpp
|