12#include <mx/improc/eigenImage.hpp>
13#include <mx/improc/milkImage.hpp>
14#include <mx/ioutils/fits/fitsFile.hpp>
25template <
typename typeT>
34 return _DATATYPE_FLOAT;
40 return _DATATYPE_DOUBLE;
88template <
class derivedT,
typename realT>
197 mx::improc::milkImage<realT>
612 const pcf::IndiProperty &
ipRecv
632 const pcf::IndiProperty &
ipRecv
642 const pcf::IndiProperty &
ipRecv
652 const pcf::IndiProperty &
ipRecv
672 const pcf::IndiProperty &
ipRecv
682 const pcf::IndiProperty &
ipRecv );
691 const pcf::IndiProperty &
ipRecv
700 const pcf::IndiProperty &
ipRecv );
709 const pcf::IndiProperty &
ipRecv
718 const pcf::IndiProperty &
ipRecv );
727 const pcf::IndiProperty &
ipRecv
747 const pcf::IndiProperty &
ipRecv
756 const pcf::IndiProperty &
ipRecv );
773 typedef int32_t cbIndexT;
775 double m_t0{ 0 }, m_tf{ 0 }, m_tsat0{ 0 }, m_tsatf{ 0 };
776 double m_tact0{ 0 }, m_tact1{ 0 }, m_tact2{ 0 }, m_tact3{ 0 }, m_tact4{ 0 };
777 double m_tdelta0 {0}, m_tdeltaf {0};
779 mx::sigproc::circularBufferIndex<double, cbIndexT> m_piTimes;
781 mx::sigproc::circularBufferIndex<double, cbIndexT> m_satSem;
783 mx::sigproc::circularBufferIndex<double, cbIndexT> m_actProc;
785 mx::sigproc::circularBufferIndex<double, cbIndexT> m_actCom;
787 mx::sigproc::circularBufferIndex<double, cbIndexT> m_satUp;
789 mx::sigproc::circularBufferIndex<double, cbIndexT> m_deltaUp;
792 std::vector<double> m_piTimesD;
793 std::vector<double> m_satSemD;
794 std::vector<double> m_actProcD;
795 std::vector<double> m_actComD;
796 std::vector<double> m_satUpD;
797 std::vector<double> m_deltaUpD;
805 return *
static_cast<derivedT *
>( this );
809template <
class derivedT,
typename realT>
812 for(
auto &mi : m_channels )
821template <
class derivedT,
typename realT>
827template <
class derivedT,
typename realT>
833template <
class derivedT,
typename realT>
839template <
class derivedT,
typename realT>
842 return m_flatDefault;
845template <
class derivedT,
typename realT>
848 return m_testDefault;
851template <
class derivedT,
typename realT>
857template <
class derivedT,
typename realT>
863template <
class derivedT,
typename realT>
869template <
class derivedT,
typename realT>
872 return m_shmimSatPerc;
875template <
class derivedT,
typename realT>
881template <
class derivedT,
typename realT>
887template <
class derivedT,
typename realT>
893template <
class derivedT,
typename realT>
899template <
class derivedT,
typename realT>
905template <
class derivedT,
typename realT>
908 return m_percThreshold;
911template <
class derivedT,
typename realT>
914 return m_intervalSatThreshold;
917template <
class derivedT,
typename realT>
920 return m_intervalSatCountThreshold;
923template <
class derivedT,
typename realT>
926 return m_satTriggerDevice;
929template <
class derivedT,
typename realT>
932 return m_satTriggerProperty;
935template <
class derivedT,
typename realT>
938 return m_calibRelDir;
941template <
class derivedT,
typename realT>
944 return m_numChannels;
947template <
class derivedT,
typename realT>
953template <
class derivedT,
typename realT>
956 return m_accumSatMap;
959template <
class derivedT,
typename realT>
965template <
class derivedT,
typename realT>
968 return m_deltaChannels;
971template <
class derivedT,
typename realT>
977template <
class derivedT,
typename realT>
983template <
class derivedT,
typename realT>
986 config.add(
"dm.calibPath",
994 "The path to calibration files, relative to the MagAO-X calibration path." );
996 config.add(
"dm.flatPath",
1004 "The path to flat files. Default is the calibration path." );
1006 config.add(
"dm.flatDefault",
1014 "The default flat file (path and extension are not required)." );
1016 config.add(
"dm.testPath",
1024 "The path to test files. Default is the calibration path plus /tests." );
1026 config.add(
"dm.testDefault",
1034 "The default test file (path and extension are not required)." );
1036 config.add(
"dm.actMaskPath",
1044 "The path to the actuator mask for this DM, relative to the calib path." );
1049 config.add(
"dm.threadPrio",
1057 "The real-time priority of the dm control thread." );
1059 config.add(
"dm.cpuset",
1067 "The cpuset for the dm control thread." );
1069 config.add(
"dm.shmimName",
1077 "The name of the ImageStreamIO shared memory image to monitor for DM comands. Will be used as "
1078 "/tmp/<shmimName>.im.shm." );
1082 config.add(
"dm.shmimFlat",
1090 "The name of the ImageStreamIO shared memory image to write the flat command to. Default is shmimName "
1091 "with 00 apended (i.e. dm00disp -> dm00disp00). " );
1093 config.add(
"dm.shmimTest",
1101 "The name of the ImageStreamIO shared memory image to write the test command to. Default is shmimName "
1102 "with 01 apended (i.e. dm00disp -> dm00disp01). " );
1104 config.add(
"dm.shmimSat",
1112 "The name of the ImageStreamIO shared memory image to write the saturation map to. Default is "
1113 "shmimName with SA apended (i.e. dm00disp -> dm00dispSA). This is created." );
1115 config.add(
"dm.shmimSatPerc",
1123 "The name of the ImageStreamIO shared memory image to write the saturation percentage map to. Default "
1124 "is shmimName with SP apended (i.e. dm00disp -> dm00dispSP). This is created." );
1126 config.add(
"dm.satAvgInt",
1134 "The interval in milliseconds over which saturation "
1135 "is accumulated before updating. Default is 100 ms." );
1137 config.add(
"dm.satThreadPrio",
1145 "The priority for the saturation thread. "
1146 "Usually ok to be 0." );
1148 config.add(
"dm.shmimShape",
1156 "The name of the ImageStreamIO shared memory image to write the desaturated shape to. Default is "
1157 "shmimName with _shape apended (i.e. dm00disp -> dm00disp_shape). This is created." );
1159 config.add(
"dm.shmimDelta",
1167 "The name of the ImageStreamIO shared memory image to write the "
1168 "desaturated delta-shape to. Default is "
1169 "shmimName with _delta apended (i.e. dm00disp -> dm00disp_delta). This is created." );
1171 config.add(
"dm.deltaChannels",
1179 "The names of the DM channels which are delta commands to be excluded from the total flat." );
1181 config.add(
"dm.width",
1189 "The width of the DM in actuators." );
1191 config.add(
"dm.height",
1199 "The height of the DM in actuators." );
1201 config.add(
"dm.percThreshold",
1209 "Threshold on percentage of frames an actuator is saturated over an interval. Default is 0.98." );
1211 config.add(
"dm.intervalSatThreshold",
1213 "dm.intervalSatThreshold",
1216 "intervalSatThreshold",
1219 "Threshold on percentage of actuators which exceed percThreshold in an interval. Default is 0.5." );
1221 config.add(
"dm.intervalSatCountThreshold",
1223 "dm.intervalSatCountThreshold",
1226 "intervalSatCountThreshold",
1229 "Threshold on number of consecutive intervals the intervalSatThreshold is exceeded. Default is 10." );
1231 config.add(
"dm.satTriggerDevice",
1233 "dm.satTriggerDevice",
1239 "Device(s) with a toggle switch to toggle on saturation trigger." );
1241 config.add(
"dm.satTriggerProperty",
1243 "dm.satTriggerProperty",
1246 "satTriggerProperty",
1249 "Property with a toggle switch to toggle on saturation trigger, one per entry in satTriggerDevice." );
1254template <
class derivedT,
typename realT>
1258 m_calibPath = derived().m_calibDir +
"/" + m_calibRelDir;
1259 config( m_calibPath,
"dm.calibPath" );
1262 m_flatPath = m_calibPath +
"/flats";
1263 config( m_flatPath,
"dm.flatPath" );
1265 config( m_flatDefault,
"dm.flatDefault" );
1266 if( m_flatDefault !=
"" )
1268 m_flatDefault = mx::ioutils::pathStem( m_flatDefault );
1269 m_flatCurrent =
"default";
1273 m_testPath = m_calibPath +
"/tests";
1274 config( m_testPath,
"dm.testPath" );
1276 config( m_testDefault,
"dm.testDefault" );
1277 if( m_testDefault !=
"" )
1279 m_testDefault = mx::ioutils::pathStem( m_testDefault );
1280 m_testCurrent =
"default";
1283 config( m_actMaskPath,
"dm.actMaskPath" );
1287 config( derived().m_smThreadPrio,
"dm.threadPrio" );
1288 config( derived().m_smCpuset,
"dm.cpuset" );
1290 config( derived().m_shmimName,
"dm.shmimName" );
1292 derived().m_getExistingFirst =
true;
1295 if( derived().m_shmimName !=
"" )
1297 m_shmimFlat = derived().m_shmimName +
"00";
1298 config( m_shmimFlat,
"dm.shmimFlat" );
1300 m_shmimTest = derived().m_shmimName +
"02";
1301 config( m_shmimTest,
"dm.shmimTest" );
1303 m_shmimSat = derived().m_shmimName +
"ST";
1304 config( m_shmimSat,
"dm.shmimSat" );
1306 m_shmimSatPerc = derived().m_shmimName +
"SP";
1307 config( m_shmimSatPerc,
"dm.shmimSatPerc" );
1309 config( m_satAvgInt,
"dm.satAvgInt" );
1311 config( m_satThreadPrio,
"dm.satSatThreadPrio" );
1313 m_shmimShape = derived().m_shmimName +
"_shape";
1314 config( m_shmimShape,
"dm.shmimShape" );
1316 m_shmimDelta = derived().m_shmimName +
"_delta";
1317 config( m_shmimDelta,
"dm.shmimDelta" );
1319 m_shmimDiff = derived().m_shmimName +
"_diff";
1320 config( m_shmimDiff,
"dm.shmimDiff" );
1322 config( m_deltaChannels,
"dm.deltaChannels" );
1327 config.isSet(
"dm.shmimFlat" );
1328 config.isSet(
"dm.shmimTest" );
1329 config.isSet(
"dm.shmimSat" );
1330 config.isSet(
"dm.shmimSatPerc" );
1331 config.isSet(
"dm.satAvgInt" );
1332 config.isSet(
"dm.shmimShape" );
1333 config.isSet(
"dm.shmimDelta" );
1334 config.isSet(
"dm.deltaChannels" );
1337 config( m_dmWidth,
"dm.width" );
1338 config( m_dmHeight,
"dm.height" );
1340 config( m_percThreshold,
"dm.percThreshold" );
1341 config( m_intervalSatThreshold,
"dm.intervalSatThreshold" );
1342 config( m_intervalSatCountThreshold,
"dm.intervalSatCountThreshold" );
1343 config( m_satTriggerDevice,
"dm.satTriggerDevice" );
1344 config( m_satTriggerProperty,
"dm.satTriggerProperty" );
1346 if( m_dmWidth > 0 && m_dmHeight > 0 )
1350 m_actMask.create( derived().m_shmimName +
"_actmask", m_dmWidth, m_dmHeight );
1352 catch(
const std::exception &e )
1354 derivedT::template log<text_log>( std::format(
"exception caught creating actuator mask: "
1356 derived().m_shmimName +
"_actmask",
1358 logPrio::LOG_ERROR ) );
1362 if( m_actMaskPath !=
"" )
1364 mx::improc::eigenImage<realT> actMask;
1366 mx::fits::fitsFile<realT> ff;
1368 mx::error_t errc = ff.read( actMask, m_calibPath +
'/' + m_actMaskPath );
1370 if( errc != mx::error_t::noerror )
1372 derivedT::template log<text_log>( std::format(
"error reading actuator mask file {}: "
1374 m_calibPath +
'/' + m_actMaskPath,
1375 mx::errorMessage( errc ),
1376 mx::errorName( errc ) ),
1377 logPrio::LOG_ERROR );
1381 if( actMask.rows() != m_dmWidth || actMask.cols() != m_dmHeight )
1383 derivedT::template log<text_log>( std::format(
"actuaor mask {}x{} is not same size as flag {}x{}",
1388 logPrio::LOG_ERROR );
1393 m_actMask = actMask;
1397 m_actMask().setConstant( 1.0 );
1404template <
class derivedT,
typename realT>
1407 if( m_dmDataType == 0 )
1409 derivedT::template log<software_error>( {
"unsupported DM data type" } );
1418 m_indiP_flatShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1419 m_indiP_flatShmim.setDevice( derived().configName() );
1420 m_indiP_flatShmim.setName(
"flat_shmim" );
1421 m_indiP_flatShmim.setPerm( pcf::IndiProperty::ReadOnly );
1422 m_indiP_flatShmim.setState( pcf::IndiProperty::Idle );
1423 m_indiP_flatShmim.add( pcf::IndiElement(
"channel" ) );
1424 m_indiP_flatShmim[
"channel"] = m_shmimFlat;
1426 if( derived().registerIndiPropertyReadOnly( m_indiP_flatShmim ) < 0 )
1428#ifndef DM_TEST_NOLOG
1429 derivedT::template log<software_error>( {
"" } );
1435 derived().createStandardIndiToggleSw( m_indiP_setFlat,
"flat_set" );
1436 if( derived().registerIndiPropertyNew( m_indiP_setFlat, st_newCallBack_setFlat ) < 0 )
1438#ifndef DM_TEST_NOLOG
1439 derivedT::template log<software_error>( {
"" } );
1449 m_indiP_testShmim = pcf::IndiProperty( pcf::IndiProperty::Text );
1450 m_indiP_testShmim.setDevice( derived().configName() );
1451 m_indiP_testShmim.setName(
"test_shmim" );
1452 m_indiP_testShmim.setPerm( pcf::IndiProperty::ReadOnly );
1453 m_indiP_testShmim.setState( pcf::IndiProperty::Idle );
1454 m_indiP_testShmim.add( pcf::IndiElement(
"channel" ) );
1455 m_indiP_testShmim[
"channel"] = m_shmimTest;
1456 derived().createStandardIndiToggleSw( m_indiP_setTest,
"test_shmim" );
1457 if( derived().registerIndiPropertyReadOnly( m_indiP_testShmim ) < 0 )
1459#ifndef DM_TEST_NOLOG
1460 derivedT::template log<software_error>( {
"" } );
1466 derived().createStandardIndiToggleSw( m_indiP_setTest,
"test_set" );
1467 if( derived().registerIndiPropertyNew( m_indiP_setTest, st_newCallBack_setTest ) < 0 )
1469#ifndef DM_TEST_NOLOG
1470 derivedT::template log<software_error>( {
"" } );
1476 derived().createStandardIndiRequestSw( m_indiP_init,
"initDM" );
1477 if( derived().registerIndiPropertyNew( m_indiP_init, st_newCallBack_init ) < 0 )
1480 #ifndef DM_TEST_NOLOG
1481 derivedT::template log<software_error>( {
""} );
1489 derived().createStandardIndiRequestSw( m_indiP_zero,
"zeroDM" );
1491 if( derived().registerIndiPropertyNew( m_indiP_zero, st_newCallBack_zero ) < 0 )
1494 #ifndef DM_TEST_NOLOG
1495 derivedT::template log<software_error>( {
""} );
1503 derived().createStandardIndiRequestSw( m_indiP_release,
"releaseDM" );
1504 if( derived().registerIndiPropertyNew( m_indiP_release, st_newCallBack_release ) < 0 )
1506 return derivedT::template log<software_error, -1>( {
"" } );
1509 derived().createStandardIndiRequestSw( m_indiP_zeroAll,
"zeroAll" );
1510 if( derived().registerIndiPropertyNew( m_indiP_zeroAll, st_newCallBack_zeroAll ) < 0 )
1512#ifndef DM_TEST_NOLOG
1513 derivedT::template log<software_error>( {
"" } );
1518 if( m_flatDefault !=
"" )
1520 loadFlat(
"default" );
1523 if( m_testDefault !=
"" )
1525 loadTest(
"default" );
1528 if( sem_init( &m_satSemaphore, 0, 0 ) < 0 )
1530 return derivedT::template log<software_critical, -1>( { errno, 0,
"Initializing sat semaphore" } );
1533 if( derived().threadStart( m_satThread,
1541 satThreadStart ) < 0 )
1543 derivedT::template log<software_error, -1>( {
"" } );
1550template <
class derivedT,
typename realT>
1554 if( pthread_tryjoin_np( m_satThread.native_handle(), 0 ) == 0 )
1556 derivedT::template log<software_error>( {
"saturation thread has exited" } );
1565 if( m_intervalSatTrip )
1568 m_intervalSatTrip =
false;
1572 static uint64_t lastMono = 0;
1574 if( m_piTimes.size() >= m_piTimes.maxEntries() && m_piTimes.maxEntries() > 0 && m_piTimes.mono() != lastMono )
1576 cbIndexT refEntry = m_piTimes.earliest();
1578 m_piTimesD.resize( m_piTimes.maxEntries() );
1579 m_satSemD.resize( m_satSem.maxEntries() );
1580 m_actProcD.resize( m_actProc.maxEntries() );
1581 m_actComD.resize( m_actCom.maxEntries() );
1582 m_satUpD.resize( m_satUp.maxEntries() );
1583 m_deltaUpD.resize( m_deltaUp.maxEntries() );
1585 for(
size_t n = 0; n < m_piTimesD.size(); ++n )
1587 m_piTimesD[n] = m_piTimes.at( refEntry, n );
1588 m_satSemD[n] = m_satSem.at( refEntry, n );
1589 m_actProcD[n] = m_actProc.at( refEntry, n );
1590 m_actComD[n] = m_actCom.at( refEntry, n );
1591 m_satUpD[n] = m_satUp.at( refEntry, n );
1592 m_deltaUpD[n] = m_deltaUp.at( refEntry, n );
1595 std::cerr <<
"Act. Process: " << mx::math::vectorMean( m_actProcD ) <<
" +/- "
1596 << sqrt( mx::math::vectorVariance( m_actProcD ) ) <<
"\n";
1597 std::cerr <<
"Act. Command: " << mx::math::vectorMean( m_actComD ) <<
" +/- "
1598 << sqrt( mx::math::vectorVariance( m_actComD ) ) <<
"\n";
1599 std::cerr <<
"Sat. Update: " << mx::math::vectorMean( m_satUpD ) <<
" +/- "
1600 << sqrt( mx::math::vectorVariance( m_satUpD ) ) <<
"\n";
1601 std::cerr <<
"Delta Update: " << mx::math::vectorMean( m_deltaUpD ) <<
" +/- "
1602 << sqrt( mx::math::vectorVariance( m_deltaUpD ) ) <<
"\n";
1603 std::cerr <<
"Tot. CommandDM: " << mx::math::vectorMean( m_piTimesD ) <<
" +/- "
1604 << sqrt( mx::math::vectorVariance( m_piTimesD ) ) <<
"\n";
1605 std::cerr <<
"Sat. Semaphore: " << mx::math::vectorMean( m_satSemD ) <<
" +/- "
1606 << sqrt( mx::math::vectorVariance( m_satSemD ) ) <<
"\n";
1609 lastMono = m_piTimes.mono();
1616template <
class derivedT,
typename realT>
1619 if( m_satThread.joinable() )
1621 pthread_kill( m_satThread.native_handle(), SIGUSR1 );
1634template <
class derivedT,
typename realT>
1642template <
class derivedT,
typename realT>
1651template <
class derivedT,
typename realT>
1654 std::string milkShmimDir = mx::sys::getEnv(
"MILK_SHM_DIR" );
1655 if( milkShmimDir ==
"" )
1657 milkShmimDir =
"/milk/shm";
1660 std::vector<std::string> dmlist;
1661 mx::error_t errc = mx::ioutils::getFileNames( dmlist, milkShmimDir, derived().m_shmimName,
".im",
".shm" );
1663 mx_error_check_rv( errc, -1 );
1665 if( dmlist.size() == 0 )
1667 derivedT::template log<software_error>( {
"no dm channels found for " + derived().m_shmimName } );
1673 for(
size_t n = 0; n < dmlist.size(); ++n )
1676 snprintf( nstr,
sizeof( nstr ),
"%02d.im.shm", (
int)n );
1677 std::string tgt = derived().m_shmimName;
1680 for(
size_t m = 0; m < dmlist.size(); ++m )
1682 if( dmlist[m].find( tgt ) != std::string::npos )
1684 if( (
int)n > m_numChannels )
1694 derivedT::template log<text_log>( {std::format(
"Found {} chanels for {} ", m_numChannels, derived().m_shmimName )} );
1696 m_channels.resize( m_numChannels,
nullptr );
1698 m_notDeltas.clear();
1701 for(
size_t n = 0; n < m_channels.size(); ++n )
1703 std::string sname = std::format(
"{}{:02}", derived().m_shmimName, n );
1707 m_channels[n] =
new mx::improc::milkImage<realT>( sname );
1709 catch(
const std::exception &e )
1711 derivedT::template log<software_error>( {
"exception opening " + sname +
": " + e.what() } );
1714 std::cerr <<
"looking for " << sname <<
'\n';
1715 auto res = std::find( m_deltaChannels.begin(), m_deltaChannels.end(), sname );
1716 if( res == m_deltaChannels.end() )
1718 std::cerr <<
" not a delta\n";
1719 m_notDeltas.push_back( n );
1723 std::cerr <<
" is a delta\n";
1724 m_deltas.push_back( n );
1728 std::cerr <<
"not deltas: ";
1729 for(
size_t n = 0; n < m_notDeltas.size(); ++n )
1731 std::cerr << m_notDeltas[n] <<
' ';
1735 std::cerr <<
"deltas: ";
1736 for(
size_t n = 0; n < m_deltas.size(); ++n )
1738 std::cerr << m_deltas[n] <<
' ';
1745template <
class derivedT,
typename realT>
1748 static_cast<void>( sp );
1752 if( derived().m_width != m_dmWidth )
1754 derivedT::template log<software_critical>( {
"shmim width does not match configured DM width" } );
1758 if( derived().m_height != m_dmHeight )
1760 derivedT::template log<software_critical>( {
"shmim height does not match configured DM height" } );
1764 if( derived().m_dataType != m_dmDataType )
1766 derivedT::template log<software_critical>( {
"shmim data type does not match configured DM data type" } );
1775 m_instSatMap.resize( m_dmWidth, m_dmHeight );
1776 m_instSatMap.setZero();
1778 m_accumSatMap.resize( m_dmWidth, m_dmHeight );
1779 m_accumSatMap.setZero();
1781 m_satPercMap.resize( m_dmWidth, m_dmHeight );
1782 m_satPercMap.setZero();
1784 if( findDMChannels() < 0 )
1786 derivedT::template log<software_critical>( {
"error finding DM channels" } );
1793 m_outputShape.create( m_shmimShape, m_dmWidth, m_dmHeight );
1794 m_outputShape().setZero();
1796 catch(
const std::exception &e )
1798 return derivedT::template log<software_error, -1>(
1799 { std::string(
"creating output shape shmim: " ) + e.what() } );
1804 m_outputDelta.create( m_shmimDelta, m_dmWidth, m_dmHeight );
1805 m_outputDelta().setZero();
1807 catch(
const std::exception &e )
1809 return derivedT::template log<software_error, -1>(
1810 { std::string(
"creating output delta shmim: " ) + e.what() } );
1815 m_outputDiff.create( m_shmimDiff, m_dmWidth, m_dmHeight );
1816 m_outputDiff().setZero();
1818 catch(
const std::exception &e )
1820 return derivedT::template log<software_error, -1>(
1821 { std::string(
"creating output diff shmim: " ) + e.what() } );
1824 m_totalFlat.resize( m_dmWidth, m_dmHeight );
1825 m_totalFlat.setZero();
1827 m_totalDelta.resize( m_dmWidth, m_dmHeight );
1828 m_totalDelta.setZero();
1831 #ifdef XWC_DMTIMINGS
1832 m_piTimes.maxEntries( 2000 );
1833 m_satSem.maxEntries( 2000 );
1834 m_actProc.maxEntries( 2000 );
1835 m_actCom.maxEntries( 2000 );
1836 m_satUp.maxEntries( 2000 );
1837 m_deltaUp.maxEntries( 2000 );
1843template <
class derivedT,
typename realT>
1846 static_cast<void>( sp );
1849 #ifdef XWC_DMTIMINGS
1850 m_t0 = mx::sys::get_curr_time();
1853 int rv = derived().commandDM( curr_src );
1857 derivedT::template log<software_critical>( { errno, rv,
"Error from commandDM" } );
1862 #ifdef XWC_DMTIMINGS
1863 m_tdelta0 = mx::sys::get_curr_time();
1866 if( m_deltaChannels.size() > 0 )
1872 derivedT::template log<software_critical>( { errno, rv,
"Error from makeDelta" } );
1878 #ifdef XWC_DMTIMINGS
1879 m_tdeltaf = mx::sys::get_curr_time();
1885 #ifdef XWC_DMTIMINGS
1886 m_tsat0 = mx::sys::get_curr_time();
1890 if( sem_post( &m_satSemaphore ) < 0 )
1892 derivedT::template log<software_critical>( { errno, 0,
"Error posting to semaphore" } );
1897 #ifdef XWC_DMTIMINGS
1899 m_tsatf = mx::sys::get_curr_time();
1902 if( m_piTimes.maxEntries() > 0 )
1904 m_piTimes.nextEntry( m_tf - m_t0 );
1905 m_satSem.nextEntry( m_tsatf - m_tsat0 );
1906 m_actProc.nextEntry( m_tact1 - m_tact0 );
1907 m_actCom.nextEntry( m_tact2 - m_tact1 );
1908 m_satUp.nextEntry( m_tact4 - m_tact3 );
1909 m_deltaUp.nextEntry( m_tdeltaf - m_tdelta0 );
1918template <
class derivedT,
typename realT>
1923 derivedT::template log<software_error>( { errno,
"DM is not ready to be initialized" } );
1931 if( ( rv = derived().initDM() ) < 0 )
1933 derivedT::template log<software_critical>( { errno, rv,
"Error from initDM" } );
1941template <
class derivedT,
typename realT>
1950 if( ( rv = derived().releaseDM() ) < 0 )
1952 derivedT::template log<software_critical>( { errno, rv,
"Error from releaseDM" } );
1957 if( ( rv = zeroAll(
true ) ) < 0 )
1959 derivedT::template log<software_error>( { errno, rv,
"Error from zeroAll" } );
1967template <
class derivedT,
typename realT>
1970 std::vector<std::string> tfs;
1971 mx::error_t errc = mx::ioutils::getFileNames( tfs, m_flatPath,
"",
"",
".fits" );
1973 mx_error_check_rv( errc, -1 );
1976 for(
size_t n = 0; n < tfs.size(); ++n )
1978 if( mx::ioutils::pathStem( tfs[n] ) ==
"default" )
1980 tfs.erase( tfs.begin() + n );
1985 unsigned m_nFlatFiles = 5;
1988 if( tfs.size() >= m_nFlatFiles )
1990 std::vector<std::filesystem::file_time_type> wtimes( tfs.size() );
1992 for(
size_t n = 0; n < wtimes.size(); ++n )
1994 wtimes[n] = std::filesystem::last_write_time( tfs[n] );
1997 std::sort( wtimes.begin(), wtimes.end() );
1999 std::filesystem::file_time_type tn = wtimes[wtimes.size() - m_nFlatFiles];
2001 for(
size_t n = 0; n < tfs.size(); ++n )
2003 std::filesystem::file_time_type lmt = std::filesystem::last_write_time( tfs[n] );
2006 tfs.erase( tfs.begin() + n );
2012 for(
auto it = m_flatCommands.begin();
it != m_flatCommands.end(); ++
it )
2017 bool changed =
false;
2018 for(
size_t n = 0; n < tfs.size(); ++n )
2021 m_flatCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2022 if( ir.second ==
true )
2025 ir.first->second = tfs[n];
2028 for(
auto it = m_flatCommands.begin();
it != m_flatCommands.end(); ++
it )
2030 if(
it->second ==
"" )
2036 m_flatCommands.erase( itdel );
2043 if( derived().m_indiDriver )
2045 derived().m_indiDriver->sendDelProperty( m_indiP_flats );
2046 derived().m_indiNewCallBacks.erase( m_indiP_flats.createUniqueKey() );
2049 m_indiP_flats = pcf::IndiProperty( pcf::IndiProperty::Switch );
2050 m_indiP_flats.setDevice( derived().configName() );
2051 m_indiP_flats.setName(
"flat" );
2052 m_indiP_flats.setPerm( pcf::IndiProperty::ReadWrite );
2053 m_indiP_flats.setState( pcf::IndiProperty::Idle );
2054 m_indiP_flats.setRule( pcf::IndiProperty::OneOfMany );
2057 for(
auto it = m_flatCommands.begin();
it != m_flatCommands.end(); ++
it )
2059 if(
it->first == m_flatCurrent || m_flatCurrent ==
"" )
2061 m_indiP_flats.add( pcf::IndiElement(
it->first, pcf::IndiElement::On ) );
2062 m_flatCurrent =
it->first;
2066 m_indiP_flats.add( pcf::IndiElement(
it->first, pcf::IndiElement::Off ) );
2070 if( m_flatDefault !=
"" )
2072 if( m_flatCurrent ==
"default" )
2074 m_indiP_flats.add( pcf::IndiElement(
"default", pcf::IndiElement::On ) );
2078 m_indiP_flats.add( pcf::IndiElement(
"default", pcf::IndiElement::Off ) );
2082 if( derived().registerIndiPropertyNew( m_indiP_flats, st_newCallBack_flats ) < 0 )
2085 #ifndef DM_TEST_NOLOG
2086 derivedT::template log<software_error>( {
""} );
2093 if( derived().m_indiDriver )
2095 derived().m_indiDriver->sendDefProperty( m_indiP_flats );
2102template <
class derivedT,
typename realT>
2105 std::string target = intarget;
2107 std::string targetPath;
2109 if( target ==
"default" )
2111 target = m_flatDefault;
2112 targetPath = m_flatPath +
"/" + m_flatDefault +
".fits";
2118 targetPath = m_flatCommands.at( target );
2122 derivedT::template log<text_log>(
"flat file " + target +
" not found", logPrio::LOG_ERROR );
2127 m_flatLoaded =
false;
2130 mx::fits::fitsFile<realT> ff;
2132 mx::error_t errc = ff.read( m_flatCommand, targetPath );
2134 if( errc != mx::error_t::noerror )
2136 derivedT::template log<text_log>( std::format(
"error reading flat file {}: "
2139 mx::errorMessage( errc ),
2140 mx::errorName( errc ) ),
2141 logPrio::LOG_ERROR );
2145 if( m_actMask.rows() != m_flatCommand.rows() || m_actMask.cols() != m_flatCommand.cols() )
2147 derivedT::template log<text_log>( std::format(
"actuaor mask {}x{} is not same size as flag {}x{}",
2150 m_flatCommand.rows(),
2151 m_flatCommand.cols() ),
2152 logPrio::LOG_ERROR );
2157 m_flatCommand *= m_actMask();
2159 derivedT::template log<text_log>(
"loaded flat file " + targetPath );
2160 m_flatLoaded =
true;
2162 m_flatCurrent = intarget;
2164 if( m_indiP_flats.find(
"default" ) )
2166 if( m_flatCurrent ==
"default" )
2168 m_indiP_flats[
"default"] = pcf::IndiElement::On;
2172 m_indiP_flats[
"default"] = pcf::IndiElement::Off;
2176 for(
auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
2178 if( !m_indiP_flats.find( i->first ) )
2183 if( i->first == m_flatCurrent )
2185 m_indiP_flats[i->first] = pcf::IndiElement::On;
2189 m_indiP_flats[i->first] = pcf::IndiElement::Off;
2193 if( derived().m_indiDriver )
2195 derived().m_indiDriver->sendSetProperty( m_indiP_flats );
2206template <
class derivedT,
typename realT>
2209 if( m_shmimFlat ==
"" )
2216 derivedT::template log<text_log>(
"can not set flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2220 if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2222 derivedT::template log<text_log>(
"could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2226 if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2228 ImageStreamIO_closeIm( &m_flatImageStream );
2229 derivedT::template log<text_log>(
"width mismatch between " + m_shmimFlat +
" and configured DM",
2230 logPrio::LOG_ERROR );
2234 if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2236 ImageStreamIO_closeIm( &m_flatImageStream );
2237 derivedT::template log<text_log>(
"height mismatch between " + m_shmimFlat +
" and configured DM",
2238 logPrio::LOG_ERROR );
2244 bool flatSet = m_flatSet;
2247 if( loadFlat( m_flatCurrent ) < 0 )
2249 derivedT::template log<text_log>(
"error loading flat " + m_flatCurrent, logPrio::LOG_ERROR );
2251 m_flatSet = flatSet;
2256 ImageStreamIO_closeIm( &m_flatImageStream );
2257 derivedT::template log<text_log>(
"no flat loaded", logPrio::LOG_ERROR );
2261 if( m_flatCommand.rows() != m_dmWidth )
2263 ImageStreamIO_closeIm( &m_flatImageStream );
2264 derivedT::template log<text_log>(
"width mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2268 if( m_flatCommand.cols() != m_dmHeight )
2270 ImageStreamIO_closeIm( &m_flatImageStream );
2271 derivedT::template log<text_log>(
"height mismatch between flat file and configured DM", logPrio::LOG_ERROR );
2275 m_flatImageStream.md->write = 1;
2280 memcpy( m_flatImageStream.array.raw, m_flatCommand.data(), m_dmWidth * m_dmHeight *
sizeof( realT ) );
2283 clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2286 m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2288 m_flatImageStream.md->cnt0++;
2289 m_flatImageStream.md->write = 0;
2292 ImageStreamIO_sempost( &m_flatImageStream, -1 );
2296 ImageStreamIO_closeIm( &m_flatImageStream );
2302 derived().updateSwitchIfChanged( m_indiP_setFlat,
"toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2304 derivedT::template log<text_log>(
"flat set" );
2310template <
class derivedT,
typename realT>
2313 if( m_shmimFlat ==
"" )
2320 derivedT::template log<text_log>(
"can not zero flat unless DM is READY or OPERATING", logPrio::LOG_WARNING );
2324 if( ImageStreamIO_openIm( &m_flatImageStream, m_shmimFlat.c_str() ) != 0 )
2326 derivedT::template log<text_log>(
"could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING );
2330 if( m_flatImageStream.md[0].size[0] != m_dmWidth )
2332 ImageStreamIO_closeIm( &m_flatImageStream );
2333 derivedT::template log<text_log>(
"width mismatch between " + m_shmimFlat +
" and configured DM",
2334 logPrio::LOG_ERROR );
2338 if( m_flatImageStream.md[0].size[1] != m_dmHeight )
2340 ImageStreamIO_closeIm( &m_flatImageStream );
2341 derivedT::template log<text_log>(
"height mismatch between " + m_shmimFlat +
" and configured DM",
2342 logPrio::LOG_ERROR );
2346 m_flatImageStream.md->write = 1;
2351 memset( m_flatImageStream.array.raw, 0, m_dmWidth * m_dmHeight *
sizeof( realT ) );
2354 clock_gettime( CLOCK_REALTIME, &m_flatImageStream.md->writetime );
2357 m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
2359 m_flatImageStream.md->cnt0++;
2360 m_flatImageStream.md->write = 0;
2361 ImageStreamIO_sempost( &m_flatImageStream, -1 );
2366 ImageStreamIO_closeIm( &m_flatImageStream );
2368 derived().updateSwitchIfChanged( m_indiP_setFlat,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2370 derivedT::template log<text_log>(
"flat zeroed" );
2372 if( derived().zeroDM() < 0 )
2374 derivedT::template log<software_error>( {
"error from zeroDM" } );
2377 if( clearSat() < 0 )
2379 derivedT::template log<software_error>( {
"error from clearSat" } );
2386template <
class derivedT,
typename realT>
2389 std::vector<std::string> tfs;
2390 mx::error_t errc = mx::ioutils::getFileNames( tfs, m_testPath,
"",
"",
".fits" );
2392 mx_error_check_rv( errc, -1 );
2394 for(
auto it = m_testCommands.begin();
it != m_testCommands.end(); ++
it )
2399 bool changed =
false;
2400 for(
size_t n = 0; n < tfs.size(); ++n )
2403 m_testCommands.insert( std::pair<std::string, std::string>( mx::ioutils::pathStem( tfs[n] ), tfs[n] ) );
2404 if( ir.second ==
true )
2407 ir.first->second = tfs[n];
2410 for(
auto it = m_testCommands.begin();
it != m_testCommands.end(); ++
it )
2412 if(
it->second ==
"" )
2418 m_testCommands.erase( itdel );
2425 if( derived().m_indiDriver )
2427 derived().m_indiDriver->sendDelProperty( m_indiP_tests );
2428 derived().m_indiNewCallBacks.erase( m_indiP_tests.createUniqueKey() );
2431 m_indiP_tests = pcf::IndiProperty( pcf::IndiProperty::Switch );
2432 m_indiP_tests.setDevice( derived().configName() );
2433 m_indiP_tests.setName(
"test" );
2434 m_indiP_tests.setPerm( pcf::IndiProperty::ReadWrite );
2435 m_indiP_tests.setState( pcf::IndiProperty::Idle );
2436 m_indiP_tests.setRule( pcf::IndiProperty::OneOfMany );
2439 for(
auto it = m_testCommands.begin();
it != m_testCommands.end(); ++
it )
2441 if(
it->first == m_testCurrent || m_testCurrent ==
"" )
2443 m_indiP_tests.add( pcf::IndiElement(
it->first, pcf::IndiElement::On ) );
2444 m_testCurrent =
it->first;
2448 m_indiP_tests.add( pcf::IndiElement(
it->first, pcf::IndiElement::Off ) );
2452 if( m_testDefault !=
"" )
2454 if( m_testCurrent ==
"default" )
2456 m_indiP_tests.add( pcf::IndiElement(
"default", pcf::IndiElement::On ) );
2460 m_indiP_tests.add( pcf::IndiElement(
"default", pcf::IndiElement::Off ) );
2464 if( derived().registerIndiPropertyNew( m_indiP_tests, st_newCallBack_tests ) < 0 )
2466#ifndef DM_TEST_NOLOG
2467 derivedT::template log<software_error>( {
"" } );
2472 if( derived().m_indiDriver )
2474 derived().m_indiDriver->sendDefProperty( m_indiP_tests );
2481template <
class derivedT,
typename realT>
2484 std::string target = intarget;
2486 if( target ==
"default" )
2488 target = m_testDefault;
2491 std::string targetPath;
2495 targetPath = m_testCommands.at( target );
2499 derivedT::template log<text_log>(
"test file " + target +
" not found", logPrio::LOG_ERROR );
2503 m_testLoaded =
false;
2505 mx::fits::fitsFile<realT> ff;
2506 mx::error_t errc = ff.read( m_testCommand, targetPath );
2507 if( errc != mx::error_t::noerror )
2509 derivedT::template log<text_log>( std::format(
"error reading test file {}: "
2512 mx::errorMessage( errc ),
2513 mx::errorName( errc ) ),
2514 logPrio::LOG_ERROR );
2518 derivedT::template log<text_log>(
"loaded test file " + targetPath );
2519 m_testLoaded =
true;
2521 m_testCurrent = intarget;
2523 if( m_indiP_tests.find(
"default" ) )
2525 if( m_testCurrent ==
"default" )
2527 m_indiP_tests[
"default"] = pcf::IndiElement::On;
2531 m_indiP_tests[
"default"] = pcf::IndiElement::Off;
2535 for(
auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
2537 if( !m_indiP_tests.find( i->first ) )
2542 if( i->first == m_testCurrent )
2544 m_indiP_tests[i->first] = pcf::IndiElement::On;
2548 m_indiP_tests[i->first] = pcf::IndiElement::Off;
2552 if( derived().m_indiDriver )
2553 derived().m_indiDriver->sendSetProperty( m_indiP_tests );
2561template <
class derivedT,
typename realT>
2565 if( m_shmimTest ==
"" )
2568 if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2570 derivedT::template log<text_log>(
"could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2574 if( m_testImageStream.md->size[0] != m_dmWidth )
2576 ImageStreamIO_closeIm( &m_testImageStream );
2577 derivedT::template log<text_log>(
"width mismatch between " + m_shmimTest +
" and configured DM",
2578 logPrio::LOG_ERROR );
2582 if( m_testImageStream.md->size[1] != m_dmHeight )
2584 ImageStreamIO_closeIm( &m_testImageStream );
2585 derivedT::template log<text_log>(
"height mismatch between " + m_shmimTest +
" and configured DM",
2586 logPrio::LOG_ERROR );
2592 bool testSet = m_testSet;
2595 if( loadTest( m_testCurrent ) < 0 )
2597 derivedT::template log<text_log>(
"error loading test " + m_testCurrent, logPrio::LOG_ERROR );
2599 m_testSet = testSet;
2604 ImageStreamIO_closeIm( &m_testImageStream );
2605 derivedT::template log<text_log>(
"no test loaded", logPrio::LOG_ERROR );
2609 if( m_testCommand.rows() != m_dmWidth )
2611 ImageStreamIO_closeIm( &m_testImageStream );
2612 derivedT::template log<text_log>(
"width mismatch between test file and configured DM", logPrio::LOG_ERROR );
2616 if( m_testCommand.cols() != m_dmHeight )
2618 ImageStreamIO_closeIm( &m_testImageStream );
2619 derivedT::template log<text_log>(
"height mismatch between test file and configured DM", logPrio::LOG_ERROR );
2623 m_testImageStream.md->write = 1;
2628 memcpy( m_testImageStream.array.raw, m_testCommand.data(), m_dmWidth * m_dmHeight *
sizeof( realT ) );
2631 clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2634 m_testImageStream.md->atime = m_testImageStream.md->writetime;
2636 m_testImageStream.md->cnt0++;
2637 m_testImageStream.md->write = 0;
2638 ImageStreamIO_sempost( &m_testImageStream, -1 );
2643 ImageStreamIO_closeIm( &m_testImageStream );
2645 derived().updateSwitchIfChanged( m_indiP_setTest,
"toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy );
2647 derivedT::template log<text_log>(
"test set" );
2652template <
class derivedT,
typename realT>
2655 if( m_shmimTest ==
"" )
2658 if( ImageStreamIO_openIm( &m_testImageStream, m_shmimTest.c_str() ) != 0 )
2660 derivedT::template log<text_log>(
"could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING );
2664 if( m_testImageStream.md[0].size[0] != m_dmWidth )
2666 ImageStreamIO_closeIm( &m_testImageStream );
2667 derivedT::template log<text_log>(
"width mismatch between " + m_shmimTest +
" and configured DM",
2668 logPrio::LOG_ERROR );
2672 if( m_testImageStream.md[0].size[1] != m_dmHeight )
2674 ImageStreamIO_closeIm( &m_testImageStream );
2675 derivedT::template log<text_log>(
"height mismatch between " + m_shmimTest +
" and configured DM",
2676 logPrio::LOG_ERROR );
2680 m_testImageStream.md->write = 1;
2685 memset( m_testImageStream.array.raw, 0, m_dmWidth * m_dmHeight *
sizeof( realT ) );
2688 clock_gettime( CLOCK_REALTIME, &m_testImageStream.md->writetime );
2691 m_testImageStream.md->atime = m_testImageStream.md->writetime;
2693 m_testImageStream.md->cnt0++;
2694 m_testImageStream.md->write = 0;
2697 ImageStreamIO_sempost( &m_testImageStream, -1 );
2701 ImageStreamIO_closeIm( &m_testImageStream );
2703 derived().updateSwitchIfChanged( m_indiP_setTest,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2705 derivedT::template log<text_log>(
"test zeroed" );
2710template <
class derivedT,
typename realT>
2713 if( derived().m_shmimName ==
"" )
2720 for(
int n = 0; n < m_numChannels; ++n )
2723 snprintf( nstr,
sizeof( nstr ),
"%02d", n );
2724 std::string shmimN = derived().m_shmimName + nstr;
2726 if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2728 derivedT::template log<text_log>(
"could not connect to channel " + shmimN, logPrio::LOG_WARNING );
2732 if( imageStream.md->size[0] != m_dmWidth )
2734 ImageStreamIO_closeIm( &imageStream );
2735 derivedT::template log<text_log>(
"width mismatch between " + shmimN +
" and configured DM",
2736 logPrio::LOG_ERROR );
2737 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2741 if( imageStream.md->size[1] != m_dmHeight )
2743 ImageStreamIO_closeIm( &imageStream );
2744 derivedT::template log<text_log>(
"height mismatch between " + shmimN +
" and configured DM",
2745 logPrio::LOG_ERROR );
2746 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2750 imageStream.md->write = 1;
2751 memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight *
sizeof( realT ) );
2753 clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2756 imageStream.md->atime = imageStream.md->writetime;
2758 imageStream.md->cnt0++;
2759 imageStream.md->write = 0;
2762 if( n == m_numChannels - 1 && !nosem )
2764 ImageStreamIO_sempost( &imageStream, -1 );
2767 ImageStreamIO_closeIm( &imageStream );
2770 derivedT::template log<text_log>(
"all channels zeroed", logPrio::LOG_NOTICE );
2772 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2776 derived().updateSwitchIfChanged( m_indiP_setFlat,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2784 derived().updateSwitchIfChanged( m_indiP_setTest,
"toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle );
2787 if( ( rv = clearSat() ) < 0 )
2789 derivedT::template log<software_error>( { errno, rv,
"Error from clearSat" } );
2796template <
class derivedT,
typename realT>
2799 if( m_notDeltas.size() == 0 )
2804 m_totalFlat = ( *m_channels[m_notDeltas[0]] )();
2806 for(
size_t n = 1; n < m_notDeltas.size(); ++n )
2808 m_totalFlat += ( *m_channels[m_notDeltas[n]] )();
2811 m_outputDelta = m_outputShape() - m_totalFlat;
2813 m_totalDelta = ( *m_channels[m_deltas[0]] )();
2815 for(
size_t n = 1; n < m_deltas.size(); ++n )
2817 m_totalDelta += ( *m_channels[m_deltas[n]] )();
2820 m_outputDiff = m_totalDelta - m_outputDelta();
2825template <
class derivedT,
typename realT>
2828 if( m_shmimSat ==
"" || m_dmWidth == 0 || m_dmHeight == 0 )
2835 std::vector<std::string> sats = { m_shmimSat, m_shmimSatPerc };
2837 for(
size_t n = 0; n < sats.size(); ++n )
2839 std::string shmimN = sats[n];
2841 if( ImageStreamIO_openIm( &imageStream, shmimN.c_str() ) != 0 )
2843 derivedT::template log<text_log>(
"could not connect to sat map " + shmimN, logPrio::LOG_WARNING );
2847 if( imageStream.md->size[0] != m_dmWidth )
2849 ImageStreamIO_closeIm( &imageStream );
2850 derivedT::template log<text_log>(
"width mismatch between " + shmimN +
" and configured DM",
2851 logPrio::LOG_ERROR );
2852 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2856 if( imageStream.md->size[1] != m_dmHeight )
2858 ImageStreamIO_closeIm( &imageStream );
2859 derivedT::template log<text_log>(
"height mismatch between " + shmimN +
" and configured DM",
2860 logPrio::LOG_ERROR );
2861 derived().updateSwitchIfChanged( m_indiP_zeroAll,
"request", pcf::IndiElement::Off,
INDI_IDLE );
2865 imageStream.md->write = 1;
2866 memset( imageStream.array.raw, 0, m_dmWidth * m_dmHeight * ImageStreamIO_typesize( imageStream.md->datatype ) );
2868 clock_gettime( CLOCK_REALTIME, &imageStream.md->writetime );
2871 imageStream.md->atime = imageStream.md->writetime;
2873 imageStream.md->cnt0++;
2874 imageStream.md->write = 0;
2875 ImageStreamIO_sempost( &imageStream, -1 );
2877 ImageStreamIO_closeIm( &imageStream );
2880 m_accumSatMap.setZero();
2881 m_instSatMap.setZero();
2886template <
class derivedT,
typename realT>
2892template <
class derivedT,
typename realT>
2896 m_satThreadID = syscall( SYS_gettid );
2899 while( m_satThreadInit ==
true && derived().shutdown() == 0 )
2904 if( derived().shutdown() )
2909 uint32_t imsize[3] = { 0, 0, 0 };
2912 while( ( m_shmimSat ==
"" || m_accumSatMap.rows() == 0 || m_accumSatMap.cols() == 0 ) && !derived().shutdown() )
2917 if( derived().shutdown() )
2922 imsize[0] = m_dmWidth;
2923 imsize[1] = m_dmHeight;
2926 ImageStreamIO_createIm_gpu( &m_satImageStream,
2935 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2937 ImageStreamIO_createIm_gpu( &m_satPercImageStream,
2938 m_shmimSatPerc.c_str(),
2946 CIRCULAR_BUFFER | ZAXIS_TEMPORAL,
2951 m_satImageStream.md->cnt1 = 0;
2952 m_satPercImageStream.md->cnt1 = 0;
2955 mx::improc::eigenImage<uint8_t> satmap( m_dmWidth, m_dmHeight );
2958 double t_accumst = mx::sys::get_curr_time();
2961 while( !derived().shutdown() )
2965 if( clock_gettime( CLOCK_REALTIME, &ts ) < 0 )
2967 derivedT::template log<software_critical>( { errno, 0,
"clock_gettime" } );
2973 if( sem_timedwait( &m_satSemaphore, &ts ) == 0 )
2976 for(
int rr = 0; rr < m_instSatMap.rows(); ++rr )
2978 for(
int cc = 0; cc < m_instSatMap.cols(); ++cc )
2980 m_accumSatMap( rr, cc ) += m_instSatMap( rr, cc );
2986 if( mx::sys::get_curr_time( ts ) - t_accumst < m_satAvgInt / 1000.0 )
2993 for(
int rr = 0; rr < m_instSatMap.rows(); ++rr )
2995 for(
int cc = 0; cc < m_instSatMap.cols(); ++cc )
2997 m_satPercMap( rr, cc ) = m_accumSatMap( rr, cc ) / naccum;
2998 if( m_satPercMap( rr, cc ) >= m_percThreshold )
3002 satmap( rr, cc ) = ( m_accumSatMap( rr, cc ) > 0 );
3008 if( m_overSatAct / ( m_satPercMap.rows() * m_satPercMap.cols() * 0.75 ) > m_intervalSatThreshold )
3010 ++m_intervalSatExceeds;
3014 m_intervalSatExceeds = 0;
3018 if( m_intervalSatExceeds >= m_intervalSatCountThreshold )
3020 m_intervalSatTrip =
true;
3023 m_satImageStream.md->write = 1;
3024 m_satPercImageStream.md->write = 1;
3026 memcpy( m_satImageStream.array.raw, satmap.data(), m_dmWidth * m_dmHeight *
sizeof( uint8_t ) );
3027 memcpy( m_satPercImageStream.array.raw, m_satPercMap.data(), m_dmWidth * m_dmHeight *
sizeof(
float ) );
3030 clock_gettime( CLOCK_REALTIME, &m_satImageStream.md->writetime );
3031 m_satPercImageStream.md->writetime = m_satImageStream.md->writetime;
3034 m_satImageStream.md->atime = m_satImageStream.md->writetime;
3035 m_satPercImageStream.md->atime = m_satPercImageStream.md->writetime;
3038 m_satImageStream.md->cnt1 = 0;
3039 m_satPercImageStream.md->cnt1 = 0;
3042 m_satImageStream.md->cnt0++;
3043 m_satPercImageStream.md->cnt0++;
3045 m_satImageStream.writetimearray[0] = m_satImageStream.md->writetime;
3046 m_satImageStream.atimearray[0] = m_satImageStream.md->atime;
3047 m_satImageStream.cntarray[0] = m_satImageStream.md->cnt0;
3049 m_satPercImageStream.writetimearray[0] = m_satPercImageStream.md->writetime;
3050 m_satPercImageStream.atimearray[0] = m_satPercImageStream.md->atime;
3051 m_satPercImageStream.cntarray[0] = m_satPercImageStream.md->cnt0;
3054 m_satImageStream.md->write = 0;
3055 ImageStreamIO_sempost( &m_satImageStream, -1 );
3057 m_satPercImageStream.md->write = 0;
3058 ImageStreamIO_sempost( &m_satPercImageStream, -1 );
3060 m_accumSatMap.setZero();
3062 t_accumst = mx::sys::get_curr_time( ts );
3067 if( errno == EINTR )
3075 if( errno != ETIMEDOUT )
3077 derivedT::template log<software_error>( { errno,
"sem_timedwait" } );
3085 ImageStreamIO_destroyIm( &m_satImageStream );
3087 ImageStreamIO_destroyIm( &m_satPercImageStream );
3091template <
class derivedT,
typename realT>
3094 if( m_satTriggerDevice.size() > 0 && m_satTriggerProperty.size() == m_satTriggerDevice.size() )
3096 for(
size_t n = 0; n < m_satTriggerDevice.size(); ++n )
3101 pcf::IndiProperty ipFreq( pcf::IndiProperty::Switch );
3103 ipFreq.setDevice( m_satTriggerDevice[n] );
3104 ipFreq.setName( m_satTriggerProperty[n] );
3105 ipFreq.add( pcf::IndiElement(
"toggle" ) );
3106 ipFreq[
"toggle"] = pcf::IndiElement::Off;
3107 derived().sendNewProperty( ipFreq );
3109 derivedT::template log<text_log>(
"DM saturation threshold exceeded. Loop opened.",
3110 logPrio::LOG_WARNING );
3119template <
class derivedT,
typename realT>
3122 if( !derived().m_indiDriver )
3130template <
class derivedT,
typename realT>
3133 return static_cast<derivedT *
>( app )->newCallBack_init(
ipRecv );
3136template <
class derivedT,
typename realT>
3139 if(
ipRecv.createUniqueKey() != m_indiP_init.createUniqueKey() )
3141 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3144 if( !
ipRecv.find(
"request" ) )
3149 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3151 int rv = baseInitDM();
3154 return derivedT::template log<software_error, -1>( {
"error from initDM in INDI callback" } );
3161template <
class derivedT,
typename realT>
3164 return static_cast<derivedT *
>( app )->newCallBack_zero(
ipRecv );
3167template <
class derivedT,
typename realT>
3170 if(
ipRecv.createUniqueKey() != m_indiP_zero.createUniqueKey() )
3172 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3175 if( !
ipRecv.find(
"request" ) )
3178 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3180 return derived().zeroDM();
3185template <
class derivedT,
typename realT>
3188 return static_cast<derivedT *
>( app )->newCallBack_release(
ipRecv );
3191template <
class derivedT,
typename realT>
3194 if(
ipRecv.createUniqueKey() != m_indiP_release.createUniqueKey() )
3196 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3199 if( !
ipRecv.find(
"request" ) )
3202 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3204 return baseReleaseDM();
3209template <
class derivedT,
typename realT>
3212 return static_cast<derivedT *
>( app )->newCallBack_flats(
ipRecv );
3215template <
class derivedT,
typename realT>
3218 if(
ipRecv.createUniqueKey() != m_indiP_flats.createUniqueKey() )
3220 derivedT::template log<software_error>( {
"invalid indi property received" } );
3224 std::string newFlat;
3226 if(
ipRecv.find(
"default" ) )
3228 if(
ipRecv[
"default"].getSwitchState() == pcf::IndiElement::On )
3230 newFlat =
"default";
3235 for(
auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i )
3237 if( !
ipRecv.find( i->first ) )
3240 if(
ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3244 derivedT::template log<text_log>(
"More than one flat selected", logPrio::LOG_ERROR );
3257 return loadFlat( newFlat );
3260template <
class derivedT,
typename realT>
3263 return static_cast<derivedT *
>( app )->newCallBack_setFlat(
ipRecv );
3266template <
class derivedT,
typename realT>
3269 if(
ipRecv.createUniqueKey() != m_indiP_setFlat.createUniqueKey() )
3271 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3274 if( !
ipRecv.find(
"toggle" ) )
3277 if(
ipRecv[
"toggle"] == pcf::IndiElement::On )
3287template <
class derivedT,
typename realT>
3290 return static_cast<derivedT *
>( app )->newCallBack_tests(
ipRecv );
3293template <
class derivedT,
typename realT>
3296 if(
ipRecv.createUniqueKey() != m_indiP_tests.createUniqueKey() )
3298 derivedT::template log<software_error>( {
"invalid indi property received" } );
3302 std::string newTest;
3304 if(
ipRecv.find(
"default" ) )
3306 if(
ipRecv[
"default"].getSwitchState() == pcf::IndiElement::On )
3308 newTest =
"default";
3313 for(
auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i )
3315 if( !
ipRecv.find( i->first ) )
3318 if(
ipRecv[i->first].getSwitchState() == pcf::IndiElement::On )
3322 derivedT::template log<text_log>(
"More than one test selected", logPrio::LOG_ERROR );
3335 return loadTest( newTest );
3338template <
class derivedT,
typename realT>
3341 return static_cast<derivedT *
>( app )->newCallBack_setTest(
ipRecv );
3344template <
class derivedT,
typename realT>
3347 if(
ipRecv.createUniqueKey() != m_indiP_setTest.createUniqueKey() )
3349 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3352 if( !
ipRecv.find(
"toggle" ) )
3355 if(
ipRecv[
"toggle"] == pcf::IndiElement::On )
3365template <
class derivedT,
typename realT>
3368 return static_cast<derivedT *
>( app )->newCallBack_zeroAll(
ipRecv );
3371template <
class derivedT,
typename realT>
3374 if(
ipRecv.createUniqueKey() != m_indiP_zeroAll.createUniqueKey() )
3376 return derivedT::template log<software_error, -1>( {
"wrong INDI-P in callback" } );
3379 if( !
ipRecv.find(
"request" ) )
3382 if(
ipRecv[
"request"].getSwitchState() == pcf::IndiElement::On )
3385 m_indiP_zeroAll,
"request", pcf::IndiElement::On, derived().m_indiDriver,
INDI_BUSY );
3387 std::lock_guard<std::mutex> guard( derived().m_indiMutex );
3397#define DM_SETUP_CONFIG( cfig ) \
3398 if( dmT::setupConfig( cfig ) < 0 ) \
3400 log<software_error>( { "Error from dmT::setupConfig" } ); \
3401 m_shutdown = true; \
3409#define DM_LOAD_CONFIG( cfig ) \
3410 if( dmT::loadConfig( cfig ) < 0 ) \
3412 return log<software_error, -1>( { "Error from dmT::loadConfig" } ); \
3416#define DM_APP_STARTUP \
3417 if( dmT::appStartup() < 0 ) \
3419 return log<software_error, -1>( { "Error from dmT::appStartup" } ); \
3423#define DM_APP_LOGIC \
3424 if( dmT::appLogic() < 0 ) \
3426 return log<software_error, -1>( { "Error from dmT::appLogic" } ); \
3430#define DM_UPDATE_INDI \
3431 if( dmT::updateINDI() < 0 ) \
3433 return log<software_error, -1>( { "Error from dmT::updateINDI" } ); \
3437#define DM_APP_SHUTDOWN \
3438 if( dmT::appShutdown() < 0 ) \
3440 return log<software_error, -1>( { "Error from dmT::appShutdown" } ); \
#define IMAGESTRUCT_FLOAT
#define IMAGESTRUCT_UINT8
std::string m_calibPath
The path to this DM's calibration files.
std::map< std::string, std::string > m_flatCommands
Map of flat file name to full path.
int newCallBack_tests(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
bool m_testSet
Flag indicating whether the test command has been set.
std::string m_flatPath
The path to this DM's flat files (usually the same as calibPath)
float percThreshold() const
Get the saturation percentage threshold.
const std::string & testDefault() const
Get the.
const std::string & calibRelDir() const
uint32_t dmWidth() const
Get the DM Width.
std::string m_shmimDiff
The name of the shmim stream to write the difference to.
const mx::improc::eigenImage< uint16_t > & accumSatMap() const
pcf::IndiProperty m_indiP_setFlat
INDI toggle switch to set the current flat.
static void satThreadStart(dm *d)
Thread starter, called by MagAOXApp::threadStart on thread construction. Calls satThreadExec.
pcf::IndiProperty m_indiP_init
uint32_t m_dmWidth
The width of the images in the stream.
int satAvgInt() const
Get the saturation accumulation interval.
pcf::IndiProperty m_indiP_flats
INDI Selection switch containing the flat files.
int zeroFlat()
Zero the flat command on the DM.
static int st_newCallBack_init(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
int checkFlats()
Check the flats directory and update the list of flats if anything changes.
pcf::IndiProperty m_indiP_testShmim
Publish the shmim being used for the test command.
int checkTests()
Check the tests directory and update the list of tests if anything changes.
static int st_newCallBack_tests(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the test file.
const mx::improc::eigenImage< float > & satPercMap() const
std::string m_flatDefault
int setTest()
Send the current test command to the DM.
int clearSat()
Clear the saturation maps and zero the shared memory.
const std::vector< std::string > & satTriggerProperty() const
Get the saturation trigger property(ies)
static int st_newCallBack_zero(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
int loadTest(const std::string &target)
Load a test file.
int loadFlat(const std::string &target)
Load a flat file.
int m_satThreadPrio
Priority of the saturation thread. Usually ok to be 0.
IMAGE m_satPercImageStream
The ImageStreamIO shared memory buffer for the sat percentage map.
uint8_t dmDataType() const
Get the DM data type.
std::string m_calibRelDir
pcf::IndiProperty m_indiP_setTest
INDI toggle switch to set the current test pattern.
const std::string & shmimShape() const
Get the.
int findDMChannels()
Find the DM comb channels.
static constexpr uint8_t m_dmDataType
The ImageStreamIO type code.
std::vector< size_t > m_notDeltas
Indices of the channels which are not delta commands.
pcf::IndiProperty m_indiP_tests
INDI Selection switch containing the test pattern files.
mx::improc::eigenImage< realT > m_totalDelta
the total of all delta channels
mx::improc::eigenImage< realT > m_totalFlat
the total of all non-delta channels
std::string m_shmimDelta
The name of the shmim stream to write the desaturated delta command to.
std::string m_shmimFlat
The name of the shmim stream to write the flat to.
int processImage(void *curr_src, const dev::shmimT &sp)
const std::string & shmimSatPerc() const
Get the stream name for saturation percentage.
const std::string & shmimDelta() const
Get the.
std::vector< mx::improc::milkImage< realT > * > m_channels
uint32_t m_dmHeight
The height of the images in the stream.
int appShutdown()
DM shutdown.
std::string m_testPath
The path to this DM's test files (default is calibPath/tests;.
int newCallBack_zeroAll(const pcf::IndiProperty &ipRecv)
The callback for the zeroAll toggle switch, called by the static version.
bool m_flatSet
Flag indicating whether the flat command has been set.
pcf::IndiProperty m_satThreadProp
The property to hold the saturation thread details.
int baseReleaseDM()
Calls derived()->releaseDM() and then 0s all channels and the sat map.
std::string m_shmimSatPerc
The name of the shmim stream to write the saturation percentage map to.
int newCallBack_setTest(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
const std::string & calibPath() const
Get the.
static int st_newCallBack_flats(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the flat file.
std::string m_actMaskPath
The file name of the actuator mask for this DM.
mx::verbose::vvv verboseT
std::string m_testCurrent
bool m_flatLoaded
Flag indicating whether a flat is loaded in memory.
bool m_testLoaded
Flag indicating whether a test command is loaded in memory.
mx::improc::eigenImage< float > m_satPercMap
int setFlat(bool update=false)
Send the current flat command to the DM.
static int st_newCallBack_zeroAll(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for zeroing all channels.
const std::vector< std::string > & deltaChannels() const
int newCallBack_init(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
const std::string & shmimTest() const
Get the.
const std::vector< size_t > & notDeltas() const
const std::string & shmimFlat() const
Get the.
mx::improc::milkImage< realT > m_outputDelta
The true output delta command after saturation.
int zeroAll(bool nosem=false)
Zero all channels.
mx::improc::milkImage< realT > m_actMask
pid_t m_satThreadID
The ID of the saturation thread.
int intervalSatCountThreshold() const
Get the interval saturation count threshold.
int baseInitDM()
Calls derived()->initDM()
int zeroTest()
Zero the test command on the DM.
int m_satAvgInt
The time in milliseconds to accumulate saturation over.
int newCallBack_release(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
std::vector< std::string > m_satTriggerProperty
const std::string & flatDefault() const
Get the.
int updateINDI()
Update the INDI properties for this device controller.
std::vector< size_t > m_deltas
Indices of the channels which are delta commands.
mx::improc::milkImage< realT > m_outputDiff
The difference between command and true delta command after saturation.
int newCallBack_flats(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
sem_t m_satSemaphore
Semaphore used to tell the saturation thread to run.
pcf::IndiProperty m_indiP_flat
Property used to set and report the current flat.
IMAGE m_satImageStream
The ImageStreamIO shared memory buffer for the sat map.
int newCallBack_zero(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
bool m_satThreadInit
Synchronizer for thread startup, to allow priority setting to finish.
std::vector< std::string > m_satTriggerDevice
Device(s) with a toggle switch to toggle on saturation trigger.
mx::improc::eigenImage< realT > m_flatCommand
Data storage for the flat command.
int m_numChannels
The number of dmcomb channels found as part of allocation.
const mx::improc::eigenImage< uint8_t > & instSatMap() const
mx::improc::eigenImage< uint8_t > m_instSatMap
void satThreadExec()
Execute saturation processing.
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
static int st_newCallBack_release(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
pcf::IndiProperty m_indiP_flatShmim
Publish the shmim being used for the flat.
float m_intervalSatThreshold
int whilePowerOff()
DM Poweroff Updates.
static int st_newCallBack_setFlat(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the flat.
static int st_newCallBack_setTest(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the test shape.
pcf::IndiProperty m_indiP_zeroAll
std::map< std::string, std::string > m_testCommands
Map of test file name to full path.
int makeDelta()
Calculate the delta command from the output shape.
mx::improc::milkImage< realT > m_outputShape
The true output shape after saturation.
std::vector< std::string > m_deltaChannels
The names of channels which are treated as delta commands.
const std::string & testPath() const
Get the.
IMAGE m_testImageStream
The ImageStreamIO shared memory buffer for the test.
std::string m_testDefault
std::string m_shmimTest
The name of the shmim stream to write the test to.
std::string m_shmimShape
The name of the shmim stream to write the desaturated true shape to.
int m_intervalSatCountThreshold
int appStartup()
Startup function.
int newCallBack_setFlat(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
mx::improc::eigenImage< uint16_t > m_accumSatMap
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
int onPowerOff()
DM Poweroff.
float m_percThreshold
Threshold on percentage of frames an actuator is saturated over an interval.
int satThreadPrio() const
Get the saturation thread priority.
std::string m_shmimSat
The name of the shmim stream to write the saturation map to.
float intervalSatThreshold() const
Get the interval saturation threshold.
const mx::improc::eigenImage< float > & totalFlat() const
mx::improc::eigenImage< realT > m_testCommand
Data storage for the test command.
IMAGE m_flatImageStream
The ImageStreamIO shared memory buffer for the flat.
std::thread m_satThread
A separate thread for the actual saturation processing.
pcf::IndiProperty m_indiP_release
pcf::IndiProperty m_indiP_zero
uint32_t dmHeight() const
Get the DM Height.
int allocate(const dev::shmimT &sp)
Called after shmimMonitor connects to the dmXXdisp stream. Checks for proper size.
std::string m_flatCurrent
The name of the current flat command.
const std::vector< std::string > & satTriggerDevice() const
Get the saturation trigger device(s)
const std::string & shmimSat() const
Get the.
void intervalSatTrip()
Trigger loop openings because of excessive saturation.
const std::string & flatPath() const
Get the.
int appLogic()
DM application logic.
@ OPERATING
The device is operating, other than homing.
@ POWEROFF
The device power is off.
@ NOTHOMED
The device has not been homed.
@ HOMING
The device is homing.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ READY
The device is ready for operation, but is not operating.
constexpr uint8_t ImageStreamTypeCode< float >()
constexpr uint8_t ImageStreamTypeCode()
constexpr uint8_t ImageStreamTypeCode< double >()
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
const pcf::IndiProperty & ipRecv
The MagAO-X generic shared memory monitor.