API
 
Loading...
Searching...
No Matches
dmRecon.hpp
Go to the documentation of this file.
1/** \file dmRecon.hpp
2 * \brief The MagAO-X DM shape reconstructor
3 *
4 * \ingroup dmRecon_files
5 */
6
7#ifndef dmRecon_hpp
8#define dmRecon_hpp
9
10#include <atomic>
11#include <limits>
12
13#include <mx/improc/eigenCube.hpp>
14#include <mx/improc/eigenImage.hpp>
15#include <mx/sigproc/gramSchmidt.hpp>
16#include <mx/math/templateBLAS.hpp>
17
18#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
19#include "../../magaox_git_version.h"
20
21#include <mx/math/cuda/cudaPtr.hpp>
22#include <mx/math/cuda/cublasHandle.hpp>
23#include <mx/math/cuda/templateCublas.hpp>
24#include <mx/math/eigenLapack.hpp>
25#include <mx/sigproc/basisUtils2D.hpp>
26
27namespace MagAOX
28{
29namespace app
30{
31
32/** \defgroup dmRecon DM Shape Reconstructor
33 * \brief Reconstruct the wavefront corresponding to a DM shape
34 *
35 * <a href="../handbook/operating/software/apps/dmRecon.html">Application Documentation</a>
36 *
37 * \ingroup apps
38 *
39 */
40
41/** \defgroup dmRecon_files DM Shape Reconstructor Files
42 * \ingroup dmRecon
43 */
44
46{
47 static std::string configSection()
48 {
49 return "dmModes";
50 };
51
52 static std::string indiPrefix()
53 {
54 return "dmModes";
55 };
56};
57
59{
60 static std::string configSection()
61 {
62 return "dmMask";
63 };
64
65 static std::string indiPrefix()
66 {
67 return "dmMask";
68 };
69};
70
72{
73 static std::string configSection()
74 {
75 return "dmCommand";
76 };
77
78 static std::string indiPrefix()
79 {
80 return "dmCommand";
81 };
82};
83
84/** MagAO-X application to perform wavefront reconstruction from a DM surface
85 *
86 * \ingroup dmRecon
87 *
88 */
89class dmRecon : public MagAOXApp<true>,
90 public dev::shmimMonitor<dmRecon, dmModesShmimT>,
91 public dev::shmimMonitor<dmRecon, dmMaskShmimT>,
92 public dev::shmimMonitor<dmRecon, dmCommandShmimT>,
93 public dev::frameGrabber<dmRecon>,
94 public dev::telemeter<dmRecon>
95{
96 // Give the test harness access.
97 friend class dmRecon_test;
98
101
104
107
108 friend class dev::frameGrabber<dmRecon>;
110
111 static constexpr bool c_frameGrabber_flippable = false;
112
113 friend class dev::telemeter<dmRecon>;
115
116 /// Floating point type in which to do all calculations.
117 typedef float realT;
118
119 protected:
120 /** \name Configurable Parameters
121 *@{
122 */
123
124 int m_loopNumber{ 1 }; ///< The loop number. Default is 1 as in aol1.
125
126 std::string m_respMPath; ///< Optional response matrix. If set then CM modes are converted by this response.
127
128 std::string m_fpsSource{ "camwfs" }; /**< Device name for getting fps of the loop.
129 This device must have *.fps.current. Default is camwfs*/
130
131 int m_numModes{ 0 }; ///< Number of modes to reconstruct. If 0 (default) all modes in CM are used.
132
134 0 }; /**< Number of modes to use for pseudo-inverse truncation. If 0 (default) all modes are used.*/
135
136 uint16_t m_gpuIndex{ 0 }; /**< Index of the GPU to use for calculations */
137
138 bool m_useGPU{ false }; /**< Flag controlling whether the GPU is used for calculations */
139
140 ///@}
141
142 mx::improc::eigenImage<float> m_respM;
143
145
147
149
150 float m_fps{ 0 }; ///< Current FPS from the FPS source.
151
152 mx::improc::eigenImage<realT> m_PInv; ///< The pseudo-inverse
153
154 // clang-format off
155 #ifdef MXLIB_CUDA
156
157 mx::cuda::cudaPtr<realT> m_PInv_GPU; ///< The pseudo-inverse on the GPU
158
159 #endif
160 // clang-format on
161
162 std::atomic<bool> m_dmModesReady{ false }; ///< Flag indicating that the DM modes are ready for processing
163
164 mx::improc::eigenImage<float> m_mask;
165
166 std::vector<size_t> m_maskIDX; ///< The index of masked pixels
167
168 std::atomic<bool> m_dmMaskReady{ false }; ///< Flag indicating that the DM mask is ready for processing
169
170 std::atomic<bool> m_commandReady{ false }; ///< Flag indicating that all sizes match and arrays are ready for processing
171
172 std::atomic<bool> m_fgWaiting{ false }; ///< Flag indicating that the FG thread is waiting for the command thread
173
174 mx::improc::eigenImage<float> m_command; ///< The DM command, copied out of the incoming shmim
175
176 mx::improc::eigenImage<float> m_modevals; ///< The calculated mode amplitudes
177
178 std::atomic<bool> m_writeDMf{ false };
179
180 std::string m_monShmimName;
181 mx::improc::milkImage<float> m_modevalMon; ///< The actual calculated modevals.
182
183 mx::improc::milkImage<float> m_modeval;
184 mx::improc::milkImage<float> m_modevalDiff;
185
186 // clang-format off
187 #ifdef MXLIB_CUDA
188
189 mx::cuda::cudaPtr<float> m_command_GPU;
190
191 mx::cuda::cudaPtr<float> m_modevals_GPU;
192
193 #endif
194 // clang-format on
195
196 sem_t m_smSemaphore{ 0 }; ///< Semaphore used to synchronize the fg thread and the dm command thread.
197
198 std::atomic<bool> m_updated{ false }; ///< Flag indicating that the mode vals have been updated
199
200 std::mutex m_modevalsMutex; ///< Guards the modevals buffer during producer/consumer handoff.
201
202 mx::cuda::cublasHandle m_cublas; ///< Handle for the cuBLAS library
203
204 public:
205 /// Default c'tor.
206 dmRecon();
207
208 /// D'tor, declared and defined for noexcept.
210 {
211 }
212
213 virtual void setupConfig();
214
215 /// Implementation of loadConfig logic, separated for testing.
216 /** This is called by loadConfig().
217 */
218 int loadConfigImpl( mx::app::appConfigurator &_config /**< [in] an application configuration
219 from which to load values*/
220 );
221
222 virtual void loadConfig();
223
224 /// Startup function
225 /**
226 *
227 */
228 virtual int appStartup();
229
230 /// Implementation of the FSM for dmRecon.
231 /**
232 * \returns 0 on no critical error
233 * \returns -1 on an error requiring shutdown
234 */
235 virtual int appLogic();
236
237 /// Shutdown the app.
238 /**
239 *
240 */
241 virtual int appShutdown();
242
243 /// Set the GPU index
244 /** Uses m_gpuIndex. On errors it sets m_useGPU to false.
245 *
246 */
247 int setGPU();
248
249 /// Allocate method for the dm modes shmimMonitor
250 /**
251 * \returns 0 on success
252 * \returns -1 on an error
253 */
254 int allocate( const dmModesShmimT & /**< [in] tag to differentiate shmimMonitor parents.*/ );
255
256 /// Process images for the dm modes shmimMonitor
257 /**
258 * \returns 0 on sucess
259 * \returns -1 on an error
260 */
261 int processImage( void *curr_src, ///< [in] pointer to start of current frame.
262 const dmModesShmimT & ///< [in] tag to differentiate shmimMonitor parents.
263 );
264
265 /// Allocate method for the dm mask shmimMonitor
266 /**
267 * \returns 0 on success
268 * \returns -1 on an error
269 */
270 int allocate( const dmMaskShmimT & /**< [in] tag to differentiate shmimMonitor parents.*/ );
271
272 /// Process images for the dm mask shmimMonitor
273 /**
274 * \returns 0 on sucess
275 * \returns -1 on an error
276 */
277 int processImage( void *curr_src, ///< [in] pointer to start of current frame.
278 const dmMaskShmimT & ///< [in] tag to differentiate shmimMonitor parents.
279 );
280
281 /// Allocate method for the dm command shmimMonitor
282 /**
283 * \returns 0 on success
284 * \returns -1 on an error
285 */
286 int allocate( const dmCommandShmimT & /**< [in] tag to differentiate shmimMonitor parents.*/ );
287
288 /// Process images for the dm command shmimMonitor
289 /**
290 * \returns 0 on sucess
291 * \returns -1 on an error
292 */
293 int processImage( void *curr_src, ///< [in] pointer to start of current frame.
294 const dmCommandShmimT & ///< [in] tag to differentiate shmimMonitor parents.
295 );
296
297 /** \name Framegrabber Interface */
298 /**
299 * @{
300 */
301
303
304 float fps();
305
306 int startAcquisition();
307
309
310 int loadImageIntoStream( void *dest );
311
312 int reconfig();
313
314 ///@}
315 protected:
316 /** \name INDI Interface
317 *
318 * @{
319 */
320 pcf::IndiProperty m_indiP_fpsSource;
322
323 pcf::IndiProperty m_indiP_fps;
324
325 pcf::IndiProperty m_indiP_writeDMf;
327
328 ///@}
329
330 /** \name Telemeter Interface
331 *
332 * @{
333 */
334
335 int checkRecordTimes();
336
337 int recordTelem( const telem_fgtimings * );
338
339 ///@}
340};
341
342inline dmRecon::dmRecon() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
343{
345
346 return;
347}
348
350{
351 config.add( "recon.loopNumber",
352 "",
353 "recon.loopNumber",
354 argType::Required,
355 "recon",
356 "loopNumber",
357 false,
358 "int",
359 "The loop number. Default is 1 as in aol1." );
360
361 config.add( "recon.respMPath",
362 "",
363 "recon.respMPath",
364 argType::Required,
365 "recon",
366 "respMPath",
367 false,
368 "int",
369 "Optional response matrix. If set then CM modes are converted by this response." );
370
371 config.add( "recon.numModes",
372 "",
373 "recon.numModes",
374 argType::Required,
375 "recon",
376 "numModes",
377 false,
378 "int",
379 "Number of modes to reconstruct. If 0 (default) all modes in CM are used." );
380
381 config.add( "recon.inverseNumModes",
382 "",
383 "recon.inverseNumModes",
384 argType::Required,
385 "recon",
386 "inverseNumModes",
387 false,
388 "int",
389 "Number of modes to use for pseudo-inverse truncation. If 0 (default) all modes are used." );
390
391 config.add( "recon.fpsSource",
392 "",
393 "recon.fpsSource",
394 argType::Required,
395 "recon",
396 "fpsSource",
397 false,
398 "string",
399 "Device name for getting fps of the loop. This device should have *.fps.current. "
400 "Default is camwfs" );
401
402 config.add( "recon.gpuIndex",
403 "",
404 "recon.gpuIndex",
405 argType::Required,
406 "recon",
407 "gpuIndex",
408 false,
409 "int",
410 "Index of the GPU to use for calculations. Default is 0." );
411
412 config.add( "recon.useGPU",
413 "",
414 "recon.useGPU",
415 argType::Required,
416 "recon",
417 "useGPU",
418 false,
419 "bool",
420 "Flag controlling whether the GPU is used for calculations. Default is false." );
421
423
425
427
429
430 TELEMETER_SETUP_CONFIG( config );
431}
432
433inline int dmRecon::loadConfigImpl( mx::app::appConfigurator &_config )
434{
435 _config( m_loopNumber, "recon.loopNumber" );
436 _config( m_respMPath, "recon.respMPath" );
437 _config( m_numModes, "recon.numModes" );
438 _config( m_inverseNumModes, "recon.inverseNumModes" );
439 _config( m_fpsSource, "recon.fpsSource" );
440 _config( m_gpuIndex, "recon.gpuIndex" );
441 _config( m_useGPU, "recon.useGPU" );
442
443 std::string loopName = std::format( "aol{}", m_loopNumber );
444
445 dmModesSMT::m_shmimName = loopName + "_CMmodesDM";
448
449 dmCommandSMT::m_shmimName = std::format( "dm{:02}disp_delta", m_loopNumber );
452
453 dmMaskSMT::m_shmimName = std::format( "dm{:02}disp_actmask", m_loopNumber );
456
457 frameGrabberT::m_shmimName = loopName + "_modevalDMf";
459
461
463
464 return 0;
465}
466
468{
469 loadConfigImpl( config );
470}
471
473{
474 REG_INDI_SETPROP( m_indiP_fpsSource, m_fpsSource, std::string( "fps" ) );
475
477 m_indiP_fps.add( pcf::IndiElement( "current" ) );
479 {
480 log<software_error>( { "" } );
481 return -1;
482 }
483
485
486 if( sem_init( &m_smSemaphore, 0, 0 ) < 0 )
487 {
488 log<software_critical>( { errno, "Initializing S.M. semaphore" } );
489 return -1;
490 }
491
492 if( m_respMPath != "" )
493 {
494 mx::fits::fitsFile<float> ff;
495 ff.read( m_respM, m_respMPath );
496
497 m_width = sqrt( m_respM.rows() );
499 }
500
506
508
510
512
513 return 0;
514}
515
517{
521
523
525
526 std::unique_lock<std::mutex> lock( m_indiMutex );
527
528 if( m_writeDMf.load() )
529 {
530 updateSwitchIfChanged( m_indiP_writeDMf, "toggle", pcf::IndiElement::On );
531 }
532 else
533 {
534 updateSwitchIfChanged( m_indiP_writeDMf, "toggle", pcf::IndiElement::Off );
535 }
536
540
542
543 return 0;
544}
545
558
560{
561 // clang-format off
562 #ifdef MXLIB_CUDA // clang-format on
563
564 if( !m_useGPU )
565 {
566 return 0;
567 }
568
569 int deviceCount;
570 int devicecntMax = 100;
571
573
574 if( ce != cudaSuccess )
575 {
576
577 log<software_error>( { std::format( "cudaGetDeviceCount returned error: "
578 "[{}] {}\nNOT USING GPU",
580 cudaGetErrorString( ce ) ) } );
581
582 m_useGPU = false;
583 state( state(), true );
584 return -1;
585 }
586
587 std::string msg = std::format( "CUDA: found {} devices\n", deviceCount );
588
590 {
591 deviceCount = 0;
592 msg += " greater than devicecntMax\n";
593 }
594 if( deviceCount < 0 )
595 {
596 msg += " less than zero\n";
597 }
598
599 if( deviceCount == 0 )
600 {
601 msg += " no devices found!\nNOT USING GPU";
603 m_useGPU = false;
604 state( state(), true );
605 return -1;
606 }
607
608 for( int k = 0; k < deviceCount; k++ )
609 {
612
613 if( ce != cudaSuccess )
614 {
615 msg += std::format( "cudaGetDeviceProperties returned error: "
616 "[{}] {}\nNOT USING GPU",
620 m_useGPU = false;
621 state( state(), true );
622 return -1;
623 }
624
625 int clockRate;
627
628 if( ce != cudaSuccess )
629 {
630 msg += std::format( "cudaGetDeviceAttribute returned error: "
631 "[{}] {}\nNOT USING GPU",
635 m_useGPU = false;
636 state( state(), true );
637 return -1;
638 }
639
640 msg += std::format( " Device {} / {} [ {} ] has compute capability {}.{}.\n",
641 k + 1,
643 deviceProp.name,
644 deviceProp.major,
645 deviceProp.minor );
646
647 msg += std::format( " Total amount of global memory: {} MBytes\n",
648 (float)deviceProp.totalGlobalMem / 1048576.0f );
649
650 msg += std::format( " Multiprocessors: {}\n", deviceProp.multiProcessorCount );
651 msg += std::format( " Clock rate: {} MHz ({} GHz)\n", clockRate * 1e-3f, clockRate * 1e-6f );
652 }
653
654 if( m_gpuIndex >= deviceCount )
655 {
656 msg += std::format( "gpuIndex = {} is not valid for {} devices\nNOT USING GPU", m_gpuIndex, deviceCount );
658 m_useGPU = false;
659 state( state(), true );
660 return -1;
661 }
662
664
665 if( ce != cudaSuccess )
666 {
667 msg += std::format( "cudaSetDevice returned error: "
668 "[{}] {}\nNOT USING GPU",
672 m_useGPU = false;
673 state( state(), true );
674 return -1;
675 }
676
677 msg += std::format( "Set GPU Index to device {} ( {} / {})\n", m_gpuIndex, m_gpuIndex + 1, deviceCount );
678
679 cublasStatus_t cbs = m_cublas.create();
680
682 {
683 msg += std::format( "cublasHandle create returned error: "
684 "[{}] {}\nNOT USING GPU",
688 m_useGPU = false;
689 state( state(), true );
690 return -1;
691 }
692
693 msg += " cuBLAS initialized";
694
696
697 return 0;
698
699 // clang-format off
700 #else // MXLIB_CUDA
701 // clang-format on
702
703 if( m_useGPU )
704 {
705 log<software_error>( { "mxlib was compiled without CUDA support. NOT USING GPU" } );
706
707 m_useGPU = false;
708 state( state(), true );
709 return -1;
710 }
711
712 return 0;
713
714 // clang-format off
715 #endif // MXLIB_CUDA
716 // clang-format on
717}
718
720{
721 m_dmModesReady = false;
722
723 std::cerr << "modes not ready\n";
724
726
727 if( m_respM.rows() == 0 )
728 {
731 }
732
733 if( m_numModes == 0 )
734 {
736 }
737 else
738 {
741 {
743 }
744 }
745
746 // Can't process modes until mask is ready.
748 {
749 mx::sys::milliSleep( 1000 );
751 return 0; // This won't log an error, but setting m_restart will cause it to loop again until sizes match
752 }
753
754 // This will let us go on to processImage
755
756 // Do a type check for float
757 return 0;
758}
759
760int dmRecon::processImage( void *curr_src, const dmModesShmimT & )
761{
762 if( m_dmModesReady.load() == true )
763 {
764 // This means new image has come in. We need to reset and restart everything.
768 return 0;
769 }
770
771 mx::improc::eigenCube<float> dmModes( dmModesSMT::m_width, dmModesSMT::m_height, m_depth );
772
773 for( size_t n = 0; n < dmModesSMT::m_width * dmModesSMT::m_height * m_depth; ++n )
774 {
775 dmModes.data()[n] = reinterpret_cast<float *>( curr_src )[n];
776 }
777
778 // Wait for m_commandReady to become false
779 while( m_commandReady.load() == true && !m_shutdown && dmModesSMT::m_restart == false )
780 {
781 mx::sys::milliSleep( 1000 );
782 }
783
784 if( m_respM.rows() > 0 )
785 {
786 mx::improc::eigenCube<realT> tmpc;
787
788 int nr = sqrt( m_respM.rows() );
789
790 tmpc.resize( nr, nr, dmModes.planes() );
791
792 std::cerr << __LINE__ << '\n';
793
794 for( int p = 0; p < tmpc.planes(); ++p )
795 {
796 // cast to matrices for math
797 Eigen::Map<Eigen::Matrix<float,-1,-1>> outim(tmpc.image(p).data(), nr*nr,1);
798 Eigen::Map<Eigen::Matrix<float,-1,-1>> inim(dmModes.image(p).data(), dmModes.rows()*dmModes.cols(),1);
799
800 outim = (m_respM.matrix() * inim);
801
802 float norm = sqrt(tmpc.image(p).square().sum()/m_maskIDX.size());
803 float scale = sqrt(dmModes.image(p).square().sum()/ (dmModes.rows()*dmModes.cols()));
804
805 tmpc.image(p) *= scale/norm;
806 }
807
808 dmModes = tmpc;
809
810 mx::fits::fitsFile<float> ff;
811 ff.write( "wmodes.fits", dmModes );
812 }
813
814 mx::improc::eigenImage<float> maskedDMModes;
815
816 maskedDMModes.resize( m_maskIDX.size(), dmModes.planes() );
817
818 // Load only the unmasked pixels
819 for( int rr = 0; rr < maskedDMModes.cols(); ++rr )
820 {
821 for( size_t n = 0; n < m_maskIDX.size(); ++n )
822 {
823 maskedDMModes( n, rr ) = dmModes.image( rr ).data()[m_maskIDX[n]];
824 }
825 }
826
828
829 int nRejected;
830
831 realT maxCondition = -1 * m_inverseNumModes; // Specify number of modes to keep. If 0 it's all.
832
833 int rv = mx::math::eigenPseudoInverse( m_PInv, condition, nRejected, maskedDMModes, maxCondition );
834
835 if( rv < 0 )
836 {
837 log<software_error>( { 0, rv, "error in eigenPseudoInverse " } );
838 m_shutdown = 1;
839 return -1;
840 }
841
842 std::cerr << "PInv: " << m_PInv.rows() << ' ' << m_PInv.cols() << '\n';
843
844 mx::fits::fitsFile<float> ff;
845 ff.write( "PInv.fits", m_PInv );
846
847 log<text_log>( std::format( "Inverted CMmodesDM. Rejected {} "
848 "of {} modes, condition numer = {}",
849 nRejected,
850 dmModes.planes(),
851 condition ) );
852
853 m_dmModesReady = true;
854
855 std::cerr << "modes ready\n";
856 return 0;
857}
858
860{
861 m_dmMaskReady = false;
862
863 std::cerr << "mask not ready\n";
864
866
867 // Do a type check for float
868 return 0;
869}
870
871int dmRecon::processImage( void *curr_src, const dmMaskShmimT & )
872{
873 if( m_dmMaskReady.load() == true )
874 {
875 // This means an new image has come in. We need to reset and restart everything.
878
880
881 return 0;
882 }
883
884 // Wait for m_commandReady to become false
885 while( m_commandReady.load() == true && !m_shutdown && dmMaskSMT::m_restart == false )
886 {
887 mx::sys::milliSleep( 1000 );
888 }
889
890 m_mask =
891 mx::improc::eigenMap<float>( reinterpret_cast<float *>( curr_src ), dmMaskSMT::m_width, dmMaskSMT::m_height );
892
893 m_maskIDX.clear();
894
895 size_t n = 0;
896
897 int nmax = 0;
898 for( int rr = 0; rr < m_mask.rows(); ++rr )
899 {
900 for( int cc = 0; cc < m_mask.cols(); ++cc )
901 {
902 if( m_mask( rr, cc ) == 1 )
903 {
904 m_maskIDX.push_back( n );
905 nmax = n;
906 }
907
908 ++n;
909 }
910 }
911
912 std::cerr << n << ' ' << nmax << '\n';
913
914 std::cerr << "Got mask of size " << m_mask.rows() << " x " << m_mask.cols() << " with " << m_maskIDX.size()
915 << " good pixels.\n";
916
917 m_dmMaskReady = true;
918 std::cerr << "mask ready\n";
919 return 0;
920}
921
923{
924 // This is the only place that m_commandReady can be changed
925 m_commandReady = false;
926 std::cerr << "command not ready\n";
927
930 {
933
934 mx::sys::milliSleep( 1000 );
935
936 return 0; // This won't log an error, but setting m_restart will cause it to reconnect again until sizes match
937 }
938
939 if( !m_fgWaiting.load() )
940 {
942 mx::sys::milliSleep( 1000 );
943
945 return 0; // This won't log an error, but setting m_restart will cause it to reconnect again until sizes match
946 }
947
948 m_command.resize( m_maskIDX.size(), 1 );
949
950 m_modevals.resize( m_PInv.rows(), 1 );
951
952 m_modevalMon.create( m_monShmimName, m_PInv.rows(), 1 );
953
954 m_modeval.open(std::format("aol{}_modevalDM", m_loopNumber));
955
956 m_modevalDiff.create( std::format("aol{}_modevalDMf_diff", m_loopNumber), m_PInv.rows(), 1 );
957
958 // clang-format off
959 #ifdef MXLIB_CUDA
960 // clang-format on
961
962 if( m_useGPU )
963 {
964 // Do all initializations and uploads here so it's in the right thread on the right device
965 if( setGPU() < 0 )
966 {
967 log<software_error>( { "setting GPU device failed." } );
968 m_useGPU = false;
969 state( state(), true );
970 return -1;
971 }
972
973 mx::error_t ec = m_PInv_GPU.upload( m_PInv.data(), m_PInv.rows(), m_PInv.cols() );
974
975 if( ec != mx::error_t::noerror )
976 {
977 return log<software_error, -1>( { std::format(
978 "error uploading PInv to GPU: [{}] {}", mx::errorName( ec ), mx::errorMessage( ec ) ) } );
979 }
980
981 ec = m_command_GPU.resize( m_command.rows() * m_command.cols() );
982 if( ec != mx::error_t::noerror )
983 {
984 return log<software_error, -1>( { std::format(
985 "error allocating command on GPU: [{}] {}", mx::errorName( ec ), mx::errorMessage( ec ) ) } );
986 }
987
988 ec = m_modevals_GPU.resize( m_modevals.rows() * m_modevals.cols() );
989 if( ec != mx::error_t::noerror )
990 {
991 return log<software_error, -1>( { std::format(
992 "error allocating modevals on GPU: [{}] {}", mx::errorName( ec ), mx::errorMessage( ec ) ) } );
993 }
994 }
995
996 // clang-format off
997 #endif // MXLIB_CUDA
998 // clang-format on
999
1000 m_updated = false;
1001 m_commandReady = true;
1002
1003 std::cerr << "command ready\n";
1004
1005 return 0;
1006}
1007
1008int dmRecon::processImage( void *curr_src, const dmCommandShmimT & )
1009{
1010 if( !m_commandReady.load() )
1011 {
1013 return 0;
1014 }
1015
1016 // Set atime to now
1018
1019 // extract masked pixels
1020 for( size_t n = 0; n < m_maskIDX.size(); ++n )
1021 {
1022 m_command( n, 0 ) = reinterpret_cast<float *>( curr_src )[m_maskIDX[n]];
1023 }
1024
1025 // clang-format off
1026 #ifdef MXLIB_CUDA // clang-format on
1027 if( !m_useGPU )
1028 {
1029 // CPU:
1030 std::lock_guard<std::mutex> guard( m_modevalsMutex );
1031 m_modevals = ( m_PInv.matrix() * m_command.matrix() ).array();
1032 }
1033 else
1034 {
1035 // GPU:
1036 mx::error_t ec = m_command_GPU.upload( m_command.data() );
1037 if( ec != mx::error_t::noerror )
1038 {
1039 return log<software_error, -1>( { std::format(
1040 "error uploading command to GPU: [{}] {}", mx::errorName( ec ), mx::errorMessage( ec ) ) } );
1041 }
1042
1043 float alpha = 1;
1044 float beta = 0;
1045
1046 cublasStatus_t cbs = mx::cuda::cublasTgemv( m_cublas,
1048 m_PInv_GPU.rows(),
1049 m_PInv_GPU.cols(),
1050 &alpha,
1051 m_PInv_GPU.data(),
1052 m_PInv_GPU.rows(),
1053 m_command_GPU.data(),
1054 1,
1055 &beta,
1056 m_modevals_GPU.data(),
1057 1 );
1058
1059 if( cbs != CUBLAS_STATUS_SUCCESS )
1060 {
1061 return log<software_error, -1>( { std::format( "error downloading modevals from GPU: [{}] {}",
1063 cublasGetStatusString( cbs ) ) } );
1064 }
1065
1066 std::lock_guard<std::mutex> guard( m_modevalsMutex );
1067 ec = m_modevals_GPU.download( m_modevals.data() );
1068 if( ec != mx::error_t::noerror )
1069 {
1070 return log<software_error, -1>( { std::format(
1071 "error downloading modevals from GPU: [{}] {}", mx::errorName( ec ), mx::errorMessage( ec ) ) } );
1072 }
1073 }
1074
1075 // clang-format off
1076 #else // MXLIB_CUDA
1077
1078 // CPU:
1079 m_modevals = (m_PInv.matrix() * m_command.matrix()).array()
1080
1081 #endif // MXLIB_CUDA
1082 // clang-format on
1083
1084 m_updated = true;
1085
1086 if( m_writeDMf.load() )
1087 {
1088 // trigger framegrabber
1089 if( sem_post( &m_smSemaphore ) < 0 )
1090 {
1091 log<software_critical>( { errno, 0, "Error posting to semaphore" } );
1092 return -1;
1093 }
1094 }
1095
1096 // write to the monitor stream
1097 m_modevalMon.setWrite(1);
1098 m_modevalDiff.setWrite();
1099 for(uint32_t r = 0; r < m_modevalMon.rows(); ++r)
1100 {
1101 m_modevalMon(r,0) = m_modevals(r,0);
1102 m_modevalDiff(r,0) = m_modevals(r,0) - m_modeval(r,0);
1103 }
1104
1105 m_modevalMon.post();
1106 m_modevalDiff.post();
1107
1108 return 0;
1109}
1110
1112{
1113 if( !m_commandReady.load() )
1114 {
1115 m_fgWaiting = true;
1116 mx::sys::milliSleep( 100 );
1117 return -1;
1118 }
1119
1120 m_fgWaiting = false;
1121
1125
1126 static int logged = 0;
1127
1128 if( frameGrabberT::m_imageStream != nullptr )
1129 {
1133 }
1134
1135 // b/c ImageStreamIO prints every single time, and latest version don't support stopping it yet, and that
1136 // isn't thread-safe-able anyway we do our own checks. This is the same code in ImageStreamIO_openIm...
1137 int SM_fd;
1138 char SM_fname[200];
1140 SM_fd = open( SM_fname, O_RDWR );
1141
1142 if( SM_fd == -1 )
1143 {
1144 if( !logged )
1145 {
1146 log<text_log>( "ImageStream " + frameGrabberT::m_shmimName + " not found (yet). Retrying . . .",
1148 logged = 1;
1149 }
1150
1151 return 1;
1152 }
1153
1154 // Found and opened, close it and then use ImageStreamIO
1155 logged = 0;
1156 close( SM_fd );
1157
1158 frameGrabberT::m_imageStream = reinterpret_cast<IMAGE *>( malloc( sizeof( IMAGE ) ) );
1159
1161 {
1163 {
1167
1168 return 1; // We just need to wait for the server process to finish startup.
1169 }
1170 else
1171 {
1172 char SM_fname[200];
1174
1175 struct stat buffer;
1176 int rv = stat( SM_fname, &buffer );
1177
1178 if( rv != 0 )
1179 {
1181 "Could not get inode for " + frameGrabberT::m_shmimName +
1182 ". Source process will need to be restarted." } );
1183
1185
1187
1189
1190 m_shutdown = true;
1191
1192 return -1;
1193 }
1194
1195 frameGrabberT::m_inode = buffer.st_ino;
1196 }
1197 }
1198 else
1199 {
1202
1203 return 1; // be patient
1204 }
1205
1206 return 0;
1207}
1208
1210{
1211 return m_fps;
1212}
1213
1215{
1216
1217 std::cerr << "startAcquisition\n";
1218 return 0;
1219}
1220
1222{
1223 timespec ts;
1224
1225 errno = 0;
1226 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
1227 {
1228 log<software_critical>( { errno, "clock_gettime" } );
1229 return -1;
1230 }
1231
1232 ts.tv_sec += 1;
1233
1234 if( !m_commandReady.load() )
1235 {
1236 return 1;
1237 }
1238
1239 if( sem_timedwait( &m_smSemaphore, &ts ) == 0 )
1240 {
1241 if( m_updated.load() && m_commandReady.load() )
1242 {
1243 return 0;
1244 }
1245 else
1246 {
1247 return 1;
1248 }
1249 }
1250 else
1251 {
1252 return 1;
1253 }
1254
1255 return 0;
1256}
1257
1259{
1260 std::lock_guard<std::mutex> guard( m_modevalsMutex );
1261 memcpy( dest, m_modevals.data(), m_modevals.rows() * m_modevals.cols() * sizeof( float ) );
1262
1263 return 0;
1264}
1265
1267{
1268 return 0;
1269}
1270
1271INDI_SETCALLBACK_DEFN( dmRecon, m_indiP_fpsSource )( const pcf::IndiProperty &ipRecv )
1272{
1273 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_fpsSource, ipRecv );
1274
1275 if( ipRecv.find( "current" ) != true ) // this isn't valid
1276 {
1277 return -1;
1278 }
1279
1280 std::lock_guard<std::mutex> guard( m_indiMutex );
1281
1282 realT fps = ipRecv["current"].get<float>();
1283
1284 if( fps != m_fps )
1285 {
1286 m_fps = fps;
1287 updateIfChanged( m_indiP_fps, "current", m_fps );
1288 }
1289
1290 return 0;
1291}
1292
1293INDI_NEWCALLBACK_DEFN( dmRecon, m_indiP_writeDMf )( const pcf::IndiProperty &ipRecv )
1294{
1295 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_writeDMf, ipRecv );
1296
1297 if( ipRecv.find( "toggle" ) != true ) // this isn't valid
1298 {
1299 return -1;
1300 }
1301
1302 std::lock_guard<std::mutex> guard( m_indiMutex );
1303
1304 if( ipRecv["toggle"].getSwitchState() == pcf::IndiElement::On )
1305 {
1306 m_writeDMf = true;
1307 log<text_log>( "writing modevalDMf", logPrio::LOG_INFO );
1308 updateSwitchIfChanged( m_indiP_writeDMf, "toggle", pcf::IndiElement::On );
1309 }
1310 else
1311 {
1312 m_writeDMf = false;
1313 log<text_log>( "not writing modevalDMf", logPrio::LOG_INFO );
1314 updateSwitchIfChanged( m_indiP_writeDMf, "toggle", pcf::IndiElement::Off );
1315 }
1316
1317 return 0;
1318}
1319
1324
1326{
1327 return recordFGTimings( true );
1328}
1329
1330} // namespace app
1331} // namespace MagAOX
1332
1333#endif // dmRecon_hpp
The base-class for XWCTk applications.
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI switch element value if it has changed.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
int createROIndiNumber(pcf::IndiProperty &prop, const std::string &propName, const std::string &propLabel="", const std::string &propGroup="")
Create a ReadOnly INDI Number property.
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
std::mutex m_indiMutex
Mutex for locking INDI communications.
timespec m_currImageTimestamp
The timestamp of the current image.
uint32_t m_width
The width of the image, once deinterlaced etc.
bool m_ownShmim
Flag controlling if the shmim is owned. If true it will be destroyed as needed.
uint8_t m_dataType
The ImageStreamIO type code.
bool m_reconfig
Flag to set if a camera reconfiguration requires a framegrabber reset.
ino_t m_inode
The inode of the image stream file.
IMAGE * m_imageStream
The ImageStreamIO shared memory buffer.
uint32_t m_height
The height of the image, once deinterlaced etc.
uint32_t m_depth
The depth of the circular buffer in the stream.
uint32_t m_width
The width of the images in the stream.
uint32_t m_height
The height of the images in the stream.
bool m_restart
Flag indicating tha the shared memory should be reinitialized.
std::atomic< bool > m_updated
Flag indicating that the mode vals have been updated.
Definition dmRecon.hpp:198
std::atomic< bool > m_fgWaiting
Flag indicating that the FG thread is waiting for the command thread.
Definition dmRecon.hpp:172
int m_loopNumber
The loop number. Default is 1 as in aol1.
Definition dmRecon.hpp:124
virtual int appLogic()
Implementation of the FSM for dmRecon.
Definition dmRecon.hpp:516
mx::improc::eigenImage< realT > m_PInv
The pseudo-inverse.
Definition dmRecon.hpp:152
std::atomic< bool > m_dmModesReady
Flag indicating that the DM modes are ready for processing.
Definition dmRecon.hpp:162
float realT
Floating point type in which to do all calculations.
Definition dmRecon.hpp:117
std::atomic< bool > m_commandReady
Flag indicating that all sizes match and arrays are ready for processing.
Definition dmRecon.hpp:170
mx::improc::milkImage< float > m_modevalMon
The actual calculated modevals.
Definition dmRecon.hpp:181
std::mutex m_modevalsMutex
Guards the modevals buffer during producer/consumer handoff.
Definition dmRecon.hpp:200
mx::improc::eigenImage< float > m_respM
Definition dmRecon.hpp:142
virtual void setupConfig()
Definition dmRecon.hpp:349
~dmRecon() noexcept
D'tor, declared and defined for noexcept.
Definition dmRecon.hpp:209
std::string m_monShmimName
Definition dmRecon.hpp:180
INDI_SETCALLBACK_DECL(dmRecon, m_indiP_fpsSource)
int processImage(void *curr_src, const dmModesShmimT &)
Process images for the dm modes shmimMonitor.
Definition dmRecon.hpp:760
int setGPU()
Set the GPU index.
Definition dmRecon.hpp:559
std::string m_fpsSource
Definition dmRecon.hpp:128
dev::frameGrabber< dmRecon > frameGrabberT
Definition dmRecon.hpp:109
pcf::IndiProperty m_indiP_writeDMf
Definition dmRecon.hpp:325
pcf::IndiProperty m_indiP_fpsSource
Definition dmRecon.hpp:320
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
Definition dmRecon.hpp:433
mx::improc::milkImage< float > m_modevalDiff
Definition dmRecon.hpp:184
INDI_NEWCALLBACK_DECL(dmRecon, m_indiP_writeDMf)
std::atomic< bool > m_dmMaskReady
Flag indicating that the DM mask is ready for processing.
Definition dmRecon.hpp:168
virtual int appStartup()
Startup function.
Definition dmRecon.hpp:472
mx::improc::eigenImage< float > m_modevals
The calculated mode amplitudes.
Definition dmRecon.hpp:176
static constexpr bool c_frameGrabber_flippable
Definition dmRecon.hpp:111
mx::improc::eigenImage< float > m_mask
Definition dmRecon.hpp:164
std::vector< size_t > m_maskIDX
The index of masked pixels.
Definition dmRecon.hpp:166
dmRecon()
Default c'tor.
Definition dmRecon.hpp:342
mx::improc::eigenImage< float > m_command
The DM command, copied out of the incoming shmim.
Definition dmRecon.hpp:174
dev::shmimMonitor< dmRecon, dmMaskShmimT > dmMaskSMT
Definition dmRecon.hpp:103
dev::shmimMonitor< dmRecon, dmModesShmimT > dmModesSMT
Definition dmRecon.hpp:100
virtual int appShutdown()
Shutdown the app.
Definition dmRecon.hpp:546
mx::cuda::cublasHandle m_cublas
Handle for the cuBLAS library.
Definition dmRecon.hpp:202
dev::telemeter< dmRecon > telemeterT
Definition dmRecon.hpp:114
int m_numModes
Number of modes to reconstruct. If 0 (default) all modes in CM are used.
Definition dmRecon.hpp:131
std::string m_respMPath
Optional response matrix. If set then CM modes are converted by this response.
Definition dmRecon.hpp:126
sem_t m_smSemaphore
Semaphore used to synchronize the fg thread and the dm command thread.
Definition dmRecon.hpp:196
dev::shmimMonitor< dmRecon, dmCommandShmimT > dmCommandSMT
Definition dmRecon.hpp:106
int recordTelem(const telem_fgtimings *)
Definition dmRecon.hpp:1325
float m_fps
Current FPS from the FPS source.
Definition dmRecon.hpp:150
int loadImageIntoStream(void *dest)
Definition dmRecon.hpp:1258
virtual void loadConfig()
Definition dmRecon.hpp:467
int allocate(const dmModesShmimT &)
Allocate method for the dm modes shmimMonitor.
Definition dmRecon.hpp:719
std::atomic< bool > m_writeDMf
Definition dmRecon.hpp:178
mx::improc::milkImage< float > m_modeval
Definition dmRecon.hpp:183
friend class dmRecon_test
Definition dmRecon.hpp:97
pcf::IndiProperty m_indiP_fps
Definition dmRecon.hpp:323
#define FRAMEGRABBER_SETUP_CONFIG(cfig)
Call frameGrabberT::setupConfig with error checking for frameGrabber.
#define FRAMEGRABBER_APP_LOGIC
Call frameGrabberT::appLogic with error checking for frameGrabber.
#define FRAMEGRABBER_APP_SHUTDOWN
Call frameGrabberT::appShutdown with error checking for frameGrabber.
#define FRAMEGRABBER_UPDATE_INDI
Call frameGrabberT::updateINDI with error checking for frameGrabber.
#define FRAMEGRABBER_LOAD_CONFIG(cfig)
Call frameGrabberT::loadConfig with error checking for frameGrabber.
#define FRAMEGRABBER_APP_STARTUP
Call frameGrabberT::appStartup with error checking for frameGrabber.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define CREATE_REG_INDI_NEW_TOGGLESWITCH(prop, name)
Create and register a NEW INDI property as a standard toggle switch, using the standard callback name...
#define INDI_SETCALLBACK_DEFN(class, prop)
Define the callback for a set property request.
#define REG_INDI_SETPROP(prop, devName, propName)
Register a SET INDI property with the class, using the standard callback name.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
std::stringstream msg
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:19
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
static constexpr logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
#define SHMIMMONITORT_APP_STARTUP(SHMIMMONITORT)
Call shmimMonitorT::appStartup with error checking for a typedef-ed shmimMonitor.
#define SHMIMMONITORT_UPDATE_INDI(SHMIMMONITORT)
Call shmimMonitorT::updateINDI with error checking for a typedef-ed shmimMonitor.
#define SHMIMMONITORT_SETUP_CONFIG(SHMIMMONITORT, cfig)
Call shmimMonitorT::setupConfig with error checking for a typedef-ed shmimMonitor.
#define SHMIMMONITORT_APP_LOGIC(SHMIMMONITORT)
Call shmimMonitorT::appLogic with error checking for a typedef-ed shmimMonitor.
#define SHMIMMONITORT_APP_SHUTDOWN(SHMIMMONITORT)
Call shmimMonitorT::appShutodwn with error checking for a typedef-ed shmimMonitor.
#define SHMIMMONITORT_LOAD_CONFIG(SHMIMMONITORT, cfig)
Call shmimMonitorT::loadConfig with error checking for a typedef-ed shmimMonitor.
A device base class which saves telemetry.
Definition telemeter.hpp:75
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
static std::string indiPrefix()
Definition dmRecon.hpp:78
static std::string configSection()
Definition dmRecon.hpp:73
static std::string configSection()
Definition dmRecon.hpp:60
static std::string indiPrefix()
Definition dmRecon.hpp:65
static std::string configSection()
Definition dmRecon.hpp:47
static std::string indiPrefix()
Definition dmRecon.hpp:52
@ OPERATING
The device is operating, other than homing.
Software ERR log entry.
Log entry recording framegrabber timings.
#define TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
#define TELEMETER_LOAD_CONFIG(cfig)
Call telemeter::loadConfig with error checking.
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
#define TELEMETER_SETUP_CONFIG(cfig)
Call telemeter::setupConfig with error checking.
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.