From 534dbcc1e8473b63b16a36378e6a9cba9bf9017d Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Wed, 12 Apr 2023 12:48:06 -0700 Subject: [PATCH 01/66] Updated Scarab to v3.9.3 --- Scarab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scarab b/Scarab index 2b140c4..25fa1e5 160000 --- a/Scarab +++ b/Scarab @@ -1 +1 @@ -Subproject commit 2b140c456c06cfd9052dbb90a44f133cbe437eca +Subproject commit 25fa1e577eb0c0fd310e4a33a0e921bf8353ad21 From 15043629b66f2ebd481e09873f8fa0239bc5c170 Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Wed, 12 Apr 2023 13:35:03 -0700 Subject: [PATCH 02/66] Copied M3 over to M4 and tested building of the M4 library (still uses HDF5; to be replaced by Zarr) --- CMakeLists.txt | 23 +- Monarch4/CMakeLists.txt | 50 ++ Monarch4/Executables/CMakeLists.txt | 55 ++ Monarch4/Executables/M4Info.cc | 185 +++++ Monarch4/Executables/M4MultithreadingTest.cc | 163 ++++ Monarch4/Executables/M4ReadTest.cc | 427 +++++++++++ Monarch4/Executables/M4WriteSpeedTest.cc | 240 ++++++ Monarch4/Executables/M4WriteTest.cc | 230 ++++++ Monarch4/Executables/Monarch3Dump.cpp | 129 ++++ Monarch4/Executables/Monarch3TimeCheck.cpp | 116 +++ Monarch4/M4Constants.hh | 55 ++ Monarch4/M4DataInterface.hh | 540 +++++++++++++ Monarch4/M4Exception.cc | 39 + Monarch4/M4Exception.hh | 53 ++ Monarch4/M4Header.cc | 759 +++++++++++++++++++ Monarch4/M4Header.hh | 408 ++++++++++ Monarch4/M4IToA.cc | 141 ++++ Monarch4/M4IToA.hh | 189 +++++ Monarch4/M4MemberVariable.hh | 89 +++ Monarch4/M4Monarch.cc | 287 +++++++ Monarch4/M4Monarch.hh | 169 +++++ Monarch4/M4Record.cc | 98 +++ Monarch4/M4Record.hh | 123 +++ Monarch4/M4Stream.cc | 667 ++++++++++++++++ Monarch4/M4Stream.hh | 229 ++++++ Monarch4/M4Types.hh | 232 ++++++ Monarch4/M4Version.cc.in | 34 + Monarch4/M4Version.hh | 27 + 28 files changed, 5756 insertions(+), 1 deletion(-) create mode 100644 Monarch4/CMakeLists.txt create mode 100644 Monarch4/Executables/CMakeLists.txt create mode 100644 Monarch4/Executables/M4Info.cc create mode 100644 Monarch4/Executables/M4MultithreadingTest.cc create mode 100644 Monarch4/Executables/M4ReadTest.cc create mode 100644 Monarch4/Executables/M4WriteSpeedTest.cc create mode 100644 Monarch4/Executables/M4WriteTest.cc create mode 100644 Monarch4/Executables/Monarch3Dump.cpp create mode 100644 Monarch4/Executables/Monarch3TimeCheck.cpp create mode 100755 Monarch4/M4Constants.hh create mode 100644 Monarch4/M4DataInterface.hh create mode 100644 Monarch4/M4Exception.cc create mode 100644 Monarch4/M4Exception.hh create mode 100644 Monarch4/M4Header.cc create mode 100644 Monarch4/M4Header.hh create mode 100644 Monarch4/M4IToA.cc create mode 100644 Monarch4/M4IToA.hh create mode 100644 Monarch4/M4MemberVariable.hh create mode 100644 Monarch4/M4Monarch.cc create mode 100644 Monarch4/M4Monarch.hh create mode 100644 Monarch4/M4Record.cc create mode 100644 Monarch4/M4Record.hh create mode 100644 Monarch4/M4Stream.cc create mode 100644 Monarch4/M4Stream.hh create mode 100644 Monarch4/M4Types.hh create mode 100644 Monarch4/M4Version.cc.in create mode 100644 Monarch4/M4Version.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index cbbd81b..0b9f7aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,12 +12,13 @@ list( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/Scarab/cmake) include( PackageBuilder ) # Egg file version -set( Egg_VERSION "3.2.0" ) +set( Egg_VERSION "4.0.0" ) add_definitions( -DEgg_VERSION=${Egg_VERSION} ) # Monarch library options set( Monarch_BUILD_MONARCH2 FALSE CACHE BOOL "Build Monarch2 library (requires Protobuf)" ) set( Monarch_BUILD_MONARCH3 TRUE CACHE BOOL "Build Monarch3 library (requires HDF5)" ) +set( Monarch_BUILD_MONARCH4 TRUE CACHE BOOL "Build Monarch4 library (requires Zarr)" ) ######################## @@ -26,6 +27,21 @@ set( Monarch_BUILD_MONARCH3 TRUE CACHE BOOL "Build Monarch3 library (requires HD set( PUBLIC_EXT_LIBS_M2 ) set( PUBLIC_EXT_LIBS_M3 ) +set( PUBLIC_EXT_LIBS_M4 ) + + +###### +# Zarr +###### + +if( Monarch_BUILD_MONARCH4 ) + set( Monarch_BUILD_MONARCH3 TRUE CACHE BOOL "Build Monarch3 library (requires HDF5)" ) + + include_directories( BEFORE ${PROJECT_SOURCE_DIR}/Monarch4 ) + + +endif( Monarch_BUILD_MONARCH4 ) + ###### # HDF5 @@ -115,6 +131,7 @@ if( Monarch_BUILD_MONARCH3 ) endif( Monarch_BUILD_MONARCH3 ) +list( APPEND PUBLIC_EXT_LIBS_M4 ${PUBLIC_EXT_LIBS_M3} ) ########## # Protobuf @@ -161,6 +178,10 @@ if( Monarch_BUILD_MONARCH3 ) add_subdirectory( Monarch3 ) endif( Monarch_BUILD_MONARCH3 ) +if( Monarch_BUILD_MONARCH4 ) + add_subdirectory( Monarch4 ) +endif( Monarch_BUILD_MONARCH4 ) + ################## # package config # diff --git a/Monarch4/CMakeLists.txt b/Monarch4/CMakeLists.txt new file mode 100644 index 0000000..945e82e --- /dev/null +++ b/Monarch4/CMakeLists.txt @@ -0,0 +1,50 @@ +#################### +# monarch4 library # +#################### + +set( MONARCH4_HEADERFILES + M4Constants.hh + M4DataInterface.hh + M4Exception.hh + M4Header.hh + M4IToA.hh + M4MemberVariable.hh + M4Monarch.hh + M4Record.hh + M4Stream.hh + M4Types.hh + M4Version.hh +) + +set( MONARCH4_SOURCEFILES + M4Exception.cc + M4Header.cc + M4IToA.cc + M4Monarch.cc + M4Record.cc + M4Stream.cc +) + +configure_file( M4Version.cc.in ${CMAKE_CURRENT_BINARY_DIR}/M4Version.cc ) +set( MONARCH4_SOURCEFILES + ${MONARCH4_SOURCEFILES} + ${CMAKE_CURRENT_BINARY_DIR}/M4Version.cc +) + + +pbuilder_library( + TARGET Monarch4 + SOURCES ${MONARCH4_SOURCEFILES} + PUBLIC_EXTERNAL_LIBRARIES ${PUBLIC_EXT_LIBS_M4} + COMPILE_DEFINITIONS ${HDF5_DEFINITIONS} +) + +pbuilder_component_install_and_export( + COMPONENT M4Library + LIBTARGETS Monarch4 +) + +pbuilder_install_headers( ${MONARCH4_HEADERFILES} ) + +# Executables +add_subdirectory( Executables ) diff --git a/Monarch4/Executables/CMakeLists.txt b/Monarch4/Executables/CMakeLists.txt new file mode 100644 index 0000000..26405a2 --- /dev/null +++ b/Monarch4/Executables/CMakeLists.txt @@ -0,0 +1,55 @@ +######################## +# monarch4 executables # +######################## + +set( Monarch4_PROGRAMS ) + +if( Monarch_ENABLE_EXECUTABLES ) + + set( lib_dependencies + Monarch4 + ) + + set( sources + M4Info.cc + #Monarch4Dump.cc + ) + + pbuilder_executables( + SOURCES ${sources} + TARGETS_VAR Monarch4_PROGRAMS + PROJECT_LIBRARIES ${lib_dependencies} + ) + +endif( Monarch_ENABLE_EXECUTABLES ) + +if( Monarch_ENABLE_TESTING ) + + set( lib_dependencies + Monarch4 + ) + + set( sources + M4MultithreadingTest.cc + M4ReadTest.cc + M4WriteTest.cc + M4WriteSpeedTest.cc + #Monarch4TimeCheck.cc + ) + + pbuilder_executables( + SOURCES ${sources} + TARGETS_VAR Monarch4_TEST_PROGRAMS + PROJECT_LIBRARIES ${lib_dependencies} + ) + + list( APPEND Monarch4_PROGRAMS ${Monarch4_TEST_PROGRAMS} ) + +endif( Monarch_ENABLE_TESTING ) + +if( Monarch4_PROGRAMS ) + pbuilder_component_install_and_export( + COMPONENT M4Executables + EXETARGETS ${Monarch4_PROGRAMS} + ) +endif() diff --git a/Monarch4/Executables/M4Info.cc b/Monarch4/Executables/M4Info.cc new file mode 100644 index 0000000..48d50ce --- /dev/null +++ b/Monarch4/Executables/M4Info.cc @@ -0,0 +1,185 @@ +#ifdef _WIN32 +#define NOMINMAX +#endif + +#include "M4DataInterface.hh" +#include "M4Monarch.hh" +#include "M4Record.hh" + +#include "application.hh" +#include "logger.hh" + +#include +#include // for strcmp +#include + +using namespace monarch4; +using std::stringstream; + +LOGGER( mlog, "M4Info" ); + +template< typename XDataType > +bool PrintChannelsReal( const M4Stream* aStream, uint32_t aDataFormat ); + +template< typename XDataType > +bool PrintChannelsComplex( const M4Stream* aStream, uint32_t aDataFormat ); + +int main( const int argc, const char** argv ) +{ + scarab::main_app theMain( false ); + + bool tHeaderOnly; + std::string tFilename; + + theMain.add_flag( "-H,--header-only", tHeaderOnly, "Only look at header information; does not check number of records" ); + theMain.add_option( "Filename", tFilename, "File to read" )->required(); + + CLI11_PARSE( theMain, argc, argv ); + + try + { + LPROG( mlog, "Opening file <" << tFilename << ">" ); + std::shared_ptr< const Monarch4 > tReadTest( Monarch4::OpenForReading( tFilename ) ); + + LPROG( mlog, "Reading header" ); + tReadTest->ReadHeader(); + + const M4Header* tReadHeader = tReadTest->GetHeader(); + LPROG( mlog, *tReadHeader ); + + if( tHeaderOnly ) + { + tReadTest->FinishReading(); + return RETURN_SUCCESS; + } + + LPROG( mlog, "Reading data" ); + + unsigned tNStreams = tReadHeader->GetNStreams(); + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + const M4StreamHeader& tStrHeader = tReadHeader->StreamHeaders().at( iStream ); + unsigned tNChannels = tStrHeader.GetNChannels(); + //unsigned tRecSize = tStrHeader.GetRecordSize(); + LPROG( mlog, "Stream " << iStream << " has " << tNChannels << " channel(s) stored in format mode " << tStrHeader.GetChannelFormat() ); + + const M4Stream* tStream = tReadTest->GetStream( iStream ); + + if( tStream->GetNAcquisitions() == 0 ) + { + LPROG( mlog, "\tThis stream has no acquisitions" ); + continue; + } + + const unsigned tMaxRecords = 2; + for( unsigned iRec = 0; iRec < tMaxRecords; ++iRec ) + { + if( ! tStream->ReadRecord() ) + { + throw M4Exception() << "There was a problem reading a record from this stream"; + } + switch( tStrHeader.GetDataFormat() ) + { + case sDigitizedUS: + if( ! PrintChannelsReal< uint64_t >( tStream, sDigitizedUS ) ) + { + throw M4Exception() << "Problem printing channels (int)"; + } + break; + case sDigitizedS: + if( ! PrintChannelsReal< int64_t >( tStream, sDigitizedS ) ) + { + throw M4Exception() << "Problem printing channels (int)"; + } + break; + case sAnalog: + switch( tStream->GetSampleSize() ) + { + case 1: + if( ! PrintChannelsReal< double >( tStream, sAnalog ) ) + { + throw M4Exception() << "Problem printing channels (float)" ; + } + break; + case 2: + switch( tStream->GetDataTypeSize() ) + { + case 4: + if( ! PrintChannelsComplex< f4_complex >( tStream, sAnalog ) ) + { + throw M4Exception() << "Problem printing channels (float-complex)" ; + } + break; + case 8: + if( ! PrintChannelsComplex< f8_complex >( tStream, sAnalog ) ) + { + throw M4Exception() << "Problem printing channels (float-complex)"; + } + break; + default: + throw M4Exception() << "Invalid data type size: " << tStream->GetDataTypeSize(); + } + break; + default: + throw M4Exception() << "Invalid sample size: " << tStream->GetSampleSize(); + } + break; + default: + throw M4Exception() << "Invalid data format: "<FinishReading(); + } + catch( M4Exception& e ) + { + LERROR( mlog, "Exception thrown during file reading:\n" << e.what() ); + return RETURN_ERROR; + } + + return RETURN_SUCCESS; +} + +template< typename XDataType > +bool PrintChannelsReal( const M4Stream* aStream, uint32_t aDataFormat ) +{ + const unsigned tMaxSamples = 30; + unsigned tRecSize = aStream->GetChannelRecordSize(); + for( unsigned iChan = 0; iChan < aStream->GetNChannels(); ++iChan ) + { + const M4DataReader< XDataType > tDataInterface( aStream->GetChannelRecord( iChan )->GetData(), + aStream->GetDataTypeSize(), aDataFormat ); + stringstream tDataOut; + for( unsigned iSample = 0; iSample < std::min( tMaxSamples, tRecSize ); ++iSample ) + { + tDataOut << tDataInterface.at( iSample ); + if( iSample != tRecSize - 1 ) tDataOut << ", "; + } + if( tRecSize > tMaxSamples ) tDataOut << " . . ."; + LPROG( mlog, "\tChannel " << iChan << ": " << tDataOut.str() ); + } + return true; +} + +template< typename XDataType > +bool PrintChannelsComplex( const M4Stream* aStream, uint32_t aDataFormat ) +{ + const unsigned tMaxSamples = 30; + unsigned tRecSize = aStream->GetChannelRecordSize(); + for( unsigned iChan = 0; iChan < aStream->GetNChannels(); ++iChan ) + { + const M4ComplexDataReader< XDataType > tDataInterface( aStream->GetChannelRecord( iChan )->GetData(), + aStream->GetDataTypeSize(), aDataFormat, aStream->GetSampleSize() ); + stringstream tDataOut; + for( unsigned iSample = 0; iSample < std::min( tMaxSamples, tRecSize ); ++iSample ) + { + tDataOut << tDataInterface.at( iSample ); + if( iSample != tRecSize - 1 ) tDataOut << ", "; + } + if( tRecSize > tMaxSamples ) tDataOut << " . . ."; + LPROG( mlog, "\tChannel " << iChan << ": " << tDataOut.str() ); + } + return true; +} diff --git a/Monarch4/Executables/M4MultithreadingTest.cc b/Monarch4/Executables/M4MultithreadingTest.cc new file mode 100644 index 0000000..5e8c67d --- /dev/null +++ b/Monarch4/Executables/M4MultithreadingTest.cc @@ -0,0 +1,163 @@ +/* + * Monarch4MultithreadingTest.cc + * + * Created on: Dec 22, 2016 + * Author: N. Oblath + * + * Use: Monarch4MultithreadingTest [options] + * + * Options: + * filename=[string] -- default: multithreading_test.egg + */ + +#include "M4DataInterface.hh" +#include "M4Monarch.hh" + +#include "application.hh" +#include "logger.hh" +#include "param.hh" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace monarch4; + +LOGGER( mlog, "M4MultithreadingTest" ); + +int main( int argc, char** argv ) +{ + try + { + scarab::main_app theMain; + + theMain.default_config().add( "filename", "multithreading_test.egg" ); + + theMain.add_config_option< std::string >( "Filename", "filename", "Test output filename" ); + + CLI11_PARSE( theMain, argc, argv ); + + std::string tFilename = theMain.primary_config()[ "filename" ]().as_string(); + + unsigned tNRecords = 5; + unsigned tNStreams = 10; + unsigned tArraySize = 1024; + unsigned tSampleSize = 1; + unsigned tDataTypeSize = 1; + + std::shared_ptr< Monarch4 > tWriteTest( Monarch4::OpenForWriting( tFilename ) ); + + LINFO( mlog, "Preparing header" ); + M4Header* tHeader = tWriteTest->GetHeader(); + tHeader->Filename() = tFilename; + tHeader->SetRunDuration( 8675309 ); + tHeader->Timestamp() = "Stardate 33515"; + tHeader->Description() = "Bigger on the inside"; + + LINFO( mlog, "Adding stream(s)" ); + + std::vector< unsigned > tStreamNums( tNStreams ); + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + std::stringstream tStr; + tStr << "Channel " << iStream; + unsigned tSingleStreamNum = tHeader->AddStream( tStr.str(), 100, tArraySize, tSampleSize, tDataTypeSize, sDigitizedUS, tDataTypeSize * 8, sBitsAlignedLeft ); + tStreamNums[ iStream ] = tSingleStreamNum; + } + + tWriteTest->WriteHeader(); + + LINFO( mlog, "Wrote header:\n" << *tHeader ); + + + LINFO( mlog, "Creating fake data array" ); + + unsigned tNBytes = tArraySize * tDataTypeSize * tSampleSize; + std::vector< byte_type > tDataMaster( tNBytes ); + + M4DataWriter< uint8_t > tDMWriter( tDataMaster.data(), tDataTypeSize, sDigitizedUS ); + for( unsigned iBin = 0; iBin < tArraySize; ++iBin ) + { + tDMWriter.set_at( 42, iBin ); + } + + + LINFO( mlog, "Getting stream pointers" ); + + std::vector< M4Stream* > tStreams( tNStreams ); + std::vector< byte_type* > tStreamData( tNStreams ); + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + tStreams[ iStream ] = tWriteTest->GetStream( tStreamNums[ iStream ] ); + tStreamData[ iStream ] = tStreams[ iStream ]->GetStreamRecord()->GetData(); + } + + LINFO( mlog, "Writing data" ); + + + std::mutex tRunMutex; + std::condition_variable tRunRelease; + + std::vector< std::thread > tThreads; + std::atomic< unsigned > tNThreadsReady( 0 ); + + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + tThreads.push_back( std::thread( [&, iStream]{ + //LDEBUG( mlog, "Starting thread " << iStream << "; waiting for start signal" ); + bool tIsNewAcq = true; + std::unique_lock< std::mutex > tRunLock( tRunMutex ); + tNThreadsReady++; + tRunRelease.wait( tRunLock ); + for( unsigned iRecord = 0; iRecord < tNRecords; ++iRecord ) + { + ::memcpy( tStreamData[ iStream ], tDataMaster.data(), tNBytes ); + if( ! tStreams[ iStream ]->WriteRecord( tIsNewAcq ) ) + { + LERROR( mlog, "Unable to write record <" << iRecord << "> for stream <" << iStream << ">" ); + return; + } + tIsNewAcq = false; + } + //LDEBUG( mlog, "Thread " << iStream << " is finished" ); + } ) ); + } + + // Synchronize threads + LINFO( mlog, "Waiting for threads to be ready" ); + while( tNThreadsReady.load() != tNStreams ) + { + std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); + } + std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); + + LINFO( mlog, "Releasing threads" ); + tRunRelease.notify_all(); + + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + tThreads[ iStream ].join(); + } + + LINFO( mlog, "Closing file" ); + + tWriteTest->FinishWriting(); + + LINFO( mlog, "Test finished" ); + + } + catch( std::exception& e ) + { + LERROR( mlog, "Exception thrown during write-speed test:\n" << e.what() ); + return RETURN_ERROR; + } + + return RETURN_SUCCESS; +} diff --git a/Monarch4/Executables/M4ReadTest.cc b/Monarch4/Executables/M4ReadTest.cc new file mode 100644 index 0000000..7bdf941 --- /dev/null +++ b/Monarch4/Executables/M4ReadTest.cc @@ -0,0 +1,427 @@ +#ifdef _WIN32 +#define NOMINMAX +#endif + +#include "M4DataInterface.hh" +#include "M4Monarch.hh" +#include "application.hh" +#include "logger.hh" +#include "M4Record.hh" + +#include +#include // for strcmp +#include + +using namespace monarch4; +using std::stringstream; + +LOGGER( mlog, "M4ReadTest" ); + +bool ReadRecordCheck( const M4Stream* aStream, int aOffset, unsigned aDataFormat ); +bool PrintChannelsUInt( const M4Stream* aStream ); +bool PrintChannelsInt( const M4Stream* aStream ); +bool PrintChannelsFloat( const M4Stream* aStream ); +bool PrintChannelsFloatComplex( const M4Stream* aStream ); + +int main( const int argc, const char** argv ) +{ + scarab::main_app theMain( false ); + + bool tHeaderOnly; + std::string tFilename; + theMain.add_flag( "-H,--header-only", tHeaderOnly, "Only look at header information; does not check number of records" ); + theMain.add_option( "Filename", tFilename, "File to read" )->required(); + + CLI11_PARSE( theMain, argc, argv ); + + try + { + LINFO( mlog, "Opening file <" << tFilename ); + std::shared_ptr< const Monarch4 > tReadTest( Monarch4::OpenForReading( tFilename ) ); + + LINFO( mlog, "Reading header" ); + tReadTest->ReadHeader(); + + const M4Header* tReadHeader = tReadTest->GetHeader(); + LINFO( mlog, *tReadHeader ); + + if( tHeaderOnly ) + { + tReadTest->FinishReading(); + return RETURN_SUCCESS; + } + + LINFO( mlog, "Reading data" ); + + LINFO( mlog, "Test 1: reading 2 sequential 1-channel records from stream 0"); + LINFO( mlog, "\tRecord 0 has values '1'" ); + LINFO( mlog, "\tRecord 1 has values '10'" ); + + const M4Stream* tStream0 = tReadTest->GetStream( 0 ); + unsigned tNAcquisitions0 = tStream0->GetNAcquisitions(); + unsigned tNRecords0 = tStream0->GetNRecordsInFile(); + LINFO( mlog, "Stream 0 has " << tNAcquisitions0 << " acquisitions and " << tNRecords0 << " records" ); + const M4StreamHeader& tStrHeader0 = tReadHeader->StreamHeaders().at( 0 ); + unsigned tNChannels0 = tStrHeader0.GetNChannels(); + //unsigned tRecSize0 = tStrHeader0.GetRecordSize(); + LINFO( mlog, "Stream 0 has " << tNChannels0 << " channel(s) stored in format mode " << tStrHeader0.GetChannelFormat() ); + if( tNAcquisitions0 != 1 || tNChannels0 != 1 || tNRecords0 != 2 ) + { + LERROR( mlog, "Invalid number of acquisitions (1 expected), channels (1 expected), or records (2 expected)" ); + return RETURN_ERROR; + } + + for( unsigned iRec = 0; iRec < tNRecords0; ++iRec ) + { + LINFO( mlog, "Checking record " << iRec ); + if( ! ReadRecordCheck( tStream0, 0, tStrHeader0.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + } + + LINFO( mlog, "Test 1 complete\n" ); + + + + LINFO( mlog, "Test 2: using non-zero reading offsets"); + LINFO( mlog, "\tRecord 0 has values '1' and '2'" ); + LINFO( mlog, "\tRecord 1 has values '1000' and '2000'" ); + LINFO( mlog, "\tRecord 2 has values '10000' and '20000'" ); + + const M4Stream* tStream1 = tReadTest->GetStream( 1 ); + unsigned tNAcquisitions1 = tStream1->GetNAcquisitions(); + unsigned tNRecords1 = tStream1->GetNRecordsInFile(); + LINFO( mlog, "Stream 1 has " << tNAcquisitions1 << " acquisitions and " << tNRecords1 << " records" ); + const M4StreamHeader& tStrHeader1 = tReadHeader->StreamHeaders().at( 1 ); + unsigned tNChannels1 = tStrHeader1.GetNChannels(); + //unsigned tRecSize1 = tStrHeader1.GetRecordSize(); + LINFO( mlog, "Stream 1 has " << tNChannels1 << " channel(s) stored in format mode " << tStrHeader1.GetChannelFormat() ); + if( tNAcquisitions1 != 2 || tNChannels1 != 2 || tNRecords1 != 3 ) + { + LERROR( mlog, "Invalid number of acquisitions (2 expected), channels (2 expected), or records (3 expected)" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Read the first record (record 0)" ); + if( ! ReadRecordCheck( tStream1, 0, tStrHeader1.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Skip to the third record, crossing to the next acquisition (record 2; acquisition 1)" ); + if( ! ReadRecordCheck( tStream1, 1, tStrHeader1.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Reread the third record (record 2; acquisition 1)" ); + if( ! ReadRecordCheck( tStream1, -1, tStrHeader1.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Go backwards to the second record (record 1; acquisition 1)" ); + if( ! ReadRecordCheck( tStream1, -2, tStrHeader1.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Go backwards to the first record (record 1; acquisition 0)" ); + if( ! ReadRecordCheck( tStream1, -2, tStrHeader1.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Reread the first record (record 1; acquisition 0)" ); + if( ! ReadRecordCheck( tStream1, -1, tStrHeader1.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Request record beyond the end of the file" ); + if( tStream1->ReadRecord( 5 ) ) + { + LERROR( mlog, "Record read did not fail" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Test 2 complete\n" ); + + + + LINFO( mlog, "Test 3: using non-zero reading offset to skip the first record"); + LINFO( mlog, "\tRecord 0 has values '1', '2', and '3'" ); + LINFO( mlog, "\tRecord 1 has values '10', '20', and '30'" ); + + const M4Stream* tStream2 = tReadTest->GetStream( 2 ); + unsigned tNAcquisitions2 = tStream2->GetNAcquisitions(); + unsigned tNRecords2 = tStream2->GetNRecordsInFile(); + LINFO( mlog, "Stream 2 has " << tNAcquisitions2 << " acquisitions and " << tNRecords2 << " records" ); + const M4StreamHeader& tStrHeader2 = tReadHeader->StreamHeaders().at( 2 ); + unsigned tNChannels2 = tStrHeader2.GetNChannels(); + //unsigned tRecSize2 = tStrHeader2.GetRecordSize(); + LINFO( mlog, "Stream 2 has " << tNChannels2 << " channel(s) stored in format mode " << tStrHeader2.GetChannelFormat() ); + if( tNAcquisitions2 != 1 || tNChannels2 != 3 || tNRecords2 != 2 ) + { + LERROR( mlog, "Invalid number of acquisitions (1 expected), channels (3 expected), or records (2 expected)" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Skipping immediately to the second record (record 1)" ); + if( ! ReadRecordCheck( tStream2, 1, tStrHeader2.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Request record before the beginning of the file" ); + if( tStream2->ReadRecord( -3 ) ) + { + LERROR( mlog, "Record read did not fail" ); + return RETURN_ERROR; + } + + LINFO( mlog, "Test 3 complete\n" ); + + + LINFO( mlog, "Test 4: reading 2 sequential 1-channel floating-point records from stream 3"); + LINFO( mlog, "\tRecord 0 has values '3.1415926535898'" ); + LINFO( mlog, "\tRecord 1 has values '2.71828182846'" ); + + const M4Stream* tStreaM4 = tReadTest->GetStream( 3 ); + unsigned tNAcquisitions3 = tStreaM4->GetNAcquisitions(); + unsigned tNRecords3 = tStreaM4->GetNRecordsInFile(); + LINFO( mlog, "Stream 3 has " << tNAcquisitions3 << " acquisitions and " << tNRecords3 << " records" ); + const M4StreamHeader& tStrHeader3 = tReadHeader->StreamHeaders().at( 3 ); + unsigned tNChannels3 = tStrHeader3.GetNChannels(); + //unsigned tRecSize3 = tStrHeader3.GetRecordSize(); + LINFO( mlog, "Stream 3 has " << tNChannels3 << " channel(s) stored in format mode " << tStrHeader3.GetChannelFormat() ); + if( tNAcquisitions3 != 2 || tNChannels3 != 1 || tNRecords3 != 2 ) + { + LERROR( mlog, "Invalid number of acquisitions (2 expected), channels (1 expected), or records (2 expected)" ); + return RETURN_ERROR; + } + + for( unsigned iRec = 0; iRec < tNRecords3; ++iRec ) + { + LINFO( mlog, "Checking record " << iRec ); + if( ! ReadRecordCheck( tStreaM4, 0, tStrHeader3.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + } + + LINFO( mlog, "Test 4 complete\n" ); + +/* + LINFO( mlog, "Test 5: reading 2 sequential 5-channel interleaved complex floating-point records from stream 4"); + LINFO( mlog, "\tRecord 0 always has values '(0, 0)'" ); + LINFO( mlog, "\tRecord 1 has values '(1.1, 1.001) and (-1.1, -1.001)'" ); + LINFO( mlog, "\tRecord 2 has values '(2.2, 2.002) and (-2.2, -2.002)'" ); + LINFO( mlog, "\tRecord 3 has values '(3.3, 3.003) and (-3.3, -3.003)'" ); + LINFO( mlog, "\tRecord 4 has values '(4.4, 4.004) and (-4.4, -4.004)'" ); + + const M4Stream* tStream4 = tReadTest->GetStream( 4 ); + unsigned tNAcquisitions4 = tStream4->GetNAcquisitions(); + unsigned tNRecords4 = tStream4->GetNRecordsInFile(); + LINFO( mlog, "Stream 4 has " << tNAcquisitions4 << " acquisitions and " << tNRecords4 << " records" ); + const M4StreamHeader& tStrHeader4 = tReadHeader->GetStreamHeaders().at( 3 ); + unsigned tNChannels4 = tStrHeader4.GetNChannels(); + //unsigned tRecSize4 = tStrHeader4.GetRecordSize(); + LINFO( mlog, "Stream 4 has " << tNChannels4 << " channel(s) stored in format mode " << tStrHeader4.GetChannelFormat() ); + if( tNAcquisitions4 != 1 || tNChannels4 != 5 || tNRecords4 != 2 ) + { + LERROR( mlog, "Invalid number of acquisitions (1 expected), channels (5 expected), or records (2 expected)" ); + return RETURN_ERROR; + } + + for( unsigned iRec = 0; iRec < tNRecords4; ++iRec ) + { + LINFO( mlog, "Checking record " << iRec ); + if( ! ReadRecordCheck( tStream4, 0, tStrHeader4.GetDataFormat() ) ) + { + LERROR( mlog, "Failed read record check" ); + return RETURN_ERROR; + } + } + + LINFO( mlog, "Test 5 complete\n" ); +*/ + + + + tReadTest->FinishReading(); + } + catch( M4Exception& e ) + { + LERROR( mlog, "Exception thrown during file reading:\n" << e.what() ); + return RETURN_ERROR; + } + + return RETURN_SUCCESS; +} + +bool ReadRecordCheck( const M4Stream* aStream, int aOffset, unsigned aDataFormat ) +{ + if( ! aStream->ReadRecord( aOffset ) ) + { + LERROR( mlog, "Failed to read record" ); + return false; + } + + switch( aDataFormat ) + { + case sDigitizedUS: + if( ! PrintChannelsUInt( aStream ) ) + { + LERROR( mlog, "Failed to print channels (digitized)" ); + return false; + } + break; + case sDigitizedS: + if( ! PrintChannelsUInt( aStream ) ) + { + LERROR( mlog, "Failed to print channels (digitized)" ); + return false; + } + break; + case sAnalog: + switch( aStream->GetSampleSize() ) + { + case 1: + if( ! PrintChannelsFloat( aStream ) ) + { + LERROR( mlog, "Problem printing channels (float)" ); + return 0; + } + break; + default: + if( ! PrintChannelsFloatComplex( aStream ) ) + { + LERROR( mlog, "Problem printing channels (float-complex)" ); + return 0; + } + break; + } + break; + default: + LERROR( mlog, "Invalid data format" ); + return false; + } + + return true; +} + +bool PrintChannelsUInt( const M4Stream* aStream ) +{ + unsigned tRecSize = aStream->GetChannelRecordSize(); + const unsigned tMaxSamples = 30; + for( unsigned iChan = 0; iChan < aStream->GetNChannels(); ++iChan ) + { + const M4DataReader< uint64_t > tDataInterface( aStream->GetChannelRecord( iChan )->GetData(), + aStream->GetDataTypeSize(), sDigitizedUS ); + stringstream tDataOut; + for( unsigned iSample = 0; iSample < std::min( tMaxSamples, tRecSize ); ++iSample ) + { + tDataOut << tDataInterface.at( iSample ); + if( iSample != tRecSize - 1 ) tDataOut << ", "; + } + if( tRecSize > tMaxSamples ) tDataOut << " . . ."; + LINFO( mlog, "\tChannel " << iChan << ": " << tDataOut.str() ); + } + return true; +} + +bool PrintChannelsInt( const M4Stream* aStream ) +{ + unsigned tRecSize = aStream->GetChannelRecordSize(); + const unsigned tMaxSamples = 30; + for( unsigned iChan = 0; iChan < aStream->GetNChannels(); ++iChan ) + { + const M4DataReader< int64_t > tDataInterface( aStream->GetChannelRecord( iChan )->GetData(), + aStream->GetDataTypeSize(), sDigitizedS ); + stringstream tDataOut; + for( unsigned iSample = 0; iSample < std::min( tMaxSamples, tRecSize ); ++iSample ) + { + tDataOut << tDataInterface.at( iSample ); + if( iSample != tRecSize - 1 ) tDataOut << ", "; + } + if( tRecSize > tMaxSamples ) tDataOut << " . . ."; + LINFO( mlog, "\tChannel " << iChan << ": " << tDataOut.str() ); + } + return true; +} + +bool PrintChannelsFloat( const M4Stream* aStream ) +{ + unsigned tRecSize = aStream->GetChannelRecordSize(); + const unsigned tMaxSamples = 30; + for( unsigned iChan = 0; iChan < aStream->GetNChannels(); ++iChan ) + { + const M4DataReader< double > tDataInterface( aStream->GetChannelRecord( iChan )->GetData(), + aStream->GetDataTypeSize(), sAnalog ); + stringstream tDataOut; + for( unsigned iSample = 0; iSample < std::min( tMaxSamples, tRecSize ); ++iSample ) + { + tDataOut << tDataInterface.at( iSample ); + if( iSample != tRecSize - 1 ) tDataOut << ", "; + } + if( tRecSize > tMaxSamples ) tDataOut << " . . ."; + LINFO( mlog, "\tChannel " << iChan << ": " << tDataOut.str() ); + } + return true; +} + +bool PrintChannelsFloatComplex( const M4Stream* aStream ) +{ + // NOTE: this implementation is specific to f4_complex and f8_complex, and therefore assumes only 2 elements in each sample + const unsigned tMaxSamples = 30; + unsigned tRecSize = aStream->GetChannelRecordSize(); + switch( aStream->GetDataTypeSize() ) + { + case 4: + for( unsigned iChan = 0; iChan < aStream->GetNChannels(); ++iChan ) + { + const M4ComplexDataReader< f4_complex > tDataInterface( aStream->GetChannelRecord( iChan )->GetData(), + aStream->GetDataTypeSize(), sAnalog, aStream->GetSampleSize() ); + stringstream tDataOut; + for( unsigned iSample = 0; iSample < std::min( tMaxSamples, tRecSize ); ++iSample ) + { + tDataOut << "(" << tDataInterface.at( iSample )[ 0 ] << ", " << tDataInterface.at( iSample )[ 1 ] << ")"; + if( iSample != tRecSize - 1 ) tDataOut << ", "; + } + if( tRecSize > tMaxSamples ) tDataOut << " . . ."; + LINFO( mlog, "\tChannel " << iChan << ": " << tDataOut.str() ); + } + break; + case 8: + for( unsigned iChan = 0; iChan < aStream->GetNChannels(); ++iChan ) + { + const M4ComplexDataReader< f8_complex > tDataInterface( aStream->GetChannelRecord( iChan )->GetData(), + aStream->GetDataTypeSize(), sAnalog, aStream->GetSampleSize() ); + stringstream tDataOut; + for( unsigned iSample = 0; iSample < std::min( tMaxSamples, tRecSize ); ++iSample ) + { + tDataOut << "(" << tDataInterface.at( iSample )[ 0 ] << ", " << tDataInterface.at( iSample )[ 1 ] << ")"; + if( iSample != tRecSize - 1 ) tDataOut << ", "; + } + if( tRecSize > tMaxSamples ) tDataOut << " . . ."; + LINFO( mlog, "\tChannel " << iChan << ": " << tDataOut.str() ); + } + break; + default: + LERROR( mlog, "Cannot print channels for complex floating-point data with type size " << aStream->GetDataTypeSize() ); + return false; + break; + } + return true; +} diff --git a/Monarch4/Executables/M4WriteSpeedTest.cc b/Monarch4/Executables/M4WriteSpeedTest.cc new file mode 100644 index 0000000..3cea330 --- /dev/null +++ b/Monarch4/Executables/M4WriteSpeedTest.cc @@ -0,0 +1,240 @@ +/* + * Monarch4WriteSpeedTest.cc + * + * Created on: Dec 21, 2016 + * Author: N. Oblath + * + * Description: Measures the speed of writing a specified amount of data to disk. + * + * Single- or multi-threaded operation; in single-threaded operation all streams are written sequentially for each record. + * + * Use: Monarch4WriteSpeedTest [options] + * + * Options: + * multithreaded=(true | false) -- default: false + * n-records=[unsigned int] -- default: 10000 + * n-streams=[unsigned int] -- default: 1 + * array-size=[unsigned int] -- default: 1024 + * data-type-size=[unsigned int] -- default: 1 + */ + +#include "M4DataInterface.hh" +#include "M4Monarch.hh" + +#include "application.hh" +#include "logger.hh" +#include "param.hh" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace monarch4; + +LOGGER( mlog, "M4WriteSpeedTest" ); + +int main( int argc, char** argv ) +{ + scarab::main_app theMain; + + theMain.default_config().add( "multithreaded", new scarab::param_value( false ) ); + theMain.default_config().add( "n-records", new scarab::param_value( 10000U ) ); + theMain.default_config().add( "n-streams", new scarab::param_value( 1U ) ); + theMain.default_config().add( "array-size", new scarab::param_value( 1024U ) ); + theMain.default_config().add( "data-type-size", new scarab::param_value( 1U ) ); + + theMain.add_config_option< std::string >( "Filename", "filename", "Test output filename" ); + theMain.add_config_flag< bool >( "-m,--multithreaded", "multithreaded", "Use multithreaded write" ); + theMain.add_config_option< unsigned >( "-n,--n-records", "n-records", "Number of records to write" ); + theMain.add_config_option< unsigned >( "-N,--n-streams", "n-streams", "Number of streams to write" ); + theMain.add_config_option< unsigned >( "-a,--array-size", "array-size", "Array size" ); + theMain.add_config_option< unsigned >( "-d,--data-type-size", "data-type-size", "Data-type size" ); + + CLI11_PARSE( theMain, argc, argv ); + + try + { + bool tMultithreaded = theMain.primary_config()[ "multithreaded" ]().as_bool(); + unsigned tNRecords = theMain.primary_config()[ "n-records" ]().as_uint(); + unsigned tNStreams = theMain.primary_config()[ "n-streams" ]().as_uint(); + unsigned tArraySize = theMain.primary_config()[ "array-size" ]().as_uint(); + unsigned tSampleSize = 1; // currently not configurable + unsigned tDataTypeSize = theMain.primary_config()[ "data-type-size" ]().as_uint(); + + double tMBToWrite = (double)(tNRecords * tNStreams * tArraySize * tSampleSize * tDataTypeSize) * 10.e-6; + + if( tNStreams == 0 ) + { + LERROR( mlog, "Please specify a number of streams > 0" ); + return RETURN_ERROR; + } + + + boost::filesystem::path tFilePath = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + std::shared_ptr< Monarch4 > tWriteTest( Monarch4::OpenForWriting( tFilePath.native() ) ); + LINFO( mlog, "Temp file is " << tFilePath ); + + LINFO( mlog, "Preparing header" ); + M4Header* tHeader = tWriteTest->GetHeader(); + tHeader->Filename() = "tempfile"; + tHeader->SetRunDuration( 8675309 ); + tHeader->Timestamp( ) ="Stardate 33515"; + tHeader->Description() = "Bigger on the inside"; + + LINFO( mlog, "Adding stream(s)" ); + + std::vector< unsigned > tStreamNums( tNStreams ); + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + std::stringstream tStr; + tStr << "Channel " << iStream; + unsigned tSingleStreamNum = tHeader->AddStream( tStr.str(), 100, tArraySize, tSampleSize, tDataTypeSize, sDigitizedUS, tDataTypeSize * 8, sBitsAlignedLeft ); + tStreamNums[ iStream ] = tSingleStreamNum; + } + + tWriteTest->WriteHeader(); + + LINFO( mlog, "Wrote header:\n" << *tHeader ); + + + LINFO( mlog, "Creating fake data array" ); + + unsigned tNBytes = tArraySize * tDataTypeSize * tSampleSize; + std::vector< byte_type > tDataMaster( tNBytes ); + + M4DataWriter< uint8_t > tDMWriter( tDataMaster.data(), tDataTypeSize, sDigitizedUS ); + for( unsigned iBin = 0; iBin < tArraySize; ++iBin ) + { + tDMWriter.set_at( 42, iBin ); + } + + + LINFO( mlog, "Getting stream pointers" ); + + std::vector< M4Stream* > tStreams( tNStreams ); + std::vector< byte_type* > tStreamData( tNStreams ); + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + tStreams[ iStream ] = tWriteTest->GetStream( tStreamNums[ iStream ] ); + tStreamData[ iStream ] = tStreams[ iStream ]->GetStreamRecord()->GetData(); + } + + LINFO( mlog, "Writing data" ); + + using std::chrono::steady_clock; + using std::chrono::duration; + using std::chrono::duration_cast; + + if( tMultithreaded ) + { + + std::mutex tRunMutex; + std::condition_variable tRunRelease; + + std::vector< std::thread > tThreads; + std::atomic< unsigned > tNThreadsReady( 0 ); + + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + tThreads.push_back( std::thread( [&, iStream]{ + //LDEBUG( mlog, "Starting thread " << iStream << "; waiting for start signal" ); + bool tIsNewAcq = true; + std::unique_lock< std::mutex > tRunLock( tRunMutex ); + tNThreadsReady++; + tRunRelease.wait( tRunLock ); + for( unsigned iRecord = 0; iRecord < tNRecords; ++iRecord ) + { + ::memcpy( tStreamData[ iStream ], tDataMaster.data(), tNBytes ); + if( ! tStreams[ iStream ]->WriteRecord( tIsNewAcq ) ) + { + LERROR( mlog, "Unable to write record <" << iRecord << "> for stream <" << iStream << ">" ); + return; + } + tIsNewAcq = false; + } + //LDEBUG( mlog, "Thread " << iStream << " is finished" ); + } ) ); + } + + // Synchronize threads + LINFO( mlog, "Waiting for threads to be ready" ); + while( tNThreadsReady.load() != tNStreams ) + { + std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); + } + std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); + + LINFO( mlog, "Releasing threads" ); + tRunRelease.notify_all(); + + steady_clock::time_point tStartTime = steady_clock::now(); + + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + tThreads[ iStream ].join(); + } + + steady_clock::time_point tEndTime = steady_clock::now(); + + duration< double > tDuration = duration_cast< duration< double > >( tEndTime - tStartTime ); + + LINFO( mlog, "Processing time: " << tDuration.count() << " sec" ); + LINFO( mlog, "Size of data written: " << tMBToWrite << " MB" ); + LINFO( mlog, "Write speed: " << tMBToWrite / tDuration.count() << " MB/s" ); + + } + else + { + + bool tIsNewAcq = true; + + steady_clock::time_point tStartTime = steady_clock::now(); + + for( unsigned iRecord = 0; iRecord < tNRecords; ++iRecord ) + { + for( unsigned iStream = 0; iStream < tNStreams; ++iStream ) + { + ::memcpy( tStreamData[ iStream ], tDataMaster.data(), tNBytes ); + if( ! tStreams[ iStream ]->WriteRecord( tIsNewAcq ) ) + { + LERROR( mlog, "Unable to write record <" << iRecord << "> for stream <" << iStream << ">" ); + return RETURN_ERROR; + } + } + tIsNewAcq = false; + } + + steady_clock::time_point tEndTime = steady_clock::now(); + + duration< double > tDuration = duration_cast< duration< double > >( tEndTime - tStartTime ); + + LINFO( mlog, "Processing time: " << tDuration.count() << " sec" ); + LINFO( mlog, "Size of data written: " << tMBToWrite << " MB" ); + LINFO( mlog, "Write speed: " << tMBToWrite / tDuration.count() << " MB/s" ); + + } + + LINFO( mlog, "Closing file" ); + + tWriteTest->FinishWriting(); + + boost::filesystem::remove( tFilePath ); + + LINFO( mlog, "Test finished" ); + + } + catch( std::exception& e ) + { + LERROR( mlog, "Exception thrown during write-speed test:\n" << e.what() ); + return RETURN_ERROR; + } + + return RETURN_SUCCESS; +} diff --git a/Monarch4/Executables/M4WriteTest.cc b/Monarch4/Executables/M4WriteTest.cc new file mode 100644 index 0000000..a08c2f8 --- /dev/null +++ b/Monarch4/Executables/M4WriteTest.cc @@ -0,0 +1,230 @@ +#include "M4DataInterface.hh" +#include "M4Monarch.hh" + +#include "application.hh" +#include "logger.hh" + +#include // for strcmp + +using namespace monarch4; + +LOGGER( mlog, "M4WriteTest" ); + +int main( const int argc, const char** argv ) +{ + scarab::main_app theMain( false ); + + std::string tFilename; + theMain.add_option( "Filename", tFilename, "Test output filename" )->default_val( "write_test_output.egg" ); + + CLI11_PARSE( theMain, argc, argv ); + + try + { + std::shared_ptr< Monarch4 > tWriteTest( Monarch4::OpenForWriting( tFilename ) ); + + M4Header* tHeader = tWriteTest->GetHeader(); + tHeader->Filename() = tFilename; + tHeader->SetRunDuration( 8675309 ); + tHeader->Timestamp() = "Stardate 33515"; + tHeader->Description() = "Bigger on the inside"; + + // number of samples in each stream + unsigned tSSSamples = 10; + unsigned tDSSamples = 5; + unsigned tTSSamples = 5; + unsigned tFlSSamples = 10; + //unsigned tFlCompSamples = 5; + + LINFO( mlog, "Adding streams" ); + unsigned tSingleStreamNum = tHeader->AddStream( "1-channel device", 500, tSSSamples, 1, 1, sDigitizedUS, 8, sBitsAlignedLeft ); + unsigned tDoubleStreamNum = tHeader->AddStream( "2-channel device", 2, sInterleaved, 250, tDSSamples, 1, 2, sDigitizedUS, 16, sBitsAlignedLeft ); + unsigned tTripleStreamNum = tHeader->AddStream( "3-channel device", 3, sSeparate, 100, tTSSamples, 1, 1, sDigitizedUS, 8, sBitsAlignedLeft ); + unsigned tFloatStreamNum = tHeader->AddStream( "Floating-point device", 100, tFlSSamples, 1, 4, sAnalog, 8, sBitsAlignedLeft ); + // multi-channel multi-sample writing commented out until fixed + //unsigned tFlCompStreamNum = tHeader->AddStream( "Complex Floating-point device", 5, sInterleaved, 100, tFlCompSamples, 2, 8, sAnalog, 16 ); + + tWriteTest->WriteHeader(); + + LINFO( mlog, "Wrote header:\n" << *tHeader ); + + LINFO( mlog, "Writing data" ); + + // Stream 0 + M4Stream* tSingleStream = tWriteTest->GetStream( tSingleStreamNum ); + byte_type* tSSData = tSingleStream->GetStreamRecord()->GetData(); + for( unsigned iSample = 0; iSample < tSSSamples; ++iSample ) + { + tSSData[ iSample ] = 1; + } + if( ! tSingleStream->WriteRecord( true ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + for( unsigned iSample = 0; iSample < tSSSamples; ++iSample ) + { + tSSData[ iSample ] = 10; + } + if( ! tSingleStream->WriteRecord( false ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + + // Stream 1 + M4Stream* tDoubleStream = tWriteTest->GetStream( tDoubleStreamNum ); + M4DataWriter< uint16_t > tDSData0( tDoubleStream->GetChannelRecord( 0 )->GetData(), 2, sDigitizedUS ); + M4DataWriter< uint16_t > tDSData1( tDoubleStream->GetChannelRecord( 1 )->GetData(), 2, sDigitizedUS ); + for( unsigned iSample = 0; iSample < tDSSamples; ++iSample ) + { + tDSData0.set_at( 1, iSample ); + tDSData1.set_at( 2, iSample ); + } + if( ! tDoubleStream->WriteRecord( true ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + for( unsigned iSample = 0; iSample < tDSSamples; ++iSample ) + { + tDSData0.set_at( 1000, iSample ); + tDSData1.set_at( 2000, iSample ); + } + if( ! tDoubleStream->WriteRecord( true ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + for( unsigned iSample = 0; iSample < tDSSamples; ++iSample ) + { + tDSData0.set_at( 10000, iSample ); + tDSData1.set_at( 20000, iSample ); + } + if( ! tDoubleStream->WriteRecord( false ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + + // Stream 2 + M4Stream* tTripleStream = tWriteTest->GetStream( tTripleStreamNum ); + byte_type* tTSData0 = tTripleStream->GetChannelRecord( 0 )->GetData(); + byte_type* tTSData1 = tTripleStream->GetChannelRecord( 1 )->GetData(); + byte_type* tTSData2 = tTripleStream->GetChannelRecord( 2 )->GetData(); + for( unsigned iSample = 0; iSample < tTSSamples; ++iSample ) + { + tTSData0[ iSample ] = 1; + tTSData1[ iSample ] = 2; + tTSData2[ iSample ] = 3; + } + if( ! tTripleStream->WriteRecord( true ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + for( unsigned iSample = 0; iSample < tTSSamples; ++iSample ) + { + tTSData0[ iSample ] = 10; + tTSData1[ iSample ] = 20; + tTSData2[ iSample ] = 30; + } + if( ! tTripleStream->WriteRecord( false ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + + + // Stream 3 + M4Stream* tFloatStream = tWriteTest->GetStream( tFloatStreamNum ); + M4DataWriter< float > tFlSData( tFloatStream->GetChannelRecord( 0 )->GetData(), 4, sAnalog ); + for( unsigned iSample = 0; iSample < tFlSSamples; ++iSample ) + { + tFlSData.set_at( 3.1415926535898, iSample ); + } + if( ! tFloatStream->WriteRecord( true ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + for( unsigned iSample = 0; iSample < tFlSSamples; ++iSample ) + { + tFlSData.set_at( 2.71828182846, iSample ); + } + if( ! tFloatStream->WriteRecord( true ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + + + + // Stream 4 + /* + M4Stream* tFlCompStream = tWriteTest->GetStream( tFlCompStreamNum ); + M4ComplexDataWriter< f8_complex > tFlCompSData0( tFlCompStream->GetChannelRecord( 0 )->GetData(), 8, sAnalog, 2 ); + M4ComplexDataWriter< f8_complex > tFlCompSData1( tFlCompStream->GetChannelRecord( 1 )->GetData(), 8, sAnalog, 2 ); + M4ComplexDataWriter< f8_complex > tFlCompSData2( tFlCompStream->GetChannelRecord( 2 )->GetData(), 8, sAnalog, 2 ); + M4ComplexDataWriter< f8_complex > tFlCompSData3( tFlCompStream->GetChannelRecord( 3 )->GetData(), 8, sAnalog, 2 ); + M4ComplexDataWriter< f8_complex > tFlCompSData4( tFlCompStream->GetChannelRecord( 3 )->GetData(), 8, sAnalog, 2 ); + f8_complex value0, value1, value2, value3, value4; + value0[ 0 ] = 0.0; value0[ 1 ] = 0.0; + value1[ 0 ] = 1.1; value1[ 1 ] = 1.001; + value2[ 0 ] = 2.2; value1[ 1 ] = 2.002; + value3[ 0 ] = 3.3; value1[ 1 ] = 3.003; + value4[ 0 ] = 4.4; value1[ 1 ] = 4.004; + for( unsigned iSample = 0; iSample < tFlCompSamples; ++iSample ) + { + tFlCompSData0.set_at( value0, iSample ); + tFlCompSData1.set_at( value1, iSample ); + tFlCompSData2.set_at( value2, iSample ); + tFlCompSData3.set_at( value3, iSample ); + tFlCompSData4.set_at( value4, iSample ); + } + if( ! tFlCompStream->WriteRecord( true ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + + value0[ 0 ] = -0.0; value0[ 1 ] = -0.0; + value1[ 0 ] = -1.1; value1[ 1 ] = -1.001; + value2[ 0 ] = -2.2; value1[ 1 ] = -2.002; + value3[ 0 ] = -3.3; value1[ 1 ] = -3.003; + value4[ 0 ] = -4.4; value1[ 1 ] = -4.004; + for( unsigned iSample = 0; iSample < tFlCompSamples; ++iSample ) + { + tFlCompSData0.set_at( value0, iSample ); + tFlCompSData1.set_at( value1, iSample ); + tFlCompSData2.set_at( value2, iSample ); + tFlCompSData3.set_at( value3, iSample ); + tFlCompSData4.set_at( value4, iSample ); + } + if( ! tFlCompStream->WriteRecord( false ) ) + { + LERROR( mlog, "Unable to write the record!" ); + return RETURN_ERROR; + } + */ + + tWriteTest->FinishWriting(); + LINFO( mlog, "File closed" ); + } + catch( M4Exception& e ) + { + LERROR( mlog, "Exception thrown during write test:\n" << e.what() ); + return RETURN_ERROR; + } + + return RETURN_SUCCESS; +} diff --git a/Monarch4/Executables/Monarch3Dump.cpp b/Monarch4/Executables/Monarch3Dump.cpp new file mode 100644 index 0000000..d84165e --- /dev/null +++ b/Monarch4/Executables/Monarch3Dump.cpp @@ -0,0 +1,129 @@ +#include "Monarch.hpp" +#include "MonarchLogger.hpp" + +#include + +#include +using std::ofstream; + +using namespace monarch; + +MLOGGER( mlog, "MonarchDump" ); + +int main( const int argc, const char** argv ) +{ + if( argc < 3 ) + { + MLINFO( mlog, "usage:\n" + << " MonarchDump <# of records per channel [optional]>\n" + << "# of records is optional; the default is 1; use 0 to dump the whole file" ); + return -1; + } + + unsigned int nRecords = 1; + if( argc >= 4 ) + { + nRecords = atoi( argv[3] ); + } + + const Monarch* tReadTest = Monarch::OpenForReading( argv[ 1 ] ); + tReadTest->ReadHeader(); + tReadTest->SetInterface( sInterfaceSeparate ); + + const MonarchHeader* tReadHeader = tReadTest->GetHeader(); + MLINFO( mlog, *tReadHeader ); + + unsigned int tRecordCount = 0; + unsigned int tAcquisitionCount = 0; + + if( tReadHeader->GetFormatMode() == sFormatSingle ) + { + ofstream tOutputOne( (string( argv[ 2 ] ) + string( "_ch1.txt" )).c_str() ); + if( tOutputOne.is_open() == false ) + { + MERROR( mlog, "could not open channel one output file!" ); + tReadTest->Close(); + delete tReadTest; + return -1; + } + + const unsigned tDataTypeSize = tReadHeader->GetDataTypeSize(); + const MonarchRecordBytes* tReadRecord = tReadTest->GetRecordSeparateOne(); + const MonarchRecordDataInterface< uint64_t > tData( tReadRecord->fData, tDataTypeSize ); + unsigned int tRecordsPerChannel = 0; + while( tReadTest->ReadRecord() != false ) + { + tRecordCount++; + tRecordsPerChannel++; + if( tReadRecord->fAcquisitionId == tAcquisitionCount ) + { + tAcquisitionCount = tAcquisitionCount + 1; + tOutputOne << "\n\n"; + } + for( unsigned int tIndex = 0; tIndex < tReadHeader->GetRecordSize(); tIndex++ ) + { + tOutputOne << tIndex << " " << tData.at( tIndex ) << "\n"; + } + if (nRecords != 0 && tRecordsPerChannel >= nRecords) + break; + } + + tOutputOne.close(); + } + if( (tReadHeader->GetFormatMode() == sFormatMultiInterleaved) || (tReadHeader->GetFormatMode() == sFormatMultiSeparate) ) + { + ofstream tOutputOne( (string( argv[ 2 ] ) + string( "_ch1.txt" )).c_str() ); + ofstream tOutputTwo( (string( argv[ 2 ] ) + string( "_ch2.txt" )).c_str() ); + if( tOutputOne.is_open() == false ) + { + MERROR( mlog, "could not open channel one output file!" ); + tReadTest->Close(); + delete tReadTest; + return -1; + } + if( tOutputTwo.is_open() == false ) + { + MERROR( mlog, "could not open channel two output file!" ); + tReadTest->Close(); + delete tReadTest; + return -1; + } + + const unsigned tDataTypeSize = tReadHeader->GetDataTypeSize(); + const MonarchRecordBytes* tReadRecordOne = tReadTest->GetRecordSeparateOne(); + const MonarchRecordDataInterface< uint64_t > tDataOne( tReadRecordOne->fData, tDataTypeSize ); + const MonarchRecordBytes* tReadRecordTwo = tReadTest->GetRecordSeparateTwo(); + const MonarchRecordDataInterface< uint64_t > tDataTwo( tReadRecordTwo->fData , tDataTypeSize); + unsigned int tRecordsPerChannel = 0; + while( tReadTest->ReadRecord() != false ) + { + tRecordCount = tRecordCount + 1; + tRecordsPerChannel++; + if( tReadRecordOne->fAcquisitionId == tAcquisitionCount ) + { + tAcquisitionCount = tAcquisitionCount + 1; + tOutputOne << "\n\n"; + tOutputTwo << "\n\n"; + } + for( unsigned int tIndex = 0; tIndex < tReadHeader->GetRecordSize(); tIndex++ ) + { + tOutputOne << tIndex << " " << tDataOne.at( tIndex ) << "\n"; + tOutputTwo << tIndex << " " << tDataTwo.at( tIndex ) << "\n"; + } + if (nRecords != 0 && tRecordsPerChannel >= nRecords) + break; + } + + tOutputOne.close(); + tOutputTwo.close(); + } + + MLINFO( mlog, "record count <" << tRecordCount << ">" ); + MLINFO( mlog, "acquisition count <" << tAcquisitionCount << ">" ); + + tReadTest->Close(); + delete tReadTest; + + return 0; +} + diff --git a/Monarch4/Executables/Monarch3TimeCheck.cpp b/Monarch4/Executables/Monarch3TimeCheck.cpp new file mode 100644 index 0000000..372d40c --- /dev/null +++ b/Monarch4/Executables/Monarch3TimeCheck.cpp @@ -0,0 +1,116 @@ +/* + * MonarchTimeCheck.cpp + * + * Created on: May 16, 2013 + * Author: nsoblath + */ + +#include "Monarch.hpp" +#include "MonarchLogger.hpp" + +#include +using std::ofstream; + +using namespace monarch; + +MLOGGER( mlog, "MonarchTimeCheck" ); + +int main( const int argc, const char** argv ) +{ + if( argc < 3 ) + { + MLINFO( mlog, "usage:\n" + << " MonarchTimeCheck " ); + return -1; + } + + ofstream tOutput( argv[ 2 ] ); + if( tOutput.is_open() == false ) + { + MERROR( mlog, "could not open output file!" ); + return -1; + } + + const Monarch* tReadTest = Monarch::OpenForReading( argv[1] ); + tReadTest->ReadHeader(); + + const MonarchHeader* tReadHeader = tReadTest->GetHeader(); + MLINFO( mlog, *tReadHeader ); + + TimeType tRecordSize = (TimeType)tReadHeader->GetRecordSize(); + TimeType tBinWidthNS = (TimeType)(1000. / tReadHeader->GetAcquisitionRate()); // in ns + + const MonarchRecordBytes* tReadRecord; + if( tReadHeader->GetAcquisitionMode() == 1 /* the FormatMode is ignored for single-channel data */ ) + { + tReadRecord = tReadTest->GetRecordSeparateOne(); + } + else if( tReadHeader->GetAcquisitionMode() == 2 && tReadHeader->GetFormatMode() == sFormatMultiSeparate ) + { + tReadRecord = tReadTest->GetRecordSeparateOne(); + } + else if( tReadHeader->GetAcquisitionMode() == 2 && tReadHeader->GetFormatMode() == sFormatMultiInterleaved ) + { + tReadRecord = tReadTest->GetRecordInterleaved(); + } + else + { + MERROR( mlog, "Unable to read a header with acquisition mode <" << tReadHeader->GetAcquisitionMode() << "> and format mode <" << tReadHeader->GetFormatMode() << ">" ); + return -1; + } + + unsigned long long tRecordCount = 0; + unsigned long long tAcquisitionCount = 0; + + TimeType tNSTimeInRunClock = 0; + TimeType tNSTimeInRunBins = 0; + TimeType tNSTimeInRunBinsCorr = 0; // corrected to the Clock time when there's a new acquisition + + // read first record + if (! tReadTest->ReadRecord()) + { + MERROR( mlog, "No records in the file" ); + return -1; + } + tRecordCount = 1; + tAcquisitionCount = 1; + tNSTimeInRunClock = tReadRecord->fTime; + tNSTimeInRunBinsCorr = tNSTimeInRunClock; + tNSTimeInRunBins = tNSTimeInRunClock; + + tOutput << tRecordCount << '\t' << tNSTimeInRunClock << '\t' << tNSTimeInRunBins << '\t' << tNSTimeInRunBinsCorr << '\n'; + + while( tReadTest->ReadRecord() != false ) + { + tRecordCount = tRecordCount + 1; + + tNSTimeInRunClock = tReadRecord->fTime; + + if( tReadRecord->fAcquisitionId == tAcquisitionCount ) + { + cout << "new acquisition" << endl; + tAcquisitionCount = tAcquisitionCount + 1; + tNSTimeInRunBinsCorr = tNSTimeInRunClock; + } + else + { + tNSTimeInRunBinsCorr += tRecordSize * tBinWidthNS; + } + tNSTimeInRunBins += tRecordSize * tBinWidthNS; + + tOutput << tRecordCount << '\t' << tNSTimeInRunClock << '\t' << tNSTimeInRunBins << '\t' << tNSTimeInRunBinsCorr << '\n'; + + //cout << " record " << tRecordCount << ": time offset: " << tReadRecord->fTime << " ns" << endl; + } + MLINFO( mlog, "record count <" << tRecordCount << ">" ); + MLINFO( mlog, "acquisition count <" << tAcquisitionCount << ">" ); + + tReadTest->Close(); + delete tReadTest; + + tOutput.close(); + + return 0; +} + + diff --git a/Monarch4/M4Constants.hh b/Monarch4/M4Constants.hh new file mode 100755 index 0000000..7487e09 --- /dev/null +++ b/Monarch4/M4Constants.hh @@ -0,0 +1,55 @@ +/* + * M4Constants.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4CONSTANTS_HH_ +#define M4CONSTANTS_HH_ + +// API export macros for windows +#ifdef _WIN32 +# ifdef M4_API_EXPORTS +# define M4_API __declspec(dllexport) +# define M4_EXPIMP_TEMPLATE +# else +# define M4_API __declspec(dllimport) +# define M4_EXPIMP_TEMPLATE extern +# endif +#else +# define M4_API +#endif + +#ifdef _WIN32 +#include +#endif + +#include +#include + +namespace monarch4 +{ + // channel information + + /// Data Format + /// Specifies whether the data is digitized (unsigned or signed) or analog + static const uint32_t sInvalidFormat = std::numeric_limits< uint32_t >::max(); + static const uint32_t sDigitizedUS = 0; + static const uint32_t sDigitizedS = 1; + static const uint32_t sAnalog = 2; + + /// Specifies whether the meaningful bits for each sample are aligned to the left or right in each sample word + /// e.g. For 14-bit digitizer bit depth, represented by a 16-bit sample word, are the 14 bits aligned to the left or right in the 16-bit word? + static const uint32_t sBitsAlignedLeft = 0; + static const uint32_t sBitsAlignedRight = 1; + + // stream information + + /// Specifies whether the data channels are interleaved or separate in a stream + static const uint32_t sInterleaved = 0; + static const uint32_t sSeparate = 1; + +} + +#endif // M4CONSTANTS_HH_ diff --git a/Monarch4/M4DataInterface.hh b/Monarch4/M4DataInterface.hh new file mode 100644 index 0000000..6374418 --- /dev/null +++ b/Monarch4/M4DataInterface.hh @@ -0,0 +1,540 @@ +/* + * M4DataInterface.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4DATAINTERFACE_HH_ +#define M4DATAINTERFACE_HH_ + +#include "M4Constants.hh" +#include "M4Exception.hh" +#include "M4Types.hh" + +namespace monarch4 +{ + + /// Binary-compatible to fftwf_complex + typedef float f4_complex[ 2 ]; + /// Binary-compatible to fftw_complex + typedef double f8_complex[ 2 ]; + + /*! + @class M4DataWriter + @author N. S. Oblath + + @brief Interface class for a variety of data types + + @details + Provides write-only access to a byte_type array as if it were an array of: + - uint8_t -- 1-byte unsigned integers (i.e. byte_type) + - uint16_t -- 2-byte unsigned integers + - uint32_t -- 4-byte unsigned integers + - uint64_t -- 8-byte unsigned integers + - int8_t -- 1-byte signed integers + - int16_t -- 2-byte signed integers + - int32_t -- 4-byte signed integers + - int64_t -- 8-byte signed integers + - float -- 4-byte floating point + - double -- 8-byte floating point + */ + template< typename SetType > + class M4DataWriter + { + public: + M4DataWriter( byte_type* aData, unsigned aDataTypeSize, uint32_t aDataFormat ) : + fUByteData( aData ) + { + SetInterface( aDataTypeSize, aDataFormat ); + } + ~M4DataWriter() + { + } + + void set_at( SetType value, unsigned index ) + { + (this->*fArrayFcn)( value, index ); + } + + void SetInterface( unsigned aDataTypeSize, uint32_t aDataFormat ) + { + if( aDataFormat == sDigitizedUS ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4DataWriter< SetType >::set_at_u1; + else if( aDataTypeSize == 2 ) fArrayFcn = &M4DataWriter< SetType >::set_at_u2; + else if( aDataTypeSize == 4 ) fArrayFcn = &M4DataWriter< SetType >::set_at_u4; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4DataWriter< SetType >::set_at_u8; + else + { + throw M4Exception() << "Unable to make a digitized unsigned data interface with data type size " << aDataTypeSize; + } + } + else if( aDataFormat == sDigitizedS ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4DataWriter< SetType >::set_at_i1; + else if( aDataTypeSize == 2 ) fArrayFcn = &M4DataWriter< SetType >::set_at_i2; + else if( aDataTypeSize == 4 ) fArrayFcn = &M4DataWriter< SetType >::set_at_i4; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4DataWriter< SetType >::set_at_i8; + else + { + throw M4Exception() << "Unable to make a digitized signed data interface with data type size " << aDataTypeSize; + } + } + else if( aDataFormat == sAnalog ) + { + if( aDataTypeSize == 4 ) fArrayFcn = &M4DataWriter< SetType >::set_at_f4; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4DataWriter< SetType >::set_at_f8; + else + { + throw M4Exception() << "Unable to make a analog data interface with data type size " << aDataTypeSize; + } + } + else + { + throw M4Exception() << "Invalid combination of data format <" << aDataFormat << ">, data type size <" << aDataTypeSize << ">"; + } + return; + } + + void SetData( const byte_type* aData ) + { + fUByteData = aData; + } + + private: + void set_at_u1( SetType value, unsigned index ) + { + fUByteData[ index ] = value; + } + + void set_at_u2( SetType value, unsigned index ) + { + fU2BytesData[ index ] = value; + } + + void set_at_u4( SetType value, unsigned index ) + { + fU4BytesData[ index ] = value; + } + + void set_at_u8( SetType value, unsigned index ) + { + fU8BytesData[ index ] = value; + } + + void set_at_i1( SetType value, unsigned index ) + { + fIByteData[ index ] = value; + } + + void set_at_i2( SetType value, unsigned index ) + { + fI2BytesData[ index ] = value; + } + + void set_at_i4( SetType value, unsigned index ) + { + fI4BytesData[ index ] = value; + } + + void set_at_i8( SetType value, unsigned index ) + { + fI8BytesData[ index ] = value; + } + + void set_at_f4( SetType value, unsigned index ) + { + fF4BytesData[ index ] = value; + } + + void set_at_f8( SetType value, unsigned index ) + { + fF8BytesData[ index ] = value; + } + + void (M4DataWriter::*fArrayFcn)( SetType, unsigned ); + + union + { + uint8_t* fUByteData; + uint16_t* fU2BytesData; + uint32_t* fU4BytesData; + uint64_t* fU8BytesData; + int8_t* fIByteData; + int16_t* fI2BytesData; + int32_t* fI4BytesData; + int64_t* fI8BytesData; + float* fF4BytesData; + double* fF8BytesData; + }; + }; + + /*! + @class M4ComplexDataWriter + @author N. S. Oblath + + @brief Interface class for complex data types + + @details + Provides write-only access to a byte_type array as if it were an array of: + - f4_complex (i.e. float[2]) -- binary-compatible to fftwf_complex + - f8_complex (i.e. double[2]) -- binary-compatible to fftw_complex + */ + template< typename SetType > + class M4ComplexDataWriter + { + public: + M4ComplexDataWriter( byte_type* aData, unsigned aDataTypeSize, uint32_t aDataFormat, unsigned aSampleSize = 2 ) : + fUByteData( aData ) + { + SetInterface( aDataTypeSize, aDataFormat, aSampleSize ); + } + ~M4ComplexDataWriter() + { + } + + void set_at( SetType value, unsigned index ) + { + (this->*fArrayFcn)( value, index ); + } + + void SetInterface( unsigned aDataTypeSize, uint32_t aDataFormat, unsigned aSampleSize = 2 ) + { + if( aDataFormat == sDigitizedUS && aSampleSize == 1 ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4ComplexDataWriter< SetType >::set_at_u1; + else + { + throw M4Exception() << "Unable to make a digitized unsigned data interface with data type size " << aDataTypeSize; + } + } + else if( aDataFormat == sDigitizedS && aSampleSize == 1 ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4ComplexDataWriter< SetType >::set_at_i1; + else + { + throw M4Exception() << "Unable to make a digitized signed data interface with data type size " << aDataTypeSize; + } + } + + else if( aDataFormat == sAnalog && aSampleSize == 2 ) + { + if( aDataTypeSize == 4 ) fArrayFcn = &M4ComplexDataWriter< SetType >::set_at_f4_comp; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4ComplexDataWriter< SetType >::set_at_f8_comp; + else + { + throw M4Exception() << "Unable to make a analog data interface with data type size " << aDataTypeSize; + } + } + else + { + throw M4Exception() << "Invalid combination of data format <" << aDataFormat << ">, data type size <" << aDataTypeSize << "> and sample size <" << aSampleSize << ">"; + } + return; + } + + void SetData( const byte_type* aData ) + { + fUByteData = aData; + } + + private: + void set_at_u1( SetType value, unsigned index ) + { + fUByteData[ index ] = value[ 0 ]; + } + + void set_at_i1( SetType value, unsigned index ) + { + fIByteData[ index ] = value[ 0 ]; + } + + void set_at_f4_comp( SetType value, unsigned index ) + { + fF4CompBytesData[ index ][ 0 ] = value[ 0 ]; + fF4CompBytesData[ index ][ 1 ] = value[ 1 ]; + } + + void set_at_f8_comp( SetType value, unsigned index ) + { + fF8CompBytesData[ index ][ 0 ] = value[ 0 ]; + fF8CompBytesData[ index ][ 1 ] = value[ 1 ]; + } + + void (M4ComplexDataWriter::*fArrayFcn)( SetType, unsigned ); + + union + { + uint8_t* fUByteData; + int8_t* fIByteData; + f4_complex* fF4CompBytesData; + f8_complex* fF8CompBytesData; + }; + }; + + + /*! + @class M4DataReader + @author N. S. Oblath + + @brief Interface class for a variety of data types + + @details + Provides read-only access to a byte_type array as if it were an array of: + - uint8_t -- 1-byte unsigned integers (i.e. byte_type) + - uint16_t -- 2-byte unsigned integers + - uint32_t -- 4-byte unsigned integers + - uint64_t -- 8-byte unsigned integers + - int8_t -- 1-byte signed integers + - int16_t -- 2-byte signed integers + - int32_t -- 4-byte signed integers + - int64_t -- 8-byte signed integers + - float -- 4-byte floating point + - double -- 8-byte floating point + */ + template< typename ReturnType > + class M4DataReader + { + public: + M4DataReader( const byte_type* aData, unsigned aDataTypeSize, uint32_t aDataFormat ) : + fUByteData( aData ) + { + SetInterface( aDataTypeSize, aDataFormat ); + } + ~M4DataReader() + { + } + + ReturnType at( unsigned index ) const + { + return (this->*fArrayFcn)( index ); + } + + void SetInterface( unsigned aDataTypeSize, uint32_t aDataFormat ) + { + if( aDataFormat == sDigitizedUS ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4DataReader< ReturnType >::at_u1; + else if( aDataTypeSize == 2 ) fArrayFcn = &M4DataReader< ReturnType >::at_u2; + else if( aDataTypeSize == 4 ) fArrayFcn = &M4DataReader< ReturnType >::at_u4; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4DataReader< ReturnType >::at_u8; + else + { + throw M4Exception() << "Unable to make a digitized unsigned data interface with data type size " << aDataTypeSize; + } + } + else if( aDataFormat == sDigitizedS ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4DataReader< ReturnType >::at_i1; + else if( aDataTypeSize == 2 ) fArrayFcn = &M4DataReader< ReturnType >::at_i2; + else if( aDataTypeSize == 4 ) fArrayFcn = &M4DataReader< ReturnType >::at_i4; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4DataReader< ReturnType >::at_i8; + else + { + throw M4Exception() << "Unable to make a digitized signed data interface with data type size " << aDataTypeSize; + } + } + + else if( aDataFormat == sAnalog ) + { + if( aDataTypeSize == 4 ) fArrayFcn = &M4DataReader< ReturnType >::at_f4; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4DataReader< ReturnType >::at_f8; + else + { + throw M4Exception() << "Unable to make a analog data interface with data type size " << aDataTypeSize; + } + } + else + { + throw M4Exception() << "Invalid combination of data format <" << aDataFormat << ">, data type size <" << aDataTypeSize << ">"; + } + return; + } + + void SetData( const byte_type* aData ) + { + fUByteData = aData; + } + + private: + ReturnType at_u1( unsigned index ) const + { + return fUByteData[ index ]; + } + + ReturnType at_u2( unsigned index ) const + { + return fU2BytesData[ index ]; + } + + ReturnType at_u4( unsigned index ) const + { + return fU4BytesData[ index ]; + } + + ReturnType at_u8( unsigned index ) const + { + return fU8BytesData[ index ]; + } + + ReturnType at_i1( unsigned index ) const + { + return fIByteData[ index ]; + } + + ReturnType at_i2( unsigned index ) const + { + return fI2BytesData[ index ]; + } + + ReturnType at_i4( unsigned index ) const + { + return fI4BytesData[ index ]; + } + + ReturnType at_i8( unsigned index ) const + { + return fI8BytesData[ index ]; + } + + ReturnType at_f4( unsigned index ) const + { + return fF4BytesData[ index ]; + } + + ReturnType at_f8( unsigned index ) const + { + return fF8BytesData[ index ]; + } + + ReturnType (M4DataReader::*fArrayFcn)( unsigned ) const; + + union + { + const uint8_t* fUByteData; + const uint16_t* fU2BytesData; + const uint32_t* fU4BytesData; + const uint64_t* fU8BytesData; + const int8_t* fIByteData; + const int16_t* fI2BytesData; + const int32_t* fI4BytesData; + const int64_t* fI8BytesData; + const float* fF4BytesData; + const double* fF8BytesData; + }; + }; + + /*! + @class M4ComplexDataReader + @author N. S. Oblath + + @brief Interface class for complex data types + + @details + Provides read-only access to a byte_type array as if it were an array of: + - f4_complex (i.e. float[2]) -- binary-compatible to fftwf_complex + - f8_complex (i.e. double[2]) -- binary-compatible to fftw_complex + */ + template< typename ReturnType > + class M4ComplexDataReader + { + public: + M4ComplexDataReader( const byte_type* aData, unsigned aDataTypeSize, uint32_t aDataFormat, unsigned aSampleSize = 2 ) : + fUByteData( aData ) + { + SetInterface( aDataTypeSize, aDataFormat, aSampleSize ); + } + ~M4ComplexDataReader() + { + } + + const ReturnType& at( unsigned index ) const + { + return (this->*fArrayFcn)( index ); + } + + void SetInterface( unsigned aDataTypeSize, uint32_t aDataFormat, unsigned aSampleSize = 2 ) + { + if( aDataFormat == sDigitizedUS && aSampleSize == 1 ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4ComplexDataReader< ReturnType >::at_u1; + else + { + throw M4Exception() << "Unable to make a digitized (unsigned) data interface with data type size " << aDataTypeSize; + } + } + else if( aDataFormat == sDigitizedS && aSampleSize == 1 ) + { + if( aDataTypeSize == 1 ) fArrayFcn = &M4ComplexDataReader< ReturnType >::at_i1; + else + { + throw M4Exception() << "Unable to make a digitized (signed) data interface with data type size " << aDataTypeSize; + } + } + else if( aDataFormat == sAnalog && aSampleSize == 2 ) + { + if( aDataTypeSize == 4 ) fArrayFcn = &M4ComplexDataReader< ReturnType >::at_f4_comp; + else if( aDataTypeSize == 8 ) fArrayFcn = &M4ComplexDataReader< ReturnType >::at_f8_comp; + else + { + throw M4Exception() << "Unable to make a analog data interface with data type size " << aDataTypeSize; + } + } + else + { + throw M4Exception() << "Invalid combination of data format <" << aDataFormat << ">, data type size <" << aDataTypeSize << "> and sample size <" << aSampleSize << ">"; + } + return; + } + + void SetData( const byte_type* aData ) + { + fUByteData = aData; + } + + private: + const ReturnType& at_u1( unsigned index ) const + { + fBuffer[ 0 ] = fUByteData[ index ]; + fBuffer[ 1 ] = fUByteData[ index ]; + return fBuffer; + } + + const ReturnType& at_i1( unsigned index ) const + { + fBuffer[ 0 ] = fIByteData[ index ]; + fBuffer[ 1 ] = fIByteData[ index ]; + return fBuffer; + } + + const ReturnType& at_f4_comp( unsigned index ) const + { + fBuffer[ 0 ] = fF4CompBytesData[ index ][ 0 ]; + fBuffer[ 1 ] = fF4CompBytesData[ index ][ 1 ]; + return fBuffer; + } + + const ReturnType& at_f8_comp( unsigned index ) const + { + fBuffer[ 0 ] = fF8CompBytesData[ index ][ 0 ]; + fBuffer[ 1 ] = fF8CompBytesData[ index ][ 1 ]; + return fBuffer; + } + + const ReturnType& (M4ComplexDataReader::*fArrayFcn)( unsigned ) const; + + union + { + const uint8_t* fUByteData; + const int8_t* fIByteData; + const f4_complex* fF4CompBytesData; + const f8_complex* fF8CompBytesData; + }; + + mutable ReturnType fBuffer; + }; + +} + +#endif /* M4DATAINTERFACE_HH_ */ diff --git a/Monarch4/M4Exception.cc b/Monarch4/M4Exception.cc new file mode 100644 index 0000000..f05cd88 --- /dev/null +++ b/Monarch4/M4Exception.cc @@ -0,0 +1,39 @@ +/* + * M4Exception.cc + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ +#define M4_API_EXPORTS + +#include "M4Exception.hh" + +namespace monarch4 +{ + + M4Exception::M4Exception() : + exception(), + fMessage() + { + } + M4Exception::M4Exception( const M4Exception& aCopy ) : + exception( aCopy ), + fMessage( aCopy.fMessage ) + { + } + M4Exception::~M4Exception() throw () + { + } + + //void M4Exception::SetWhat( const char* aWhat ) + //{ + // fMessage = std::string( aWhat ); + // //fStream << aWhat; + //} + + const char* M4Exception::what() const throw () + { + return fMessage.c_str(); + } + +} diff --git a/Monarch4/M4Exception.hh b/Monarch4/M4Exception.hh new file mode 100644 index 0000000..77c002c --- /dev/null +++ b/Monarch4/M4Exception.hh @@ -0,0 +1,53 @@ +/* + * M4Exception.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4EXCEPTION_HH_ +#define M4EXCEPTION_HH_ + +#include "M4Constants.hh" + +#include +#include +#include + +namespace monarch4 +{ + + /*! + @class M4Exception + @author D. Furse + + @brief Specialized exception class for Monarch4 + + @details + */ + class M4_API M4Exception : public std::exception + { + public: + M4Exception(); + M4Exception( const M4Exception& aCopy ); + virtual ~M4Exception() throw(); + + public: + virtual const char* what() const throw(); + + template< class XType > + M4Exception& operator<< ( const XType& aReference ) + { + std::stringstream tStream; + tStream << aReference; + fMessage += tStream.str(); + return *this; + } + + private: + std::string fMessage; + }; + +} + +#endif /* M4EXCEPTION_HH_ */ diff --git a/Monarch4/M4Header.cc b/Monarch4/M4Header.cc new file mode 100644 index 0000000..cbe2099 --- /dev/null +++ b/Monarch4/M4Header.cc @@ -0,0 +1,759 @@ +/* + + * M4Header.cc + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#define M4_API_EXPORTS +#define hdf5_EXPORTS + +#include "M4Header.hh" + +#include "M4IToA.hh" +#include "logger.hh" +#include "M4Version.hh" + +#include // for atol in parsing timestamp +#include // for strcpy + +// for parsing timestamp +#include +using std::stringstream; +using std::string; + +namespace monarch4 +{ + LOGGER( mlog, "M4Header" ); + + //********************* + // M4StreamHeader + //********************* + + M4StreamHeader::M4StreamHeader() : + fLabel( NULL ), + fNumber( 0 ), + fSource(), + fNChannels( 0 ), + fChannels(), + fChannelFormat( sSeparate ), + fAcquisitionRate( 0 ), + fRecordSize( 0 ), + fSampleSize( 0 ), + fDataTypeSize( 0 ), + fDataFormat( sDigitizedUS ), + fBitDepth( 0 ), + fBitAlignment( sBitsAlignedLeft ), + fNAcquisitions( 0 ), + fNRecords( 0 ) + { + } + + M4StreamHeader::M4StreamHeader( const std::string& aSource, uint32_t aNumber, uint32_t aNChannels, uint32_t aFirstChannel, uint32_t aFormat, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment ) : + fLabel( NULL ), + fNumber( 0 ), + fSource( aSource ), + fNChannels( aNChannels ), + fChannels( aNChannels ), + fChannelFormat( aFormat ), + fAcquisitionRate( anAcqRate ), + fRecordSize( aRecSize ), + fSampleSize( aSampleSize ), + fDataTypeSize( aDataTypeSize ), + fDataFormat( aDataFormat ), + fBitDepth( aBitDepth ), + fBitAlignment( aBitAlignment ), + fNAcquisitions( 0 ), + fNRecords( 0 ) + { + SetNumber( aNumber ); + for( unsigned iChannel = 0; iChannel < fNChannels; ++iChannel ) + { + fChannels[ iChannel ] = aFirstChannel + iChannel; + } + } + + M4StreamHeader::M4StreamHeader( const M4StreamHeader& orig ) : + fLabel( NULL ), + fNumber( 0 ), + fSource( orig.fSource ), + fNChannels( orig.fNChannels ), + fChannels( orig.fChannels ), + fChannelFormat( orig.fChannelFormat ), + fAcquisitionRate( orig.fAcquisitionRate ), + fRecordSize( orig.fRecordSize ), + fSampleSize( orig.fSampleSize ), + fDataTypeSize( orig.fDataTypeSize ), + fDataFormat( orig.fDataFormat ), + fBitDepth( orig.fBitDepth ), + fBitAlignment( orig.fBitAlignment ), + fNAcquisitions( orig.fNAcquisitions ), + fNRecords( orig.fNRecords ) + { + SetNumber( orig.fNumber ); + } + + M4StreamHeader::~M4StreamHeader() + { + delete [] fLabel; + } + + void M4StreamHeader::SetNumber( uint32_t aNumber ) const + { + fNumber = aNumber; + + static const size_t prefixSize = 6; // # of characters in "stream" + delete [] fLabel; + fLabel = new char[ prefixSize + 10 ]; // 10 = max digits in 32-bit integer + strcpy( fLabel, "stream" ); + u32toa( aNumber, fLabel + prefixSize ); + + return; + } + + void M4StreamHeader::WriteToHDF5( HAS_GRP_IFC* aParent ) + { + LDEBUG( mlog, "Writing stream <" << fLabel << ">" ); + H5::Group tThisStreamGroup = aParent->createGroup( fLabel ); + + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "number", fNumber ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "source", fSource ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "n_channels", fNChannels ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "channel_format", fChannelFormat ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "acquisition_rate", fAcquisitionRate ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "record_size", fRecordSize ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "sample_size", fSampleSize ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "data_type_size", fDataTypeSize ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "data_format", fDataFormat ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "bit_depth", fBitDepth ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "bit_alignment", fBitAlignment ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "n_acquisitions", fNAcquisitions ); + M4Header::WriteScalarToHDF5( &tThisStreamGroup, "n_records", fNRecords ); + + WriteChannels( &tThisStreamGroup ); + + return; + } + + void M4StreamHeader::ReadFromHDF5( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const + { + LDEBUG( mlog, "Reading stream <" << aLabel << ">" ); + H5::Group tThisStreamGroup = aParent->openGroup( aLabel.c_str() ); + + SetNumber( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "number" ) ); + Source() = M4Header::ReadScalarFromHDF5< string >( &tThisStreamGroup, "source" ); + SetNChannels( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "n_channels" ) ); + SetChannelFormat( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "channel_format" ) ); + SetAcquisitionRate( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "acquisition_rate" ) ); + SetRecordSize( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "record_size" ) ); + SetSampleSize( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "sample_size" ) ); + SetDataTypeSize( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "data_type_size" ) ); + SetDataFormat( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "data_format" ) ); + SetBitDepth( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "bit_depth" ) ); + SetBitAlignment( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "bit_alignment", fBitAlignment ) ); + SetNAcquisitions( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "n_acquisitions" ) ); + SetNRecords( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisStreamGroup, "n_records" ) ); + + ReadChannels( &tThisStreamGroup ); + + return; + } + + void M4StreamHeader::WriteChannels( HAS_ATTR_IFC* aLoc ) + { + const unsigned tNDims = 1; + hsize_t tDims[ tNDims ] = { fNChannels }; + + H5::DataSpace tDataspace( tNDims, tDims ); + //H5::DataSet tDataset = aLoc->createDataSet( "channels", MH5Type< uint32_t >::H5(), tDataspace ); + H5::Attribute tAttr = aLoc->createAttribute( "channels", MH5Type< uint32_t >::H5(), tDataspace ); + + uint32_t* tCSBuffer = new uint32_t[ fNChannels ]; + for( unsigned i = 0; i < fNChannels; ++i ) + { + tCSBuffer[ i ] = fChannels[ i ]; + } + + //tDataset.write( tCSBuffer, MH5Type< uint32_t >::Native(), tDataspace ); + tAttr.write( MH5Type< uint32_t >::Native(), tCSBuffer ); + + delete [] tCSBuffer; + + return; + } + + void M4StreamHeader::ReadChannels( const HAS_ATTR_IFC* aLoc ) const + { + const unsigned tNDims = 1; + hsize_t tDims[ tNDims ]; + + //H5::DataSet tDataset = aLoc->openDataSet( "channels" ); + H5::Attribute tAttr = aLoc->openAttribute( "channels" ); + //H5::DataSpace tDataspace( tDataset.getSpace() ); + H5::DataSpace tDataspace( tAttr.getSpace() ); + tDataspace.getSimpleExtentDims( tDims ); + + if( tDims[ 0 ] != fNChannels ) + { + LERROR( mlog, "Channels dataset dimensions (" << tDims[ 0 ] << ") do not match number of channels, " << fNChannels ); + return; + } + + uint32_t* tCSBuffer = new uint32_t[ fNChannels ]; + //tDataset.read( tCSBuffer, MH5Type< uint32_t >::Native(), tDataspace ); + tAttr.read( MH5Type< uint32_t >::Native(), tCSBuffer ); + + fChannels.clear(); + fChannels.resize( fNChannels ); + for( unsigned i = 0; i < fNChannels; ++i ) + { + fChannels[ i ] = tCSBuffer[ i ]; + } + + delete [] tCSBuffer; + + return; + } + + //********************* + // M4ChannelHeader + //********************* + + M4ChannelHeader::M4ChannelHeader() : + fLabel( NULL ), + fNumber( 0 ), + fSource(), + fAcquisitionRate( 0 ), + fRecordSize( 0 ), + fSampleSize( 0 ), + fDataTypeSize( 0 ), + fDataFormat( sDigitizedUS ), + fBitDepth( 0 ), + fBitAlignment( sBitsAlignedLeft ), + fVoltageOffset( 0. ), + fVoltageRange( 0. ), + fDACGain( 0. ), + fFrequencyMin( 0. ), + fFrequencyRange( 0. ) + { + } + + M4ChannelHeader::M4ChannelHeader( const std::string& aSource, uint32_t aNumber, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment ) : + fLabel( NULL ), + fNumber( 0 ), + fSource( aSource ), + fAcquisitionRate( anAcqRate ), + fRecordSize( aRecSize ), + fSampleSize( aSampleSize ), + fDataTypeSize( aDataTypeSize ), + fDataFormat( aDataFormat ), + fBitDepth( aBitDepth ), + fBitAlignment( aBitAlignment ), + fVoltageOffset( 0. ), + fVoltageRange( 0. ), + fDACGain( 0. ), + fFrequencyMin( 0. ), + fFrequencyRange( 0. ) + { + SetNumber( aNumber ); + } + + M4ChannelHeader::M4ChannelHeader( const M4ChannelHeader& orig ) : + fLabel( NULL ), + fNumber( 0 ), + fSource( orig.fSource ), + fAcquisitionRate( orig.fAcquisitionRate ), + fRecordSize( orig.fRecordSize ), + fSampleSize( orig.fSampleSize ), + fDataTypeSize( orig.fDataTypeSize ), + fDataFormat( orig.fDataFormat ), + fBitDepth( orig.fBitDepth ), + fBitAlignment( orig.fBitAlignment ), + fVoltageOffset( orig.fVoltageOffset ), + fVoltageRange( orig.fVoltageRange ), + fDACGain( orig.fDACGain ), + fFrequencyMin( orig.fFrequencyMin ), + fFrequencyRange( orig.fFrequencyRange ) + { + SetNumber( orig.fNumber ); + } + + M4ChannelHeader::~M4ChannelHeader() + { + delete [] fLabel; + } + + void M4ChannelHeader::SetNumber( uint32_t aNumber ) const + { + fNumber = aNumber; + + static const size_t prefixSize = 7; // # of characters in "channel" + delete [] fLabel; + fLabel = new char[ prefixSize + 10 ]; // 10 = max digits in 32-bit integer + strcpy( fLabel, "channel" ); + u32toa( aNumber, fLabel + prefixSize ); + + return; + } + + void M4ChannelHeader::WriteToHDF5( HAS_GRP_IFC* aParent ) + { + LDEBUG( mlog, "Writing channel <" << fLabel << ">" ); + H5::Group tThisChannelGroup = aParent->createGroup( fLabel ); + + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "number", fNumber ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "source", fSource ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "acquisition_rate", fAcquisitionRate ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "record_size", fRecordSize ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "sample_size", fSampleSize ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "data_type_size", fDataTypeSize ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "data_format", fDataFormat ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "bit_depth", fBitDepth ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "bit_alignment", fBitAlignment ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "voltage_offset", fVoltageOffset ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "voltage_range", fVoltageRange ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "dac_gain", fDACGain ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "frequency_min", fFrequencyMin ); + M4Header::WriteScalarToHDF5( &tThisChannelGroup, "frequency_range", fFrequencyRange ); + + return; + } + + void M4ChannelHeader::ReadFromHDF5( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const + { + LDEBUG( mlog, "Reading channel <" << aLabel << ">" ); + H5::Group tThisChannelGroup = aParent->openGroup( aLabel.c_str() ); + + SetNumber( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "number" ) ); + Source() = M4Header::ReadScalarFromHDF5< string >( &tThisChannelGroup, "source" ); + SetAcquisitionRate( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "acquisition_rate" ) ); + SetRecordSize( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "record_size" ) ); + SetSampleSize( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "sample_size" ) ); + SetDataTypeSize( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "data_type_size" ) ); + SetDataFormat( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "data_format" ) ); + SetBitDepth( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "bit_depth" ) ); + SetBitAlignment( M4Header::ReadScalarFromHDF5< uint32_t >( &tThisChannelGroup, "bit_alignment", fBitAlignment ) ); + SetVoltageOffset( M4Header::ReadScalarFromHDF5< double >( &tThisChannelGroup, "voltage_offset" ) ); + SetVoltageRange( M4Header::ReadScalarFromHDF5< double >( &tThisChannelGroup, "voltage_range" ) ); + SetDACGain( M4Header::ReadScalarFromHDF5< double >( &tThisChannelGroup, "dac_gain" ) ); + SetFrequencyMin( M4Header::ReadScalarFromHDF5< double >( &tThisChannelGroup, "frequency_min" ) ); + SetFrequencyRange( M4Header::ReadScalarFromHDF5< double >( &tThisChannelGroup, "frequency_range" ) ); + + return; + } + + + //********************* + // M4Header + //********************* + + M4Header::M4Header() : + fEggVersion( TOSTRING(Egg_VERSION) ), + fFilename(), + fRunDuration( 0 ), + fTimestamp(), + fDescription(), + fNChannels( 0 ), + fNStreams( 0 ), + fChannelStreams(), + fChannelCoherence(), + fFile( NULL ), + fStreamsGroup( NULL ), + fChannelsGroup( NULL ) + { + } + + M4Header::~M4Header() + { + delete fStreamsGroup; + delete fChannelsGroup; + } + + void M4Header::CopyBasicInfo( const M4Header& aOrig ) + { + fEggVersion = aOrig.fEggVersion; + fFilename = aOrig.fFilename; + fRunDuration = aOrig.fRunDuration; + fTimestamp = aOrig.fTimestamp; + fDescription = aOrig.fDescription; + return; + } + + void M4Header::SetCoherence( unsigned aChanA, unsigned aChanB, bool aCoherence ) + { + if( aChanA >= fNChannels || aChanB >= fNChannels ) + { + throw M4Exception() << "Channel out of bounds: " << aChanA << " or " << aChanB << " >= " << fNChannels; + } + fChannelCoherence[ aChanA ][ aChanB ] = aCoherence; + fChannelCoherence[ aChanB ][ aChanA ] = aCoherence; + return; + } + + uint32_t M4Header::AddStream( const std::string& aSource, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment, + std::vector< unsigned >* aChanVec ) + { + LDEBUG( mlog, "Adding stream " << fNStreams << " for channel " << fNChannels << " with record size " << aRecSize ); + if( aChanVec != NULL ) aChanVec->push_back( fNChannels ); + fChannelStreams.push_back( fNStreams ); + fChannelHeaders.push_back( M4ChannelHeader( aSource, fNChannels, anAcqRate, aRecSize, aSampleSize, aDataTypeSize, aDataFormat, aBitDepth, aBitAlignment ) ); + fStreamHeaders.push_back( M4StreamHeader( aSource, fNStreams, 1, fNChannels, sSeparate, anAcqRate, aRecSize, aSampleSize, aDataTypeSize, aDataFormat, aBitDepth, aBitAlignment ) ); + ++fNChannels; + //std::cout << "resizing to " << fNChannels << std::endl; + fChannelCoherence.resize( fNChannels ); // resize number of columns + for( unsigned i = 0; i < fNChannels; ++i ) // resize each row + { + fChannelCoherence[ i ].resize( fNChannels, false ); + } + fChannelCoherence.back().back() = true; // each channel is coherent with itself + return fNStreams++; + } + + uint32_t M4Header::AddStream( const std::string& aSource, uint32_t aNChannels, uint32_t aFormat, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment, + std::vector< unsigned >* aChanVec ) + { + LDEBUG( mlog, "Adding stream " << fNStreams << " for multiple channels with record size " << aRecSize ); + unsigned tFirstNewChannel = fNChannels; + for( uint32_t iNewChannel = 0; iNewChannel < aNChannels; ++iNewChannel ) + { + LDEBUG( mlog, "Adding channel " << fNChannels ); + if( aChanVec != NULL ) aChanVec->push_back( fNChannels ); + fChannelStreams.push_back( fNStreams ); + fChannelHeaders.push_back( M4ChannelHeader( aSource, fNChannels, anAcqRate, aRecSize, aSampleSize, aDataTypeSize, aDataFormat, aBitDepth, aBitAlignment ) ); + ++fNChannels; + fChannelCoherence.resize( fNChannels ); // resize number of columns + for( unsigned i = 0; i < fNChannels; ++i ) // resize all rows + { + fChannelCoherence[ i ].resize( fNChannels, false ); + } + fChannelCoherence.back().back() = true; // each channel is coherent with itself + if( fNChannels > 0 ) // this condition is necessary because if it's not the case, fNChannels-1 will be some huge number, since we're dealing with unsigned ints, so the for-loop condition won't be sufficient + { + for( unsigned i = tFirstNewChannel; i < fNChannels - 1; ++i ) + { + fChannelCoherence[ fNChannels - 1 ][ i ] = true; + fChannelCoherence[ i ][ fNChannels - 1 ] = true; + } + } + } + fStreamHeaders.push_back( M4StreamHeader( aSource, fNStreams, aNChannels, tFirstNewChannel, aFormat, anAcqRate, aRecSize, aSampleSize, aDataTypeSize, aDataFormat, aBitDepth, aBitAlignment ) ); + return fNStreams++; + } + + void M4Header::WriteToHDF5( H5::H5File* aFile ) + { + try + { + LDEBUG( mlog, "Writing run header" ); + fFile = aFile; + WriteScalarToHDF5( fFile, "egg_version", fEggVersion ); + WriteScalarToHDF5( fFile, "filename", fFilename ); + WriteScalarToHDF5( fFile, "n_channels", fNChannels ); + WriteScalarToHDF5( fFile, "n_streams", fNStreams ); + WriteScalarToHDF5( fFile, "run_duration", fRunDuration ); + WriteScalarToHDF5( fFile, "timestamp", fTimestamp ); + WriteScalarToHDF5( fFile, "description", fDescription ); + //////////////Write1DToHDF5( fFile, "channel_streams", fChannelStreams ); + WriteChannelStreams( fFile ); + WriteChannelCoherence( fFile ); + + LDEBUG( mlog, "Writing stream headers" ); + fStreamsGroup = new H5::Group( fFile->createGroup( "streams" ) ); + for( uint32_t iStream = 0; iStream < fNStreams; ++iStream ) + { + fStreamHeaders[ iStream ].WriteToHDF5( fStreamsGroup ); + } + + LDEBUG( mlog, "Writing channel headers" ); + fChannelsGroup = new H5::Group( fFile->createGroup( "channels" ) ); + for( uint32_t iChan = 0; iChan < fNChannels; ++iChan ) + { + fChannelHeaders[ iChan ].WriteToHDF5( fChannelsGroup ); + } + } + catch( H5::Exception& e ) + { + throw M4Exception() << "HDF5 error while writing header: " << e.getCDetailMsg(); + } + catch( M4Exception& e ) + { + LDEBUG( mlog, "M4Exception: " << e.what() ); + throw; + } + + return; + } + + void M4Header::ReadFromHDF5( const H5::H5File* aFile ) const + { + try + { + LDEBUG( mlog, "Reading run header" ); + fFile = const_cast< H5::H5File* >( aFile ); + EggVersion() = ReadScalarFromHDF5< string >( fFile, string("egg_version") ); + Filename() = ReadScalarFromHDF5< string >( fFile, "filename" ); + SetNChannels( ReadScalarFromHDF5< uint32_t >( fFile, "n_channels" ) ); + SetNStreams( ReadScalarFromHDF5< uint32_t >( fFile, "n_streams" ) ); + SetRunDuration( ReadScalarFromHDF5< uint32_t >( fFile, "run_duration" ) ); + Timestamp() = ReadScalarFromHDF5< string >( fFile, "timestamp" ); + Description() = ReadScalarFromHDF5< string >( fFile, "description" ); + + //fChannelStreams.clear(); + //Read1DFromHDF5< std::vector< uint32_t > >( fFile, "channel_streams", fChannelStreams ); + ReadChannelStreams( aFile ); + ReadChannelCoherence( aFile ); + /* + for( unsigned i = 0; i < fNChannels; ++i ) + { + std::cout << " "; + for( unsigned j = 0; j < fNChannels; ++j ) + { + std::cout << fChannelCoherence[ i ][ j ] << " "; + } + std::cout << std::endl; + } + */ + + // Windows' HDF5 really doesn't like using std::strings + const unsigned tBuffSize = 256; + char tBuffer[ tBuffSize ]; + + LDEBUG( mlog, "Reading stream headers" ); + fStreamHeaders.clear(); + fStreamsGroup = new H5::Group( fFile->openGroup( "streams" ) ); + hsize_t nStreams = fStreamsGroup->getNumObjs(); + if( nStreams != fNStreams ) + { + throw M4Exception() << "Number of streams <" << fNStreams << "> disagrees with the number of objects in the stream group <" << nStreams << ">"; + } + for( hsize_t iStream = 0; iStream < nStreams; ++iStream ) + { + //string tStreamLabel = fStreamsGroup->getObjnameByIdx( iStream ); + unsigned tLabelSize = fStreamsGroup->getObjnameByIdx( iStream, tBuffer, tBuffSize ); + std::string tStreamLabel( tBuffer, tLabelSize ); + LDEBUG( mlog, "Attempting to read stream header #" << iStream << "; label <" << tStreamLabel << ">" ); + fStreamHeaders.push_back( M4StreamHeader() ); + LDEBUG( mlog, "Testing if we can access the last header: " << fStreamHeaders.back().GetLabel() ); + fStreamHeaders.back().ReadFromHDF5( fStreamsGroup, tStreamLabel ); + } + + LDEBUG( mlog, "Reading channel headers" ); + fChannelHeaders.clear(); + fChannelsGroup = new H5::Group( fFile->openGroup( "channels" ) ); + hsize_t nChannels = fChannelsGroup->getNumObjs(); + for( hsize_t iChan = 0; iChan < nChannels; ++iChan ) + { + //string tChannelLabel = fChannelsGroup->getObjnameByIdx( iChan ); + unsigned tLabelSize = fChannelsGroup->getObjnameByIdx( iChan, tBuffer, tBuffSize ); + std::string tChannelLabel( tBuffer, tLabelSize ); + fChannelHeaders.push_back( M4ChannelHeader() ); + fChannelHeaders.back().ReadFromHDF5( fChannelsGroup, tChannelLabel ); + } + } + catch( H5::Exception& e ) + { + //H5::Exception::printErrorStack(); + throw M4Exception() << "Unable to open header group or find header data\n"; + } + return; + } + + void M4Header::WriteChannelStreams( HAS_ATTR_IFC* aLoc ) + { + const unsigned tNDims = 1; + hsize_t tDims[ tNDims ] = { fNChannels }; + + H5::DataSpace tDataspace( tNDims, tDims ); + //H5::DataSet tDataset = aLoc->createDataSet( "channel_streams", MH5Type< uint32_t >::H5(), tDataspace ); + H5::Attribute tAttr = aLoc->createAttribute( "channel_streams", MH5Type< uint32_t >::H5(), tDataspace ); + + uint32_t* tCSBuffer = new uint32_t[ fNChannels ]; + for( unsigned i = 0; i < fNChannels; ++i ) + { + tCSBuffer[ i ] = fChannelStreams[ i ]; + } + + //tDataset.write( tCSBuffer, MH5Type< uint32_t >::Native(), tDataspace ); + tAttr.write( MH5Type< uint32_t >::Native(), tCSBuffer ); + + delete [] tCSBuffer; + + return; + } + + void M4Header::ReadChannelStreams( const HAS_ATTR_IFC* aLoc ) const + { + const unsigned tNDims = 1; + hsize_t tDims[ tNDims ]; + + //H5::DataSet tDataset = aLoc->openDataSet( "channel_streams" ); + H5::Attribute tAttr = aLoc->openAttribute( "channel_streams" ); + //H5::DataSpace tDataspace( tDataset.getSpace() ); + H5::DataSpace tDataspace( tAttr.getSpace() ); + tDataspace.getSimpleExtentDims( tDims ); + + if( tDims[ 0 ] != fNChannels ) + { + LERROR( mlog, "Channel-streams dataset dimensions (" << tDims[ 0 ] << ") do not match number of channels, " << fNChannels ); + return; + } + + uint32_t* tCSBuffer = new uint32_t[ fNChannels ]; + //tDataset.read( tCSBuffer, MH5Type< uint32_t >::Native(), tDataspace ); + tAttr.read( MH5Type< uint32_t >::Native(), tCSBuffer ); + + fChannelStreams.clear(); + fChannelStreams.resize( fNChannels ); + for( unsigned i = 0; i < fNChannels; ++i ) + { + fChannelStreams[ i ] = tCSBuffer[ i ]; + } + + delete [] tCSBuffer; + + return; + } + + void M4Header::WriteChannelCoherence( HAS_ATTR_IFC* aLoc ) + { + const unsigned tNDims = 2; + hsize_t tDims[ tNDims ] = { fNChannels, fNChannels }; + + H5::DataSpace tDataspace( tNDims, tDims ); + //H5::DataSet tDataset = aLoc->createDataSet( "channel_coherence", MH5Type< bool >::H5(), tDataspace ); + H5::Attribute tAttr = aLoc->createAttribute( "channel_coherence", MH5Type< bool >::H5(), tDataspace ); + + uint8_t* tCCBuffer = new uint8_t[ fNChannels * fNChannels ]; + for( unsigned i = 0; i < fNChannels; ++i ) + { + for( unsigned j = 0; j < fNChannels; ++j ) + { + tCCBuffer[ i * fNChannels + j ] = (uint8_t)fChannelCoherence[ i ][ j ]; + } + } + + //tDataset.write( tCCBuffer, MH5Type< bool >::Native(), tDataspace ); + tAttr.write( MH5Type< bool >::Native(), tCCBuffer ); + + delete [] tCCBuffer; + + return; + } + + void M4Header::ReadChannelCoherence( const HAS_ATTR_IFC* aLoc ) const + { + const unsigned tNDims = 2; + hsize_t tDims[ tNDims ]; + + //H5::DataSet tDataset = aLoc->openDataSet( "channel_coherence" ); + H5::Attribute tAttr = aLoc->openAttribute( "channel_coherence" ); + //H5::DataSpace tDataspace( tDataset.getSpace() ); + H5::DataSpace tDataspace( tAttr.getSpace() ); + tDataspace.getSimpleExtentDims( tDims ); + + if( tDims[ 0 ] != fNChannels || tDims[ 1 ] != fNChannels ) + { + LERROR( mlog, "Channel coherence dataset dimensions (" << tDims[ 0 ] << ", " << tDims[ 1 ] << ") do not match number of channels, " << fNChannels ); + return; + } + + uint8_t* tCCBuffer = new uint8_t[ fNChannels * fNChannels ]; + //tDataset.read( tCCBuffer, MH5Type< bool >::Native(), tDataspace ); + tAttr.read( MH5Type< bool >::Native(), tCCBuffer ); + + fChannelCoherence.clear(); + fChannelCoherence.resize( fNChannels ); + for( unsigned i = 0; i < fNChannels; ++i ) + { + fChannelCoherence[ i ].resize( fNChannels ); + for( unsigned j = 0; j < fNChannels; ++j ) + { + fChannelCoherence[ i ][ j ] = (bool)tCCBuffer[ i * fNChannels + j ]; + } + } + + delete [] tCCBuffer; + + return; + } + +} + +M4_API std::ostream& operator<<( std::ostream& out, const monarch4::M4StreamHeader& hdr ) +{ + out << "Stream Header Content:\n"; + out << "\tStream Number: " << hdr.GetNumber() << '\n'; + out << "\tSource: " << hdr.Source() << '\n'; + out << "\tNumber of Channels: " << hdr.GetNChannels() << '\n'; + out << "\tChannel Format: " << hdr.GetChannelFormat() << '\n'; + out << "\tAcquisition Rate: " << hdr.GetAcquisitionRate() << " MHz\n"; + out << "\tRecord Size: " << hdr.GetRecordSize() << " samples\n"; + out << "\tSample Size: " << hdr.GetSampleSize() << " elements\n"; + out << "\tData Type Size: " << hdr.GetDataTypeSize() << " bytes\n"; + out << "\tData Format: " << hdr.GetDataFormat() << '\n'; + out << "\tBit Depth: " << hdr.GetBitDepth() << " bits\n"; + out << "\tBit Alignment: " << hdr.GetBitAlignment() << '\n'; + out << "\tNumber of Acquisitions: " << hdr.GetNAcquisitions() << '\n'; + out << "\tNumber of Records: " << hdr.GetNRecords() << '\n'; + return out; +} + +M4_API std::ostream& operator<<( std::ostream& out, const monarch4::M4ChannelHeader& hdr ) +{ + out << "Channel Header Content:\n"; + out << "\tChannel Number: " << hdr.GetNumber() << '\n'; + out << "\tSource: " << hdr.Source() << '\n'; + out << "\tAcquisition Rate: " << hdr.GetAcquisitionRate() << " MHz\n"; + out << "\tRecord Size: " << hdr.GetRecordSize() << " samples\n"; + out << "\tSample Size: " << hdr.GetSampleSize() << " elements\n"; + out << "\tData Type Size: " << hdr.GetDataTypeSize() << " bytes\n"; + out << "\tData Format: " << hdr.GetDataFormat() << '\n'; + out << "\tBit Depth: " << hdr.GetBitDepth() << " bits\n"; + out << "\tBit Alignment: " << hdr.GetBitAlignment() << '\n'; + out << "\tVoltage Offset: " << hdr.GetVoltageOffset() << " V\n"; + out << "\tVoltage Range: " << hdr.GetVoltageRange() << " V\n"; + out << "\tFrequency Min: " << hdr.GetFrequencyMin() << " Hz\n"; + out << "\tFrequency Range: " << hdr.GetFrequencyRange() << " Hz\n"; + return out; +} + +M4_API std::ostream& operator<<( std::ostream& out, const monarch4::M4Header& hdr ) +{ + out << "Monarch Header Content:\n"; + out << "\tEgg Version: " << hdr.EggVersion() << "\n"; + out << "\tFilename: " << hdr.Filename() << "\n"; + out << "\tRun Duration: " << hdr.GetRunDuration() << " ms\n"; + out << "\tTimestamp: " << hdr.Timestamp() << "\n"; + out << "\tDescription: " << hdr.Description() << "\n"; + out << "\tNumber of Channels: " << hdr.GetNChannels() << "\n"; + out << "\tNumber of Streams: " << hdr.GetNStreams() << "\n"; + out << "\tChannel-to-stream mapping:\n"; + for( uint32_t iChan = 0; iChan < hdr.ChannelStreams().size(); ++iChan ) + { + out << "\t\tChannel " << iChan << " --> Stream " << hdr.ChannelStreams()[ iChan ] << "\n"; + } + out << "\tStream headers:\n"; + for( uint32_t iStream = 0; iStream < hdr.StreamHeaders().size(); ++iStream ) + { + out << hdr.StreamHeaders()[ iStream ]; + } + out << "\tChannel headers:\n"; + for( uint32_t iChan = 0; iChan < hdr.ChannelHeaders().size(); ++iChan ) + { + out << hdr.ChannelHeaders()[ iChan ]; + } + return out; +} diff --git a/Monarch4/M4Header.hh b/Monarch4/M4Header.hh new file mode 100644 index 0000000..4057b91 --- /dev/null +++ b/Monarch4/M4Header.hh @@ -0,0 +1,408 @@ +/* + * M4Header.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4HEADER_HH_ +#define M4HEADER_HH_ + +#include "M4Constants.hh" +#include "logger.hh" +#include "M4MemberVariable.hh" +#include "M4Types.hh" + +#include "H5Cpp.h" + +#include +#include + +namespace monarch4 +{ + LOGGER( mlog_mheader, "M4Header.hh" ); + + /*! + @class M4StreamHeader + @author N. S. Oblath + + @brief Single-stream header information + + @details + */ + class M4_API M4StreamHeader + { + public: + M4StreamHeader(); + M4StreamHeader( const std::string& aSource, uint32_t aNumber, uint32_t aNChannels, uint32_t aFirstChannel, uint32_t aFormat, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment ); + M4StreamHeader( const M4StreamHeader& orig ); + ~M4StreamHeader(); + + M4MEMBERVARIABLE_PTR( char, Label ); + + M4MEMBERVARIABLE_NOSET( uint32_t, Number ); + void SetNumber( uint32_t aNumber ) const; /// In addition to setting the number, sets the label to "stream[aNumber]" + + M4MEMBERVARIABLE_REF( std::string, Source ); + + M4MEMBERVARIABLE( uint32_t, NChannels ); + + M4MEMBERVARIABLE_REF_CONST( std::vector< uint32_t >, Channels ); + + M4MEMBERVARIABLE( uint32_t, ChannelFormat ); + + M4MEMBERVARIABLE( uint32_t, AcquisitionRate ); + + M4MEMBERVARIABLE( uint32_t, RecordSize ); + + M4MEMBERVARIABLE( uint32_t, SampleSize ); + + M4MEMBERVARIABLE( uint32_t, DataTypeSize ); + + M4MEMBERVARIABLE( uint32_t, DataFormat ); + + M4MEMBERVARIABLE( uint32_t, BitDepth ); + + M4MEMBERVARIABLE( uint32_t, BitAlignment ); + + M4MEMBERVARIABLE( uint32_t, NAcquisitions ); + + M4MEMBERVARIABLE( uint32_t, NRecords ); + + public: + void WriteToHDF5( HAS_GRP_IFC* aParent ); + void ReadFromHDF5( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const; + + private: + void WriteChannels( HAS_ATTR_IFC* aLoc ); + void ReadChannels( const HAS_ATTR_IFC* aLoc ) const; + }; + + /*! + @class M4ChannelHeader + @author N. S. Oblath + + @brief Single-channel header information + + @details + */ + class M4_API M4ChannelHeader + { + public: + M4ChannelHeader(); + M4ChannelHeader( const std::string& aSource, uint32_t aNumber, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment ); + M4ChannelHeader( const M4ChannelHeader& orig ); + ~M4ChannelHeader(); + + M4MEMBERVARIABLE_PTR( char, Label ); + + M4MEMBERVARIABLE_NOSET( uint32_t, Number ); + void SetNumber( uint32_t aNumber ) const; /// In addition to setting the number, sets the label to "channel[aNumber]" + + M4MEMBERVARIABLE_REF( std::string, Source ); + + M4MEMBERVARIABLE( uint32_t, AcquisitionRate ); + + M4MEMBERVARIABLE( uint32_t, RecordSize ); + + M4MEMBERVARIABLE( uint32_t, SampleSize ); + + M4MEMBERVARIABLE( uint32_t, DataTypeSize ); + + M4MEMBERVARIABLE( uint32_t, DataFormat ); + + M4MEMBERVARIABLE( uint32_t, BitDepth ); + + M4MEMBERVARIABLE( uint32_t, BitAlignment ); + + M4MEMBERVARIABLE( double, VoltageOffset ); + + M4MEMBERVARIABLE( double, VoltageRange ); + + M4MEMBERVARIABLE( double, DACGain ); + + M4MEMBERVARIABLE( double, FrequencyMin ); + + M4MEMBERVARIABLE( double, FrequencyRange ); + + public: + void WriteToHDF5( HAS_GRP_IFC* aParent ); + void ReadFromHDF5( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const; + + }; + + + /*! + @class M4Header + @author N. S. Oblath + + @brief Egg file header information + + @details + General run setup can be configured by setting variables in this header object. + + The stream structure of the data is also configured using the AddStream functions. + */ + class M4_API M4Header + { + public: + typedef std::vector< M4ChannelHeader > M4ChannelHeaders; + typedef std::vector< M4StreamHeader > M4StreamHeaders; + + public: + M4Header(); + ~M4Header(); + + void CopyBasicInfo( const M4Header& aOrig ); + + M4MEMBERVARIABLE_REF( std::string, EggVersion ); + + M4MEMBERVARIABLE_REF( std::string, Filename ); + + M4MEMBERVARIABLE( uint32_t, RunDuration ); + + M4MEMBERVARIABLE_REF( std::string, Timestamp ); + + M4MEMBERVARIABLE_REF( std::string, Description ); + + M4MEMBERVARIABLE( uint32_t, NChannels ); + + M4MEMBERVARIABLE( uint32_t, NStreams ); + + M4MEMBERVARIABLE_REF_CONST( std::vector< uint32_t >, ChannelStreams ); + + M4MEMBERVARIABLE_REF_CONST( std::vector< std::vector< bool > >, ChannelCoherence ); + void SetCoherence( unsigned aChanA, unsigned aChanB, bool aCoherence ); + + M4MEMBERVARIABLE_REF_CONST( std::vector< M4ChannelHeader >, ChannelHeaders ); + std::vector< M4ChannelHeader >& GetChannelHeaders(); + + M4MEMBERVARIABLE_REF_CONST( std::vector< M4StreamHeader >, StreamHeaders ); + std::vector< M4StreamHeader >& GetStreamHeaders(); + + public: + /// Add a stream with one channel with aRecSize samples per record + /// Returns the stream number (used to address the stream later) + unsigned AddStream( const std::string& aSource, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment, + std::vector< unsigned >* aChanVec = NULL ); + /// Add a stream with multiple channels with aRecSize samples per record + /// Returns the stream number (used to address the stream later) + unsigned AddStream( const std::string& aSource, uint32_t aNChannels, uint32_t aFormat, + uint32_t anAcqRate, uint32_t aRecSize, uint32_t aSampleSize, + uint32_t aDataTypeSize, uint32_t aDataFormat, + uint32_t aBitDepth, uint32_t aBitAlignment, + std::vector< unsigned >* aChanVec = NULL ); + + public: + void WriteToHDF5( H5::H5File* aFile ); + void ReadFromHDF5( const H5::H5File* aFile ) const; + + const H5::Group* GetStreamsGroup() const; + H5::Group* GetStreamsGroup(); + + const H5::Group* GetChannelsGroup() const; + H5::Group* GetChannelsGroup(); + + private: + void WriteChannelStreams( HAS_ATTR_IFC* aLoc ); + void ReadChannelStreams( const HAS_ATTR_IFC* aLoc ) const; + + void WriteChannelCoherence( HAS_ATTR_IFC* aLoc ); + void ReadChannelCoherence( const HAS_ATTR_IFC* aLoc ) const; + + mutable H5::H5File* fFile; + mutable H5::Group* fStreamsGroup; + mutable H5::Group* fChannelsGroup; + + public: + static void WriteScalarToHDF5( HAS_ATTR_IFC* aLoc, const std::string& aName, const std::string& aValue ); + template< typename XType > + static void WriteScalarToHDF5( HAS_ATTR_IFC* aLoc, const std::string& aName, XType aValue ); + + //template< typename XArrayType > + //static void Write1DToHDF5( HAS_GRP_IFC* aLoc, const std::string& aName, const XArrayType& anArray ); + + template< typename XType > + static XType ReadScalarFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName ); + + template< typename XType > + static XType ReadScalarFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName, const XType& aDefault ); + + //template< typename XArrayType > + //static void Read1DFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName, XArrayType& anArray ); + + }; + + inline const H5::Group* M4Header::GetStreamsGroup() const + { + return fStreamsGroup; + } + + inline H5::Group* M4Header::GetStreamsGroup() + { + return fStreamsGroup; + } + + inline const H5::Group* M4Header::GetChannelsGroup() const + { + return fChannelsGroup; + } + + inline H5::Group* M4Header::GetChannelsGroup() + { + return fChannelsGroup; + } + + inline std::vector< M4ChannelHeader >& M4Header::GetChannelHeaders() + { + return fChannelHeaders; + } + + inline std::vector< M4StreamHeader >& M4Header::GetStreamHeaders() + { + return fStreamHeaders; + } + + + inline void M4Header::WriteScalarToHDF5( HAS_ATTR_IFC* aLoc, const std::string& aName, const std::string& aValue ) + { + LTRACE( mlog_mheader, "Writing string to new scalar metadata <" << aName << ">: " << aValue << "; size = " << aValue.size() ); + // aName.c_str() and aValue.c_str() are used because while using the std::string itself, the value was getting mangled + H5::DataType tType = MH5Type< std::string >::H5( aValue ); + aLoc->createAttribute( aName.c_str(), tType, H5::DataSpace( H5S_SCALAR ) ).write( tType, aValue.c_str() ); + return; + } + + template< typename XType > + void M4Header::WriteScalarToHDF5( HAS_ATTR_IFC* aLoc, const std::string& aName, XType aValue ) + { + LTRACE( mlog_mheader, "Writing value to new scalar metadata <" << aName << ">: " << aValue ); + // aName.c_str() is used because while using the std::string itself, the value was getting mangled + aLoc->createAttribute( aName.c_str(), MH5Type< XType >::H5(), H5::DataSpace( H5S_SCALAR ) ).write( MH5Type< XType >::Native(), &aValue ); + return; + } +/* + template< typename XArrayType > + void M4Header::Write1DToHDF5( HAS_GRP_IFC* aLoc, const std::string& aName, const XArrayType& anArray ) + { + typedef typename XArrayType::value_type XValueType; + LDEBUG( mlog_mheader, "Writing vector to new 1-D metadata <" << aName << ">; size = " << anArray.size() ); + hsize_t tDims[ 1 ] = { anArray.size() }; + std::cout << "tDims[0] = " << tDims[0] << std::endl; + //H5::ArrayType tTypeNative( MH5Type< XValueType >::Native(), 1, tDims ); + //H5::ArrayType tTypeH5( MH5Type< XValueType >::H5(), 1, tDims ); + H5::DataSpace dspace( 1, tDims, tDims ); + XValueType* buffer = new XValueType( anArray.size() ); + for( unsigned i = 0; i < anArray.size(); ++i ) + { + buffer[ i ] = anArray[ i ]; + std::cout << "writing bin " << i << ": " << buffer[i] << " <-- " << anArray[i] << std::endl; + } + //aLoc->createAttribute( aName, MH5Type< XValueType >::H5(), dspace ).write( MH5Type< XValueType >::Native(), buffer ); + aLoc->createDataSet( aName, MH5Type< XValueType >::H5(), dspace ).write( buffer, MH5Type< XValueType >::Native() ); + delete [] buffer; + return; + } +*/ + + // Read functions + + // read specialization for strings + template<> + inline std::string M4Header::ReadScalarFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName ) + { + //std::string tValue; + char tBuffer[ 65536 ]; // this array size matches the maximum standard attribute size according to the HDF5 documentation + H5::Attribute* tAttr = new H5::Attribute( aLoc->openAttribute( aName.c_str() ) ); + //tAttr->read( tAttr->getDataType(), tValue ); + tAttr->read( tAttr->getDataType(), tBuffer ); + delete tAttr; + std::string tValue( tBuffer ); + LTRACE( mlog_mheader, "Reading string <" << aName << ">: " << tValue << "; size = " << tValue.size() ); + return tValue; + } + + // templated read function + template< typename XType > + XType M4Header::ReadScalarFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName ) + { + XType tValue; + H5::Attribute* tAttr = new H5::Attribute( aLoc->openAttribute( aName.c_str() ) ); + tAttr->read( tAttr->getDataType(), &tValue ); + delete tAttr; + LTRACE( mlog_mheader, "Reading value <" << aName << ">: " << tValue ); + return tValue; + } + + + // Read functions with default values + + // read specialization for strings + template<> + inline std::string M4Header::ReadScalarFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName, const std::string& aDefaultValue ) + { + if( ! aLoc->attrExists( aName.c_str() ) ) return aDefaultValue; + //std::string tValue; + char tBuffer[ 65536 ]; // this array size matches the maximum standard attribute size according to the HDF5 documentation + H5::Attribute* tAttr = new H5::Attribute( aLoc->openAttribute( aName.c_str() ) ); + //tAttr->read( tAttr->getDataType(), tValue ); + tAttr->read( tAttr->getDataType(), tBuffer ); + delete tAttr; + std::string tValue( tBuffer ); + LTRACE( mlog_mheader, "Reading string <" << aName << ">: " << tValue << "; size = " << tValue.size() ); + return tValue; + } + + // templated read function + template< typename XType > + XType M4Header::ReadScalarFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName, const XType& aDefaultValue ) + { + if( ! aLoc->attrExists( aName.c_str() ) ) return aDefaultValue; + XType tValue; + H5::Attribute* tAttr = new H5::Attribute( aLoc->openAttribute( aName.c_str() ) ); + tAttr->read( tAttr->getDataType(), &tValue ); + delete tAttr; + LTRACE( mlog_mheader, "Reading value <" << aName << ">: " << tValue ); + return tValue; + } +/* + template< typename XArrayType > + void M4Header::Read1DFromHDF5( const HAS_ATTR_IFC* aLoc, const std::string& aName, XArrayType& anArray ) + { + typedef typename XArrayType::value_type XValueType; + H5::Attribute tAttr( aLoc->openAttribute( aName ) ); + H5::DataSpace tDataspace( tAttr.getSpace() ); + if( tDataspace.getSimpleExtentNdims() != 1 ) + { + throw M4Exception() << "Attribute <" << aName << "> has " << tDataspace.getSimpleExtentNdims() << " dimensions; 1 dimension was expected"; + } + hsize_t tDims[ 1 ]; + tDataspace.getSimpleExtentDims( tDims ); + XValueType* buffer = new XValueType( tDims[0] ); + LDEBUG( mlog_mheader, "Reading 1-D metadata <" << aName << "> to vector; size = " << tDims[0] ); + tAttr.read( tAttr.getDataType(), buffer ); + for( unsigned i = 0; i < anArray.size(); ++i ) + { + anArray[ i ] = buffer[ i ]; + } + delete [] buffer; + return; + } +*/ +} + +// Pretty printing methods +M4_API std::ostream& operator<<( std::ostream& out, const monarch4::M4StreamHeader& hdr ); +M4_API std::ostream& operator<<( std::ostream& out, const monarch4::M4ChannelHeader& hdr ); +M4_API std::ostream& operator<<( std::ostream& out, const monarch4::M4Header& hdr ); + +#endif diff --git a/Monarch4/M4IToA.cc b/Monarch4/M4IToA.cc new file mode 100644 index 0000000..85a95ea --- /dev/null +++ b/Monarch4/M4IToA.cc @@ -0,0 +1,141 @@ +/* + * M4IToA.cc + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + * + * Code is the "countlut" method from https://github.com/miloyip/itoa-benchmark + * Original code copyright: Copyright(c) 2014 Milo Yip (miloyip@gmail.com) + * License: + * Copyright (C) 2014 Milo Yip + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + + +#define M4_API_EXPORTS + +#include "M4IToA.hh" + +// Additional count number of digit pass +// Use lookup table of two gDigitsLut + +namespace monarch4 +{ + + M4_API void u32toa( uint32_t value, char* buffer ) + { + unsigned digit = CountDecimalDigit32(value); + buffer += digit; + *buffer = '\0'; + + while ( value >= 100 ) + { + const unsigned i = (value % 100) << 1; + value /= 100; + *--buffer = gDigitsLut[i + 1]; + *--buffer = gDigitsLut[i]; + } + + if ( value < 10 ) + { + *--buffer = char(value) + '0'; + } + else + { + const unsigned i = value << 1; + *--buffer = gDigitsLut[i + 1]; + *--buffer = gDigitsLut[i]; + } + } + + M4_API void i32toa( int32_t value, char* buffer ) + { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + + u32toa( static_cast(value), buffer ); + } + + M4_API void u64toa( uint64_t value, char* buffer ) + { + unsigned digit = CountDecimalDigit64(value); + buffer += digit; + *buffer = '\0'; + + while ( value >= 100000000 ) + { + const uint32_t a = static_cast(value % 100000000); + value /= 100000000; + + const uint32_t b = a / 10000; + const uint32_t c = a % 10000; + + const uint32_t b1 = (b / 100) << 1; + const uint32_t b2 = (b % 100) << 1; + const uint32_t c1 = (c / 100) << 1; + const uint32_t c2 = (c % 100) << 1; + + buffer -= 8; + + buffer[0] = gDigitsLut[b1]; + buffer[1] = gDigitsLut[b1 + 1]; + buffer[2] = gDigitsLut[b2]; + buffer[3] = gDigitsLut[b2 + 1]; + buffer[4] = gDigitsLut[c1]; + buffer[5] = gDigitsLut[c1 + 1]; + buffer[6] = gDigitsLut[c2]; + buffer[7] = gDigitsLut[c2 + 1]; + } + + uint32_t value32 = static_cast(value); + while ( value32 >= 100 ) + { + const unsigned i = static_cast(value32 % 100) << 1; + value32 /= 100; + *--buffer = gDigitsLut[i + 1]; + *--buffer = gDigitsLut[i]; + } + + if ( value32 < 10 ) + { + *--buffer = char(value32) + '0'; + } + else + { + const unsigned i = static_cast(value32) << 1; + *--buffer = gDigitsLut[i + 1]; + *--buffer = gDigitsLut[i]; + } + } + + M4_API void i64toa( int64_t value, char* buffer ) + { + if ( value < 0 ) + { + *buffer++ = '-'; + value = -value; + } + + u64toa( static_cast(value), buffer ); + } + +} /* namespace monarch4 */ diff --git a/Monarch4/M4IToA.hh b/Monarch4/M4IToA.hh new file mode 100644 index 0000000..e14d880 --- /dev/null +++ b/Monarch4/M4IToA.hh @@ -0,0 +1,189 @@ +/* + * M4IToA.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + * Code is the "countlut" method from https://github.com/miloyip/itoa-benchmark + * Original code copyright: Copyright(c) 2014 Milo Yip (miloyip@gmail.com) + * License: + * Copyright (C) 2014 Milo Yip + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +#pragma once + +#include "M4Constants.hh" + +// from digitslut.h + +namespace monarch4 +{ + const char gDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; +} + + +// from countdecimaldigit.h + +#include +#ifdef _MSC_VER +#include "intrin.h" +#endif + +namespace monarch4 +{ + //****************************** + // Integer-to-string conversions + //****************************** + + /// Quickly convert a 32-bit unsigned integer to a char array (buffer should already be allocated) + M4_API void u32toa( uint32_t value, char* buffer ); + /// Quickly convert a 32-bit signed integer to a char array (buffer should already be allocated) + M4_API void i32toa( int32_t value, char* buffer ); + /// Quickly convert a 64-bit unsigned integer to a char array (buffer should already be allocated) + M4_API void u64toa( uint64_t value, char* buffer ); + /// Quickly convert a 64-bit signed integer to a char array (buffer should already be allocated) + M4_API void i64toa( int64_t value, char* buffer ); + + + + + //****************** + // Utility functions + //****************** + + inline unsigned CountDecimalDigit32( uint32_t n ) + { +#if defined(_MSC_VER) || defined(__GNUC__) + static const uint32_t powers_of_10[] = + { + 0, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 + }; + +#ifdef _MSC_VER + unsigned long i = 0; + _BitScanReverse(&i, n | 1); + uint32_t t = (i + 1) * 1233 >> 12; +#elif __GNUC__ + uint32_t t = (32 - __builtin_clz(n | 1)) * 1233 >> 12; +#endif + return t - (n < powers_of_10[t]) + 1; +#else + // Simple pure C++ implementation + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + if (n < 1000000000) return 9; + return 10; +#endif + } + + inline unsigned CountDecimalDigit64( uint64_t n ) + { +#if defined(_MSC_VER) || defined(__GNUC__) + static const uint64_t powers_of_10[] = + { + 0, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U + }; + +#if _M_IX86 + unsigned long i = 0; + uint64_t m = n | 1; + if (_BitScanReverse(&i, m >> 32)) + i += 32; + else + _BitScanReverse(&i, m & 0xFFFFFFFF); + uint32_t t = (i + 1) * 1233 >> 12; +#elif _M_X64 + unsigned long i = 0; + _BitScanReverse64(&i, n | 1); + uint32_t t = (i + 1) * 1233 >> 12; +#elif __GNUC__ + uint32_t t = (64 - __builtin_clzll(n | 1)) * 1233 >> 12; +#endif + return t - (n < powers_of_10[t]) + 1; +#else + // Simple pure C++ implementation + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + if (n < 1000000000) return 9; + if (n < 10000000000) return 10; + if (n < 100000000000) return 11; + if (n < 1000000000000) return 12; + if (n < 10000000000000) return 13; + if (n < 100000000000000) return 14; + if (n < 1000000000000000) return 15; + if (n < 10000000000000000) return 16; + if (n < 100000000000000000) return 17; + if (n < 1000000000000000000) return 18; + if (n < 10000000000000000000) return 19; + return 20; +#endif + } +} /* namespace monarch4 */ diff --git a/Monarch4/M4MemberVariable.hh b/Monarch4/M4MemberVariable.hh new file mode 100644 index 0000000..9c3356f --- /dev/null +++ b/Monarch4/M4MemberVariable.hh @@ -0,0 +1,89 @@ +/* + * M4MemberVariable.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + * This version has been specialized for Monarch: variables are mutable and setters are const. + */ + +#ifndef M4MEMBERVARIABLE_HH_ +#define M4MEMBERVARIABLE_HH_ + +/** + * Macros for class member variables + * + * Special setup for Monarch: all of the variables are mutable so that they can be modified when a file is being read. + * This follows the Monarch use of "const" for read-only mode. + * + * In all cases remember to initialize the variables! + * + * For "normal" variables + * Defines accessors [type GetMyVar() const], [void SetMyVar( type )], and member variable [type fMyVar] + * The Set function is not available if the _NOSET macros are used + * - M4MEMBERVARIABLE + * - M4MEMBERVARIABLE_NOSET + * - M4MEMBERVARIABLE_STATIC + * - M4MEMBERVARIABLE_STATIC_NOSET + * + * For variables accessed by reference + * Defines accessors [const type& MyVar() const], [type& MyVar()], and member variable [type fMyVar] + * The non-const function is not available if the _CONST macros are used + * - M4MEMBERVARIABLE_REF + * - M4MEMBERVARIABLE_REF_CONST + * - M4MEMBERVARIABLE_REF_STATIC + * - M4MEMBERVARIABLE_REF_STATIC_CONST + * + * For pointer variables + * Defines accessors [type* GetMyVar() const], [void SetMyVar( type* )], and member variable [type* fMyVar] + * The Set function is not available if the _NOSET macros are used + * - M4MEMBERVARIABLE_PTR + * - M4MEMBERVARIABLE_PTR_NOSET + * - M4MEMBERVARIABLE_PTR_STATIC + * - M4MEMBERVARIABLE_PTR_STATIC_NOSET + * + * For shared_ptr's + * Defines accessors [const std::shared_ptr< type > MyVar() const], [std::shared_ptr< type > MyVar()], and member variable [std::shared_ptr< type > fMyVar] + * The non-const function is not available if the _CONST macros are used + * - M4MEMBERVARIABLE_SHARED_PTR + * - M4MEMBERVARIABLE_SHARED_PTR_CONST + * - M4MEMBERVARIABLE_SHARED_PTR_STATIC + * - M4MEMBERVARIABLE_SHARED_PTR_STATIC_CONST + * + * For atomic variables + * Defines accessors [type GetMyVar() const], [void SetMyVar( type )], and member variable [std::atomic< type > fMyVar] + * The Set function is not available if the _NOSET macros are used + * - M4MEMBERVARIABLE_ATOMIC + * - M4MEMBERVARIABLE_ATOMIC_NOSET + * - M4MEMBERVARIABLE_ATOMIC_STATIC + * - M4MEMBERVARIABLE_ATOMIC_STATIC_NOSET + * + */ + +#include "_camel_case_member_variables.hh" + +#define M4MEMBERVARIABLE camel_case_mv_accessible_mutable +#define M4MEMBERVARIABLE_NOSET camel_case_mv_accessible_mutable_noset +#define M4MEMBERVARIABLE_STATIC camel_case_mv_accessible_static +#define M4MEMBERVARIABLE_STATIC_NOSET camel_case_mv_accessible_static_noset + +#define M4MEMBERVARIABLE_REF camel_case_mv_referrable_mutable +#define M4MEMBERVARIABLE_REF_CONST camel_case_mv_referrable_mutable +#define M4MEMBERVARIABLE_REF_STATIC camel_case_mv_referrable_static +#define M4MEMBERVARIABLE_REF_STATIC_CONST camel_case_mv_referrable_static_const + +#define M4MEMBERVARIABLE_PTR camel_case_mv_assignable_mutable +#define M4MEMBERVARIABLE_PTR_NOSET camel_case_mv_assignable_mutable_noset +#define M4MEMBERVARIABLE_PTR_STATIC camel_case_mv_assignable_static +#define M4MEMBERVARIABLE_PTR_STATIC_NOSET camel_case_mv_assignable_static_noset + +#define M4MEMBERVARIABLE_SHARED_PTR camel_case_mv_shared_ptr_mutable +#define M4MEMBERVARIABLE_SHARED_PTR_NOSET camel_case_mv_shared_ptr_mutable +#define M4MEMBERVARIABLE_SHARED_PTR_STATIC camel_case_mv_shared_ptr_static +#define M4MEMBERVARIABLE_SHARED_PTR_STATIC_NOSET camel_case_mv_shared_ptr_static + +#define M4MEMBERVARIABLE_ATOMIC camel_case_mv_atomic_mutable +#define M4MEMBERVARIABLE_ATOMIC_NOSET camel_case_mv_atomic_mutable_noset +#define M4MEMBERVARIABLE_ATOMIC_STATIC camel_case_mv_atomic_static +#define M4MEMBERVARIABLE_ATOMIC_STATIC_NOSET camel_case_mv_atomic_static_noset + +#endif /* M4MEMBERVARIABLE_HH_ */ diff --git a/Monarch4/M4Monarch.cc b/Monarch4/M4Monarch.cc new file mode 100644 index 0000000..a924beb --- /dev/null +++ b/Monarch4/M4Monarch.cc @@ -0,0 +1,287 @@ +/* + * M4Monarch.cc + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ +#define M4_API_EXPORTS + +#include "M4Monarch.hh" + +#include "logger.hh" + +using std::string; + +namespace monarch4 +{ + LOGGER( mlog, "MMonarch4" ); + + Monarch4::Monarch4() : + fState( eClosed ), + fFile( nullptr ), + fHeader( nullptr ), + fMutexPtr( new std::mutex() ) + { + } + + Monarch4::~Monarch4() + { + if( fState == eOpenToRead || fState == eReadyToRead) FinishReading(); + if( fState == eOpenToWrite || fState == eReadyToWrite) FinishWriting(); + + if( fHeader != nullptr ) + { + delete fHeader; + fHeader = nullptr; + } + + while( ! fStreams.empty() ) + { + delete fStreams.back(); + fStreams.pop_back(); + } + + if( fFile != nullptr ) + { + delete fFile; + fFile = nullptr; + } + } + + Monarch4::State Monarch4::GetState() const + { + return fState; + } + + const Monarch4* Monarch4::OpenForReading( const string& aFilename ) + { + Monarch4* tMonarch4 = new Monarch4(); + + try + { + tMonarch4->fFile = new H5::H5File( aFilename.c_str(), H5F_ACC_RDONLY ); + } + catch( H5::Exception& e ) + { + delete tMonarch4; + throw M4Exception() << "Could not open <" << aFilename << "> for reading; an H5::Exception was thrown: " << e.getCDetailMsg(); + return nullptr; + } + catch( std::exception& e ) + { + delete tMonarch4; + throw M4Exception() << "Could not open <" << aFilename << "> for reading; a std::exception was thrown: " << e.what(); + return nullptr; + } + if( tMonarch4->fFile == nullptr ) + { + delete tMonarch4; + throw M4Exception() << "Could not open <" << aFilename << "> for reading"; + return nullptr; + } + LDEBUG( mlog, "Opened egg file <" << aFilename << "> for reading" ); + + tMonarch4->fHeader = new M4Header(); + tMonarch4->fHeader->Filename() = aFilename; + + tMonarch4->fState = eOpenToRead; + + return tMonarch4; + } + + Monarch4* Monarch4::OpenForWriting( const string& aFilename ) + { + Monarch4* tMonarch4 = new Monarch4(); + + try + { + tMonarch4->fFile = new H5::H5File( aFilename.c_str(), H5F_ACC_TRUNC ); + } + catch( H5::Exception& e ) + { + delete tMonarch4; + throw M4Exception() << "Could not open <" << aFilename << "> for writing; an H5::Exception was thrown: " << e.getCDetailMsg(); + return nullptr; + } + catch( std::exception& e ) + { + delete tMonarch4; + throw M4Exception() << "Could not open <" << aFilename << "> for writing; a std::exception was thrown: " << e.what(); + return nullptr; + } + if( tMonarch4->fFile == nullptr ) + { + delete tMonarch4; + throw M4Exception() << "Could not open <" << aFilename << "> for writing"; + return nullptr; + } + LDEBUG( mlog, "Opened egg file <" << aFilename << "> for writing" ); + + tMonarch4->fHeader = new M4Header(); + tMonarch4->fHeader->Filename() = aFilename; + + tMonarch4->fState = eOpenToWrite; + + return tMonarch4; + } + + void Monarch4::ReadHeader() const + { + if( fState != eOpenToRead ) + { + throw M4Exception() << "File not opened to read"; + } + + // Read the header information from the file (run header, plus all stream and channel headers) + try + { + fHeader->ReadFromHDF5( fFile ); + } + catch( H5::Exception& e ) + { + throw M4Exception() << "HDF5 error while reading the header:\n\t" << e.getCDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + catch( M4Exception& e ) + { + throw; + } + + + H5::Group* tStreamsGroup = fHeader->GetStreamsGroup(); + + try + { + // Create the stream objects based on the configuration from the header + for( M4Header::M4StreamHeaders::const_iterator streamIt = fHeader->StreamHeaders().begin(); + streamIt != fHeader->StreamHeaders().end(); + ++streamIt ) + { + fStreams.push_back( new M4Stream( *streamIt, tStreamsGroup ) ); + fStreams.back()->SetMutex( fMutexPtr ); + } + } + catch( H5::Exception& e ) + { + throw M4Exception() << "HDF5 error while creating stream objects for reading:\n\t" << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + catch( M4Exception& e ) + { + throw; + } + + fState = eReadyToRead; + return; + } + + void Monarch4::WriteHeader() + { + if( fState != eOpenToWrite ) + { + throw M4Exception() << "File not opened to write"; + } + + // Write the header to the file + // This will create the following groups: run, streams, and channels + try + { + fHeader->WriteToHDF5( fFile ); + } + catch( H5::Exception& e ) + { + throw M4Exception() << "HDF5 error while writing header:\n\t" << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + catch( M4Exception& e ) + { + throw; + } + + H5::Group* tStreamsGroup = fHeader->GetStreamsGroup(); + + try + { + // Create the stream objects based on the configuration from the header + for( M4Header::M4StreamHeaders::const_iterator streamIt = fHeader->StreamHeaders().begin(); + streamIt != fHeader->StreamHeaders().end(); + ++streamIt ) + { + fStreams.push_back( new M4Stream( *streamIt, tStreamsGroup ) ); + fStreams.back()->SetMutex( fMutexPtr ); + } + } + catch( H5::Exception& e ) + { + throw M4Exception() << "HDF5 error while creating stream objects:\n\t" << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + catch( M4Exception& e ) + { + throw; + } + + fState = eReadyToWrite; + return; + } + + void Monarch4::FinishReading() const + { + LDEBUG( mlog, "Finishing reading <" << fHeader->Filename() << ">" ); + try + { + if( fHeader != nullptr ) + { + delete fHeader; + fHeader = nullptr; + } + for( std::vector< M4Stream* >::iterator streamIt = fStreams.begin(); streamIt != fStreams.end(); ++streamIt ) + { + const_cast< const M4Stream* >(*streamIt)->Close(); + delete *streamIt; + *streamIt = nullptr; + } + if( fFile != nullptr ) + { + fFile->close(); + delete fFile; + fFile = nullptr; + } + } + catch( H5::Exception& e ) + { + throw M4Exception() << "Error while closing: " << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + fState = eClosed; + return; + } + + void Monarch4::FinishWriting() + { + LINFO( mlog, "Finishing writing <" << fHeader->Filename() << ">" ); + try + { + if( fHeader != nullptr ) + { + delete fHeader; + fHeader = nullptr; + } + for( std::vector< M4Stream* >::iterator streamIt = fStreams.begin(); streamIt != fStreams.end(); ++streamIt ) + { + (*streamIt)->Close(); + delete *streamIt; + *streamIt = nullptr; + } + if( fFile != nullptr ) + { + fFile->close(); + delete fFile; + fFile = nullptr; + } + fFile = nullptr; + } + catch( H5::Exception& e ) + { + throw M4Exception() << "Error while closing: " << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + fState = eClosed; + return; + } + +} diff --git a/Monarch4/M4Monarch.hh b/Monarch4/M4Monarch.hh new file mode 100644 index 0000000..6b1243e --- /dev/null +++ b/Monarch4/M4Monarch.hh @@ -0,0 +1,169 @@ +/* + * M4Monarch.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4MONARCH_HH_ +#define M4MONARCH_HH_ + +#include "M4Exception.hh" +#include "M4Header.hh" +#include "logger.hh" +#include "M4Stream.hh" + +#include "H5Cpp.h" + +#include +#include + +namespace monarch4 +{ + LOGGER( mlog_mmonarch, "M4Monarch4.h" ); + + /*! + @class Monarch4 + @author N. S. Oblath (v3), D. Furse (original) + + @brief Egg file read/write access + + @details + Monarch4 objects are created with one of the two static Open functions: + - OpenForReading() will create a read-only Monarch4 object; + - OpenForWriting() will create a writeable Monarch4 object. + + The data structure and general header information is setup/read via the header (accessed with GetHeader()). + + Stream data is written or read via the stream objects (accessed with GetStream( unsigned )). + */ + class M4_API Monarch4 + { + //*********************** + // constructors and state + //*********************** + + private: + //private to force use of static constructor methods + Monarch4(); + Monarch4( const Monarch4& ) = delete; + Monarch4( Monarch4&& ) = delete; + + //current state of monarch + public: + typedef enum + { + eOpenToRead, // state when monarch has a file open but hasn't read the header + eOpenToWrite, // state when monarch has a file open but hasn't written the header + eReadyToRead, // state when monarch has dealt with the header and is reading records + eReadyToWrite, // state when monarch has dealt with the header and is writing records + eClosed // state when monarch has no file + } State; + State GetState() const; + private: + mutable State fState; + + public: + ~Monarch4(); + + //******************************** + // methods for reading (all const) + //******************************** + + public: + + /// This static method opens the file for reading. + /// If the file exists and can be read, this returns a prepared monarch pointer, and memory is allocated for the header. + /// Upon successful return monarch is in the eOpen state. + static const Monarch4* OpenForReading( const std::string& filename ); + + /// This method extracts the header information from the file. + /// If the header read correctly, this returns and the header may be examined, and memory is allocated for the record. + /// Upon successful return monarch is in the eReady state. + /// An exception is thrown if the header is not read. + void ReadHeader() const; + + /// Get the pointer to the header. + const M4Header* GetHeader() const; + + /// Get the pointer to a particular stream + const M4Stream* GetStream( unsigned stream ) const; + + /// Close the file. + void FinishReading() const; + + //********************************* + // methods for writing (none const) + //********************************* + + public: + + /// This static method opens the file for writing. + /// If the file exists and can be written, this returns a prepared monarch pointer, and memory is allocated for the header. + /// Upon successful return monarch is in the eOpen state. + static Monarch4* OpenForWriting( const std::string& filename ); + + /// This method marshals the current header to the file. + /// If the header marshalled correctly, this returns true, memory is allocated for the record(s). + /// Upon successful return monarch is in the eReady state. + void WriteHeader(); + + /// Get the pointer to the header. + M4Header* GetHeader(); + + /// Get the pointer to a particular stream + M4Stream* GetStream( unsigned stream ); + + /// Close the file. + void FinishWriting(); + + private: + // the HDF5 file + mutable H5::H5File* fFile; + + // the header + mutable M4Header* fHeader; + + // the streams + mutable std::vector< M4Stream* > fStreams; + + // stream read/write mutex + mutable mutex_ptr fMutexPtr; + + }; + + inline const M4Header* Monarch4::GetHeader() const + { + return fHeader; + } + inline M4Header* Monarch4::GetHeader() + { + return fHeader; + } + + inline const M4Stream* Monarch4::GetStream( unsigned iStream ) const + { + try + { + return fStreams.at( iStream ); + } + catch( std::out_of_range& e ) + { + return nullptr; + } + } + inline M4Stream* Monarch4::GetStream( unsigned iStream ) + { + try + { + return fStreams.at( iStream ); + } + catch( std::out_of_range& e ) + { + return nullptr; + } + } + +} + +#endif /* M4MONARCH_HH_ */ diff --git a/Monarch4/M4Record.cc b/Monarch4/M4Record.cc new file mode 100644 index 0000000..05510e8 --- /dev/null +++ b/Monarch4/M4Record.cc @@ -0,0 +1,98 @@ +/* + * M4Record.cc + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ +#define M4_API_EXPORTS + +#include "M4Record.hh" + +namespace monarch4 +{ + M4Record::M4Record( unsigned aNBytes ) : + fOwnsData( true ), + fRecordId( NULL ), + fTime( NULL ), + fData( NULL ) + { + if( aNBytes != 0 ) + { + fRecordId = new RecordIdType(); + fTime = new TimeType(); + fData = new byte_type[ aNBytes ]; + } + } + + M4Record::M4Record( RecordIdType* aRecPtr, TimeType* aTimePtr, byte_type* aDataPtr ) : + fOwnsData( false ), + fRecordId( aRecPtr ), + fTime( aTimePtr ), + fData( aDataPtr ) + { + } + + M4Record::~M4Record() + { + ClearData(); + } + + void M4Record::Initialize() + { + ClearData(); + return; + } + + void M4Record::Initialize( unsigned aNBytes ) + { + ClearData(); + fRecordId = new RecordIdType(); + fTime = new TimeType(); + SetRecordId( 0 ); + SetTime( 0 ); + fData = new byte_type[ aNBytes ]; + return; + } + + void M4Record::Initialize( RecordIdType* aRecPtr, TimeType* aTimePtr, byte_type* aDataPtr ) + { + ClearData(); + fOwnsData = false; + fRecordId = aRecPtr; + fTime = aTimePtr; + fData = aDataPtr; + return; + } + + void M4Record::ClearData() + { + if( fOwnsData ) + { + delete fRecordId; + delete fTime; + delete [] fData; + } + fRecordId = NULL; + fTime = NULL; + fData = NULL; + fOwnsData = true; + return; + } + + void M4Record::UpdateDataPtr( const byte_type* aDataPtr ) const + { + const_cast< M4Record* >(this)->UpdateDataPtr( const_cast< byte_type* >( aDataPtr ) ); + return; + } + + void M4Record::UpdateDataPtr( byte_type* aDataPtr ) + { + if( fOwnsData ) + { + throw M4Exception() << "Cannot update data pointer when the record owns the data"; + } + fData = aDataPtr; + return; + } + +} diff --git a/Monarch4/M4Record.hh b/Monarch4/M4Record.hh new file mode 100644 index 0000000..0da2b84 --- /dev/null +++ b/Monarch4/M4Record.hh @@ -0,0 +1,123 @@ +/* + * M4Record.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4RECORD_HH_ +#define M4RECORD_HH_ + +#include "M4Exception.hh" +#include "M4MemberVariable.hh" +#include "M4Types.hh" + +namespace monarch4 +{ + /*! + @class M4Record + @author N. S. Oblath + + @brief Contains the information that makes up a record. + + @details + Includes the record ID, timestamp (ns since the start of the run), and data array. + + The array can either own its own data, or point to data elsewhere. + This is useful for having streams with multiple non-interleaved channels + The channel record objects can point to the appropriate location in the stream record object. + + If the M4Record( unsigned ) constructor or SetData( unsigned ) function is used, the record will allocate its own data. + If the M4Record( byte_type* ) constructor or SetData( byte_type* ) function is used, the record will point to data elsewhere that it does not own. + + If the record does not own the data, then the data pointer can be updatd with UpdateDataPtr(). + */ + class M4_API M4Record + { + public: + M4Record( unsigned aNBytes = 0 ); + M4Record( RecordIdType* aRecPtr, TimeType* aTimePtr, byte_type* aDataPtr ); + ~M4Record(); + + M4Record( const M4Record& ) = delete; + M4Record& operator=( const M4Record& ) = delete; + + /// Allocate no memory for the record; data pointer is to NULL + void Initialize(); + /// Allocate aNBytes of memory for the record + void Initialize( unsigned aNBytes ); + /// Do not allocate memory for the record; instead point to someone else's memory. + /// Warning: if the memory pointed to is de-allocated, use of this record should be discontinued + void Initialize( RecordIdType* aRecPtr, TimeType* aTimePtr, byte_type* aDataPtr ); + + void ClearData(); + + RecordIdType GetRecordId() const; + RecordIdType* GetRecordIdPtr() const; + void SetRecordId( RecordIdType aId ); + + TimeType GetTime() const; + TimeType* GetTimePtr() const; + void SetTime( TimeType aTime ); + + const byte_type* GetData() const; + byte_type* GetData(); + + void UpdateDataPtr( const byte_type* aDataPtr ) const; + void UpdateDataPtr( byte_type* aDataPtr ); + + M4MEMBERVARIABLE_NOSET( bool, OwnsData ); + + private: + RecordIdType* fRecordId; + TimeType* fTime; + + mutable byte_type* fData; + + }; + + inline RecordIdType M4Record::GetRecordId() const + { + return *fRecordId; + } + + inline RecordIdType* M4Record::GetRecordIdPtr() const + { + return fRecordId; + } + + inline void M4Record::SetRecordId( RecordIdType aId ) + { + (*fRecordId) = aId; + return; + } + + inline TimeType M4Record::GetTime() const + { + return *fTime; + } + + inline TimeType* M4Record::GetTimePtr() const + { + return fTime; + } + + inline void M4Record::SetTime( TimeType aTime ) + { + (*fTime) = aTime; + return; + } + + inline const byte_type* M4Record::GetData() const + { + return fData; + } + + inline byte_type* M4Record::GetData() + { + return fData; + } + +} + +#endif /* M4RECORD_HH_ */ diff --git a/Monarch4/M4Stream.cc b/Monarch4/M4Stream.cc new file mode 100644 index 0000000..e84f91b --- /dev/null +++ b/Monarch4/M4Stream.cc @@ -0,0 +1,667 @@ +/* + * M4Stream.cc + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#define M4_API_EXPORTS + +#include "M4Stream.hh" + +#include "M4IToA.hh" +#include "logger.hh" + +#include // for abs + +/* Notes on reading: + * + * During reading, both fNRecordsInAcq and fRecordsCount are used, as well as both fAcquisitionId and fNAcquisitions; + * All four variables will always be valid. + * + */ + +/* Notes on writing: + * + * As writing progresses, fRecordCountInAcq is incremented, and fNRecordsInAcq is ignored. + * When the acquisition is finalized with FinalizeCurrentAcq(), fNRecordsInAcq is updated to be fRecordCountInAcq + 1. + * Therefore fRecordCountInAcq is only valid for the last completed acquisition. + * + * As writing progresses, fAcquisitionId is incremented and fNAcquisitions is ignored. + * When the stream is finalized with FinalizeStream(), fNAcquisitions is updated to be fAcquisitionId + 1. + * Therefore fNAcquisitions is only valid after the stream is finalized. + * + */ + +namespace monarch4 +{ + LOGGER( mlog, "M4Stream" ); + + M4Stream::M4Stream( const M4StreamHeader& aHeader, HAS_GRP_IFC* aH5StreamsLoc, uint32_t aAccessFormat ) : + fMode( kRead ), + fDoReadRecord( NULL ), + fDoWriteRecord( NULL ), + fIsInitialized( false ), + fRecordsAccessed( false ), + fDataTypeSize( aHeader.GetDataTypeSize() ), + fSampleSize( aHeader.GetSampleSize() ), + fStrRecNBytes( aHeader.GetNChannels() * aHeader.GetRecordSize() * aHeader.GetSampleSize() * aHeader.GetDataTypeSize() ), + fStrRecSize( aHeader.GetNChannels() * aHeader.GetRecordSize() ), + fChanRecNBytes( aHeader.GetRecordSize() * aHeader.GetSampleSize() * aHeader.GetDataTypeSize() ), + fChanRecSize( aHeader.GetRecordSize() ), + fChanRecLength( (double)aHeader.GetRecordSize() / ((double)aHeader.GetAcquisitionRate() * 1.e-3) ), + fStreamRecord(), + fNChannels( aHeader.GetNChannels() ), + fChannelRecords( new M4Record[ aHeader.GetNChannels() ] ), + fNAcquisitions( 0 ), + fAcquisitionId( 0 ), + fRecordCountInAcq( 0 ), + fNRecordsInAcq( 0 ), + fAcqFirstRecTime( 0 ), + fAcqFirstRecId( 0 ), + fAcqFirstRecTimes( NULL ), + fAcqFirstRecIds( NULL ), + fDataInterleaved( aHeader.GetChannelFormat() == sInterleaved ), + fAccessFormat( aAccessFormat ), + fRecordIndex(), + fRecordCountInFile( 0 ), + fNRecordsInFile( 0 ), + fFirstRecordInFile( 0 ), + fH5StreamParentLoc( new H5::Group( aH5StreamsLoc->openGroup( aHeader.GetLabel() ) ) ), + fH5AcqLoc( NULL ), + fH5CurrentAcqDataSet( NULL ), + fH5DataSpaceUser( NULL ), + fMutexPtr( new std::mutex() ) + { + LDEBUG( mlog, "Creating stream for <" << aHeader.GetLabel() << ">" ); + + if( aHeader.GetDataFormat() == sDigitizedUS ) + { + switch( fDataTypeSize ) + { + case 1: + fDataTypeInFile = H5::PredType::STD_U8LE; + fDataTypeUser = H5::PredType::NATIVE_UINT8; + break; + case 2: + fDataTypeInFile = H5::PredType::STD_U16LE; + fDataTypeUser = H5::PredType::NATIVE_UINT16; + break; + case 4: + fDataTypeInFile = H5::PredType::STD_U32LE; + fDataTypeUser = H5::PredType::NATIVE_UINT32; + break; + case 8: + fDataTypeInFile = H5::PredType::STD_U64LE; + fDataTypeUser = H5::PredType::NATIVE_UINT64; + break; + default: + throw M4Exception() << "Unknown unsigned integer data type size: " << fDataTypeSize; + } + } + else if( aHeader.GetDataFormat() == sDigitizedS ) + { + switch( fDataTypeSize ) + { + case 1: + fDataTypeInFile = H5::PredType::STD_I8LE; + fDataTypeUser = H5::PredType::NATIVE_INT8; + break; + case 2: + fDataTypeInFile = H5::PredType::STD_I16LE; + fDataTypeUser = H5::PredType::NATIVE_INT16; + break; + case 4: + fDataTypeInFile = H5::PredType::STD_I32LE; + fDataTypeUser = H5::PredType::NATIVE_INT32; + break; + case 8: + fDataTypeInFile = H5::PredType::STD_I64LE; + fDataTypeUser = H5::PredType::NATIVE_INT64; + break; + default: + throw M4Exception() << "Unknown signed integer data type size: " << fDataTypeSize; + } + } + else // aHeader.GetDataFormat() == sAnalog + { + switch( fDataTypeSize ) + { + case 4: + fDataTypeInFile = H5::PredType::IEEE_F32LE; + fDataTypeUser = H5::PredType::NATIVE_FLOAT; + break; + case 8: + fDataTypeInFile = H5::PredType::IEEE_F64LE ; + fDataTypeUser = H5::PredType::NATIVE_DOUBLE; + break; + default: + throw M4Exception() << "Unknown floating-point data type size: " << fDataTypeSize; + } + } + + // variables to store the HDF5 error printing state + H5E_auto2_t tAutoPrintFunc; + void* tClientData; + + // Determine if we're in read or write mode + // and get/create the acquisitions group + // Nested exceptions are used so that the outer try block can be used to determine whether we're reading or writing + try + { + // turn off HDF5 error printing because the throwing of an exception here means we're writing instead of reading + H5::Exception::getAutoPrint( tAutoPrintFunc, &tClientData ); + H5::Exception::dontPrint(); + + fH5AcqLoc = new H5::Group( fH5StreamParentLoc->openGroup( "acquisitions" ) ); + LDEBUG( mlog, "Opened acquisition group in mode" ); + + // turn HDF5 error printing back on + H5::Exception::setAutoPrint( tAutoPrintFunc, tClientData ); + + try + { + H5::Attribute tAttrNAcq( fH5StreamParentLoc->openAttribute( "n_acquisitions" ) ); + tAttrNAcq.read( tAttrNAcq.getDataType(), &fNAcquisitions ); + H5::Attribute tAttrNRec( fH5StreamParentLoc->openAttribute( "n_records" ) ); + tAttrNRec.read( tAttrNRec.getDataType(), &fNRecordsInFile ); + BuildIndex(); + } + catch( H5::Exception& ) + { + throw M4Exception() << "Acquisitions group is not properly setup for reading\n"; + } + + LDEBUG( mlog, "Number of acquisitions found: " << fNAcquisitions << "; Number of records found: " << fNRecordsInFile ); + fMode = kRead; + } + catch( H5::Exception& ) + { + // if we ended up here, the acquisitions group doesn't exist, so we must be in write mode + + // turn HDF5 error printing back on + H5::Exception::setAutoPrint( tAutoPrintFunc, tClientData ); + + try + { + fH5AcqLoc = new H5::Group( fH5StreamParentLoc->createGroup( "acquisitions" ) ); + LDEBUG( mlog, "Opened acquisition group in mode" ); + fMode = kWrite; + } + catch( H5::Exception& ) + { + throw M4Exception() << "Unable to open new acquisitions group for writing\n"; + } + } + + Initialize(); + } + + M4Stream::~M4Stream() + { + delete fH5DataSpaceUser; fH5DataSpaceUser = NULL; + delete fH5CurrentAcqDataSet; fH5CurrentAcqDataSet = NULL; + delete fH5AcqLoc; fH5AcqLoc = NULL; + delete fH5StreamParentLoc; fH5StreamParentLoc = NULL; + + delete [] fChannelRecords; + } + + void M4Stream::Initialize() const + { + LDEBUG( mlog, "Initializing stream" ); + fIsInitialized = false; + + // The case where the access format is separate, but the data in the file is interleaved is special. + // In this case, the stream record memory is not used. + // Reading and writing is done directly from the channel records using HDF5's interleaving capabilities. + if( fAccessFormat == sSeparate && fDataInterleaved && fNChannels != 1 ) + { + // no memory is allocated for the stream record + fStreamRecord.Initialize(); + + // allocate memory for each channel record + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + fChannelRecords[ iChan ].Initialize( fChanRecNBytes ); + } + + // set the read/write functions to the special versions + fDoReadRecord = &M4Stream::ReadRecordInterleavedToSeparate; + fDoWriteRecord = &M4Stream::WriteRecordSeparateToInterleaved; + + // Arrays for HDF5 file reading/writing + fStrDataDims[ 0 ] = 1; fStrDataDims[ 1 ] = fStrRecSize * fSampleSize; + fStrMaxDataDims[ 0 ] = H5S_UNLIMITED; fStrMaxDataDims[ 1 ] = fStrRecSize * fSampleSize; + fStrDataChunkDims[ 0 ] = 1; fStrDataChunkDims[ 1 ] = fStrRecSize * fSampleSize; + fDataDims1Rec[ 0 ] = 1; fDataDims1Rec[ 1 ] = fChanRecSize * fSampleSize; + fDataOffset[ 0 ] = 0; fDataOffset[ 1 ] = 0; + fDataStride[ 0 ] = 1; fDataStride[ 1 ] = fNChannels; + fDataBlock[ 0 ] = 1; fDataBlock[ 1 ] = fSampleSize; + /* + std::cout << "str data dims: " << fStrDataDims[0] << " " << fStrDataDims[1] << std::endl; + std::cout << "str max data dims: " << fStrMaxDataDims[0] << " " << fStrMaxDataDims[1] << std::endl; + std::cout << "str data chunk dims: " << fStrDataChunkDims[0] << " " << fStrDataChunkDims[1] << std::endl; + std::cout << "str data dims 1 rec: " << fDataDims1Rec[0] << " " << fDataDims1Rec[1] << std::endl; + std::cout << "str data offset: " << fDataOffset[0] << " " << fDataOffset[1] << std::endl; + std::cout << "str data stride: " << fDataStride[0] << " " << fDataStride[1] << std::endl; + std::cout << "str data block: " << fDataBlock[0] << " " << fDataBlock[1] << std::endl; + */ + + // HDF5 object initialization + delete fH5DataSpaceUser; + fH5DataSpaceUser = new H5::DataSpace( N_DATA_DIMS, fDataDims1Rec, NULL ); + + fIsInitialized = true; + return; + } + + // allocate stream record memory + fStreamRecord.Initialize( fStrRecNBytes ); + + // channel records point to portions of the stream record and do not own their own data + byte_type* tChanDataPtr = fStreamRecord.GetData(); + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + fChannelRecords[ iChan ].Initialize( fStreamRecord.GetRecordIdPtr(), fStreamRecord.GetTimePtr(), tChanDataPtr + fChanRecNBytes*iChan ); + } + + // set the read/write functions to the general versions + fDoReadRecord = &M4Stream::ReadRecordAsIs; + fDoWriteRecord = &M4Stream::WriteRecordAsIs; + + // Arrays for HDF5 file reading/writing + fStrDataDims[ 0 ] = 1; fStrDataDims[ 1 ] = fStrRecSize * fSampleSize; + fStrMaxDataDims[ 0 ] = H5S_UNLIMITED; fStrMaxDataDims[ 1 ] = fStrRecSize * fSampleSize; + fStrDataChunkDims[ 0 ] = 1; fStrDataChunkDims[ 1 ] = fStrRecSize * fSampleSize; + fDataDims1Rec[ 0 ] = 1; fDataDims1Rec[ 1 ] = fStrRecSize * fSampleSize; + fDataOffset[ 0 ] = 0; fDataOffset[ 1 ] = 0; + fDataStride[ 0 ] = 1; fDataStride[ 1 ] = fSampleSize; + fDataBlock[ 0 ] = 1; fDataBlock[ 1 ] = fStrRecSize * fSampleSize; + /* + std::cout << "str data dims: " << fStrDataDims[0] << " " << fStrDataDims[1] << std::endl; + std::cout << "str max data dims: " << fStrMaxDataDims[0] << " " << fStrMaxDataDims[1] << std::endl; + std::cout << "str data chunk dims: " << fStrDataChunkDims[0] << " " << fStrDataChunkDims[1] << std::endl; + std::cout << "str data dims 1 rec: " << fDataDims1Rec[0] << " " << fDataDims1Rec[1] << std::endl; + std::cout << "str data offset: " << fDataOffset[0] << " " << fDataOffset[1] << std::endl; + std::cout << "str data stride: " << fDataStride[0] << " " << fDataStride[1] << std::endl; + std::cout << "str data block: " << fDataBlock[0] << " " << fDataBlock[1] << std::endl; + */ + + // HDF5 object initialization + delete fH5DataSpaceUser; + fH5DataSpaceUser = new H5::DataSpace( N_DATA_DIMS, fDataDims1Rec, NULL ); + + fIsInitialized = true; + return; + } + + const M4Record* M4Stream::GetStreamRecord() const + { + return &fStreamRecord; + } + + const M4Record* M4Stream::GetChannelRecord( unsigned aChannel ) const + { + if( aChannel < fNChannels ) + { + return &(fChannelRecords[ aChannel ]); + } + throw M4Exception() << "Channel <" << aChannel << "> requested; only " << fNChannels << " in this stream."; + } + + bool M4Stream::ReadRecord( int anOffset, bool aIfNewAcqStartAtFirstRec ) const + { + if( ! fIsInitialized ) Initialize(); + + std::unique_lock< std::mutex >( *fMutexPtr.get() ); + + // anOffset should not move us forward if this is the very first record read in the file (fRecordsAccessed == false) + // Otherwise anOffset should be incremented to 1 to move us forward appropriately (fRecordsAccessed == true) + anOffset += (int)fRecordsAccessed; + + LDEBUG( mlog, "Before moving: Record count in file = " << fRecordCountInFile << "; Record ID (in acquisition) = " << fRecordCountInAcq ); + + if( ( anOffset < 0 && (unsigned)abs( anOffset ) > fRecordCountInFile ) || + ( anOffset > 0 && fRecordCountInFile + anOffset >= fNRecordsInFile ) || + ( anOffset == 0 && fNRecordsInFile == 0 )) + { + // either requested to go back before the beginning of the file, or past the end + LDEBUG( mlog, "Requested offset would move is out of range for the file" ); + return false; + } + + fRecordCountInFile = fRecordCountInFile + anOffset; + unsigned nextAcq = fRecordIndex.at( fRecordCountInFile ).first; + fRecordCountInAcq = fRecordIndex.at( fRecordCountInFile ).second; + LDEBUG( mlog, "After offset calculation: Record count in file = " << fRecordCountInFile << "; Record ID (in acquisition) = " << fRecordCountInAcq ); + + try + { + bool tIsNewAcq = false; + if( nextAcq != fAcquisitionId || ! fRecordsAccessed ) + { + // we are going to a new acquisition + + // check if we need to correct our position in the new acquisition back to the beginning of the acquisition + if( aIfNewAcqStartAtFirstRec && fRecordCountInAcq != 0 ) + { + fRecordCountInFile -= fRecordCountInAcq; + // make sure the record correction ended up in the same new acquisition + if( fRecordIndex.at( fRecordCountInFile ).first != nextAcq ) + { + throw M4Exception() << "Tried to start at the beginning of the new acquisition, but ended up in a different acquisition: " << fRecordIndex.at( fRecordCountInFile ).first << " != " << nextAcq; + } + fRecordCountInAcq = 0; + LDEBUG( mlog, "After offset calc + new acq correction: Record count in file = " << fRecordCountInFile << "; Record ID (in acquisition) = " << fRecordCountInAcq ); + } + + tIsNewAcq = true; + fAcquisitionId = nextAcq; + delete fH5CurrentAcqDataSet; + u32toa( fAcquisitionId, fAcqNameBuffer ); + fH5CurrentAcqDataSet = new H5::DataSet( fH5AcqLoc->openDataSet( fAcqNameBuffer ) ); + H5::Attribute tAttrNRIA( fH5CurrentAcqDataSet->openAttribute( "n_records" ) ); + tAttrNRIA.read( tAttrNRIA.getDataType(), &fNRecordsInAcq ); + } + + LDEBUG( mlog, "Going to record: record count in file: " << fRecordCountInFile << " -- acquisition: " << nextAcq << " -- record in acquisition: " << fRecordCountInAcq ); + + fDataOffset[ 0 ] = fRecordCountInAcq; + + (this->*fDoReadRecord)( tIsNewAcq ); + + // can now update the first record in the file + if( ! fRecordsAccessed ) + { + fRecordsAccessed = true; + fFirstRecordInFile = fAcqFirstRecId; + LDEBUG( mlog, "First record in file: " << fFirstRecordInFile ); + } + } + catch( H5::Exception& e ) + { + throw M4Exception() << "HDF5 error while reading a record:\n\t" << e.getCDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + + return true; + } + + void M4Stream::Close() const + { + //LDEBUG( mlog, "const M4Stream::Close()" ); + + delete fH5DataSpaceUser; fH5DataSpaceUser = NULL; + delete fH5CurrentAcqDataSet; fH5CurrentAcqDataSet = NULL; + delete fH5AcqLoc; fH5AcqLoc = NULL; + delete fH5StreamParentLoc; fH5StreamParentLoc = NULL; + + return; + } + + + M4Record* M4Stream::GetChannelRecord( unsigned aChannel ) + { + if( aChannel < fNChannels ) + { + return &(fChannelRecords[ aChannel ]); + } + throw M4Exception() << "Channel <" << aChannel << "> requested; only " << fNChannels << " in this stream."; + } + + bool M4Stream::WriteRecord( bool aIsNewAcquisition ) + { + // note: fRecordCountInAcq is used to keep track of the number of records written in each acquisition; + // fNRecordsInAcq is only valid for the last completed acquisition. + + if( ! fIsInitialized ) Initialize(); + if( ! fRecordsAccessed ) aIsNewAcquisition = true; + + try + { + std::unique_lock< std::mutex >( *fMutexPtr.get() ); + + if( aIsNewAcquisition ) + { + FinalizeCurrentAcq(); + + if( fRecordsAccessed ) ++fAcquisitionId; + else fRecordsAccessed = true; + + // Setup the new dataset + fStrDataDims[ 0 ] = 1; + H5::DSetCreatPropList tPropList; + tPropList.setChunk( N_DATA_DIMS, fStrDataChunkDims ); + + u32toa( fAcquisitionId, fAcqNameBuffer ); + fH5CurrentAcqDataSet = new H5::DataSet( fH5AcqLoc->createDataSet( fAcqNameBuffer, fDataTypeInFile, H5::DataSpace( N_DATA_DIMS, fStrDataDims, fStrMaxDataDims ), tPropList ) ); + } + else + { + // Extend the current dataset + fStrDataDims[ 0 ] = fStrDataDims[ 0 ] + 1; + fH5CurrentAcqDataSet->extend( fStrDataDims ); + } + + LTRACE( mlog, "Writing acq. " << fAcquisitionId << ", record " << fRecordCountInAcq ); + + fDataOffset[ 0 ] = fRecordCountInAcq; + + (this->*fDoWriteRecord)( aIsNewAcquisition ); + + ++fRecordCountInAcq; + ++fRecordCountInFile; + return true; + } + catch( H5::Exception& e ) + { + LWARN( mlog, "DIAGNOSTIC: id of fH5CurrentAcqDataSet: " << fH5CurrentAcqDataSet->getId() ); + LWARN( mlog, "DIAGNOSTIC: class name: " << fH5CurrentAcqDataSet->fromClass() ); + H5D_space_status_t t_status; + fH5CurrentAcqDataSet->getSpaceStatus( t_status ); + LWARN( mlog, "DIAGNOSTIC: offset: " << fH5CurrentAcqDataSet->getOffset() << " space status: " << t_status << " storage size: " << fH5CurrentAcqDataSet->getStorageSize() << " in mem data size: " << fH5CurrentAcqDataSet->getInMemDataSize() ); + throw M4Exception() << "HDF5 error while writing a record:\n\t" << e.getCDetailMsg() << " (function: " << e.getFuncName() << ")"; + } + catch( std::exception& e ) + { + throw M4Exception() << e.what(); + } + + return false; + } + + void M4Stream::Close() + { + //LDEBUG( mlog, "non-const M4Stream::Close()" ); + FinalizeStream(); + + delete fH5DataSpaceUser; fH5DataSpaceUser = NULL; + delete fH5CurrentAcqDataSet; fH5CurrentAcqDataSet = NULL; + delete fH5AcqLoc; fH5AcqLoc = NULL; + delete fH5StreamParentLoc; fH5StreamParentLoc = NULL; + + return; + } + + void M4Stream::SetAccessFormat( uint32_t aFormat ) const + { + fAccessFormat = aFormat; + fIsInitialized = false; + return; + } + + void M4Stream::ReadRecordInterleavedToSeparate( bool aIsNewAcquisition ) const + { + if( aIsNewAcquisition ) + { + try + { + delete [] fAcqFirstRecTimes; + fAcqFirstRecTimes = new TimeType[ fNChannels ]; + H5::Attribute tAttrAFRT( fH5CurrentAcqDataSet->openAttribute( "first_record_time" ) ); + tAttrAFRT.read( tAttrAFRT.getDataType(), fAcqFirstRecTimes ); + + delete [] fAcqFirstRecIds; + fAcqFirstRecIds = new RecordIdType[ fNChannels ]; + H5::Attribute tAttrAFRI( fH5CurrentAcqDataSet->openAttribute( "first_record_id" ) ); + tAttrAFRI.read( tAttrAFRI.getDataType(), fAcqFirstRecIds ); + } + catch( H5::Exception& ) + { + // Backwards compatibility with older files that don't have first_record_time and first_record_id + // Times increment by the record length starting at 0, but ID increments starting at 0 + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + fAcqFirstRecTimes[ iChan ] = 0; + fAcqFirstRecIds[ iChan ] = 0; + } + } + fAcqFirstRecTime = fAcqFirstRecTimes[0]; + fAcqFirstRecId = fAcqFirstRecIds[0]; + } + + H5::DataSpace tDataSpaceInFile = fH5CurrentAcqDataSet->getSpace(); + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + fDataOffset[ 1 ] = iChan; + tDataSpaceInFile.selectHyperslab( H5S_SELECT_SET, fDataDims1Rec, fDataOffset, fDataStride, fDataBlock ); + fH5CurrentAcqDataSet->read( fChannelRecords[ iChan ].GetData(), fDataTypeUser, *fH5DataSpaceUser, tDataSpaceInFile ); + fChannelRecords[ iChan ].SetTime( fAcqFirstRecTimes[ iChan ] + fRecordCountInAcq * fChanRecLength ); + fChannelRecords[ iChan ].SetRecordId( fAcqFirstRecIds[ iChan ] + fRecordCountInAcq ); + } + return; + } + + void M4Stream::ReadRecordAsIs( bool aIsNewAcquisition ) const + { + if( aIsNewAcquisition ) + { + try + { + H5::Attribute tAttrAFRT( fH5CurrentAcqDataSet->openAttribute( "first_record_time" ) ); + tAttrAFRT.read( tAttrAFRT.getDataType(), &fAcqFirstRecTime ); + H5::Attribute tAttrAFRI( fH5CurrentAcqDataSet->openAttribute( "first_record_id" ) ); + tAttrAFRI.read( tAttrAFRI.getDataType(), &fAcqFirstRecId ); + } + catch( H5::Exception& ) + { + // Backwards compatibility with older files that don't have first_record_time and first_record_id + // Times increment by the record length starting at 0, but ID increments starting at 0 + fAcqFirstRecTime = 0; + fAcqFirstRecId = 0; + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + fAcqFirstRecTimes[ iChan ] = 0; + fAcqFirstRecIds[ iChan ] = 0; + } + } + } + + H5::DataSpace tDataSpaceInFile = fH5CurrentAcqDataSet->getSpace(); + tDataSpaceInFile.selectHyperslab( H5S_SELECT_SET, fDataDims1Rec, fDataOffset ); + fH5CurrentAcqDataSet->read( fStreamRecord.GetData(), fDataTypeUser, *fH5DataSpaceUser, tDataSpaceInFile ); + fStreamRecord.SetTime( fAcqFirstRecTime + fRecordCountInAcq * fChanRecLength ); + fStreamRecord.SetRecordId( fAcqFirstRecId + fRecordCountInAcq ); + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + fChannelRecords[ iChan ].SetTime( fStreamRecord.GetTime() ); + fChannelRecords[ iChan ].SetRecordId( fStreamRecord.GetRecordId() ); + } + return; + } + + void M4Stream::WriteRecordSeparateToInterleaved( bool aIsNewAcquisition ) + { + H5::DataSpace tDataSpaceInFile = fH5CurrentAcqDataSet->getSpace(); + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + fDataOffset[ 1 ] = iChan; + tDataSpaceInFile.selectHyperslab( H5S_SELECT_SET, fDataDims1Rec, fDataOffset, fDataStride, fDataBlock ); + //std::cout << "about to write separate to interleaved " << fDataTypeUser.fromClass() << std::endl; + fH5CurrentAcqDataSet->write( fChannelRecords[ iChan ].GetData(), fDataTypeUser, *fH5DataSpaceUser, tDataSpaceInFile ); + } + if( aIsNewAcquisition ) + { + TimeType* tTimes = new TimeType[ fNChannels ]; + RecordIdType* tIds = new RecordIdType[ fNChannels ]; + for( unsigned iChan = 0; iChan < fNChannels; ++iChan ) + { + tTimes[ iChan ] = fChannelRecords[ iChan ].GetTime(); + tIds[ iChan ] = fChannelRecords[ iChan ].GetRecordId(); + } + fH5CurrentAcqDataSet->createAttribute( "first_record_time", MH5Type< TimeType >::H5(), H5::DataSpace( H5S_SCALAR ) ).write( MH5Type< TimeType >::Native(), tTimes ); + fH5CurrentAcqDataSet->createAttribute( "first_record_id", MH5Type< RecordIdType >::H5(), H5::DataSpace( H5S_SCALAR ) ).write( MH5Type< RecordIdType >::Native(), tIds ); + delete [] tTimes; + delete [] tIds; + } + return; + } + + void M4Stream::WriteRecordAsIs( bool aIsNewAcquisition ) + { + H5::DataSpace tDataSpaceInFile = fH5CurrentAcqDataSet->getSpace(); + tDataSpaceInFile.selectHyperslab( H5S_SELECT_SET, fDataDims1Rec, fDataOffset ); + fH5CurrentAcqDataSet->write( fStreamRecord.GetData(), fDataTypeUser, *fH5DataSpaceUser, tDataSpaceInFile ); + if( aIsNewAcquisition ) + { + TimeType tTime = fStreamRecord.GetTime(); + RecordIdType tId = fStreamRecord.GetRecordId(); + fH5CurrentAcqDataSet->createAttribute( "first_record_time", MH5Type< TimeType >::H5(), H5::DataSpace( H5S_SCALAR ) ).write( MH5Type< TimeType >::Native(), &tTime ); + fH5CurrentAcqDataSet->createAttribute( "first_record_id", MH5Type< RecordIdType >::H5(), H5::DataSpace( H5S_SCALAR ) ).write( MH5Type< RecordIdType >::Native(), &tId ); + } + return; + } + + void M4Stream::BuildIndex() const + { + fRecordIndex.resize( fNRecordsInFile ); + unsigned tNRecInAcq; + unsigned iRecInFile = 0; + for( unsigned iAcq = 0; iAcq < fNAcquisitions; ++iAcq ) + { + u32toa( iAcq, fAcqNameBuffer ); + H5::Attribute tAttr( fH5AcqLoc->openDataSet( fAcqNameBuffer ).openAttribute( "n_records" ) ); + tAttr.read( tAttr.getDataType(), &tNRecInAcq ); + LDEBUG( mlog, "Acquisition <" << fAcqNameBuffer << "> has " << tNRecInAcq << " records" ); + for( unsigned iRecInAcq = 0; iRecInAcq < tNRecInAcq; ++iRecInAcq ) + { + fRecordIndex.at( iRecInFile ).first = iAcq; + fRecordIndex.at( iRecInFile ).second = iRecInAcq; + LTRACE( mlog, "Record index: " << iRecInFile << " -- " << iAcq << " -- " << iRecInAcq ); + ++iRecInFile; + } + } + return; + } + + void M4Stream::FinalizeCurrentAcq() + { + if( fH5CurrentAcqDataSet == NULL ) return; + + fNRecordsInAcq = fRecordCountInAcq; + + fH5CurrentAcqDataSet->createAttribute( "n_records", MH5Type< unsigned >::H5(), H5::DataSpace( H5S_SCALAR ) ).write( MH5Type< unsigned >::Native(), &fNRecordsInAcq ); + LDEBUG( mlog, "Finalizing acq. " << fAcquisitionId << " with " << fNRecordsInAcq << " records" ); + + fRecordCountInAcq = 0; + delete fH5CurrentAcqDataSet; + fH5CurrentAcqDataSet = NULL; + + return; + } + + void M4Stream::FinalizeStream() + { + FinalizeCurrentAcq(); + + if( fH5AcqLoc == NULL ) return; + + fNAcquisitions = ( fAcquisitionId + 1 ) * (unsigned)fRecordsAccessed; + fH5StreamParentLoc->openAttribute( "n_acquisitions" ).write( MH5Type< unsigned >::Native(), &fNAcquisitions ); + fH5StreamParentLoc->openAttribute( "n_records" ).write( MH5Type< unsigned >::Native(), &fRecordCountInFile ); + LDEBUG( mlog, "Finalizing stream with " << fNAcquisitions << " acquisitions and " << fRecordCountInFile << " records" ); + + return; + } + +} /* namespace monarch */ diff --git a/Monarch4/M4Stream.hh b/Monarch4/M4Stream.hh new file mode 100644 index 0000000..0cacb16 --- /dev/null +++ b/Monarch4/M4Stream.hh @@ -0,0 +1,229 @@ +/* + * M4Stream.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4STREAM_HH_ +#define M4STREAM_HH_ + +#include "M4Header.hh" +#include "M4MemberVariable.hh" +#include "M4Record.hh" +#include "M4Types.hh" + +#include "H5Cpp.h" + +namespace monarch4 +{ + /*! + @class M4Stream + @author N. S. Oblath + + @brief Read/write access for a data stream. + + @details + Data can be read/written either for the stream as a whole, or for individual channels within the stream by + accessing the relevant record objects. + + The function ReadRecord extracts a record from disk. The offset allows the file to move forward or back within the file. + After that call, the new record is available via the record objects. + + The function WriteRecord moves the data in memory to disk. Before calling WriteRecord, the appropriate data should be copied + to the record object(s). + + When a stream object is created, most of the information needed to define the stream characteristics is extracted from the + stream header object that is passed to the constructor. Most users won't need to worry about this, as it's already taken + care of by the Monarch4 object. + */ + + class M4_API M4Stream + { + public: + enum Mode + { + kRead, + kWrite + }; + + private: + typedef void (M4Stream::*DoReadRecordFunc)( bool ) const; + typedef void (M4Stream::*DoWriteRecordFunc)( bool ); + + public: + M4Stream( const M4StreamHeader& aHeader, HAS_GRP_IFC* aH5StreamParentLoc, uint32_t aAccessFormat = sSeparate ); + virtual ~M4Stream(); + + M4Stream( const M4Stream& ) = delete; + M4Stream& operator=( const M4Stream& ) = delete; + + M4MEMBERVARIABLE( Mode, Mode ); + + public: + /// Setup to read/write data (called in constructor; only call this if read/write parameters change during file reading) + void Initialize() const; + + void SetMutex( const mutex_ptr& aMutexPtr ); + + //******************************** + // methods for reading (all const) + //******************************** + + public: + /// Get the pointer to the stream record + const M4Record* GetStreamRecord() const; + /// Get the pointer to a particular channel record + const M4Record* GetChannelRecord( unsigned aChannel ) const; + + /// Read the next record from the file + /** + * Assuming the last record read was [J], ReadRecord will access the [J+1+offset] record. + * This means that the offset parameter has the following meanings: + * - if offset == 0 (default), the next record will be accessed; + * - if offset == -1, the current record will be reread; + * - offsets < -1 will go backwards in the file; + * - offsets > 0 will skip forward in the file. + * The flag aIfNewAcqStartAtFirstRec determines the behavior if the read operation moves to a new acquisition. + * If true, the record read will be the first in the acquisition. + * If false, the record read will be whatever was stepped to given the offset. + * Starting at the first record in a new acquisition is behavior that was added (and deemed more useful, and therefore is the default), and this flag provides backwards compatibility. + **/ + bool ReadRecord( int anOffset = 0, bool aIfNewAcqStartAtFirstRec = true ) const; + + /// Close the file + void Close() const; + + + //********************************* + // methods for writing (none const) + //********************************* + + public: + /// Get the pointer to the stream record + M4Record* GetStreamRecord(); + /// Get the pointer to a particular channel record + M4Record* GetChannelRecord( unsigned aChannel ); + + /// Write the record contents to the file + bool WriteRecord( bool aIsNewAcquisition ); + + /// Close the stream + void Close(); + + + public: + bool GetIsInitialized() const { return fIsInitialized; } + bool GetRecordsAccessed() const { return fRecordsAccessed; } + unsigned GetDataTypeSize() const { return fDataTypeSize; } + unsigned GetSampleSize() const { return fSampleSize; } + unsigned GetStreamRecordNBytes() const { return fStrRecNBytes; } + unsigned GetStreamRecordSize() const { return fStrRecSize; } + unsigned GetChannelRecordNBytes() const { return fChanRecNBytes; } + unsigned GetChannelRecordSize() const { return fChanRecSize; } + unsigned GetNChannels() const { return fNChannels; } + unsigned GetNAcquisitions() const { return fNAcquisitions; } + AcquisitionIdType GetAcquisitionId() const { return fAcquisitionId; } + unsigned GetRecordCountInAcq() const { return fRecordCountInAcq; } + unsigned GetNRecordsInAcquisition() const { return fNRecordsInAcq; } + TimeType GetAcqFirstRecordTime() const { return fAcqFirstRecTime; } + RecordIdType GetAcqFirstRecordId() const { return fAcqFirstRecId; } + TimeType* GetAcqFirstRecTimes() const { return fAcqFirstRecTimes; } + RecordIdType* GetAcqFirstRecordIds() const { return fAcqFirstRecIds; } + unsigned GetRecordCountInFile() const { return fRecordCountInFile; } + unsigned GetNRecordsInFile() const { return fNRecordsInFile; } + unsigned GetFirstRecordInFile() const { return fFirstRecordInFile; } + bool GetIsInterleaved() const { return fDataInterleaved; } + + /// Access format can be changed during read or write; must call Initialize() after this + void SetAccessFormat( uint32_t aFormat ) const; + uint32_t GetAccessFormat() const { return fAccessFormat; } + + private: + void ReadRecordInterleavedToSeparate( bool aIsNewAcquisition ) const; + void ReadRecordAsIs( bool aIsNewAcquisition ) const; + mutable DoReadRecordFunc fDoReadRecord; + + void WriteRecordSeparateToInterleaved( bool aIsNewAcquisition ); + void WriteRecordAsIs( bool aIsNewAcquisition ); + mutable DoWriteRecordFunc fDoWriteRecord; + + mutable bool fIsInitialized; + mutable bool fRecordsAccessed; + + mutable unsigned fDataTypeSize; + mutable unsigned fSampleSize; + + mutable unsigned fStrRecNBytes; + mutable unsigned fStrRecSize; + + mutable unsigned fChanRecNBytes; + mutable unsigned fChanRecSize; + mutable uint64_t fChanRecLength; // ns + + mutable M4Record fStreamRecord; + + mutable unsigned fNChannels; + mutable M4Record* fChannelRecords; + + mutable unsigned fNAcquisitions; + mutable AcquisitionIdType fAcquisitionId; + + mutable unsigned fRecordCountInAcq; + mutable unsigned fNRecordsInAcq; + mutable TimeType fAcqFirstRecTime; // used when reading with the stream record + mutable RecordIdType fAcqFirstRecId; // used when reading with the stream record + mutable TimeType* fAcqFirstRecTimes; // used when reading with the channel records + mutable RecordIdType* fAcqFirstRecIds; // used when reading with the channel records + + mutable bool fDataInterleaved; + mutable uint32_t fAccessFormat; + + mutable std::vector< std::pair< unsigned, unsigned > > fRecordIndex; // has an entry for every record: (acquisition ID, record ID) + mutable unsigned fRecordCountInFile; + mutable unsigned fNRecordsInFile; + mutable unsigned fFirstRecordInFile; + + private: + void BuildIndex() const; // for reading + + void FinalizeCurrentAcq(); // for writing + void FinalizeStream(); // for writing + + mutable char fAcqNameBuffer[ 10 ]; + + mutable H5::Group* fH5StreamParentLoc; + mutable H5::Group* fH5AcqLoc; + mutable H5::DataSet* fH5CurrentAcqDataSet; + + mutable H5::DataSpace* fH5DataSpaceUser; + + mutable H5::DataType fDataTypeInFile; + mutable H5::DataType fDataTypeUser; + + enum { N_DATA_DIMS = 2 }; + mutable hsize_t fStrDataDims[ N_DATA_DIMS ]; + mutable hsize_t fStrMaxDataDims[ N_DATA_DIMS ]; + mutable hsize_t fStrDataChunkDims[ N_DATA_DIMS ]; + mutable hsize_t fDataDims1Rec[ N_DATA_DIMS ]; + mutable hsize_t fDataOffset[ N_DATA_DIMS ]; + mutable hsize_t fDataStride[ N_DATA_DIMS ]; + mutable hsize_t fDataBlock[ N_DATA_DIMS ]; + + mutable mutex_ptr fMutexPtr; + }; + + inline void M4Stream::SetMutex( const mutex_ptr& aMutexPtr ) + { + fMutexPtr = aMutexPtr; + return; + } + + inline M4Record* M4Stream::GetStreamRecord() + { + return &fStreamRecord; + } + +} /* namespace monarch */ + +#endif /* MSTREAM_HH_ */ diff --git a/Monarch4/M4Types.hh b/Monarch4/M4Types.hh new file mode 100644 index 0000000..e2d9c69 --- /dev/null +++ b/Monarch4/M4Types.hh @@ -0,0 +1,232 @@ +/* + * MTypes.hh + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4TYPES_HH_ +#define M4TYPES_HH_ + +#include "M4Exception.hh" + +#include "H5Cpp.h" + +#include +#include +#include +#include + +namespace monarch4 +{ + typedef uint8_t byte_type; + + typedef uint64_t AcquisitionIdType; + typedef uint64_t RecordIdType; + typedef uint64_t TimeType; + + typedef std::shared_ptr< std::mutex > mutex_ptr; + + template + struct is_same + { + static const bool value = false; + }; + + template + struct is_same //specialization + { + static const bool value = true; + }; + + template struct staticassert; + template<> struct staticassert {}; //specialization + + template< typename T > + struct MH5Type + { + static H5::DataType Native( T ) + { + throw M4Exception() << "Unknown native type requested"; + } + static H5::DataType H5( T ) + { + throw M4Exception() << "Unknown H5 type requested"; + } + }; + + template<> + struct MH5Type< bool > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_UINT8; + } + static H5::DataType H5() + { + return H5::PredType::STD_U8LE; + } + }; + + template<> + struct MH5Type< int8_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_INT8; + } + static H5::DataType H5() + { + return H5::PredType::STD_I8LE; + } + }; + + template<> + struct MH5Type< uint8_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_UINT8; + } + static H5::DataType H5() + { + return H5::PredType::STD_U8LE; + } + }; + + template<> + struct MH5Type< int16_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_INT16; + } + static H5::DataType H5() + { + return H5::PredType::STD_I16LE; + } + }; + + template<> + struct MH5Type< uint16_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_UINT16; + } + static H5::DataType H5() + { + return H5::PredType::STD_U16LE; + } + }; + + template<> + struct MH5Type< int32_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_INT32; + } + static H5::DataType H5() + { + return H5::PredType::STD_I32LE; + } + }; + + template<> + struct MH5Type< uint32_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_UINT32; + } + static H5::DataType H5() + { + return H5::PredType::STD_U32LE; + } + }; + + template<> + struct MH5Type< int64_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_INT64; + } + static H5::DataType H5() + { + return H5::PredType::STD_I64LE; + } + }; + + template<> + struct MH5Type< uint64_t > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_UINT64; + } + static H5::DataType H5() + { + return H5::PredType::STD_U64LE; + } + }; + + template<> + struct MH5Type< float > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_FLOAT; + } + static H5::DataType H5() + { + return H5::PredType::IEEE_F32LE; + } + }; + + template<> + struct MH5Type< double > + { + static H5::DataType Native() + { + return H5::PredType::NATIVE_DOUBLE; + } + static H5::DataType H5() + { + return H5::PredType::IEEE_F64LE; + } + }; + + template<> + struct MH5Type< std::string > + { + static H5::DataType Native() + { + return H5::StrType( H5::PredType::NATIVE_CHAR, 0 ); + } + static H5::DataType Native( const std::string& aString ) + { + return H5::StrType( H5::PredType::NATIVE_CHAR, aString.length() + 1 ); + } + static H5::DataType Native( size_t aSize ) + { + return H5::StrType( H5::PredType::NATIVE_CHAR, aSize + 1 ); + } + static H5::DataType H5() + { + return H5::StrType( H5::PredType::C_S1, 0 ); + } + static H5::DataType H5( const std::string& aString ) + { + return H5::StrType( H5::PredType::C_S1, aString.length() + 1 ); + } + static H5::DataType H5( size_t aSize ) + { + return H5::StrType( H5::PredType::C_S1, aSize + 1 ); + } + }; + +} + +#endif // MTYPES_HH_ diff --git a/Monarch4/M4Version.cc.in b/Monarch4/M4Version.cc.in new file mode 100644 index 0000000..8b52341 --- /dev/null +++ b/Monarch4/M4Version.cc.in @@ -0,0 +1,34 @@ +/* + * M4Version.cpp + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#define M4_API_EXPORTS +#define SCARAB_API_EXPORTS + +#include "M4Version.hh" + +#include "logger.hh" // for TOSTRING macro + +namespace monarch4 +{ + M4Version::M4Version() : + scarab::version_semantic() + { + f_major_ver = @Monarch_VERSION_MAJOR@; + f_minor_ver = @Monarch_VERSION_MINOR@; + f_patch_ver = @Monarch_VERSION_PATCH@; + f_version = TOSTRING(@Monarch_VERSION@); + f_package = TOSTRING(@Monarch_PACKAGE_NAME@); + f_commit = TOSTRING(@Monarch_GIT_COMMIT@); + } + + M4Version::~M4Version() + { + } + +} /* namespace monarch4 */ + + diff --git a/Monarch4/M4Version.hh b/Monarch4/M4Version.hh new file mode 100644 index 0000000..7d9f4c8 --- /dev/null +++ b/Monarch4/M4Version.hh @@ -0,0 +1,27 @@ +/* + * M4Version.hh + * + * Generated by cmake from M4Version.hh.in + * + * Created on: Apr 12, 2023 + * Author: N.S. Oblath + */ + +#ifndef M4VERSION_HH_ +#define M4VERSION_HH_ + +#include "M4Constants.hh" + +#include "scarab_version.hh" + +namespace monarch4 +{ + class M4_API M4Version : public scarab::version_semantic + { + public: + M4Version(); + ~M4Version(); + }; +} + +#endif /* M4VERSION_HH_ */ From b4ac2dc23e137080b99d6f4a1905b04d4c37232a Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Thu, 13 Apr 2023 11:16:44 -0700 Subject: [PATCH 03/66] Add copying of std::exception to M4Exception --- Monarch4/M4Exception.cc | 13 +++++++------ Monarch4/M4Exception.hh | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Monarch4/M4Exception.cc b/Monarch4/M4Exception.cc index f05cd88..a12cc7d 100644 --- a/Monarch4/M4Exception.cc +++ b/Monarch4/M4Exception.cc @@ -14,16 +14,17 @@ namespace monarch4 M4Exception::M4Exception() : exception(), fMessage() - { - } + {} M4Exception::M4Exception( const M4Exception& aCopy ) : exception( aCopy ), fMessage( aCopy.fMessage ) - { - } + {} + M4Exception::M4Exception( const std::exception& aCopy ) : + exception( aCopy ), + fMessage( aCopy.what() ) + {} M4Exception::~M4Exception() throw () - { - } + {} //void M4Exception::SetWhat( const char* aWhat ) //{ diff --git a/Monarch4/M4Exception.hh b/Monarch4/M4Exception.hh index 77c002c..65c33c9 100644 --- a/Monarch4/M4Exception.hh +++ b/Monarch4/M4Exception.hh @@ -30,6 +30,7 @@ namespace monarch4 public: M4Exception(); M4Exception( const M4Exception& aCopy ); + M4Exception( const std::exception& aCopy ); virtual ~M4Exception() throw(); public: From c7c0962480cdeea31a4bf203f9e1bc2d6aac6c23 Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Thu, 13 Apr 2023 11:17:42 -0700 Subject: [PATCH 04/66] Initial z5 inclusion and use of Zarr at the Monarch/File level. --- CMakeLists.txt | 3 +- Monarch4/M4Monarch.cc | 109 ++++++++++++------------------------------ Monarch4/M4Monarch.hh | 4 +- 3 files changed, 34 insertions(+), 82 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b9f7aa..973a47a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,8 +35,9 @@ set( PUBLIC_EXT_LIBS_M4 ) ###### if( Monarch_BUILD_MONARCH4 ) - set( Monarch_BUILD_MONARCH3 TRUE CACHE BOOL "Build Monarch3 library (requires HDF5)" ) +# set( Monarch_BUILD_MONARCH3 TRUE CACHE BOOL "Build Monarch3 library (requires HDF5)" ) + find_package( z5 REQUIRED ) include_directories( BEFORE ${PROJECT_SOURCE_DIR}/Monarch4 ) diff --git a/Monarch4/M4Monarch.cc b/Monarch4/M4Monarch.cc index a924beb..43b7c61 100644 --- a/Monarch4/M4Monarch.cc +++ b/Monarch4/M4Monarch.cc @@ -10,6 +10,8 @@ #include "logger.hh" +#include "z5/factory.hxx" + using std::string; namespace monarch4 @@ -29,11 +31,8 @@ namespace monarch4 if( fState == eOpenToRead || fState == eReadyToRead) FinishReading(); if( fState == eOpenToWrite || fState == eReadyToWrite) FinishWriting(); - if( fHeader != nullptr ) - { - delete fHeader; - fHeader = nullptr; - } + delete fHeader; + fHeader = nullptr; while( ! fStreams.empty() ) { @@ -41,11 +40,8 @@ namespace monarch4 fStreams.pop_back(); } - if( fFile != nullptr ) - { - delete fFile; - fFile = nullptr; - } + delete fFile; + fFile = nullptr; } Monarch4::State Monarch4::GetState() const @@ -59,18 +55,12 @@ namespace monarch4 try { - tMonarch4->fFile = new H5::H5File( aFilename.c_str(), H5F_ACC_RDONLY ); - } - catch( H5::Exception& e ) - { - delete tMonarch4; - throw M4Exception() << "Could not open <" << aFilename << "> for reading; an H5::Exception was thrown: " << e.getCDetailMsg(); - return nullptr; + tMonarch4->fFile = new z5::filesystem::handle::File( aFilename, z5::FileMode::r ); } catch( std::exception& e ) { delete tMonarch4; - throw M4Exception() << "Could not open <" << aFilename << "> for reading; a std::exception was thrown: " << e.what(); + throw M4Exception() << "Could not open <" << aFilename << "> for reading; a std::exception was thrown:\n" << e.what(); return nullptr; } if( tMonarch4->fFile == nullptr ) @@ -95,18 +85,13 @@ namespace monarch4 try { - tMonarch4->fFile = new H5::H5File( aFilename.c_str(), H5F_ACC_TRUNC ); - } - catch( H5::Exception& e ) - { - delete tMonarch4; - throw M4Exception() << "Could not open <" << aFilename << "> for writing; an H5::Exception was thrown: " << e.getCDetailMsg(); - return nullptr; + tMonarch4->fFile = new z5::filesystem::handle::File( aFilename, z5::FileMode::w ); + z5::createFile( *(tMonarch4->fFile), true ); } catch( std::exception& e ) { delete tMonarch4; - throw M4Exception() << "Could not open <" << aFilename << "> for writing; a std::exception was thrown: " << e.what(); + throw M4Exception() << "Could not open <" << aFilename << "> for writing; a std::exception was thrown:\n" << e.what(); return nullptr; } if( tMonarch4->fFile == nullptr ) @@ -133,20 +118,9 @@ namespace monarch4 } // Read the header information from the file (run header, plus all stream and channel headers) - try - { - fHeader->ReadFromHDF5( fFile ); - } - catch( H5::Exception& e ) - { - throw M4Exception() << "HDF5 error while reading the header:\n\t" << e.getCDetailMsg() << " (function: " << e.getFuncName() << ")"; - } - catch( M4Exception& e ) - { - throw; - } - + fHeader->ReadFromFile( fFile ); + //TODO Z5: Creation of streams H5::Group* tStreamsGroup = fHeader->GetStreamsGroup(); try @@ -182,19 +156,9 @@ namespace monarch4 // Write the header to the file // This will create the following groups: run, streams, and channels - try - { - fHeader->WriteToHDF5( fFile ); - } - catch( H5::Exception& e ) - { - throw M4Exception() << "HDF5 error while writing header:\n\t" << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; - } - catch( M4Exception& e ) - { - throw; - } + fHeader->WriteToFile( fFile ); + //TODO Z5: Creation of streams H5::Group* tStreamsGroup = fHeader->GetStreamsGroup(); try @@ -223,30 +187,24 @@ namespace monarch4 void Monarch4::FinishReading() const { - LDEBUG( mlog, "Finishing reading <" << fHeader->Filename() << ">" ); + std::string filename = fHeader != nullptr ? fHeader->Filename() : std::string(); + LDEBUG( mlog, "Finishing reading <" << filename << ">" ); try { - if( fHeader != nullptr ) - { - delete fHeader; - fHeader = nullptr; - } + delete fHeader; + fHeader = nullptr; for( std::vector< M4Stream* >::iterator streamIt = fStreams.begin(); streamIt != fStreams.end(); ++streamIt ) { const_cast< const M4Stream* >(*streamIt)->Close(); delete *streamIt; *streamIt = nullptr; } - if( fFile != nullptr ) - { - fFile->close(); - delete fFile; - fFile = nullptr; - } + delete fFile; + fFile = nullptr; } - catch( H5::Exception& e ) + catch( std::exception& e ) { - throw M4Exception() << "Error while closing: " << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; + throw M4Exception() << "Error while closing file <" << filename << ">:\n" << e.what(); } fState = eClosed; return; @@ -254,31 +212,24 @@ namespace monarch4 void Monarch4::FinishWriting() { - LINFO( mlog, "Finishing writing <" << fHeader->Filename() << ">" ); + std::string filename = fHeader != nullptr ? fHeader->Filename() : std::string(); + LINFO( mlog, "Finishing writing <" << filename << ">" ); try { - if( fHeader != nullptr ) - { - delete fHeader; - fHeader = nullptr; - } + delete fHeader; + fHeader = nullptr; for( std::vector< M4Stream* >::iterator streamIt = fStreams.begin(); streamIt != fStreams.end(); ++streamIt ) { (*streamIt)->Close(); delete *streamIt; *streamIt = nullptr; } - if( fFile != nullptr ) - { - fFile->close(); - delete fFile; - fFile = nullptr; - } + delete fFile; fFile = nullptr; } - catch( H5::Exception& e ) + catch( std::exception& e ) { - throw M4Exception() << "Error while closing: " << e.getDetailMsg() << " (function: " << e.getFuncName() << ")"; + throw M4Exception() << "Error while closing file <" << filename << ">:\n" << e.what(); } fState = eClosed; return; diff --git a/Monarch4/M4Monarch.hh b/Monarch4/M4Monarch.hh index 6b1243e..6b3b6ed 100644 --- a/Monarch4/M4Monarch.hh +++ b/Monarch4/M4Monarch.hh @@ -13,7 +13,7 @@ #include "logger.hh" #include "M4Stream.hh" -#include "H5Cpp.h" +#include " z5/handle.hxx" #include #include @@ -119,7 +119,7 @@ namespace monarch4 private: // the HDF5 file - mutable H5::H5File* fFile; + mutable z5::Handle* fFile; // the header mutable M4Header* fHeader; From 32e9e93d988beb13e7ccf0f7e1ce50ae6d3032ff Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Fri, 14 Apr 2023 09:24:24 -0700 Subject: [PATCH 05/66] Further work on M4Header, M4Monarch, and M4Stream --- Monarch4/M4Header.cc | 6 ++-- Monarch4/M4Header.hh | 74 +++++++++++-------------------------------- Monarch4/M4Monarch.cc | 20 ++++-------- Monarch4/M4Monarch.hh | 4 +-- Monarch4/M4Stream.cc | 20 ++++++------ Monarch4/M4Stream.hh | 6 ++-- 6 files changed, 43 insertions(+), 87 deletions(-) diff --git a/Monarch4/M4Header.cc b/Monarch4/M4Header.cc index cbe2099..4973803 100644 --- a/Monarch4/M4Header.cc +++ b/Monarch4/M4Header.cc @@ -364,9 +364,9 @@ namespace monarch4 fNStreams( 0 ), fChannelStreams(), fChannelCoherence(), - fFile( NULL ), - fStreamsGroup( NULL ), - fChannelsGroup( NULL ) + fFile( nullptr ), + fStreamsGroup( nullptr ), + fChannelsGroup( nullptr ) { } diff --git a/Monarch4/M4Header.hh b/Monarch4/M4Header.hh index 4057b91..3ec1a85 100644 --- a/Monarch4/M4Header.hh +++ b/Monarch4/M4Header.hh @@ -13,7 +13,7 @@ #include "M4MemberVariable.hh" #include "M4Types.hh" -#include "H5Cpp.h" +#include " z5/handle.hxx" #include #include @@ -42,39 +42,25 @@ namespace monarch4 ~M4StreamHeader(); M4MEMBERVARIABLE_PTR( char, Label ); - M4MEMBERVARIABLE_NOSET( uint32_t, Number ); void SetNumber( uint32_t aNumber ) const; /// In addition to setting the number, sets the label to "stream[aNumber]" - M4MEMBERVARIABLE_REF( std::string, Source ); - M4MEMBERVARIABLE( uint32_t, NChannels ); - M4MEMBERVARIABLE_REF_CONST( std::vector< uint32_t >, Channels ); - M4MEMBERVARIABLE( uint32_t, ChannelFormat ); - M4MEMBERVARIABLE( uint32_t, AcquisitionRate ); - M4MEMBERVARIABLE( uint32_t, RecordSize ); - M4MEMBERVARIABLE( uint32_t, SampleSize ); - M4MEMBERVARIABLE( uint32_t, DataTypeSize ); - M4MEMBERVARIABLE( uint32_t, DataFormat ); - M4MEMBERVARIABLE( uint32_t, BitDepth ); - M4MEMBERVARIABLE( uint32_t, BitAlignment ); - M4MEMBERVARIABLE( uint32_t, NAcquisitions ); - M4MEMBERVARIABLE( uint32_t, NRecords ); public: - void WriteToHDF5( HAS_GRP_IFC* aParent ); - void ReadFromHDF5( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const; + void WriteToFile( HAS_GRP_IFC* aParent ); + void ReadFromFile( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const; private: void WriteChannels( HAS_ATTR_IFC* aLoc ); @@ -101,39 +87,25 @@ namespace monarch4 ~M4ChannelHeader(); M4MEMBERVARIABLE_PTR( char, Label ); - M4MEMBERVARIABLE_NOSET( uint32_t, Number ); void SetNumber( uint32_t aNumber ) const; /// In addition to setting the number, sets the label to "channel[aNumber]" - M4MEMBERVARIABLE_REF( std::string, Source ); - M4MEMBERVARIABLE( uint32_t, AcquisitionRate ); - M4MEMBERVARIABLE( uint32_t, RecordSize ); - M4MEMBERVARIABLE( uint32_t, SampleSize ); - M4MEMBERVARIABLE( uint32_t, DataTypeSize ); - M4MEMBERVARIABLE( uint32_t, DataFormat ); - M4MEMBERVARIABLE( uint32_t, BitDepth ); - M4MEMBERVARIABLE( uint32_t, BitAlignment ); - M4MEMBERVARIABLE( double, VoltageOffset ); - M4MEMBERVARIABLE( double, VoltageRange ); - M4MEMBERVARIABLE( double, DACGain ); - M4MEMBERVARIABLE( double, FrequencyMin ); - M4MEMBERVARIABLE( double, FrequencyRange ); public: - void WriteToHDF5( HAS_GRP_IFC* aParent ); - void ReadFromHDF5( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const; + void WriteToFile( HAS_GRP_IFC* aParent ); + void ReadFromFile( const HAS_GRP_IFC* aParent, const std::string& aLabel ) const; }; @@ -162,27 +134,17 @@ namespace monarch4 void CopyBasicInfo( const M4Header& aOrig ); M4MEMBERVARIABLE_REF( std::string, EggVersion ); - M4MEMBERVARIABLE_REF( std::string, Filename ); - M4MEMBERVARIABLE( uint32_t, RunDuration ); - M4MEMBERVARIABLE_REF( std::string, Timestamp ); - M4MEMBERVARIABLE_REF( std::string, Description ); - M4MEMBERVARIABLE( uint32_t, NChannels ); - M4MEMBERVARIABLE( uint32_t, NStreams ); - M4MEMBERVARIABLE_REF_CONST( std::vector< uint32_t >, ChannelStreams ); - M4MEMBERVARIABLE_REF_CONST( std::vector< std::vector< bool > >, ChannelCoherence ); void SetCoherence( unsigned aChanA, unsigned aChanB, bool aCoherence ); - M4MEMBERVARIABLE_REF_CONST( std::vector< M4ChannelHeader >, ChannelHeaders ); std::vector< M4ChannelHeader >& GetChannelHeaders(); - M4MEMBERVARIABLE_REF_CONST( std::vector< M4StreamHeader >, StreamHeaders ); std::vector< M4StreamHeader >& GetStreamHeaders(); @@ -203,14 +165,14 @@ namespace monarch4 std::vector< unsigned >* aChanVec = NULL ); public: - void WriteToHDF5( H5::H5File* aFile ); - void ReadFromHDF5( const H5::H5File* aFile ) const; + void WriteToFile( z5::handle::File* aFile ); + void ReadFromFile( const z5::handle::File* aFile ) const; - const H5::Group* GetStreamsGroup() const; - H5::Group* GetStreamsGroup(); + const z5::filesystem::handle::Group* GetStreamsGroup() const; + z5::filesystem::handle::Group* GetStreamsGroup(); - const H5::Group* GetChannelsGroup() const; - H5::Group* GetChannelsGroup(); + const z5::filesystem::handle::Group* GetChannelsGroup() const; + z5::filesystem::handle::Group* GetChannelsGroup(); private: void WriteChannelStreams( HAS_ATTR_IFC* aLoc ); @@ -219,9 +181,9 @@ namespace monarch4 void WriteChannelCoherence( HAS_ATTR_IFC* aLoc ); void ReadChannelCoherence( const HAS_ATTR_IFC* aLoc ) const; - mutable H5::H5File* fFile; - mutable H5::Group* fStreamsGroup; - mutable H5::Group* fChannelsGroup; + mutable z5::Handle* fFile; + mutable z5::filesystem::handle::Group* fStreamsGroup; + mutable z5::filesystem::handle::Group* fChannelsGroup; public: static void WriteScalarToHDF5( HAS_ATTR_IFC* aLoc, const std::string& aName, const std::string& aValue ); @@ -242,22 +204,22 @@ namespace monarch4 }; - inline const H5::Group* M4Header::GetStreamsGroup() const + inline const z5::filesystem::handle::Group* M4Header::GetStreamsGroup() const { return fStreamsGroup; } - inline H5::Group* M4Header::GetStreamsGroup() + inline z5::filesystem::handle::Group* M4Header::GetStreamsGroup() { return fStreamsGroup; } - inline const H5::Group* M4Header::GetChannelsGroup() const + inline const z5::filesystem::handle::Group* M4Header::GetChannelsGroup() const { return fChannelsGroup; } - inline H5::Group* M4Header::GetChannelsGroup() + inline z5::filesystem::handle::Group* M4Header::GetChannelsGroup() { return fChannelsGroup; } diff --git a/Monarch4/M4Monarch.cc b/Monarch4/M4Monarch.cc index 43b7c61..3d34189 100644 --- a/Monarch4/M4Monarch.cc +++ b/Monarch4/M4Monarch.cc @@ -20,8 +20,8 @@ namespace monarch4 Monarch4::Monarch4() : fState( eClosed ), - fFile( nullptr ), - fHeader( nullptr ), + fFile(), + fHeader(), fMutexPtr( new std::mutex() ) { } @@ -31,17 +31,11 @@ namespace monarch4 if( fState == eOpenToRead || fState == eReadyToRead) FinishReading(); if( fState == eOpenToWrite || fState == eReadyToWrite) FinishWriting(); - delete fHeader; - fHeader = nullptr; - while( ! fStreams.empty() ) { delete fStreams.back(); fStreams.pop_back(); } - - delete fFile; - fFile = nullptr; } Monarch4::State Monarch4::GetState() const @@ -55,7 +49,7 @@ namespace monarch4 try { - tMonarch4->fFile = new z5::filesystem::handle::File( aFilename, z5::FileMode::r ); + tMonarch4->fFile = std::make_unique< z5::filesystem::handle::File >( aFilename, z5::FileMode::r ); } catch( std::exception& e ) { @@ -71,7 +65,7 @@ namespace monarch4 } LDEBUG( mlog, "Opened egg file <" << aFilename << "> for reading" ); - tMonarch4->fHeader = new M4Header(); + tMonarch4->fHeader = std::make_unique< M4Header >(); tMonarch4->fHeader->Filename() = aFilename; tMonarch4->fState = eOpenToRead; @@ -85,8 +79,8 @@ namespace monarch4 try { - tMonarch4->fFile = new z5::filesystem::handle::File( aFilename, z5::FileMode::w ); - z5::createFile( *(tMonarch4->fFile), true ); + tMonarch4->fFile = std::make_unique< z5::filesystem::handle::File >( aFilename, z5::FileMode::w ); + z5::createFile( *tMonarch4->fFile, true ); } catch( std::exception& e ) { @@ -102,7 +96,7 @@ namespace monarch4 } LDEBUG( mlog, "Opened egg file <" << aFilename << "> for writing" ); - tMonarch4->fHeader = new M4Header(); + tMonarch4->fHeader = std::make_unique< M4Header >(); tMonarch4->fHeader->Filename() = aFilename; tMonarch4->fState = eOpenToWrite; diff --git a/Monarch4/M4Monarch.hh b/Monarch4/M4Monarch.hh index 6b3b6ed..8cd61d0 100644 --- a/Monarch4/M4Monarch.hh +++ b/Monarch4/M4Monarch.hh @@ -119,10 +119,10 @@ namespace monarch4 private: // the HDF5 file - mutable z5::Handle* fFile; + mutable std::unique_ptr< z5::filesystem::handle::File > fFile; // the header - mutable M4Header* fHeader; + mutable sd::unique_ptr< M4Header > fHeader; // the streams mutable std::vector< M4Stream* > fStreams; diff --git a/Monarch4/M4Stream.cc b/Monarch4/M4Stream.cc index e84f91b..2c312d7 100644 --- a/Monarch4/M4Stream.cc +++ b/Monarch4/M4Stream.cc @@ -39,8 +39,8 @@ namespace monarch4 M4Stream::M4Stream( const M4StreamHeader& aHeader, HAS_GRP_IFC* aH5StreamsLoc, uint32_t aAccessFormat ) : fMode( kRead ), - fDoReadRecord( NULL ), - fDoWriteRecord( NULL ), + fDoReadRecord( nullptr ), + fDoWriteRecord( nullptr ), fIsInitialized( false ), fRecordsAccessed( false ), fDataTypeSize( aHeader.GetDataTypeSize() ), @@ -59,17 +59,17 @@ namespace monarch4 fNRecordsInAcq( 0 ), fAcqFirstRecTime( 0 ), fAcqFirstRecId( 0 ), - fAcqFirstRecTimes( NULL ), - fAcqFirstRecIds( NULL ), + fAcqFirstRecTimes( nullptr ), + fAcqFirstRecIds( nullptr ), fDataInterleaved( aHeader.GetChannelFormat() == sInterleaved ), fAccessFormat( aAccessFormat ), fRecordIndex(), fRecordCountInFile( 0 ), fNRecordsInFile( 0 ), fFirstRecordInFile( 0 ), - fH5StreamParentLoc( new H5::Group( aH5StreamsLoc->openGroup( aHeader.GetLabel() ) ) ), - fH5AcqLoc( NULL ), - fH5CurrentAcqDataSet( NULL ), + fZ5StreamParentLoc( new H5::Group( aH5StreamsLoc->openGroup( aHeader.GetLabel() ) ) ), + fZ5AcqLoc( nullptr ), + fZ5CurrentAcqDataSet( nullptr ), fH5DataSpaceUser( NULL ), fMutexPtr( new std::mutex() ) { @@ -200,9 +200,9 @@ namespace monarch4 M4Stream::~M4Stream() { delete fH5DataSpaceUser; fH5DataSpaceUser = NULL; - delete fH5CurrentAcqDataSet; fH5CurrentAcqDataSet = NULL; - delete fH5AcqLoc; fH5AcqLoc = NULL; - delete fH5StreamParentLoc; fH5StreamParentLoc = NULL; + delete fZ5CurrentAcqDataSet; fH5CurrentAcqDataSet = nullptr; + delete fZ5AcqLoc; fH5AcqLoc = nullptr; + delete fZ5StreamParentLoc; fH5StreamParentLoc = nullptr; delete [] fChannelRecords; } diff --git a/Monarch4/M4Stream.hh b/Monarch4/M4Stream.hh index 0cacb16..7a5f0bc 100644 --- a/Monarch4/M4Stream.hh +++ b/Monarch4/M4Stream.hh @@ -192,9 +192,9 @@ namespace monarch4 mutable char fAcqNameBuffer[ 10 ]; - mutable H5::Group* fH5StreamParentLoc; - mutable H5::Group* fH5AcqLoc; - mutable H5::DataSet* fH5CurrentAcqDataSet; + mutable z5::filesystem::handle::Group* fZ5StreamParentLoc; + mutable z5::filesystem::handle::Group* fZ5AcqLoc; + mutable z5::filesystem::handle::Dataset* fZ5CurrentAcqDataSet; mutable H5::DataSpace* fH5DataSpaceUser; From 55c511c19db7b98d59231d64e2d339eac067f94a Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Fri, 28 Apr 2023 16:55:33 -0700 Subject: [PATCH 06/66] Additional build changes to use in z5 --- CMakeLists.txt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 973a47a..7ab926d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,8 @@ project( Monarch VERSION 3.8.2 ) list( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/Scarab/cmake) include( PackageBuilder ) +set( CMAKE_CXX_STANDARD 17 ) + # Egg file version set( Egg_VERSION "4.0.0" ) add_definitions( -DEgg_VERSION=${Egg_VERSION} ) @@ -28,6 +30,7 @@ set( Monarch_BUILD_MONARCH4 TRUE CACHE BOOL "Build Monarch4 library (requires Za set( PUBLIC_EXT_LIBS_M2 ) set( PUBLIC_EXT_LIBS_M3 ) set( PUBLIC_EXT_LIBS_M4 ) +set( PRIVATE_EXT_LIBS_M4 ) ###### @@ -38,8 +41,16 @@ if( Monarch_BUILD_MONARCH4 ) # set( Monarch_BUILD_MONARCH3 TRUE CACHE BOOL "Build Monarch3 library (requires HDF5)" ) find_package( z5 REQUIRED ) - include_directories( BEFORE ${PROJECT_SOURCE_DIR}/Monarch4 ) - + find_package(xtl REQUIRED) + find_package( xtensor REQUIRED ) + include_directories( BEFORE + ${PROJECT_SOURCE_DIR}/Monarch4 + ${z5_INCLUDE_DIRS} + ) + list( APPEND PUBLIC_EXT_LIBS_M4 + nlohmann_json::nlohmann_json + xtensor + ) endif( Monarch_BUILD_MONARCH4 ) From 9ba32e52241f19d84f7288850c67af3e61ccd83f Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Fri, 28 Apr 2023 16:55:58 -0700 Subject: [PATCH 07/66] Very temporary executables to demonstrate and practice the use of z5 --- Monarch4/CMakeLists.txt | 3 ++ Monarch4/Temp/CMakeLists.txt | 22 ++++++++ Monarch4/Temp/read.cc | 81 ++++++++++++++++++++++++++++ Monarch4/Temp/readme.cc | 56 ++++++++++++++++++++ Monarch4/Temp/write.cc | 100 +++++++++++++++++++++++++++++++++++ 5 files changed, 262 insertions(+) create mode 100644 Monarch4/Temp/CMakeLists.txt create mode 100644 Monarch4/Temp/read.cc create mode 100644 Monarch4/Temp/readme.cc create mode 100644 Monarch4/Temp/write.cc diff --git a/Monarch4/CMakeLists.txt b/Monarch4/CMakeLists.txt index 945e82e..0b8df4d 100644 --- a/Monarch4/CMakeLists.txt +++ b/Monarch4/CMakeLists.txt @@ -2,6 +2,9 @@ # monarch4 library # #################### +add_subdirectory( Temp ) +return() + set( MONARCH4_HEADERFILES M4Constants.hh M4DataInterface.hh diff --git a/Monarch4/Temp/CMakeLists.txt b/Monarch4/Temp/CMakeLists.txt new file mode 100644 index 0000000..4d9436a --- /dev/null +++ b/Monarch4/Temp/CMakeLists.txt @@ -0,0 +1,22 @@ +######################## +# monarch4 executables # +######################## + +set( Monarch4_PROGRAMS ) + +set( sources + read.cc + readme.cc + write.cc +) + +pbuilder_executables( + SOURCES ${sources} + TARGETS_VAR Monarch4_PROGRAMS + PUBLIC_EXTERNAL_LIBS ${PUBLIC_EXT_LIBS_M4} +) + +pbuilder_component_install_and_export( + COMPONENT M4Executables + EXETARGETS ${Monarch4_PROGRAMS} +) diff --git a/Monarch4/Temp/read.cc b/Monarch4/Temp/read.cc new file mode 100644 index 0000000..c8d45cb --- /dev/null +++ b/Monarch4/Temp/read.cc @@ -0,0 +1,81 @@ +#include "nlohmann/json.hpp" +#include "xtensor/xarray.hpp" +#include "xtensor/xio.hpp" + +// factory functions to create files, groups and datasets +#include "z5/factory.hxx" +// handles for z5 filesystem objects +#include "z5/filesystem/handle.hxx" +// io for xtensor multi-arrays +#include "z5/multiarray/xtensor_access.hxx" +// attribute functionality +#include "z5/attributes.hxx" + +int main() { + + // get handle to a File on the filesystem + z5::filesystem::handle::File f("readme2.zr"); + + // readme + + // open the zarr dataset + const std::string dsName = "data"; + const auto dsHandle = z5::filesystem::handle::Dataset(f, dsName); + auto ds = z5::openDataset(f, dsName); + + // read array from roi (values that were not written before are filled with a fill-value) + z5::types::ShapeType offset2 = { 100, 100, 100 }; + xt::xarray::shape_type shape2 = { 300, 200, 75 }; + xt::xarray array2(shape2); + z5::multiarray::readSubarray(ds, array2, offset2.begin()); + + std::cout << "Data:" << std::endl; + std::cout << array2 << std::endl; + + nlohmann::json attributesOut; + z5::readAttributes(dsHandle, attributesOut); + std::cout << "Attributes:" << std::endl; + std::cout << attributesOut << std::endl; + + // channels + auto channelsHandle = z5::filesystem::handle::Group( f, "channels" ); + nlohmann::json chGroupAttr; + z5::readAttributes( channelsHandle, chGroupAttr ); + std::cout << "Channels" << std::endl; + std::cout << "N channels: " << chGroupAttr["nChannels"] << std::endl; + + int nChannels = chGroupAttr["nChannels"]; + for( int iCh = 0; iCh < nChannels; ++iCh ) + { + std::stringstream str; + str << "channel" << iCh; + std::string name( str.str() ); + std::cout << "Extracting channel <" << name << ">" << std::endl; + z5::filesystem::handle::Group channelHandle = z5::filesystem::handle::Group( channelsHandle, name ); + nlohmann::json oneChGroupAttr; + z5::readAttributes( channelHandle, oneChGroupAttr ); + std::cout << "Channel " << iCh << " attributes:\n" << oneChGroupAttr << std::endl; + } + + // streams + auto streamsHandle = z5::filesystem::handle::Group( f, "streams" ); + nlohmann::json strGroupAttr; + z5::readAttributes( streamsHandle, strGroupAttr ); + std::cout << "Streams" << std::endl; + std::cout << "N streams: " << strGroupAttr["nStreams"] << std::endl; + + int nStreams = strGroupAttr["nStreams"]; + for( int iStr = 0; iStr < nStreams; ++iStr ) + { + std::stringstream str; + str << "stream" << iStr; + std::string name( str.str() ); + std::cout << "Extracting stream <" << name << ">" << std::endl; + z5::filesystem::handle::Group streamHandle = z5::filesystem::handle::Group( streamsHandle, name ); + nlohmann::json oneStrGroupAttr; + z5::readAttributes( streamHandle, oneStrGroupAttr ); + std::cout << "Stream " << iStr << " attributes:\n" << oneStrGroupAttr << std::endl; + } + + return 0; +} diff --git a/Monarch4/Temp/readme.cc b/Monarch4/Temp/readme.cc new file mode 100644 index 0000000..30e4065 --- /dev/null +++ b/Monarch4/Temp/readme.cc @@ -0,0 +1,56 @@ +#include "nlohmann/json.hpp" +#include "xtensor/xarray.hpp" + +// factory functions to create files, groups and datasets +#include "z5/factory.hxx" +// handles for z5 filesystem objects +#include "z5/filesystem/handle.hxx" +// io for xtensor multi-arrays +#include "z5/multiarray/xtensor_access.hxx" +// attribute functionality +#include "z5/attributes.hxx" + +int main() { + + // get handle to a File on the filesystem + z5::filesystem::handle::File f("data_readme.zr"); + // if you wanted to use a different backend, for example AWS, you + // would need to use this instead: + // z5::s3::handle::File f; + + // create the file in zarr format + const bool createAsZarr = true; + z5::createFile(f, createAsZarr); + + // create a new zarr dataset + const std::string dsName = "data"; + std::vector shape = { 1000, 1000, 1000 }; + std::vector chunks = { 100, 100, 100 }; + auto ds = z5::createDataset(f, dsName, "float32", shape, chunks); + + // write array to roi + z5::types::ShapeType offset1 = { 50, 100, 150 }; + xt::xarray::shape_type shape1 = { 150, 200, 100 }; + xt::xarray array1(shape1, 42.0); + z5::multiarray::writeSubarray(ds, array1, offset1.begin()); + + // read array from roi (values that were not written before are filled with a fill-value) + z5::types::ShapeType offset2 = { 100, 100, 100 }; + xt::xarray::shape_type shape2 = { 300, 200, 75 }; + xt::xarray array2(shape2); + z5::multiarray::readSubarray(ds, array2, offset2.begin()); + + // get handle for the dataset + const auto dsHandle = z5::filesystem::handle::Dataset(f, dsName); + + // read and write json attributes + nlohmann::json attributesIn; + attributesIn["bar"] = "foo"; + attributesIn["pi"] = 3.141593; + z5::writeAttributes(dsHandle, attributesIn); + + nlohmann::json attributesOut; + z5::readAttributes(dsHandle, attributesOut); + + return 0; +} diff --git a/Monarch4/Temp/write.cc b/Monarch4/Temp/write.cc new file mode 100644 index 0000000..d8bf099 --- /dev/null +++ b/Monarch4/Temp/write.cc @@ -0,0 +1,100 @@ +#include "nlohmann/json.hpp" +#include "xtensor/xarray.hpp" + +// factory functions to create files, groups and datasets +#include "z5/factory.hxx" +// handles for z5 filesystem objects +#include "z5/filesystem/handle.hxx" +// io for xtensor multi-arrays +#include "z5/multiarray/xtensor_access.hxx" +// attribute functionality +#include "z5/attributes.hxx" + +int main() { + + // get handle to a File on the filesystem + z5::filesystem::handle::File f( "readme2.zr", z5::FileMode::modes::a ); + + // create the file in zarr format + const bool createAsZarr = true; + z5::createFile(f, createAsZarr); + + // create a new zarr dataset + const std::string dsName = "data"; + std::vector shape = { 1000, 1000, 1000 }; + std::vector chunks = { 100, 100, 100 }; + auto ds = z5::createDataset(f, dsName, "float32", shape, chunks); + // get handle for the dataset + const auto dsHandle = z5::filesystem::handle::Dataset(f, dsName); + + // write array to roi + z5::types::ShapeType offset1 = { 50, 100, 150 }; + xt::xarray::shape_type shape1 = { 150, 200, 100 }; + xt::xarray array1(shape1, 42.0); + z5::multiarray::writeSubarray(ds, array1, offset1.begin()); + + // write json attributes + nlohmann::json attributesIn; + attributesIn["bar"] = "foo"; + attributesIn["pi"] = 3.141593; + z5::writeAttributes(dsHandle, attributesIn); + + // channels + + z5::createGroup(f, "channels"); + auto channelsHandle = z5::filesystem::handle::Group(f, "channels"); + + const int totalChannels = 2; + int nChannels = 0; + for( int iCh = 0; iCh < totalChannels; ++iCh ) + { + std::stringstream str; + str << "channel" << iCh; + std::string name( str.str() ); + z5::createGroup( channelsHandle, name ); + z5::filesystem::handle::Group channelHandle = z5::filesystem::handle::Group( channelsHandle, name ); + nlohmann::json oneChGroupAttr; + oneChGroupAttr["name"] = name; + z5::writeAttributes(channelHandle, oneChGroupAttr); + ++nChannels; + } + + nlohmann::json chGroupAttr; + chGroupAttr["nChannels"] = nChannels; + z5::writeAttributes(channelsHandle, chGroupAttr); + + // streams + + z5::createGroup(f, "streams"); + auto streamsHandle = z5::filesystem::handle::Group(f, "streams"); + + const int totalStreams = 3; + int nStreams = 0; + std::vector< z5::filesystem::handle::Group > streamHandles; + for( int iStr = 0; iStr < totalStreams; ++iStr ) + { + std::stringstream str; + str << "stream" << iStr; + std::string name( str.str() ); + z5::createGroup( streamsHandle, name ); + streamHandles.emplace_back( z5::filesystem::handle::Group( streamsHandle, name ) ); + nlohmann::json oneStrGroupAttr; + oneStrGroupAttr["name"] = name; + z5::writeAttributes(streamHandles.back(), oneStrGroupAttr); + + z5::createGroup( streamHandles.back(), "acquisitions" ); + z5::filesystem::handle::Group acqHandle = z5::filesystem::handle::Group( streamHandles.back(), "acquisitions" ); + + ++nStreams; + } + + nlohmann::json strGroupAttr; + strGroupAttr["nStreams"] = nStreams; + z5::writeAttributes(streamsHandle, strGroupAttr); + + // take data + const int recSize = 1024; + + + return 0; +} From 3b7d2702208a1390443b19a11b73201d1b5090c3 Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Fri, 2 Jun 2023 15:43:45 -0700 Subject: [PATCH 08/66] Progress on writing data --- Monarch4/Temp/write.cc | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Monarch4/Temp/write.cc b/Monarch4/Temp/write.cc index d8bf099..6725417 100644 --- a/Monarch4/Temp/write.cc +++ b/Monarch4/Temp/write.cc @@ -68,9 +68,20 @@ int main() { z5::createGroup(f, "streams"); auto streamsHandle = z5::filesystem::handle::Group(f, "streams"); + // constants for data + const std::string acqDataName = "data"; + const int recSize = 16; + const int datasetNRec = 100; + const int chunkNRec = 10; + const int maxRecs = datasetNRec / chunkNRec; + std::vector< size_t > datasetShape = { datasetNRec, recSize, 1 }; + std::vector< size_t > chunkShape = {chunkNRec, recSize, 1 }; + const int totalStreams = 3; int nStreams = 0; std::vector< z5::filesystem::handle::Group > streamHandles; + std::vector< std::unique_ptr< z5::Dataset > > acqDatasets; + std::vector< const z5::filesystem::handle::Dataset > acqDataHandles; for( int iStr = 0; iStr < totalStreams; ++iStr ) { std::stringstream str; @@ -85,6 +96,11 @@ int main() { z5::createGroup( streamHandles.back(), "acquisitions" ); z5::filesystem::handle::Group acqHandle = z5::filesystem::handle::Group( streamHandles.back(), "acquisitions" ); + // create a new zarr dataset + acqDatasets.emplace_back( z5::createDataset( acqHandle, acqDataName, "int16", datasetShape, chunkShape ) ); + // get handle for the dataset + acqDataHandles.push_back( z5::filesystem::handle::Dataset( acqHandle, dsName ) ); + ++nStreams; } @@ -92,9 +108,18 @@ int main() { strGroupAttr["nStreams"] = nStreams; z5::writeAttributes(streamsHandle, strGroupAttr); - // take data - const int recSize = 1024; + // simulate data taking + z5::types::ShapeType writeOffset = { 0, 0, 0 }; + xt::xarray< int16_t >::shape_type writeShape = { 1, recSize, 1 }; + xt::xarray< int16_t > arrayPrototype( writeShape, 42.0 ); + for( int iStr = 0; iStr < totalStreams; ++iStr ) + { + for( ; writeOffset[0] < maxRecs; writeOffset[0] += 1 ) + { + z5::multiarray::writeSubarray< int16_t >( acqDatasets[iStr], arrayPrototype, writeOffset.begin() ); + } + } return 0; } From 79b92623c285873e7662036c745522428d3ce35b Mon Sep 17 00:00:00 2001 From: Noah Oblath Date: Tue, 25 Jul 2023 01:20:30 -0700 Subject: [PATCH 09/66] Incremental progress --- Monarch4/Temp/write.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Monarch4/Temp/write.cc b/Monarch4/Temp/write.cc index 6725417..cb6d974 100644 --- a/Monarch4/Temp/write.cc +++ b/Monarch4/Temp/write.cc @@ -74,6 +74,7 @@ int main() { const int datasetNRec = 100; const int chunkNRec = 10; const int maxRecs = datasetNRec / chunkNRec; + std::cout << "Will write <" << maxRecs << "> records" << std::endl; std::vector< size_t > datasetShape = { datasetNRec, recSize, 1 }; std::vector< size_t > chunkShape = {chunkNRec, recSize, 1 }; @@ -113,9 +114,11 @@ int main() { xt::xarray< int16_t >::shape_type writeShape = { 1, recSize, 1 }; xt::xarray< int16_t > arrayPrototype( writeShape, 42.0 ); + std::cout << "Writing records" << std::endl; for( int iStr = 0; iStr < totalStreams; ++iStr ) { - for( ; writeOffset[0] < maxRecs; writeOffset[0] += 1 ) + std::cout << "Writing records for stream " << iStr << std::endl; + for( writeOffset[0] = 0; writeOffset[0] < maxRecs; writeOffset[0] += 1 ) { z5::multiarray::writeSubarray< int16_t >( acqDatasets[iStr], arrayPrototype, writeOffset.begin() ); } From 952411cc477aab0c1d4a5974a6b1c40b902475e9 Mon Sep 17 00:00:00 2001 From: Raymond Dunn Date: Wed, 16 Aug 2023 15:33:33 -0700 Subject: [PATCH 10/66] create Doxyfile for graphical analysis --- Doxyfile | 2662 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2662 insertions(+) create mode 100644 Doxyfile diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..3cc4d47 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,2662 @@ +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = Monarch + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doxygen + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = /home/dunn874/Projects/project8/monarch + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /