diff --git a/src/classes/PRunDataHandler.cpp b/src/classes/PRunDataHandler.cpp index 393615313..0492ce550 100644 --- a/src/classes/PRunDataHandler.cpp +++ b/src/classes/PRunDataHandler.cpp @@ -5067,11 +5067,753 @@ Bool_t PRunDataHandler::WriteNexusFile(TString fln) if (!fAny2ManyInfo->useStandardOutput) std::cout << std::endl << ">> PRunDataHandler::WriteNexusFile(): writing a NeXus data file (" << fln.Data() << ") ... " << std::endl; - // create NeXus object - std::unique_ptr nxs = std::make_unique(); - if (nxs == nullptr) { - std::cerr << std::endl << ">> PRunDataHandler::WriteNexusFile(): **ERROR** couldn't invoke the NeXus object." << std::endl; - return false; + if (format.Contains("HDF4", TString::kIgnoreCase)) { // HDF4 +#ifdef HAVE_HDF4 + try { + // create NeXus object + std::unique_ptr nxs = std::make_unique(); + if (nxs == nullptr) { + std::cerr << std::endl << ">> PRunDataHandler::WriteNexusFile(): **ERROR** couldn't invoke the NeXus object." << std::endl; + return false; + } + + // set NeXus version + nxs->AddGroupAttribute("/", "NeXus_version", std::string("4.3.0")); + + // set HDF4 version + nxs->AddGroupAttribute("/", "HDF_version", nxs->GetHdf4LibVersion()); + + // set file name + nxs->AddGroupAttribute("/", "file_name", std::string(fln.Data())); + + // set creation time + std::string dt = nxs::getIso8601TimestampLocal(); + nxs->AddGroupAttribute("/", "file_time", dt); + + if (fAny2ManyInfo->idf == 1) { // IDF V1 + // set IDF version + nxs->AddDataset("/run/IDF_version", {(int)fAny2ManyInfo->idf}, {1}, nxH4::H4DataType::kINT32); + + // set program name + nxs->AddDataset("/run/program_name", {"any2many"}, {1}, nxH4::H4DataType::kCHAR8); + str="n/a"; + #ifdef HAVE_CONFIG_H + str = PACKAGE_VERSION; + #endif + nxs->AddDatasetAttribute("/run/program_name", "version", str); + + // set run number + nxs->AddDataset("/run/number", {fData[0].GetRunNumber()}, {1}, nxH4::H4DataType::kINT32); + + // set title + nxs->AddDataset("/run/title", {fData[0].GetRunTitle()->Data()}, {1}, nxH4::H4DataType::kCHAR8); + + // set notes + nxs->AddDataset("/run/notes", {std::string("n/a")}, {1}, nxH4::H4DataType::kCHAR8); + + // set analysis + nxs->AddDataset("/run/analysis", {std::string("muonTD")}, {1}, nxH4::H4DataType::kCHAR8); + + // set lab + str = *fData[0].GetLaboratory(); + nxs->AddDataset("/run/lab", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set beamline + str = *fData[0].GetBeamline(); + nxs->AddDataset("/run/beamline", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set start time + str = std::string(fData[0].GetStartDate()->Data()) + std::string("T") + std::string(fData[0].GetStartTime()->Data()); + nxs->AddDataset("/run/start_time", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set stop time + str = std::string(fData[0].GetStopDate()->Data()) + std::string("T") + std::string(fData[0].GetStopTime()->Data()); + nxs->AddDataset("/run/stop_time", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set switching state + nxs->AddDataset("/run/switching_states", {1}, {1}, nxH4::H4DataType::kINT32); + + // set user name + nxs->AddDataset("/run/user/name", {std::string("n/a")}, {1}, nxH4::H4DataType::kCHAR8); + + // set user experiment_number + nxs->AddDataset("/run/user/experiment_number", {std::string("n/a")}, {1}, nxH4::H4DataType::kCHAR8); + + // set sample name + nxs->AddDataset("/run/sample/name", {fData[0].GetSample()->Data()}, {1}, nxH4::H4DataType::kCHAR8); + + // set sample temperature + nxs->AddDataset("/run/sample/temperature", {(float)fData[0].GetTemperature(0)}, {1}, nxH4::H4DataType::kFLOAT32); + nxs->AddDatasetAttribute("/run/sample/temperature", "units", std::string("Kelvin")); + + // set magnetic field + nxs->AddDataset("/run/sample/magnetic_field", {(float)fData[0].GetField()}, {1}, nxH4::H4DataType::kFLOAT32); + nxs->AddDatasetAttribute("/run/sample/magnetic_field", "units", std::string("Gauss")); + + // set sample environment + nxs->AddDataset("/run/sample/environment", {fData[0].GetSetup()->Data()}, {1}, nxH4::H4DataType::kCHAR8); + + // set sample shape + nxs->AddDataset("/run/sample/shape", {std::string("n/a")}, {1}, nxH4::H4DataType::kCHAR8); + + // set magnetic field vector + nxs->AddDataset("/run/sample/magnetic_field_vector", {1.0f, 1.0f, 1.0f}, {3}, nxH4::H4DataType::kFLOAT32); + nxs->AddDatasetAttribute("/run/sample/magnetic_field_vector", "coordinate_system", std::string("cartesian")); + nxs->AddDatasetAttribute("/run/sample/magnetic_field_vector", "units", std::string("Gauss")); + nxs->AddDatasetAttribute("/run/sample/magnetic_field_vector", "available", 0); + + // set instrument name + str = *fData[0].GetInstrument(); + nxs->AddDataset("/run/instrument/name", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set instrument number of detectors + nxs->AddDataset("/run/instrument/detector/number", {(int)fData[0].GetNoOfHistos()}, {1}, nxH4::H4DataType::kINT32); + + // set instrument collimator + nxs->AddDataset("/run/instrument/collimator/type", {std::string("n/a")}, {1}, nxH4::H4DataType::kCHAR8); + + // set instrument beam total number of counts in Mev + // calculate the total number of counts + double total_counts = 0; + PRawRunDataSet *dataSet = nullptr; + for (unsigned int i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=0" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + for (unsigned int j=0; jGetData()->size(); j++) + total_counts += dataSet->GetData()->at(j); + } + float total_counts_mev = (float) total_counts / 1.0e6; + nxs->AddDataset("/run/instrument/beam/total_counts", {total_counts_mev}, {1}, nxH4::H4DataType::kFLOAT32); + nxs->AddDatasetAttribute("/run/instrument/beam/total_counts", "units", std::string("MEv")); + + // set time resolution (use FLOAT instead of INT) + float res = (float)(fData[0].GetTimeResolution()*fAny2ManyInfo->rebin*1.0e3); + nxs->AddDataset("/run/histogram_data_1/resolution", {res}, {1}, nxH4::H4DataType::kFLOAT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/resolution", "units", std::string("picoseconds")); + + // set time zero time to 0. see t0_bin attribute of counts! + nxs->AddDataset("/run/histogram_data_1/time_zero", {0}, {1}, nxH4::H4DataType::kINT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/time_zero", "units", std::string("microseconds")); + nxs->AddDatasetAttribute("/run/histogram_data_1/time_zero", "available", 0); + + // set raw_time + res = (float)(fData[0].GetTimeResolution()*fAny2ManyInfo->rebin*1.0e-3); + dataSet = fData[0].GetDataSet(0, false); // i.e. the false means, that i is the index and NOT the histo number + unsigned int length = (int)(dataSet->GetData()->size() / fAny2ManyInfo->rebin); + std::vector time; + for (unsigned int i=0; iAddDataset("/run/histogram_data_1/raw_time", time, {length}, nxH4::H4DataType::kFLOAT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "axis", 1); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "primary", 0); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "units", std::string("microseconds")); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "available", 0); + + // set corrected_time + nxs->AddDataset("/run/histogram_data_1/corrected_time", time, {length}, nxH4::H4DataType::kFLOAT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/corrected_time", "axis", 1); + nxs->AddDatasetAttribute("/run/histogram_data_1/corrected_time", "units", std::string("microseconds")); + nxs->AddDatasetAttribute("/run/histogram_data_1/corrected_time", "available", 0); + + // set grouping + std::vector grouping(fData[0].GetNoOfHistos(), 0); + nxs->AddDataset("/run/histogram_data_1/grouping", grouping, {(uint32_t)grouping.size()}, nxH4::H4DataType::kINT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/grouping", "avaliabe", 0); + + // set alpha + int ival=1; + nxs->AddDataset("/run/histogram_data_1/alpha", {ival}, {1}, nxH4::H4DataType::kINT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/alpha", "avaliabe", 0); + + // set counts + // feed histos + PIntVector data; + UInt_t size = 0; + int noHisto, histoLength; + if (fAny2ManyInfo->rebin == 1) { + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + histoLength = size; + for (UInt_t j=0; jGetData()->at(j)); + } + } + } else { // rebin > 1 + UInt_t dataRebin = 0; + UInt_t dataCount = 0; + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + dataCount = 0; + for (UInt_t j=0; j 0) && (j % fAny2ManyInfo->rebin == 0)) { + dataCount++; + data.push_back(dataRebin); + dataRebin = 0; + } + dataRebin += static_cast(dataSet->GetData()->at(j)); + } + } + size = dataCount; + } + nxs->AddDataset("/run/histogram_data_1/counts", data, {(uint32_t)noHisto, (uint32_t)size}, nxH4::H4DataType::kINT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "units", std::string("counts")); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "signal", 1); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "number", noHisto); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "length", (int)size); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "t0_bin", 0); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "first_good_bin", 0); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "last_good_bin", 0); + res = (float)(fData[0].GetTimeResolution()*fAny2ManyInfo->rebin*1.0e3)/2.0; + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "offset", res); + } else { // IDF V2 + nxs->AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + + // set IDF version + nxs->AddDataset("/raw_data_1/IDF_version", {(int)fAny2ManyInfo->idf}, {1}, nxH4::H4DataType::kINT32); + + // set beamline + str = *fData[0].GetBeamline(); + nxs->AddDataset("/raw_data_1/beamline", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set definition + nxs->AddDataset("/raw_data_1/definition", {std::string("muonTD")}, {1}, nxH4::H4DataType::kCHAR8); + + // set run_number + nxs->AddDataset("/raw_data_1/run_number", {fData[0].GetRunNumber()}, {1}, nxH4::H4DataType::kINT32); + + // set title + nxs->AddDataset("/raw_data_1/title", {fData[0].GetRunTitle()->Data()}, {1}, nxH4::H4DataType::kCHAR8); + + // set start time + str = std::string(fData[0].GetStartDate()->Data()) + std::string("T") + std::string(fData[0].GetStartTime()->Data()); + nxs->AddDataset("/raw_data_1/start_time", {str}, {1}, nxH4::H4DataType::kCHAR8); + nxs->AddDatasetAttribute("/raw_data_1/start_time", "units", "ISO8601"); + + // set end time + str = std::string(fData[0].GetStopDate()->Data()) + std::string("T") + std::string(fData[0].GetStopTime()->Data()); + nxs->AddDataset("/raw_data_1/end_time", {str}, {1}, nxH4::H4DataType::kCHAR8); + nxs->AddDatasetAttribute("/raw_data_1/end_time", "units", "ISO8601"); + + // set experiment_identifier + str = "n/a"; + nxs->AddDataset("/raw_data_1/experiment_identifier", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set instrument attribute + nxs->AddGroupAttribute("/raw_data_1/instrument", "NX_class", std::string("NXinstrument")); + + // set instrument name + str = *fData[0].GetInstrument(); + nxs->AddDataset("/raw_data_1/instrument/name", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set instrument/source attribute + nxs->AddGroupAttribute("/raw_data_1/instrument/source", "NX_class", std::string("NXsource")); + + // set instrument/source/name + str = fData[0].GetLaboratory()->Data(); + nxs->AddDataset("/raw_data_1/instrument/source/name", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set instrument/source/type + TString tstr = *fData[0].GetInstrument(); + std::string type{"n/a"}; + if (tstr.Contains("LEM", TString::kIgnoreCase)) { + type = "low energy muon source"; + } else if (tstr.Contains("GPS", TString::kIgnoreCase) || tstr.Contains("GPD", TString::kIgnoreCase) || + tstr.Contains("LTF", TString::kIgnoreCase) || tstr.Contains("FLAME", TString::kIgnoreCase) || + tstr.Contains("HAL-9500", TString::kIgnoreCase) || tstr.Contains("DOLLY", TString::kIgnoreCase) || + tstr.Contains("VMS", TString::kIgnoreCase)) { + type = "quasi-continous muon source"; + } else if (tstr.Contains("EMU", TString::kIgnoreCase) || tstr.Contains("MUSR", TString::kIgnoreCase) || + tstr.Contains("HIFI", TString::kIgnoreCase)) { + type = "pulsed muon source"; + } + nxs->AddDataset("/raw_data_1/instrument/source/type", {type}, {1}, nxH4::H4DataType::kCHAR8); + + // set instrument/source/probe + str = "positive muons"; + nxs->AddDataset("/raw_data_1/instrument/source/probe", {str}, {1}, nxH4::H4DataType::kCHAR8); + + // set instrument/detector info + nxs->AddGroupAttribute("/raw_data_1/instrument/detector_1", "NX_class", std::string("NXdetector")); + + // set instrument/detector/spectrum_index + int noHistos = fData[0].GetNoOfHistos(); + std::vector spectrum_index(noHistos); + for (unsigned int i=0; iAddDataset("/raw_data_1/instrument/detector_1/spectrum_index", spectrum_index, {(uint32_t)spectrum_index.size()}, nxH4::H4DataType::kINT32); + + // set instrument/detector/raw_time (not useful for quasi-continuous sources) + int ival=0; + nxs->AddDataset("/raw_data_1/instrument/detector_1/raw_time", {ival}, {1}, nxH4::H4DataType::kINT32); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/raw_time", "available", 0); + + // set instrument/detector/counts + // set counts + // feed histos + PRawRunDataSet *dataSet = nullptr; + PIntVector data; + UInt_t size = 0; + int noHisto, histoLength; + if (fAny2ManyInfo->rebin == 1) { + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + histoLength = size; + for (UInt_t j=0; jGetData()->at(j)); + } + } + } else { // rebin > 1 + UInt_t dataRebin = 0; + UInt_t dataCount = 0; + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + dataCount = 0; + for (UInt_t j=0; j 0) && (j % fAny2ManyInfo->rebin == 0)) { + dataCount++; + data.push_back(dataRebin); + dataRebin = 0; + } + dataRebin += static_cast(dataSet->GetData()->at(j)); + } + } + size = dataCount; + } + nxs->AddDataset("/raw_data_1/instrument/detector_1/counts", data, {(uint32_t)noHisto, (uint32_t)size}, nxH4::H4DataType::kINT32); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "axes", std::string("spectrum_index,time_bin")); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "long_name", std::string("positon counts")); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "signal", 1); + // t0_bin attributes + std::vector t0_bin; + for (unsigned int i=0; irebin)); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "t0_bin", {t0_bin}); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "units", std::string("counts")); + + // set raw_data_1/detector info + nxs->AddGroupAttribute("/raw_data_1/detector_1", "NX_class", std::string("NXdata")); + + // set detector/counts + nxs->AddDataset("/raw_data_1/detector_1/counts", data, {(uint32_t)noHisto, (uint32_t)size}, nxH4::H4DataType::kINT32); + nxs->AddDatasetAttribute("/raw_data_1/detector_1/counts", "axes", std::string("spectrum_index,time_bin")); + nxs->AddDatasetAttribute("/raw_data_1/detector_1/counts", "long_name", std::string("positon counts")); + nxs->AddDatasetAttribute("/raw_data_1/detector_1/counts", "signal", 1); + } + + int result = nxs->WriteNexusFile(fln.Data(), fAny2ManyInfo->idf); + if (result != 0) { + std::cerr << std::endl << "**ERROR** PRunDataHandler::WriteNexusFile, fln=" << fln << std::endl; + return false; + } + } catch (const std::runtime_error& e) { + std::cerr << std::endl << "HDF4 error: " << e.what() << std::endl; + return false; + } +#endif + } else { // HDF5 + try { + // create NeXus object + std::unique_ptr nxs = std::make_unique(); + if (nxs == nullptr) { + std::cerr << std::endl << ">> PRunDataHandler::WriteNexusFile(): **ERROR** couldn't invoke the NeXus object." << std::endl; + return false; + } + + // set NeXus version + nxs->AddGroupAttribute("/", "NeXus_version", std::string("4.3.0")); + + // set HDF5 version + nxs->AddGroupAttribute("/", "HDF_version", nxs->GetHdf5LibVersion()); + + // set file name + nxs->AddGroupAttribute("/", "file_name", std::string(fln.Data())); + + // set creation time + std::string dt = nxs::getIso8601TimestampLocal(); + nxs->AddGroupAttribute("/", "file_time", dt); + + if (fAny2ManyInfo->idf == 1) { // IDF V1 + // set IDF version + nxs->AddDataset("/run/IDF_version", {(int)fAny2ManyInfo->idf}, {1}, H5::PredType::NATIVE_INT32); + + // set program name + nxs->AddDataset("/run/program_name", {"any2many"}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + str="n/a"; + #ifdef HAVE_CONFIG_H + str = PACKAGE_VERSION; + #endif + nxs->AddDatasetAttribute("/run/program_name", "version", str); + + // set run number + nxs->AddDataset("/run/number", {fData[0].GetRunNumber()}, {1}, H5::PredType::NATIVE_INT32); + + // set title + nxs->AddDataset("/run/title", {fData[0].GetRunTitle()->Data()}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set notes + nxs->AddDataset("/run/notes", {std::string("n/a")}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set analysis + nxs->AddDataset("/run/analysis", {std::string("muonTD")}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set lab + str = *fData[0].GetLaboratory(); + nxs->AddDataset("/run/lab", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set beamline + str = *fData[0].GetBeamline(); + nxs->AddDataset("/run/beamline", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set start time + str = std::string(fData[0].GetStartDate()->Data()) + std::string("T") + std::string(fData[0].GetStartTime()->Data()); + nxs->AddDataset("/run/start_time", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set stop time + str = std::string(fData[0].GetStopDate()->Data()) + std::string("T") + std::string(fData[0].GetStopTime()->Data()); + nxs->AddDataset("/run/stop_time", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set switching state + nxs->AddDataset("/run/switching_states", {1}, {1}, H5::PredType::NATIVE_INT32); + + // set user name + nxs->AddDataset("/run/user/name", {std::string("n/a")}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set user experiment_number + nxs->AddDataset("/run/user/experiment_number", {std::string("n/a")}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set sample name + nxs->AddDataset("/run/sample/name", {fData[0].GetSample()->Data()}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set sample temperature + nxs->AddDataset("/run/sample/temperature", {(float)fData[0].GetTemperature(0)}, {1}, H5::PredType::NATIVE_FLOAT); + nxs->AddDatasetAttribute("/run/sample/temperature", "units", std::string("Kelvin")); + + // set magnetic field + nxs->AddDataset("/run/sample/magnetic_field", {(float)fData[0].GetField()}, {1}, H5::PredType::NATIVE_FLOAT); + nxs->AddDatasetAttribute("/run/sample/magnetic_field", "units", std::string("Gauss")); + + // set sample environment + nxs->AddDataset("/run/sample/environment", {fData[0].GetSetup()->Data()}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set sample shape + nxs->AddDataset("/run/sample/shape", {std::string("n/a")}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set magnetic field vector + nxs->AddDataset("/run/sample/magnetic_field_vector", {1.0, 1.0, 1.0}, {3}, H5::PredType::NATIVE_FLOAT); + nxs->AddDatasetAttribute("/run/sample/magnetic_field_vector", "coordinate_system", std::string("cartesian")); + nxs->AddDatasetAttribute("/run/sample/magnetic_field_vector", "units", std::string("Gauss")); + nxs->AddDatasetAttribute("/run/sample/magnetic_field_vector", "available", 0); + + // set instrument name + str = *fData[0].GetInstrument(); + nxs->AddDataset("/run/instrument/name", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set instrument number of detectors + nxs->AddDataset("/run/instrument/detector/number", {(int)fData[0].GetNoOfHistos()}, {1}, H5::PredType::NATIVE_INT32); + + // set instrument collimator + nxs->AddDataset("/run/instrument/collimator/type", {std::string("n/a")}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set instrument beam total number of counts in Mev + // calculate the total number of counts + double total_counts = 0; + PRawRunDataSet *dataSet = nullptr; + for (unsigned int i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=0" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + for (unsigned int j=0; jGetData()->size(); j++) + total_counts += dataSet->GetData()->at(j); + } + float total_counts_mev = (float) total_counts / 1.0e6; + nxs->AddDataset("/run/instrument/beam/total_counts", {total_counts_mev}, {1}, H5::PredType::NATIVE_FLOAT); + nxs->AddDatasetAttribute("/run/instrument/beam/total_counts", "units", std::string("MEv")); + + // set time resolution (use FLOAT instead of INT) + float res = (float)(fData[0].GetTimeResolution()*fAny2ManyInfo->rebin*1.0e3); + nxs->AddDataset("/run/histogram_data_1/resolution", {res}, {1}, H5::PredType::NATIVE_FLOAT); + nxs->AddDatasetAttribute("/run/histogram_data_1/resolution", "units", std::string("picoseconds")); + + // set time zero time to 0. see t0_bin attribute of counts! + nxs->AddDataset("/run/histogram_data_1/time_zero", {0}, {1}, H5::PredType::NATIVE_INT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/time_zero", "units", std::string("microseconds")); + nxs->AddDatasetAttribute("/run/histogram_data_1/time_zero", "available", 0); + + // set raw_time + res = (float)(fData[0].GetTimeResolution()*fAny2ManyInfo->rebin*1.0e-3); + dataSet = fData[0].GetDataSet(0, false); // i.e. the false means, that i is the index and NOT the histo number + unsigned int length = (int)(dataSet->GetData()->size() / fAny2ManyInfo->rebin); + std::vector time; + for (unsigned int i=0; iAddDataset("/run/histogram_data_1/raw_time", time, {length}, H5::PredType::NATIVE_FLOAT); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "axis", 1); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "primary", 0); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "units", std::string("microseconds")); + nxs->AddDatasetAttribute("/run/histogram_data_1/raw_time", "available", 0); + + // set corrected_time + nxs->AddDataset("/run/histogram_data_1/corrected_time", time, {length}, H5::PredType::NATIVE_FLOAT); + nxs->AddDatasetAttribute("/run/histogram_data_1/corrected_time", "axis", 1); + nxs->AddDatasetAttribute("/run/histogram_data_1/corrected_time", "units", std::string("microseconds")); + nxs->AddDatasetAttribute("/run/histogram_data_1/corrected_time", "available", 0); + + // set grouping + std::vector grouping(fData[0].GetNoOfHistos(), 0); + nxs->AddDataset("/run/histogram_data_1/grouping", grouping, {grouping.size()}, H5::PredType::NATIVE_INT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/grouping", "avaliabe", 0); + + // set alpha + int ival=1; + nxs->AddDataset("/run/histogram_data_1/alpha", {ival}, {1}, H5::PredType::NATIVE_INT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/alpha", "avaliabe", 0); + + // set counts + // feed histos + PIntVector data; + UInt_t size = 0; + int noHisto, histoLength; + if (fAny2ManyInfo->rebin == 1) { + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + histoLength = size; + for (UInt_t j=0; jGetData()->at(j)); + } + } + } else { // rebin > 1 + UInt_t dataRebin = 0; + UInt_t dataCount = 0; + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + dataCount = 0; + for (UInt_t j=0; j 0) && (j % fAny2ManyInfo->rebin == 0)) { + dataCount++; + data.push_back(dataRebin); + dataRebin = 0; + } + dataRebin += static_cast(dataSet->GetData()->at(j)); + } + } + size = dataCount; + } + nxs->AddDataset("/run/histogram_data_1/counts", data, {(long unsigned int)noHisto, size}, H5::PredType::NATIVE_INT32); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "units", std::string("counts")); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "signal", 1); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "number", noHisto); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "length", size); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "t0_bin", 0); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "first_good_bin", 0); + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "last_good_bin", 0); + res = (float)(fData[0].GetTimeResolution()*fAny2ManyInfo->rebin*1.0e3)/2.0; + nxs->AddDatasetAttribute("/run/histogram_data_1/counts", "offset", res); + } else { // IDF V2 + nxs->AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + + // set IDF version + nxs->AddDataset("/raw_data_1/IDF_version", {(int)fAny2ManyInfo->idf}, {1}, H5::PredType::NATIVE_INT32); + + // set beamline + str = *fData[0].GetBeamline(); + nxs->AddDataset("/raw_data_1/beamline", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set definition + nxs->AddDataset("/raw_data_1/definition", {std::string("muonTD")}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set run_number + nxs->AddDataset("/raw_data_1/run_number", {fData[0].GetRunNumber()}, {1}, H5::PredType::NATIVE_INT32); + + // set title + nxs->AddDataset("/raw_data_1/title", {fData[0].GetRunTitle()->Data()}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set start time + str = std::string(fData[0].GetStartDate()->Data()) + std::string("T") + std::string(fData[0].GetStartTime()->Data()); + nxs->AddDataset("/raw_data_1/start_time", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + nxs->AddDatasetAttribute("/raw_data_1/start_time", "units", "ISO8601"); + + // set end time + str = std::string(fData[0].GetStopDate()->Data()) + std::string("T") + std::string(fData[0].GetStopTime()->Data()); + nxs->AddDataset("/raw_data_1/end_time", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + nxs->AddDatasetAttribute("/raw_data_1/end_time", "units", "ISO8601"); + + // set experiment_identifier + str = "n/a"; + nxs->AddDataset("/raw_data_1/experiment_identifier", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set instrument attribute + nxs->AddGroupAttribute("/raw_data_1/instrument", "NX_class", std::string("NXinstrument")); + + // set instrument name + str = *fData[0].GetInstrument(); + nxs->AddDataset("/raw_data_1/instrument/name", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set instrument/source attribute + nxs->AddGroupAttribute("/raw_data_1/instrument/source", "NX_class", std::string("NXsource")); + + // set instrument/source/name + str = fData[0].GetLaboratory()->Data(); + nxs->AddDataset("/raw_data_1/instrument/source/name", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set instrument/source/type + TString tstr = *fData[0].GetInstrument(); + std::string type{"n/a"}; + if (tstr.Contains("LEM", TString::kIgnoreCase)) { + type = "low energy muon source"; + } else if (tstr.Contains("GPS", TString::kIgnoreCase) || tstr.Contains("GPD", TString::kIgnoreCase) || + tstr.Contains("LTF", TString::kIgnoreCase) || tstr.Contains("FLAME", TString::kIgnoreCase) || + tstr.Contains("HAL-9500", TString::kIgnoreCase) || tstr.Contains("DOLLY", TString::kIgnoreCase) || + tstr.Contains("VMS", TString::kIgnoreCase)) { + type = "quasi-continous muon source"; + } else if (tstr.Contains("EMU", TString::kIgnoreCase) || tstr.Contains("MUSR", TString::kIgnoreCase) || + tstr.Contains("HIFI", TString::kIgnoreCase)) { + type = "pulsed muon source"; + } + nxs->AddDataset("/raw_data_1/instrument/source/type", {type}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set instrument/source/probe + str = "positive muons"; + nxs->AddDataset("/raw_data_1/instrument/source/probe", {str}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // set instrument/detector info + nxs->AddGroupAttribute("/raw_data_1/instrument/detector_1", "NX_class", std::string("NXdetector")); + + // set instrument/detector/spectrum_index + int noHistos = fData[0].GetNoOfHistos(); + std::vector spectrum_index(noHistos); + for (unsigned int i=0; iAddDataset("/raw_data_1/instrument/detector_1/spectrum_index", spectrum_index, {spectrum_index.size()}, H5::PredType::NATIVE_INT32); + + // set instrument/detector/raw_time (not useful for quasi-continuous sources) + int ival=0; + nxs->AddDataset("/raw_data_1/instrument/detector_1/raw_time", {ival}, {1}, H5::PredType::NATIVE_INT32); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/raw_time", "available", 0); + + // set instrument/detector/counts + // set counts + // feed histos + PRawRunDataSet *dataSet = nullptr; + PIntVector data; + UInt_t size = 0; + int noHisto, histoLength; + if (fAny2ManyInfo->rebin == 1) { + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + histoLength = size; + for (UInt_t j=0; jGetData()->at(j)); + } + } + } else { // rebin > 1 + UInt_t dataRebin = 0; + UInt_t dataCount = 0; + noHisto = fData[0].GetNoOfHistos(); + for (UInt_t i=0; i> PRunDataHandler::WriteNexusFile: **ERROR** Couldn't get data set (idx=" << i << ")"; + std::cerr << std::endl << ">> something is really wrong!" << std::endl; + return false; + } + size = dataSet->GetData()->size(); + dataCount = 0; + for (UInt_t j=0; j 0) && (j % fAny2ManyInfo->rebin == 0)) { + dataCount++; + data.push_back(dataRebin); + dataRebin = 0; + } + dataRebin += static_cast(dataSet->GetData()->at(j)); + } + } + size = dataCount; + } + nxs->AddDataset("/raw_data_1/instrument/detector_1/counts", data, {(long unsigned int)noHisto, size}, H5::PredType::NATIVE_INT32); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "axes", std::string("spectrum_index,time_bin")); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "long_name", std::string("positon counts")); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "signal", 1); + // t0_bin attributes + std::vector t0_bin; + for (unsigned int i=0; irebin)); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "t0_bin", {t0_bin}); + nxs->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "units", std::string("counts")); + + // set raw_data_1/detector info + nxs->AddGroupAttribute("/raw_data_1/detector_1", "NX_class", std::string("NXdata")); + + // set detector/counts + nxs->AddDataset("/raw_data_1/detector_1/counts", data, {(long unsigned int)noHisto, size}, H5::PredType::NATIVE_INT32); + nxs->AddDatasetAttribute("/raw_data_1/detector_1/counts", "axes", std::string("spectrum_index,time_bin")); + nxs->AddDatasetAttribute("/raw_data_1/detector_1/counts", "long_name", std::string("positon counts")); + nxs->AddDatasetAttribute("/raw_data_1/detector_1/counts", "signal", 1); + } + + int result = nxs->WriteNexusFile(fln.Data(), fAny2ManyInfo->idf); + if (result != 0) { + std::cerr << std::endl << "**ERROR** PRunDataHandler::WriteNexusFile, fln=" << fln << std::endl; + return false; + } + } catch (const H5::Exception& e) { + std::cerr << std::endl << "HDF5 error: " << e.getDetailMsg() << std::endl; + } } // set IDF version diff --git a/src/external/nexus/PNeXus.cpp b/src/external/nexus/PNeXus.cpp index c7f2897c2..77b319819 100644 --- a/src/external/nexus/PNeXus.cpp +++ b/src/external/nexus/PNeXus.cpp @@ -27,5468 +27,5804 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include -#include -#include -#include +/** + * @file PNeXus.cpp + * @brief Implementation of the PNeXus class - NeXus HDF4/HDF5 file reader and writer + * + * This file contains the implementation of the PNeXus class for reading and + * writing ISIS muon NeXus HDF5 files. It provides case-insensitive path + * lookup functionality using the HDF4 C / HDF5 C++ API. + * + * The implementation includes: + * + * **Read Functionality:** + * - String utilities for case-insensitive comparison + * - Path parsing for HDF4 / HDF5 hierarchical paths + * - Recursive path traversal with case-insensitive matching + * - Attribute, group, and dataset lookup functions + * - Support for IDF version 1 and 2 + * + * **Write Functionality:** + * - Group hierarchy creation + * - Dataset writing for int, float, and string types + * - Attribute preservation + * - Multi-dimensional array support + * - Type-safe operations using PNXdata template class + * + * **Error Handling:** + * - Comprehensive error handling with descriptive exceptions + * - Validation of data consistency + * - HDF4 error propagation / HDF5 exception propagation + * + * @author Andreas Suter + * @date 2007-2026 + * @copyright GNU General Public License v2 + * @version 1.0 + * + * @see nxH4::PNeXus + * @see nxH4::PNXdata + * @see nxH5::PNeXus + * @see nxH5::PNXdata + */ #include +#include #include +#include +#include +#include +#include +#include +#include + +#include "Minuit2/MnStrategy.h" +#include "Minuit2/MnMinimize.h" +#include "Minuit2/FunctionMinimum.h" #include "PNeXus.h" -#ifndef SIZE_FLOAT32 -# define SIZE_FLOAT32 4 -# define SIZE_FLOAT64 8 -# define SIZE_INT8 1 -# define SIZE_UINT8 1 -# define SIZE_INT16 2 -# define SIZE_UINT16 2 -# define SIZE_INT32 4 -# define SIZE_UINT32 4 -# define SIZE_INT64 8 -# define SIZE_UINT64 8 -# define SIZE_CHAR8 1 -# define SIZE_CHAR 1 -# define SIZE_UCHAR8 1 -# define SIZE_UCHAR 1 -# define SIZE_CHAR16 2 -# define SIZE_UCHAR16 2 -#endif /* SIZE_FLOAT32 */ - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusProp Constructor -//------------------------------------------------------------------------------------------ +//============================================================================= +// nxs::checkHDFType - Determine HDF file format from magic bytes +//============================================================================= /** - *

- */ -PNeXusProp::PNeXusProp() -{ - fName = "n/a"; - fUnit = "n/a"; - fValue = 1.0e99; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXbeam data. + * @brief Determine the HDF format type of a file by reading its header * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusBeam1::IsValid(bool strict) -{ - bool valid = true; - - if (fTotalCounts == 0) { - std::cerr << ">> **WARNING** NXbeam total_counts not given" << std::endl; - } else if (!fUnits.compare("n/a")) { - std::cerr << ">> **WARNING** NXbeam total_counts units not given" << std::endl; - } - - return valid; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXdetector data. + * Opens the file in binary mode and reads the first 8 bytes to compare + * against known HDF4 and HDF5 magic byte signatures. This allows automatic + * format detection before attempting to open the file with the appropriate + * library. * - * \param strict flag if true a strict NeXus validation is performed. + * @param filename Path to the file to check + * @return nxs::HDFType indicating the format (HDF4, HDF5, or Unknown) */ -bool PNeXusDetector1::IsValid(bool strict) -{ - bool valid = true; - - if (fNumber == 0) { - std::cerr << ">> **WARNING** NXdetector number of detectors not given" << std::endl; - } - - return valid; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXinstrument data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusInstrument1::IsValid(bool strict) -{ - bool valid = true; - - if (!fName.compare("n/a")) { - std::cerr << ">> **ERROR** NXinstrument name not given" << std::endl; - valid = false; - } else if (!fDetector.IsValid(strict)) { - valid = false; - } else if (!fCollimator.IsValid(strict)) { - valid = false; - } else if (!fBeam.IsValid(strict)) { - valid = false; - } - - return valid; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusSample1 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusSample1::PNeXusSample1() -{ - fName = "n/a"; - fShape = "n/a"; - fMagneticFieldState = "n/a"; - fEnvironment = "n/a"; - fMagneticFieldVectorAvailable = -1; - fMagneticFieldVectorUnits = "n/a"; - fMagneticFieldVectorCoordinateSystem = "n/a"; -} - -//------------------------------------------------------------------------------------------ -// PNeXusSample1 Destructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusSample1::~PNeXusSample1() -{ - fPhysProp.clear(); - fMagneticFieldVector.clear(); -} - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXsample data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusSample1::IsValid(bool strict) -{ - bool valid = true; - - if (!fName.compare("n/a")) { - std::cerr << ">> **ERROR** NXsample name not given." << std::endl; - valid = false; - } else if (!fShape.compare("n/a")) { - std::cerr << ">> **WARNING** NXsample shape not given." << std::endl; - } - - int count=0; // at the end count holds the number of required physiscal properties. Currently these are: temperature, magnetic_field - for (unsigned int i=0; i> **ERROR** NXsample temperature unit not given." << std::endl; - valid = false; - } - if (fPhysProp[i].GetValue() == 1.0e99) { - std::cerr << ">> **ERROR** NXsample temperature value not given." << std::endl; - valid = false; - } - } - if (!fPhysProp[i].GetName().compare("magnetic_field")) { - count++; - if (!fPhysProp[i].GetUnit().compare("n/a")) { - std::cerr << ">> **ERROR** NXsample magnetic_field unit not given." << std::endl; - valid = false; - } - if (fPhysProp[i].GetValue() == 1.0e99) { - std::cerr << ">> **ERROR** NXsample magnetic_field value not given." << std::endl; - valid = false; - } - } - } - if (count < 2) { - std::cerr << ">> **ERROR** not all required physical properties (e.g. temperature, magnetic_field) are given." << std::endl; - valid = false; - } - - return valid; -} - -//------------------------------------------------------------------------------------------ -// GetPhysPropValue (public) -//------------------------------------------------------------------------------------------ -/** - *

- * - * \param name - * \param ok - */ -double PNeXusSample1::GetPhysPropValue(std::string name, bool &ok) -{ - double dval=0.0; - ok = false; - - for (unsigned int i=0; i - * - * \param name - * \param ok - */ -void PNeXusSample1::GetPhysPropUnit(std::string name, std::string &unit, bool &ok) -{ - unit = "n/a"; - ok = false; - - for (unsigned int i=0; iSet the physical property with 'name' and 'value' at index 'idx'. If idx==-1 add it at the - * end, otherwise set it at index idx. - * - * \param name of the physical property - * \param value of the physical property - * \param idx index where to set the physical property - */ -void PNeXusSample1::SetPhysProp(std::string name, double value, std::string unit, int idx) -{ - PNeXusProp prop; - - prop.SetName(name); - prop.SetValue(value); - prop.SetUnit(unit); - - if (idx == -1) { - fPhysProp.push_back(prop); - } else if (idx >= (int)fPhysProp.size()) { - fPhysProp.resize(idx+1); - fPhysProp[idx] = prop; - } else { - fPhysProp[idx] = prop; - } -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusUser1 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusUser1::PNeXusUser1() -{ - fName = "n/a"; - fExperimentNumber = "n/a"; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusEntry1 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusEntry1::PNeXusEntry1() -{ - fProgramName = "n/a"; - fProgramVersion = "n/a"; - fRunNumber = -1; - fTitle = "n/a"; - fNotes = "n/a"; - fAnalysis = "n/a"; - fLaboratory = "n/a"; - fBeamline = "n/a"; - fStartTime = "n/a"; - fStopTime = "n/a"; - fSwitchingState = -1; -} - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXentry data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusEntry1::IsValid(bool strict) -{ - if (!fProgramName.compare("n/a")) { - std::cerr << ">> **WARNING** NXentry: program_name not given." << std::endl; - } else if (!fProgramVersion.compare("n/a")) { - std::cerr << ">> **WARNING** NXentry: program_name/version not given." << std::endl; - } else if (fRunNumber == -1) { - std::cerr << ">> **ERROR** NXentry: number (i.e. run number) not given." << std::endl; - return false; - } else if (!fTitle.compare("n/a")) { - std::cerr << ">> **ERROR** NXentry: title not given." << std::endl; - return false; - } else if (!fNotes.compare("n/a")) { - std::cerr << ">> **WARNING** NXentry: notes not given." << std::endl; - } else if (!fAnalysis.compare("n/a")) { - std::cerr << ">> **ERROR** NXentry: analysis (i.e. type of muon experiment like 'TF', 'ALC', ...) not given." << std::endl; - return false; - } else if (!fLaboratory.compare("n/a")) { - std::cerr << ">> **ERROR** NXentry: lab (e.g. 'PSI') not given." << std::endl; - return false; - } else if (!fBeamline.compare("n/a")) { - std::cerr << ">> **ERROR** NXentry: beamline (e.g. 'piE3') not given." << std::endl; - return false; - } else if (!fStartTime.compare("n/a")) { - std::cerr << ">> **ERROR** NXentry: start_time not given." << std::endl; - return false; - } else if (!fStopTime.compare("n/a")) { - std::cerr << ">> **ERROR** NXentry: stop_time not given." << std::endl; - return false; - } else if (fSwitchingState == -1) { - std::cerr << ">> **ERROR** NXentry: switching_state (i.e. '1' normal data taking, '2' red/green mode) not given." << std::endl; - return false; - } else if (!fUser.IsValid(strict)) { - return false; - } else if (!fSample.IsValid(strict)) { - return false; - } else if (!fInstrument.IsValid(strict)) { - return false; - } else if (!fData.IsValid(strict)) { - return false; - } - - return true; -} - -//------------------------------------------------------------------------------------------ -// SetStartTime (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the start time std::string and check that it is ISO 8601 conform. - * - * \param time start time std::string - */ -int PNeXusEntry1::SetStartTime(std::string time) -{ - struct tm tm; - memset(&tm, 0, sizeof(tm)); - strptime(time.c_str(), "%Y-%m-%d %H:%M:S", &tm); - if (tm.tm_year == 0) - strptime(time.c_str(), "%Y-%m-%dT%H:%M:S", &tm); - if (tm.tm_year == 0) - return NX_ERROR; - - fStartTime = time; - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// SetStopTime (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the stop time std::string and check that it is ISO 8601 conform. - * - * \param time stop time std::string - */ -int PNeXusEntry1::SetStopTime(std::string time) -{ - struct tm tm; - memset(&tm, 0, sizeof(tm)); - strptime(time.c_str(), "%Y-%m-%d %H:%M:S", &tm); - if (tm.tm_year == 0) - strptime(time.c_str(), "%Y-%m-%dT%H:%M:S", &tm); - if (tm.tm_year == 0) - return NX_ERROR; - - fStopTime = time; - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// SetSwitchingState (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the switching state tag. '1' normal data collection, '2' Red/Green mode - * - * \param state switching state tag - */ -int PNeXusEntry1::SetSwitchingState(int state) -{ - if ((state != 1) && (state != 2)) - return NX_ERROR; - - fSwitchingState = state; - - return NX_OK; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusAlpha1 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusAlpha1::PNeXusAlpha1() -{ - fGroupFirst=0; - fGroupSecond=0; - fAlphaVal=0.0; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusData1 Destructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusData1::~PNeXusData1() -{ - fT0.clear(); - fFirstGoodBin.clear(); - fLastGoodBin.clear(); - fHistoName.clear(); - for (unsigned int i=0; iValidates the NXbeam data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusData1::IsValid(bool strict) -{ - if (GetNoOfHistos() == 0) { - std::cerr << ">> **ERROR** NXdata no histograms given." << std::endl; - return false; - } else if (fTimeResolution == 0.0) { - std::cerr << ">> **ERROR** NXdata time resolution not given." << std::endl; - return false; - } else if (fT0.size() == 0) { - std::cerr << ">> **WARNING** NXdata t0 not given." << std::endl; - } else if (fFirstGoodBin.size() == 0) { - std::cerr << ">> **WARNING** NXdata first good bin not given." << std::endl; - } else if (fLastGoodBin.size() == 0) { - std::cerr << ">> **WARNING** NXdata last good bin not given." << std::endl; - } - - return true; -} - - -//------------------------------------------------------------------------------------------ -// GetTimeResolution (public) -//------------------------------------------------------------------------------------------ -/** - *

Get time resolution in the requested units. Allowed units are 'fs', 'ps', 'ns', 'us'. - * If unsupported time units are given, a time resolution == 0.0 is returned. Internally the - * time resolution is stored in 'ps'. - * - * \param units requested units for the time resolution. - */ -double PNeXusData1::GetTimeResolution(std::string units) -{ - double result=0.0; - - if (!units.compare("fs")) - result = fTimeResolution * 1.0e3; - else if (!units.compare("ps")) - result = fTimeResolution; - else if (!units.compare("ns")) - result = fTimeResolution * 1.0e-3; - else if (!units.compare("us")) - result = fTimeResolution * 1.0e-6; - else - result = 0.0; - - return result; -} - -//------------------------------------------------------------------------------------------ -// GetT0 (public) -//------------------------------------------------------------------------------------------ -/** - *

Returns the t0 at index 'idx' or -1 if the index is out of range. - * - * \param idx index of the requested t0 - */ -int PNeXusData1::GetT0(unsigned int idx) -{ - if (idx >= fT0.size()) - return -1; - - return (int)fT0[idx]; -} +nxs::HDFType nxs::checkHDFType(const std::string& filename) { -//------------------------------------------------------------------------------------------ -// GetFirstGoodBin (public) -//------------------------------------------------------------------------------------------ -/** - *

Returns the first good bin at index 'idx' or -1 if the index is out of range. - * - * \param idx index of the requested first good bin - */ -int PNeXusData1::GetFirstGoodBin(unsigned int idx) -{ - if (idx >= fFirstGoodBin.size()) - return -1; - - return (int)fFirstGoodBin[idx]; -} - -//------------------------------------------------------------------------------------------ -// GetLastGoodBin (public) -//------------------------------------------------------------------------------------------ -/** - *

Returns the last good bin at index 'idx' or -1 if the index is out of range. - * - * \param idx index of the requested last good bin - */ -int PNeXusData1::GetLastGoodBin(unsigned int idx) -{ - if (idx >= fLastGoodBin.size()) - return -1; - - return (int)fLastGoodBin[idx]; -} - - -//------------------------------------------------------------------------------------------ -// GetHistoName (public) -//------------------------------------------------------------------------------------------ -/** - *

Extract the histogram name at position 'idx'. If 'idx' is out of range, the 'ok' flag - * is set to 'false' in which case the 'name' is undefined. - * - * \param idx index of the requested histogram name. - * \param name histogram name if idx is within range - * \param ok =true if idx was within range, otherwise false - */ -void PNeXusData1::GetHistoName(unsigned int idx, std::string &name, bool &ok) -{ - if (idx >= fHistoName.size()) { - ok = false; - } else { - ok = true; - name = fHistoName[idx]; - } -} - -//------------------------------------------------------------------------------------------ -// GetHistoLength (public) -//------------------------------------------------------------------------------------------ -/** - *

Returns the length of histogram histoNo, or 0 if histoNo is out of bound. - * - * \param histoNo index of the histogram for which the size needs to be determined. - */ -unsigned int PNeXusData1::GetHistoLength(unsigned int histoNo) -{ - if (histoNo >= fHisto.size()) - return 0; - - return fHisto[histoNo].size(); -} - -//------------------------------------------------------------------------------------------ -// GetHistoLength (public) -//------------------------------------------------------------------------------------------ -unsigned int PNeXusData1::GetHistoCounts(unsigned int histoNo) -{ - if (histoNo >= fHisto.size()) - return 0; - - unsigned int counts=0; - for (unsigned int i=0; iReturns the histogram with index 'histoNo' or 0 if 'histoNo' is out of range. - * - * \param histoNo index of the requested histogram - */ -std::vector *PNeXusData1::GetHisto(unsigned int histoNo) -{ - if (histoNo >= fHisto.size()) - return 0; - - return &fHisto[histoNo]; -} - - -//------------------------------------------------------------------------------------------ -// SetTimeResolution (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the time resolution. 'units' tells in which units 'val' is provided. Acteptable units - * are 'fs', 'ps', 'ns', 'us'. - * - * \param val time resolution value - * \param units time resolution units - */ -void PNeXusData1::SetTimeResolution(double val, std::string units) -{ - if (!units.compare("fs")) - fTimeResolution = val * 1.0e-3; - else if (!units.compare("ps")) - fTimeResolution = val; - else if (!units.compare("ns")) - fTimeResolution = val * 1.0e3; - else if (!units.compare("us")) - fTimeResolution = val * 1.0e6; -} - -//------------------------------------------------------------------------------------------ -// SetT0 (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the t0 value at index 'idx'. If 'idx'==-1, the t0 value will be appended - * - * \param t0 bin value - * \param idx index where to set t0 bin value - */ -void PNeXusData1::SetT0(unsigned int t0, int idx) -{ - if (idx == -1) { - fT0.push_back(t0); - } else if (idx >= (int)fT0.size()) { - fT0.resize(idx+1); - fT0[idx] = t0; - } else { - fT0[idx] = t0; - } -} - -//------------------------------------------------------------------------------------------ -// SetFirstGoodBin (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the first good bin value at index 'idx'. If 'idx'==-1, the first good bin value will be appended - * - * \param fgb first good bin value - * \param idx index where to set first good bin value - */ -void PNeXusData1::SetFirstGoodBin(unsigned int fgb, int idx) -{ - if (idx == -1) { - fFirstGoodBin.push_back(fgb); - } else if (idx >= (int)fFirstGoodBin.size()) { - fFirstGoodBin.resize(idx+1); - fFirstGoodBin[idx] = fgb; - } else { - fFirstGoodBin[idx] = fgb; - } -} - -//------------------------------------------------------------------------------------------ -// SetLastGoodBin (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the last good bin value at index 'idx'. If 'idx'==-1, the last good bin value will be appended - * - * \param lgb last good bin value - * \param idx index where to set last good bin value - */ -void PNeXusData1::SetLastGoodBin(unsigned int lgb, int idx) -{ - if (idx == -1) { - fLastGoodBin.push_back(lgb); - } else if (idx >= (int)fLastGoodBin.size()) { - fLastGoodBin.resize(idx+1); - fLastGoodBin[idx] = lgb; - } else { - fLastGoodBin[idx] = lgb; - } -} - -//------------------------------------------------------------------------------------------ -// FlushHistos (public) -//------------------------------------------------------------------------------------------ -/** - *

Flushes all previously allocated histograms. - * - */ -void PNeXusData1::FlushHistos() -{ - for (unsigned int i=0; iSet a histogram at index 'histoNo'. If 'histoNo'==-1, the histogram will be appended - * - * \param data histogram vector - * \param histoNo index where to set the histogram - */ -void PNeXusData1::SetHisto(std::vector &data, int histoNo) -{ - if (histoNo == -1) { - fHisto.push_back(data); - } else if (histoNo >= (int)fHisto.size()) { - fHisto.resize(histoNo+1); - fHisto[histoNo] = data; - } else { - fHisto[histoNo] = data; + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) { + std::cerr << "Error: Cannot open file '" << filename << "'" << std::endl; + return nxs::HDFType::Unknown; } -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXbeamline data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusBeamline2::IsValid(bool strict) -{ - std::string msg(""); + // Read first 8 bytes (enough for both signatures) + unsigned char header[8]; + file.read(reinterpret_cast(header), 8); - if (!fName.compare("n/a")) { - msg = "IDF2 NXbeamline 'name' not set."; - if (strict) { - std::cerr << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << ">> **WARNING** " << msg << std::endl; - } + if (file.gcount() < 4) { + std::cerr << "**Error**: File too small to be HDF4 or HDF5" << std::endl; + return nxs::HDFType::Unknown; } - return true; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusDetector2 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusDetector2::PNeXusDetector2() -{ - fErrorMsg = ""; - - fDescription = "n/a"; - fTimeResolution = 0; - fNoOfPeriods = -1; - fNoOfSpectra = -1; - fNoOfBins = -1; - - fT0Tag = -1; - fT0 = nullptr; - fFirstGoodBin = nullptr; - fLastGoodBin = nullptr; - fHisto = nullptr; -} - -//------------------------------------------------------------------------------------------ -// PNeXusDetector2 Destructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusDetector2::~PNeXusDetector2() -{ - fSpectrumIndex.clear(); - fRawTime.clear(); + // HDF5 signature: 0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a + const unsigned char hdf5_sig[8] = {0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a}; - if (fT0) { - delete [] fT0; - fT0 = nullptr; - } - if (fFirstGoodBin) { - delete [] fFirstGoodBin; - fFirstGoodBin = nullptr; - } - if (fLastGoodBin) { - delete [] fLastGoodBin; - fLastGoodBin = nullptr; - } - if (fHisto) { - delete [] fHisto; - fHisto = nullptr; - } -} - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXdetector data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusDetector2::IsValid(bool strict) -{ - std::string msg(""); - - if (!fDescription.compare("n/a")) { - msg = "IDF2 NXdetector 'description' not set."; - if (strict) { - std::cerr << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << ">> **WARNING** " << msg << std::endl; - } - } - - if (fTimeResolution == 0.0) { - std::cerr << ">> **ERROR** IDF2 NXdetector 'histogram_resolution' is not set." << std::endl; - return false; - } - - if (fSpectrumIndex.size() == 0) { - std::cerr << ">> **ERROR** IDF2 NXdetector 'spectrum_index' is not set." << std::endl; - return false; - } + // HDF4 signature: 0x0e 0x03 0x13 0x01 + const unsigned char hdf4_sig[4] = {0x0e, 0x03, 0x13, 0x01}; - if ((fT0Tag != 1) && (fT0Tag !=2)) { - std::cerr << ">> **ERROR** IDF2 NXdetector problem with t0/first_good_bin/last_good_bin/counts settings (tagging)." << std::endl; - return false; + // Check HDF5 first (needs 8 bytes) + if (file.gcount() >= 8 && std::memcmp(header, hdf5_sig, 8) == 0) { + return nxs::HDFType::HDF5; } - if (fT0 == 0) { - std::cerr << ">> **ERROR** IDF2 NXdetector t0 settings pointer is null." << std::endl; - return false; + // Check HDF4 (needs 4 bytes) + if (std::memcmp(header, hdf4_sig, 4) == 0) { + return nxs::HDFType::HDF4; } - if (fHisto == 0) { - std::cerr << ">> **ERROR** IDF2 NXdetector counts settings pointer is null." << std::endl; - return false; - } - - if (fNoOfBins == -1) { - std::cerr << ">> **ERROR** IDF2 NXdetector fNoOfBins==-1." << std::endl; - return false; - } - - return true; -} - -//------------------------------------------------------------------------------------------ -// GetT0 (public) -//------------------------------------------------------------------------------------------ -/** - *

Get T0 bin if present, otherwise return -1. - * - * \param idxp period index - * \param idxs spectrum index - */ -int PNeXusDetector2::GetT0(int idxp, int idxs) -{ - int result = -1; - - if (fT0Tag == 1) { // there is only ONE t0 present - if (fT0 != nullptr) - result = *fT0; - return result; - } - - if ((idxp < 0) && (idxs < 0)) { // assumption: there is only ONE t0 for all spectra - if (fT0 != nullptr) { - result = *fT0; - } - } else if ((idxp < 0) && (idxs >= 0)) { // assumption: t0's are represented as t0[ns] - if (idxs < fNoOfSpectra) { - result = *(fT0+idxs); - } - } else if ((idxp >= 0) && (idxs >= 0)) { // assumption: t0's are represented as t0[np][ns] - if ((idxp < fNoOfPeriods) || (idxs < fNoOfSpectra)) { - result = *(fT0+idxp*fNoOfSpectra+idxs); - } - } else { - result = -1; - } - - return result; + return nxs::HDFType::Unknown; } -//------------------------------------------------------------------------------------------ -// SetT0 (public) -//------------------------------------------------------------------------------------------ -/** - *

Set T0s. - * - *

return: - * -1 of everything is OK, - * -0 if there was an error, in which case GetErrorMsg() will give the internal error message. - * - * \param t0 pointer to t0's. - */ -int PNeXusDetector2::SetT0(int *t0) -{ - if (t0 == 0) { - fErrorMsg = "PNeXusDetector2::SetT0(int *t0): t0 pointer is null."; - return 0; - } - - int result = 1; - unsigned int size=0; - std::stringstream ss; +//============================================================================= +// nxs::getIso8601TimestampLocal - current time stamp in ISO8601 format +//============================================================================= +std::string nxs::getIso8601TimestampLocal() { + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + std::tm* local_tm = std::localtime(&now_c); - switch (fT0Tag) { - case -1: - ss << "PNeXusDetector2::SetT0(int *t0): unkown fT0tag: " << fT0Tag << "!"; - fErrorMsg = ss.str(); - result = 0; - break; - case 1: // just one single t0 - size = 1; - break; - case 2: // t0[#histos] - if (fNoOfSpectra <= 0) { - fErrorMsg = "PNeXusDetector2::SetT0(int *t0): ask for t0 vector (ns), but ns <= 0!"; - result = 0; - } else { - size = fNoOfSpectra; - } - break; - case 3: // t0[np][#histos] - if ((fNoOfPeriods <= 0) || (fNoOfSpectra <= 0)) { - fErrorMsg = "PNeXusDetector2::SetT0(int *t0): ask for t0 vector (np, ns), but either np or ns <= 0!"; - result = 0; - } else { - size = fNoOfPeriods * fNoOfSpectra; - } - break; - default: - ss << "PNeXusDetector2::SetT0(int *t0): unkown fT0tag: " << fT0Tag << "!"; - fErrorMsg = ss.str(); - result = 0; - break; - } + // Get timezone offset + char tz_offset[10]; + std::strftime(tz_offset, sizeof(tz_offset), "%z", local_tm); - // check for error - if (!result) - return result; + std::ostringstream oss; + oss << std::put_time(local_tm, "%Y-%m-%dT%H:%M:%S"); - // make sure fT0 memory is cleaned up before filled - if (fT0) { - delete [] fT0; - fT0 = 0; - } - - // allocate memory - fT0 = new int[size]; - if (fT0 == 0) { - fErrorMsg = "PNeXusDetector2::SetT0(int *t0): couldn't allocate necessary memory."; - result = 0; - } else { - for (unsigned int i=0; iGet first good bin if present, otherwise return -1. - * - * \param idxp period index - * \param idxs spectrum index - */ -int PNeXusDetector2::GetFirstGoodBin(int idxp, int idxs) +#ifdef HAVE_HDF4 +//============================================================================= +// nxH4::PNeXusDeadTime Constructor +//============================================================================= +nxH4::PNeXusDeadTime::PNeXusDeadTime(const nxH4::PNeXus *nxs, bool debug) : fDebug(debug) { - int result = -1; - - if (fT0Tag == 1) { // there is only ONE t0 present - if (fT0 != nullptr) - result = *fFirstGoodBin; - return result; - } - - if ((idxp < 0) && (idxs < 0)) { // assumption: there is only ONE t0 for all spectra - if (fFirstGoodBin != nullptr) { - result = *fFirstGoodBin; - } - } else if ((idxp < 0) && (idxs >= 0)) { // assumptions: fgb's are represented as fgb[ns] - if ((idxs < fNoOfSpectra) && (fFirstGoodBin != nullptr)) { - result = *(fFirstGoodBin+idxs); - } - } else if ((idxp >= 0) && (idxs >= 0)) { // assumption: fgb's are represented as fgb[np][ns] - if ((idxp < fNoOfPeriods) || (idxs < fNoOfSpectra)) { - if (fFirstGoodBin != nullptr) - result = *(fFirstGoodBin+idxp*fNoOfSpectra+idxs); - } - } else { - result = -1; - } - - return result; -} + std::map dataMap = nxs->GetDataMap(); -//------------------------------------------------------------------------------------------ -// SetFirstGoodBin (public) -//------------------------------------------------------------------------------------------ -/** - *

Set first good bins. - * - *

return: - * -1 of everything is OK, - * -0 if there was an error, in which case GetErrorMsg() will give the internal error message. - * - * \param fgb is the pointer to the first good bin array. - */ -int PNeXusDetector2::SetFirstGoodBin(int *fgb) -{ - if (fgb == 0) { - fErrorMsg = "PNeXusDetector2::SetFirstGoodBin(int *fgb): fgb pointer is null."; - return 0; - } - - int result = 1; - unsigned int size=0; - std::stringstream ss; - - switch (fT0Tag) { - case -1: - ss << "PNeXusDetector2::SetFirstGoodBin(int *fgb): unkown fT0tag: " << fT0Tag << "!"; - fErrorMsg = ss.str(); - result = 0; - break; - case 1: // single fgb - size = 1; - break; - case 2: // fgb[#histos] - if (fNoOfSpectra <= 0) { - fErrorMsg = "PNeXusDetector2::SetFirstGoodBin(int *fgb): ask for fgb vector (ns), but ns <= 0!"; - result = 0; - } else { - size = fNoOfSpectra; - } - break; - case 3: // fgb[np][#histos] - if ((fNoOfPeriods <= 0) || (fNoOfSpectra <= 0)) { - fErrorMsg = "PNeXusDetector2::SetFirstGoodBin(int *fgb): ask for fgb vector (np, ns), but either np or ns <= 0!"; - result = 0; - } else { - size = fNoOfPeriods * fNoOfSpectra; - } - break; - default: - ss << "PNeXusDetector2::SetFirstGoodBin(int *fgb): unkown fT0tag: " << fT0Tag << "!"; - fErrorMsg = ss.str(); - result = 0; - break; - } - - // check for error - if (!result) - return result; - - // make sure fFirstGoodBin memory is cleaned up before filled - if (fFirstGoodBin) { - delete [] fFirstGoodBin; - fFirstGoodBin = 0; - } - - // allocate memory - fFirstGoodBin = new int[size]; - if (fFirstGoodBin == 0) { - fErrorMsg = "PNeXusDetector2::SetFirstGoodBin(int *fgb): couldn't allocate necessary memory."; - result = 0; - } else { - for (unsigned int i=0; iGet last good bin if present, otherwise return -1. - * - * \param idxp period index - * \param idxs spectrum index - */ -int PNeXusDetector2::GetLastGoodBin(int idxp, int idxs) -{ - int result = -1; - - if (fT0Tag == 1) { // there is only ONE t0 present - if (fT0 != nullptr) - result = *fLastGoodBin; - return result; - } - - if ((idxp < 0) && (idxs < 0)) { // assumption: there is only ONE t0 for all spectra - if (fLastGoodBin != nullptr) { - result = *fLastGoodBin; - } - } else if ((idxp < 0) && (idxs >= 0)) { // assumption: lgb's are represented as lgb[ns] - if ((idxs < fNoOfSpectra) && (fLastGoodBin != nullptr)) { - result = *(fLastGoodBin+idxs); - } - } else if ((idxp >= 0) && (idxs >= 0)) { // assumption: lgb's are represented as lgb[np][ns] - if ((idxp < fNoOfPeriods) || (idxs < fNoOfSpectra)) { - if (fLastGoodBin != nullptr) - result = *(fLastGoodBin+idxp*fNoOfSpectra+idxs); - } - } else { - result = -1; - } - - return result; -} - -//------------------------------------------------------------------------------------------ -// SetLastGoodBin (public) -//------------------------------------------------------------------------------------------ -/** - *

Set last good bins. - * - *

return: - * -1 of everything is OK, - * -0 if there was an error, in which case GetErrorMsg() will give the internal error message. - * - * \param lgb is the pointer to the first good bin array. - */ -int PNeXusDetector2::SetLastGoodBin(int *lgb) -{ - if (lgb == 0) { - fErrorMsg = "PNeXusDetector2::SetLastGoodBin(int *lgb): fgb pointer is null."; - return 0; - } - - int result = 1; - unsigned int size=0; - std::stringstream ss; - - switch (fT0Tag) { - case -1: - ss << "PNeXusDetector2::SetLastGoodBin(int *lgb): unkown fT0tag: " << fT0Tag << "!"; - fErrorMsg = ss.str(); - result = 0; - break; - case 1: // single lgb only - size = 1; - break; - case 2: // lgb[#histos] - if (fNoOfSpectra <= 0) { - fErrorMsg = "PNeXusDetector2::SetLastGoodBin(int *lgb): ask for lgb vector (ns), but ns <= 0!"; - result = 0; - } else { - size = fNoOfSpectra; - } - break; - case 3: // lgb[np][#histos] - if ((fNoOfPeriods <= 0) || (fNoOfSpectra <= 0)) { - fErrorMsg = "PNeXusDetector2::SetLastGoodBin(int *lgb): ask for lgb vector (np, ns), but either np or ns <= 0!"; - result = 0; - } else { - size = fNoOfPeriods * fNoOfSpectra; - } - break; - default: - ss << "PNeXusDetector2::SetLastGoodBin(int *lgb): unkown fT0tag: " << fT0Tag << "!"; - fErrorMsg = ss.str(); - result = 0; - break; - } - - // check for error - if (!result) - return result; - - // make sure fLastGoodBin memory is cleaned up before filled - if (fLastGoodBin) { - delete [] fLastGoodBin; - fLastGoodBin = 0; - } - - // allocate memory - fLastGoodBin = new int[size]; - if (fLastGoodBin == 0) { - fErrorMsg = "PNeXusDetector2::SetLastGoodBin(int *lgb): couldn't allocate necessary memory."; - result = 0; - } else { - for (unsigned int i=0; iGet number of counts in a given histogram histoNo - * - * \param idx_p period index - * \param idx_s spectrum index - * - * \return the number of entries in the selected histogram histoNo, or 0 if the histoNo is out of scope. - */ -unsigned int PNeXusDetector2::GetHistoCounts(int idx_p, int idx_s) -{ - unsigned counts = 0; - - if (idx_p > 0) - if (idx_p > fNoOfPeriods) - return counts; - - if (idx_s > 0) - if (idx_s > fNoOfSpectra) - return counts; - - for (int i=0; iGet histogram value. If any of the indices is out of range, -1 is returned. Since the - * histogram can have the structures: histo[][][], histo[][], or histo[] - * - * \param idx_p period index - * \param idx_s spectrum index - * \param idx_b histogram bin index - */ -int PNeXusDetector2::GetHistoValue(int idx_p, int idx_s, int idx_b) -{ - int value = -1; - - if ((idx_b < 0) || (idx_b >= fNoOfBins)) { // make sure that idx_b is within proper bounds - return -1; - } - - if (fNoOfSpectra > 0) { // make sure that idx_s is within proper bounds if there are different spectra - if ((idx_s < 0) || (idx_s >= fNoOfSpectra)) { - return -1; - } - } - - if (fNoOfPeriods > 0) { // make sure that idx_p is within proper bounds if there are different periods - if ((idx_p < 0) || (idx_p >= fNoOfPeriods)) { - return -1; - } - } - - if (fNoOfPeriods > 0) { - value = *(fHisto+idx_p*fNoOfSpectra*fNoOfBins+idx_s*fNoOfBins+idx_b); - } else { - if (fNoOfSpectra > 0) { - value = *(fHisto+idx_s*fNoOfBins+idx_b); - } else { - value = *(fHisto+idx_b); - } - } - - return value; -} - -//------------------------------------------------------------------------------------------ -// SetHistos (public) -//------------------------------------------------------------------------------------------ -/** - *

Set values of all histograms. - * - *

return: - * - 1 if everything is OK. - * - 0 something is wrong, check the internal error message via GetErrorMsg(). - * - * \param histo pointer to the data. - */ -int PNeXusDetector2::SetHistos(int *histo) -{ - // make sure that histos are cleaned up before filled - if (fHisto) { - delete [] fHisto; - fHisto = 0; - } - - // check needed size and its consistency - unsigned int size = 0; - if (fNoOfPeriods > 0) { // (np, ns, nb) - if ((fNoOfSpectra <= 0) || (fNoOfBins <= 0)) { // error - fErrorMsg = "PNeXusDetector2::SetHistos(int *histo): claims format (np, ns, nb), but ns or nb < 0."; - return 0; - } - size = fNoOfPeriods * fNoOfSpectra * fNoOfBins; - } else { // (ns, nb) or (nb) - if (fNoOfSpectra > 0) { // (ns, nb) - if (fNoOfBins <= 0) { // error - fErrorMsg = "PNeXusDetector2::SetHistos(int *histo): claims format (ns, nb), but nb < 0."; - return 0; - } - size = fNoOfSpectra * fNoOfBins; - } else { // (nb) - if (fNoOfBins <= 0) { - fErrorMsg = "PNeXusDetector2::SetHistos(int *histo): claims format (nb), but nb < 0."; - return 0; - } - size = fNoOfBins; - } - } - - // allocate memory for fHisto - fHisto = new int[size]; - if (fHisto == 0) { - fErrorMsg = "PNeXusDetector2::SetHistos(int *histo): couldn't allocate necessary memory for fHisto."; - return 0; - } - - for (unsigned int i=0; iGet time resolution in the requested units. Allowed units are 'fs', 'ps', 'ns', 'us'. - * If unsupported time units are given, a time resolution == 0.0 is returned. Internally the - * time resolution is stored in 'ps'. - * - * \param units requested units for the time resolution. - */ -double PNeXusDetector2::GetTimeResolution(std::string units) -{ - double result=0.0; - - if (!units.compare("fs") || !units.compare("femto.second") || !units.compare("femto second") || !units.compare("femtoseconds")) - result = fTimeResolution * 1.0e3; - else if (!units.compare("ps") || !units.compare("pico.second") || !units.compare("pico second") || !units.compare("picoseconds")) - result = fTimeResolution; - else if (!units.compare("ns") || !units.compare("nano.second") || !units.compare("nano second") || !units.compare("nanoseconds")) - result = fTimeResolution * 1.0e-3; - else if (!units.compare("us") || !units.compare("micro.second") || !units.compare("micro second") || !units.compare("microseconds")) - result = fTimeResolution * 1.0e-6; - else - result = 0.0; - - return result; -} - -//------------------------------------------------------------------------------------------ -// SetTimeResolution (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the time resolution. 'units' tells in which units 'val' is provided. Acteptable units - * are 'fs', 'ps', 'ns', 'us'. - * - * \param val time resolution value - * \param units time resolution units - */ -void PNeXusDetector2::SetTimeResolution(double val, std::string units) -{ - if (!units.compare("fs") || !units.compare("femto.second") || !units.compare("femto second") || !units.compare("femtoseconds")) - fTimeResolution = val * 1.0e-3; - else if (!units.compare("ps") || !units.compare("pico.second") || !units.compare("pico second") || !units.compare("picoseconds")) - fTimeResolution = val; - else if (!units.compare("ns") || !units.compare("nano.second") || !units.compare("nano second") || !units.compare("nanoseconds")) - fTimeResolution = val * 1.0e3; - else if (!units.compare("us") || !units.compare("micro.second") || !units.compare("micro second") || !units.compare("microseconds")) - fTimeResolution = val * 1.0e6; -} - -//------------------------------------------------------------------------------------------ -// SetRawTime (public) -//------------------------------------------------------------------------------------------ -/** - *

sets the raw time (deep copy). - * - * \param rawTime raw time vector. - */ -void PNeXusDetector2::SetRawTime(std::vector &rawTime) -{ - for (unsigned int i=0; ireturns the global spectrum index of index idx. - * - * \param idx spectrum index idx - */ -int PNeXusDetector2::GetSpectrumIndex(unsigned int idx) -{ - if (idx >= fSpectrumIndex.size()) - return -1; - - return fSpectrumIndex[idx]; -} - -//------------------------------------------------------------------------------------------ -// SetSpectrumIndex (public) -//------------------------------------------------------------------------------------------ -/** - *

set the global spectrum index of index idx. - * - * \param spectIdx spectrum index value - * \param idx spectrum index idx - */ -void PNeXusDetector2::SetSpectrumIndex(int spectIdx, int idx) -{ - if (idx < 0) { - fSpectrumIndex.push_back(spectIdx); - } else if (idx >= (int)fSpectrumIndex.size()) { - fSpectrumIndex.resize(idx+1); - fSpectrumIndex[idx] = spectIdx; - } else { - fSpectrumIndex[idx] = spectIdx; - } -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXinstrument data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusInstrument2::IsValid(bool strict) -{ - std::string msg(""); - - if (!fName.compare("n/a")) { - msg = "IDF2 NXinstrument 'name' not set."; - if (strict) { - std::cerr << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fSource.IsValid(strict)) - return false; - - if (!fBeamline.IsValid(strict)) - return false; - - if (!fDetector.IsValid(strict)) - return false; - - return true; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusSource2 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusSource2::PNeXusSource2() -{ - fName = "n/a"; - fType = "n/a"; - fProbe = "n/a"; -} - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXsource data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusSource2::IsValid(bool strict) -{ - std::string msg(""); - - if (!fName.compare("n/a")) { - msg = "IDF2 NXsample 'name' not set."; - if (strict) { - std::cerr << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fType.compare("n/a")) { - msg = "IDF2 NXsample 'type' not set."; - if (strict) { - std::cerr << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fProbe.compare("n/a")) { - msg = "IDF2 NXsample 'probe' not set."; - if (strict) { - std::cerr << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << ">> **WARNING** " << msg << std::endl; - } - } - - return true; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusSample2 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusSample2::PNeXusSample2() -{ - fName = "n/a"; - fDescription = "n/a"; - fMagneticFieldState = "n/a"; - fEnvironmentTemp = "n/a"; - fEnvironmentField = "n/a"; -} - -//------------------------------------------------------------------------------------------ -// PNeXusSample2 Destructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusSample2::~PNeXusSample2() -{ - fPhysProp.clear(); -} - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXsample data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusSample2::IsValid(bool strict) -{ - std::string msg(""); - - if (!fName.compare("n/a")) { - std::cerr << ">> **WARNING** IDF2 NXsample 'name' not set." << msg << std::endl; - } - - if (!fDescription.compare("n/a")) { - std::cerr << std::endl << ">> **WARNING** IDF2 NXsample 'description' not set." << std::endl; - } - - if (!fMagneticFieldState.compare("n/a")) { - std::cerr << std::endl << ">> **WARNING** IDF2 NXsample 'magnetic_field_state' not set." << std::endl; - } - - if (!fEnvironmentTemp.compare("n/a")) { - std::cerr << std::endl << ">> **WARNING** IDF2 NXsample 'temperature_1_env' not set." << std::endl; - } - - if (!fEnvironmentField.compare("n/a")) { - std::cerr << std::endl << ">> **WARNING** IDF2 NXsample 'magnetic_field_1_env' not set." << std::endl; - } - - bool ok; - double dummy; - - dummy = GetPhysPropValue("temperature_1", ok); - if (!ok) { - std::cerr << std::endl << ">> **WARNING** IDF2 NXsample 'temperature_1' not set." << std::endl; - } - - dummy = GetPhysPropValue("magnetic_field_1", ok); - if (!ok) { - std::cerr << std::endl << ">> **WARNING** IDF2 NXsample 'magnetic_field_1' not set." << std::endl; - } - - return true; -} - -//------------------------------------------------------------------------------------------ -// GetPhysPropValue (public) -//------------------------------------------------------------------------------------------ -/** - *

- * - * \param name - * \param ok - */ -double PNeXusSample2::GetPhysPropValue(std::string name, bool &ok) -{ - double dval=0.0; - ok = false; - - for (unsigned int i=0; i - * - * \param name - * \param ok - */ -void PNeXusSample2::GetPhysPropUnit(std::string name, std::string &unit, bool &ok) -{ - unit = "n/a"; - ok = false; - - for (unsigned int i=0; iSet the physical property with 'name' and 'value' at index 'idx'. If idx==-1 add it at the - * end, otherwise set it at index idx. - * - * \param name of the physical property - * \param value of the physical property - * \param idx index where to set the physical property - */ -void PNeXusSample2::SetPhysProp(std::string name, double value, std::string unit, int idx) -{ - PNeXusProp prop; - - prop.SetName(name); - prop.SetValue(value); - prop.SetUnit(unit); - - if (idx == -1) { - fPhysProp.push_back(prop); - } else if (idx >= (int)fPhysProp.size()) { - fPhysProp.resize(idx+1); - fPhysProp[idx] = prop; - } else { - fPhysProp[idx] = prop; - } -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// PNeXusEntry2 Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXusEntry2::PNeXusEntry2() -{ - fErrorMsg = ""; - fDefinition = "n/a"; - fProgramName = "n/a"; - fProgramVersion = "n/a"; - fRunNumber = -1; - fTitle = "n/a"; - fStartTime = "n/a"; - fStopTime = "n/a"; - fExperimentIdentifier = "n/a"; -} - -//------------------------------------------------------------------------------------------ -// IsValid (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NXentry data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXusEntry2::IsValid(bool strict) -{ - std::string msg(""); - - if (!fDefinition.compare("n/a")) { - msg = "IDF2 NXentry definition not set."; - if (strict) { - std::cerr << std::endl << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** " << msg << std::endl; - } - } - - if (fRunNumber == -1) { - msg = "run number not set."; - if (strict) { - std::cerr << std::endl << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fTitle.compare("n/a")) { - msg = "run title not set."; - if (strict) { - std::cerr << std::endl << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fStartTime.compare("n/a")) { - msg = "start time not set."; - if (strict) { - std::cerr << std::endl << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fStopTime.compare("n/a")) { - msg = "end time not set."; - if (strict) { - std::cerr << std::endl << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fExperimentIdentifier.compare("n/a")) { - msg = "experiment identifier not set."; - if (strict) { - std::cerr << std::endl << ">> **ERROR** " << msg << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** " << msg << std::endl; - } - } - - if (!fUser.IsValid(strict)) - return false; - - if (!fSample.IsValid(strict)) - return false; - - if (!fInstrument.IsValid(strict)) - return false; - - return true; -} - -//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -//------------------------------------------------------------------------------------------ -// SetStartTime (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the start time std::string and check that it is ISO 8601 conform. - * - * \param time start time std::string - */ -int PNeXusEntry2::SetStartTime(std::string time) -{ - struct tm tm; - memset(&tm, 0, sizeof(tm)); - strptime(time.c_str(), "%Y-%m-%d %H:%M:S", &tm); - if (tm.tm_year == 0) - strptime(time.c_str(), "%Y-%m-%dT%H:%M:S", &tm); - if (tm.tm_year == 0) { - fErrorMsg = "PNeXusEntry2::SetStartTime(): get year zero!"; - return NX_ERROR; - } - - fStartTime = time; - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// SetStopTime (public) -//------------------------------------------------------------------------------------------ -/** - *

Set the stop time std::string and check that it is ISO 8601 conform. - * - * \param time stop time std::string - */ -int PNeXusEntry2::SetStopTime(std::string time) -{ - struct tm tm; - memset(&tm, 0, sizeof(tm)); - strptime(time.c_str(), "%Y-%m-%d %H:%M:S", &tm); - if (tm.tm_year == 0) - strptime(time.c_str(), "%Y-%m-%dT%H:%M:S", &tm); - if (tm.tm_year == 0) { - fErrorMsg = "PNeXusEntry2::SetStopTime(): get year zero!"; - return NX_ERROR; - } - - fStopTime = time; - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// PNeXus Constructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXus::PNeXus() -{ - Init(); -} - -//------------------------------------------------------------------------------------------ -// PNeXus Constructor -//------------------------------------------------------------------------------------------ -/** - *

- * - * \param fileName - */ -PNeXus::PNeXus(const char* fileName) -{ - Init(); - - fFileName = fileName; - - if (ReadFile(fileName) != NX_OK) { - std::cerr << std::endl << fErrorMsg << " (error code=" << fErrorCode << ")" << std::endl << std::endl; - } else { - fValid = true; - } -} - -//------------------------------------------------------------------------------------------ -// PNeXus Destructor -//------------------------------------------------------------------------------------------ -/** - *

- */ -PNeXus::~PNeXus() -{ - for (unsigned int i=0; iDefine which IDF version shall be written/created. - * - * \param idf version number - */ -void PNeXus::SetIdfVersion(unsigned int idf) -{ - if ((idf != 1) && (idf != 2)) { - std::cerr << std::endl << ">> **ERROR** Only IDF versions 1 and 2 are supported." << std::endl; + int idfVersion = nxs->GetIdfVersion(); + if ((idfVersion != 1) && (idfVersion != 2)) { + std::stringstream err; + err << "**ERROR** Found unsupported IDF version '" << idfVersion << "'"; + std::cout << err.str() << std::endl; + fValid = false; return; } - fIdfVersion = idf; - - if (idf == 1) { // IDF 1 - fNxEntry2.reset(); - - fNxEntry1 = std::make_unique(); - if (fNxEntry1 == nullptr) { - std::cerr << std::endl << ">> **ERROR** couldn't invoke IDF 1 object PNeXusEntry1." << std::endl; + if (idfVersion == 1) { + // not yet implemented + } else { // idfVersion == 2 + // get counts + if (dataMap.find("/raw_data_1/instrument/detector_1/counts") != dataMap.end()) { + auto counts_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/counts"]); + const auto& counts = counts_data.GetData(); + fCounts = counts; + auto dims = counts_data.GetDimensions(); + if (fDebug) { + std::cout << " counts dimensions: " << dims[0] << " x " + << dims[1] << " x " << dims[2] << std::endl; + } + fDims = dims; + } else { + std::cout << "**ERROR** Couldn't find 'counts' dataset" << std::endl; + fValid = false; return; } - } else { // IDF 2 - fNxEntry1.reset(); - fNxEntry2 = std::make_unique(); - if (fNxEntry2 == nullptr) { - std::cerr << std::endl << ">> **ERROR** couldn't invoke IDF 2 object PNeXusEntry1." << std::endl; + // get time_resolution + if (dataMap.find("/raw_data_1/instrument/detector_1/time_resolution") != dataMap.end()) { + auto time_res_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/time_resolution"]); + const auto& time_res = time_res_data.GetData(); + if (time_res.size() > 0) { + fTimeResolution = time_res[0]; + if (fDebug) { + std::cout << " time resolution: " << fTimeResolution << " ps" << std::endl; + } + } + } else { + std::cout << "**ERROR** Couldn't find 'time_resolution' dataset" << std::endl; + fValid = false; return; } + + // get good_frames + if (dataMap.find("/raw_data_1/good_frames") != dataMap.end()) { + auto good_frames_data = std::any_cast>(dataMap["/raw_data_1/good_frames"]); + const auto& good_frames = good_frames_data.GetData(); + if (good_frames.size() > 0) { + fGoodFrames = good_frames[0]; + if (fDebug) { + std::cout << " good frames: " << fGoodFrames << std::endl; + } + } + } else { + std::cout << "**ERROR** Couldn't find 'good_frames' dataset" << std::endl; + fValid = false; + return; + } + + // get t0_bin, first_good_bin, last_good_bin + if (dataMap.find("/raw_data_1/instrument/detector_1/spectrum_index") != dataMap.end()) { + auto spec_idx_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/spectrum_index"]); + if (spec_idx_data.HasAttribute("t0_bin")) { + try { + fT0Bin = std::any_cast(spec_idx_data.GetAttribute("t0_bin")); + } catch (...) { + std::cout << "**WARNING** Couldn't read t0_bin attribute" << std::endl; + } + } + + if (spec_idx_data.HasAttribute("first_good_bin")) { + try { + fFgbBin = std::any_cast(spec_idx_data.GetAttribute("first_good_bin")); + } catch (...) { + std::cout << "**WARNING** Couldn't read first_good_bin attribute" << std::endl; + } + } + + if (spec_idx_data.HasAttribute("last_good_bin")) { + try { + fLgbBin = std::any_cast(spec_idx_data.GetAttribute("last_good_bin")); + } catch (...) { + std::cout << "**WARNING** Couldn't read last_good_bin attribute" << std::endl; + } + } + } + + if (fDebug) { + std::cout << " t0_bin: " << fT0Bin << ", first_good_bin: " << fFgbBin + << ", last_good_bin: " << fLgbBin << std::endl; + } + } + + fValid = true; +} + +//============================================================================= +// PNeXusDeadTime::operator() +//============================================================================= +double nxH4::PNeXusDeadTime::operator()(const std::vector &par) const +{ + double chisq = 0.0; + double tau = par[0]; // dead time in microseconds + + // Convert time resolution from ps to microseconds + double dt = fTimeResolution * 1.0e-6; // ps -> us + + // Calculate chi-square for the specified spectrum + int period = 0; // assuming single period + + for (unsigned int bin = fFgbBin; bin <= static_cast(fLgbBin); bin++) { + int idx = period * fDims[1] * fDims[2] + fIdx * fDims[2] + bin; + double n_obs = static_cast(fCounts[idx]); + + if (n_obs > 0) { + // Dead time correction: n_true = n_obs / (1 - n_obs * tau / (good_frames * dt)) + double correction = 1.0 - (n_obs * tau) / (fGoodFrames * dt); + if (correction > 0) { + double n_expected = n_obs / correction; + double diff = n_obs - n_expected; + chisq += (diff * diff) / n_expected; + } + } + } + + return chisq; +} + +//============================================================================= +// PNeXusDeadTime::minimize +//============================================================================= +void nxH4::PNeXusDeadTime::Minimize(const int i) +{ + if (i < 0 || i >= static_cast(fDims[1])) { + std::cerr << "**ERROR** Invalid spectrum index: " << i << std::endl; + return; + } + + fIdx = static_cast(i); + + ROOT::Minuit2::MnUserParameters upar; + upar.Add("dead_time", 0.1, 0.01); // initial value, step size + upar.SetLimits("dead_time", 0.0, 10.0); // limits in microseconds + + ROOT::Minuit2::MnMinimize minimize(*this, upar); + ROOT::Minuit2::FunctionMinimum min = minimize(); + + std::cout << "Spectrum " << i << ": "; + if (min.IsValid()) { + double dt = min.UserState().Value("dead_time"); + double dt_err = min.UserState().Error("dead_time"); + std::cout << "Dead time = " << dt << " +/- " << dt_err << " us" << std::endl; + } else { + std::cout << "Minimization failed" << std::endl; + } +} + +//============================================================================= +// PNeXus Default Constructor +//============================================================================= +nxH4::PNeXus::PNeXus() : fSdId(-1), fFileId(-1) +{ + uint32 major_v, minor_v, release; + char string[256]; + Hgetlibversion(&major_v, &minor_v, &release, string); + fHdf4LibVersion = string; +} + +//============================================================================= +// PNeXus Constructor with filename +//============================================================================= +nxH4::PNeXus::PNeXus(const std::string fln, const bool printDebug) + : fPrintDebug(printDebug), fFileName(fln), fSdId(-1), fFileId(-1) +{ + uint32 major_v, minor_v, release; + char string[256]; + Hgetlibversion(&major_v, &minor_v, &release, string); + fHdf4LibVersion = string; + + if (ReadNexusFile() != 0) { + throw std::runtime_error("Failed to read NeXus file: " + fln); + } +} + +//============================================================================= +// PNeXus Destructor +//============================================================================= +nxH4::PNeXus::~PNeXus() +{ + if (fSdId != -1) { + SDend(fSdId); + } + if (fFileId != -1) { + Hclose(fFileId); + } +} + +//============================================================================= +// PNeXus::ReadNexusFile +//============================================================================= +int nxH4::PNeXus::ReadNexusFile() +{ + // Open the HDF4 file + fSdId = SDstart(fFileName.c_str(), DFACC_READ); + if (fSdId == FAIL) { + std::cerr << "**ERROR** Failed to open HDF4 file: " << fFileName << std::endl; + return 1; + } + + fFileId = Hopen(fFileName.c_str(), DFACC_READ, 0); + if (fFileId == FAIL) { + std::cerr << "**ERROR** Failed to open HDF4 file (H interface): " << fFileName << std::endl; + SDend(fSdId); + fSdId = -1; + return 1; + } + + // Read file-level attributes + int32 n_datasets, n_file_attrs; + if (SDfileinfo(fSdId, &n_datasets, &n_file_attrs) == FAIL) { + std::cerr << "**ERROR** Failed to get file info" << std::endl; + return 1; + } + + if (fPrintDebug) { + std::cout << "Number of datasets: " << n_datasets << std::endl; + std::cout << "Number of file attributes: " << n_file_attrs << std::endl; + } + + // Read HDF4 version and NeXus version from attributes + char attr_name[H4_MAX_NC_NAME]; + int32 attr_type, attr_count; + + for (int32 i = 0; i < n_file_attrs; i++) { + if (SDattrinfo(fSdId, i, attr_name, &attr_type, &attr_count) == FAIL) { + continue; + } + + if (CaseInsensitiveEquals(attr_name, "HDF_version") || + CaseInsensitiveEquals(attr_name, "HDF4_version")) { + std::vector buffer(attr_count + 1, '\0'); + if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { + fHdf4Version = std::string(buffer.data()); + } + } else if (CaseInsensitiveEquals(attr_name, "NeXus_version")) { + std::vector buffer(attr_count + 1, '\0'); + if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { + fNeXusVersion = std::string(buffer.data()); + } + } else if (CaseInsensitiveEquals(attr_name, "file_name")) { + std::vector buffer(attr_count + 1, '\0'); + if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { + fFileNameNxs = std::string(buffer.data()); + } + } else if (CaseInsensitiveEquals(attr_name, "file_time")) { + std::vector buffer(attr_count + 1, '\0'); + if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { + fFileTimeNxs = std::string(buffer.data()); + } + } + } + + // Determine IDF version + // Try to find IDF_version dataset - check multiple possible locations + + // First try simple root-level name + int32 idf_idx = SDnametoindex(fSdId, "IDF_version"); + if (idf_idx != FAIL) { + int32 sds_id = SDselect(fSdId, idf_idx); + if (sds_id != FAIL) { + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + char name[H4_MAX_NC_NAME]; + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { + if (data_type == DFNT_INT32) { + int32 idf_val; + int32 start[H4_MAX_VAR_DIMS] = {0}; + int32 edges[H4_MAX_VAR_DIMS] = {1}; + if (SDreaddata(sds_id, start, nullptr, edges, &idf_val) != FAIL) { + fIdfVersion = idf_val; + } + } + } + SDendaccess(sds_id); + } + } + + // If not found, try /run/IDF_version (IDF version 1 location) + if (fIdfVersion == -1) { + try { + int32 sds_ref = FindDatasetRefByPath("/run/IDF_version"); + if (sds_ref != -1) { + int32 sds_idx = SDreftoindex(fSdId, sds_ref); + if (sds_idx != FAIL) { + int32 sds_id = SDselect(fSdId, sds_idx); + if (sds_id != FAIL) { + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + char name[H4_MAX_NC_NAME]; + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { + if (data_type == DFNT_INT32) { + int32 idf_val; + int32 start[H4_MAX_VAR_DIMS] = {0}; + int32 edges[H4_MAX_VAR_DIMS] = {1}; + if (SDreaddata(sds_id, start, nullptr, edges, &idf_val) != FAIL) { + fIdfVersion = idf_val; + } + } + } + SDendaccess(sds_id); + } + } + } + } catch (...) { + // Path not found, continue to next attempt + } + } + + // If still not found, try /raw_data_1/idf_version (IDF version 2 location) + if (fIdfVersion == -1) { + try { + int32 sds_ref = FindDatasetRefByPath("/raw_data_1/idf_version"); + if (sds_ref != -1) { + int32 sds_idx = SDreftoindex(fSdId, sds_ref); + if (sds_idx != FAIL) { + int32 sds_id = SDselect(fSdId, sds_idx); + if (sds_id != FAIL) { + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + char name[H4_MAX_NC_NAME]; + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { + if (data_type == DFNT_INT32) { + int32 idf_val; + int32 start[H4_MAX_VAR_DIMS] = {0}; + int32 edges[H4_MAX_VAR_DIMS] = {1}; + if (SDreaddata(sds_id, start, nullptr, edges, &idf_val) != FAIL) { + fIdfVersion = idf_val; + } + } + } + SDendaccess(sds_id); + } + } + } + } catch (...) { + // Path not found, will use default + } + } + + if (fIdfVersion == -1) { + // Try alternate approach - check for raw_data_1 structure + // If we find raw_data_1/detector_1, assume IDF v2 + // Otherwise assume IDF v1 + fIdfVersion = 2; // default to version 2 + } + + if (fPrintDebug) { + std::cout << "HDF4 Version: " << fHdf4Version << std::endl; + std::cout << "NeXus Version: " << fNeXusVersion << std::endl; + std::cout << "IDF Version: " << fIdfVersion << std::endl; + } + + // Read datasets based on IDF version + if (fIdfVersion == 1) { + HandleIdfV1(fSdId); + } else if (fIdfVersion == 2) { + HandleIdfV2(fSdId); + } else { + std::cerr << "**ERROR** Unsupported IDF version: " << fIdfVersion << std::endl; + return 1; + } + + return 0; +} + +//============================================================================= +// nxH4::PNeXus::HandleIdfV1 +//============================================================================= +void nxH4::PNeXus::HandleIdfV1(int32 sd_id) +{ + // IDF version 1 handling - not yet implemented + if (fPrintDebug) { + std::cout << "IDF version 1 handling ..." << std::endl; + } + + std::vector int_datasets = { + "/run/IDF_version", + "/run/number", + "/run/switching_states", + "/run/instrument/detector/number", + "/run/instrument/beam/frames_good", + "/run/histogram_data_1/counts", + "/run/histogram_data_1/resolution", + "/run/histogram_data_1/grouping" + }; + + std::vector float_datasets = { + "/run/sample/temperature", + "/run/sample/magnetic_field", + "/run/sample/magnetic_field_vector", + "/run/instrument/detector/deadtimes", + "/run/histogram_data_1/time_zero", + "/run/histogram_data_1/raw_time", + "/run/histogram_data_1/corrected_time", + "/run/histogram_data_1/alpha" + }; + + std::vector string_datasets = { + "/run/program_name", + "/run/title", + "/run/notes", + "/run/analysis", + "/run/lab", + "/run/beamline", + "/run/start_time", + "/run/stop_time", + "/run/user/name", + "/run/user/experiment_number", + "/run/sample/name", + "/run/sample/magnetic_field_state", + "/run/sample/environment", + "/run/instrument/name", + "/run/instrument/collimator/type" + }; + + // Read integer datasets + for (const auto& path : int_datasets) { + try { + ReadIntDataset(sd_id, path); + } catch (const std::exception& e) { + if (fPrintDebug) { + std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; + } + } + } + + // Read float datasets + for (const auto& path : float_datasets) { + try { + ReadFloatDataset(sd_id, path); + } catch (const std::exception& e) { + if (fPrintDebug) { + std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; + } + } + } + + // Read string datasets + for (const auto& path : string_datasets) { + try { + ReadStringDataset(sd_id, path); + } catch (const std::exception& e) { + if (fPrintDebug) { + std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; + } + } } } -//------------------------------------------------------------------------------------------ -// Init (public) -//------------------------------------------------------------------------------------------ -/** - *

Validates the NeXus data. A flag 'strict' controls the degree of validation. If 'strict' == true - * a full NeXus validation is done, otherwise a much sloppier validation is performed. This sloppier - * validation is needed when converting data. - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXus::IsValid(bool strict) +//============================================================================= +// nxH4::PNeXus::HandleIdfV2 +//============================================================================= +void nxH4::PNeXus::HandleIdfV2(int32 sd_id) { - bool valid = true; + // Read common datasets for IDF version 2 + // These are typical datasets found in muon NeXus files - if (fIdfVersion == 1) // IDF Version 1 - valid = IsValidIdf1(strict); - else if (fIdfVersion == 2) // IDF Version 2 - valid = IsValidIdf2(strict); - else { - std::cerr << std::endl << ">> **ERROR** found IDF Version " << fIdfVersion << ", which is not implemented yet." << std::endl << std::endl; - valid = false; + std::vector int_datasets = { + "/raw_data_1/instrument/detector_1/counts", + "/raw_data_1/instrument/detector_1/spectrum_index", + "/raw_data_1/good_frames", + "/raw_data_1/instrument/detector_1/raw_time", + "/raw_data_1/run_number" + }; + + std::vector float_datasets = { + "/raw_data_1/instrument/detector_1/resolution", + "/raw_data_1/instrument/detector_1/time_zero" + }; + + std::vector string_datasets = { + "/raw_data_1/name", + "/raw_data_1/title", + "/raw_data_1/start_time", + "/raw_data_1/end_time" + }; + + // Read integer datasets + for (const auto& path : int_datasets) { + try { + ReadIntDataset(sd_id, path); + } catch (const std::exception& e) { + if (fPrintDebug) { + std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; + } + } } - return valid; + // Read float datasets + for (const auto& path : float_datasets) { + try { + ReadFloatDataset(sd_id, path); + } catch (const std::exception& e) { + if (fPrintDebug) { + std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; + } + } + } + + // Read string datasets + for (const auto& path : string_datasets) { + try { + ReadStringDataset(sd_id, path); + } catch (const std::exception& e) { + if (fPrintDebug) { + std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; + } + } + } } -//------------------------------------------------------------------------------------------ -// GetGroupedHisto (public) -//------------------------------------------------------------------------------------------ -/** - *

Returns the grouped histogram if idx is within range, otherwise 0. - * - * \param idx index of the grouped histogram to be returned. - */ -std::vector* PNeXus::GetGroupedHisto(unsigned int idx) +//============================================================================= +// nxH4::PNeXus::ReadIntDataset +//============================================================================= +void nxH4::PNeXus::ReadIntDataset(int32 sd_id, const std::string& path) { - if (idx >= fGroupedHisto.size()) + // Extract dataset name from path + std::vector components = SplitPath(path); + if (components.empty()) { + throw std::runtime_error("Invalid path: " + path); + } + std::string dataset_name = components.back(); + + // Use VGroup navigation to find the correct dataset reference + int32 sds_ref = -1; + if (components.size() > 1) { + // Try to resolve via VGroup hierarchy + sds_ref = FindDatasetRefByPath(path); + } + + int32 sds_idx = -1; + if (sds_ref != -1) { + // Find SDS index from reference + sds_idx = SDreftoindex(sd_id, sds_ref); + } + + // Fallback to name-based search if VGroup navigation failed + if (sds_idx == FAIL || sds_idx == -1) { + sds_idx = FindDatasetIndex(sd_id, dataset_name); + } + + int32 sds_id = SDselect(sd_id, sds_idx); + if (sds_id == FAIL) { + throw std::runtime_error("Failed to select dataset: " + dataset_name); + } + + // Get dataset info + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + char name[H4_MAX_NC_NAME]; + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to get dataset info: " + dataset_name); + } + + // Calculate total number of elements + int32 n_elements = 1; + std::vector dimensions; + for (int32 i = 0; i < rank; i++) { + n_elements *= dim_sizes[i]; + dimensions.push_back(static_cast(dim_sizes[i])); + } + + // Read data + std::vector data(n_elements); + int32 start[H4_MAX_VAR_DIMS] = {0}; + if (SDreaddata(sds_id, start, nullptr, dim_sizes, data.data()) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to read dataset: " + dataset_name); + } + + // Convert to int vector + std::vector int_data(data.begin(), data.end()); + + // Create PNXdata object + PNXdata pnx_data(H4DataType::kINT32); + pnx_data.SetData(int_data); + pnx_data.SetDimensions(dimensions); + + // Read attributes + ReadDatasetAttributes(sds_id, pnx_data); + + // Store in data map + fDataMap[path] = pnx_data; + + SDendaccess(sds_id); +} + +//============================================================================= +// nxH4::PNeXus::ReadFloatDataset +//============================================================================= +void nxH4::PNeXus::ReadFloatDataset(int32 sd_id, const std::string& path) +{ + // Extract dataset name from path + std::vector components = SplitPath(path); + if (components.empty()) { + throw std::runtime_error("Invalid path: " + path); + } + std::string dataset_name = components.back(); + + // Use VGroup navigation to find the correct dataset reference + int32 sds_ref = -1; + if (components.size() > 1) { + // Try to resolve via VGroup hierarchy + sds_ref = FindDatasetRefByPath(path); + } + + int32 sds_idx = -1; + if (sds_ref != -1) { + // Find SDS index from reference + sds_idx = SDreftoindex(sd_id, sds_ref); + } + + // Fallback to name-based search if VGroup navigation failed + if (sds_idx == FAIL || sds_idx == -1) { + sds_idx = FindDatasetIndex(sd_id, dataset_name); + } + + int32 sds_id = SDselect(sd_id, sds_idx); + if (sds_id == FAIL) { + throw std::runtime_error("Failed to select dataset: " + dataset_name); + } + + // Get dataset info + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + char name[H4_MAX_NC_NAME]; + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to get dataset info: " + dataset_name); + } + + // Calculate total number of elements + int32 n_elements = 1; + std::vector dimensions; + for (int32 i = 0; i < rank; i++) { + n_elements *= dim_sizes[i]; + dimensions.push_back(static_cast(dim_sizes[i])); + } + + // Read data + std::vector data(n_elements); + int32 start[H4_MAX_VAR_DIMS] = {0}; + if (SDreaddata(sds_id, start, nullptr, dim_sizes, data.data()) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to read dataset: " + dataset_name); + } + + // Convert to float vector + std::vector float_data(data.begin(), data.end()); + + // Create PNXdata object + PNXdata pnx_data(H4DataType::kFLOAT32); + pnx_data.SetData(float_data); + pnx_data.SetDimensions(dimensions); + + // Read attributes + ReadDatasetAttributes(sds_id, pnx_data); + + // Store in data map + fDataMap[path] = pnx_data; + + SDendaccess(sds_id); +} + +//============================================================================= +// nxH4::PNeXus::ReadStringDataset +//============================================================================= +void nxH4::PNeXus::ReadStringDataset(int32 sd_id, const std::string& path) +{ + // Extract dataset name from path + std::vector components = SplitPath(path); + if (components.empty()) { + throw std::runtime_error("Invalid path: " + path); + } + std::string dataset_name = components.back(); + + // Use VGroup navigation to find the correct dataset reference + int32 sds_ref = -1; + if (components.size() > 1) { + // Try to resolve via VGroup hierarchy + sds_ref = FindDatasetRefByPath(path); + } + + int32 sds_idx = -1; + if (sds_ref != -1) { + // Find SDS index from reference + sds_idx = SDreftoindex(sd_id, sds_ref); + } + + // Fallback to name-based search if VGroup navigation failed + if (sds_idx == FAIL || sds_idx == -1) { + sds_idx = FindDatasetIndex(sd_id, dataset_name); + } + + int32 sds_id = SDselect(sd_id, sds_idx); + if (sds_id == FAIL) { + throw std::runtime_error("Failed to select dataset: " + dataset_name); + } + + // Get dataset info + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + char name[H4_MAX_NC_NAME]; + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to get dataset info: " + dataset_name); + } + + // Calculate total size for string + int32 n_elements = 1; + std::vector dimensions; + for (int32 i = 0; i < rank; i++) { + n_elements *= dim_sizes[i]; + dimensions.push_back(static_cast(dim_sizes[i])); + } + + // Read data as char array + std::vector data(n_elements + 1, '\0'); + int32 start[H4_MAX_VAR_DIMS] = {0}; + if (SDreaddata(sds_id, start, nullptr, dim_sizes, data.data()) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to read dataset: " + dataset_name); + } + + // Create string + std::vector string_data; + string_data.push_back(std::string(data.data())); + + // Create PNXdata object + PNXdata pnx_data(H4DataType::kCHAR8); + pnx_data.SetData(string_data); + pnx_data.SetDimensions(dimensions); + + // Read attributes + ReadDatasetAttributes(sds_id, pnx_data); + + // Store in data map + fDataMap[path] = pnx_data; + + SDendaccess(sds_id); +} + +//============================================================================= +// nxH4::PNeXus::ReadDatasetAttributes (template specializations) +//============================================================================= +template +void nxH4::PNeXus::ReadDatasetAttributes(int32 sds_id, PNXdata& data) +{ + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + char name[H4_MAX_NC_NAME]; + + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { + return; + } + + for (int32 i = 0; i < n_attrs; i++) { + char attr_name[H4_MAX_NC_NAME]; + int32 attr_type, attr_count; + + if (SDattrinfo(sds_id, i, attr_name, &attr_type, &attr_count) == FAIL) { + continue; + } + + // Read attribute based on type - store as primitives like h5nexus + if (attr_type == DFNT_INT32) { + if (attr_count == 1) { + // Single value - store as int directly + int32 value; + if (SDreadattr(sds_id, i, &value) != FAIL) { + data.AddAttribute(attr_name, static_cast(value)); + } + } else { + // Multiple values - store as vector + std::vector attr_data(attr_count); + if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) { + std::vector int_data(attr_data.begin(), attr_data.end()); + data.AddAttribute(attr_name, int_data); + } + } + } else if (attr_type == DFNT_FLOAT32) { + if (attr_count == 1) { + // Single value - store as float directly + float32 value; + if (SDreadattr(sds_id, i, &value) != FAIL) { + data.AddAttribute(attr_name, static_cast(value)); + } + } else { + // Multiple values - store as vector + std::vector attr_data(attr_count); + if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) { + std::vector float_data(attr_data.begin(), attr_data.end()); + data.AddAttribute(attr_name, float_data); + } + } + } else if (attr_type == DFNT_CHAR8 || attr_type == DFNT_UCHAR8) { + // String attributes - always store as string + std::vector attr_data(attr_count + 1, '\0'); + if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) { + data.AddAttribute(attr_name, std::string(attr_data.data())); + } + } + } +} + +//============================================================================= +// nxH4::PNeXus::Dump +//============================================================================= +void nxH4::PNeXus::Dump() +{ + int first_good_bin{0}; + + if (fIdfVersion == 1) { + std::cout << std::endl; + std::cout << "========================================" << std::endl; + std::cout << "NeXus File Dump" << std::endl; + std::cout << "========================================" << std::endl; + std::cout << "Filename: " << fFileName << std::endl; + std::cout << "HDF4 Version: " << fHdf4Version << std::endl; + std::cout << "NeXus Version: " << fNeXusVersion << std::endl; + std::cout << "IDF Version: " << fIdfVersion << std::endl; + std::cout << "----------------------------------------" << std::endl; + + std::cout << std::endl << "++++"; + std::cout << std::endl << "run"; + std::cout << std::endl << "----"; + std::cout << std::endl << " IDF_version: " << fIdfVersion; + if (fDataMap.find("/run/program_name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/program_name"]); + std::cout << std::endl << " program_name : " << str_data.GetData()[0]; + + // Check for attributes + if (str_data.HasAttribute("version")) { + try { + auto version = std::any_cast(str_data.GetAttribute("version")); + std::cout << " version : " << version; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast version attribute" << std::endl; + } + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast program_name data" << std::endl; + } + } else { + std::cout << std::endl << " program_name : n/a"; + } + + if (fDataMap.find("/run/number") != fDataMap.end()) { + try { + auto number = std::any_cast>(fDataMap["/run/number"]); + std::cout << std::endl << " number : " << number.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast number data" << std::endl; + } + } else { + std::cout << std::endl << " number : n/a"; + } + + if (fDataMap.find("/run/title") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/title"]); + std::cout << std::endl << " title : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast title data" << std::endl; + } + } else { + std::cout << std::endl << " title : n/a"; + } + + if (fDataMap.find("/run/notes") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/notes"]); + std::cout << std::endl << " notes : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast notes data" << std::endl; + } + } else { + std::cout << std::endl << " notes : n/a"; + } + + if (fDataMap.find("/run/analysis") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/analysis"]); + std::cout << std::endl << " analysis : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast analysis data" << std::endl; + } + } else { + std::cout << std::endl << " analysis : n/a"; + } + + if (fDataMap.find("/run/lab") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/lab"]); + std::cout << std::endl << " lab : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast lab data" << std::endl; + } + } else { + std::cout << std::endl << " lab : n/a"; + } + + if (fDataMap.find("/run/beamline") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/beamline"]); + std::cout << std::endl << " beamline : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast beamline data" << std::endl; + } + } else { + std::cout << std::endl << " beamline : n/a"; + } + + if (fDataMap.find("/run/start_time") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/start_time"]); + std::cout << std::endl << " start_time : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast start_time data" << std::endl; + } + } else { + std::cout << std::endl << " start_time : n/a"; + } + + if (fDataMap.find("/run/stop_time") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/stop_time"]); + std::cout << std::endl << " stop_time : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast stop_time data" << std::endl; + } + } else { + std::cout << std::endl << " stop_time : n/a"; + } + + if (fDataMap.find("/run/switching_state") != fDataMap.end()) { + try { + auto int_data = std::any_cast>(fDataMap["/run/switching_state"]); + std::cout << std::endl << " switching_state : " << int_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast switching_state data" << std::endl; + } + } else { + std::cout << std::endl << " switching_state : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " user"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/user/name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/user/name"]); + std::cout << std::endl << " name : '" << str_data.GetData()[0] << "'"; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + if (fDataMap.find("/run/user/experiment_number") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/user/experiment_number"]); + std::cout << std::endl << " experiment_number : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast experiment_number data" << std::endl; + } + } else { + std::cout << std::endl << " experiment_number : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " sample"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/sample/name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/sample/name"]); + std::cout << std::endl << " name : '" << str_data.GetData()[0] << "'"; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + if (fDataMap.find("/run/sample/temperature") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/sample/temperature"]); + std::cout << std::endl << " temperature : " << float_data.GetData()[0]; + + // Check for attributes + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast temperature data" << std::endl; + } + } else { + std::cout << std::endl << " temperature : n/a"; + } + + if (fDataMap.find("/run/sample/magnetic_field") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field"]); + std::cout << std::endl << " magnetic_field : " << float_data.GetData()[0]; + + // Check for attributes + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast magnetic_field data" << std::endl; + } + } else { + std::cout << std::endl << " magnetic_field : n/a"; + } + + if (fDataMap.find("/run/sample/magnetic_field_vector") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field_vector"]); + std::cout << std::endl << " magnetic_field_vector : " << float_data.GetData()[0]; + + // Check for attributes + if (float_data.HasAttribute("coordinate_system")) { + try { + auto coordinate_system = std::any_cast(float_data.GetAttribute("coordinate_system")); + std::cout << std::endl << " coordinate_system : " << coordinate_system << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast coordinate_system attribute" << std::endl; + } + } else { + std::cout << std::endl << " coordinate_system : n/a" << std::endl; + } + + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + + if (float_data.HasAttribute("available")) { + try { + auto available = std::any_cast(float_data.GetAttribute("available")); + std::cout << " available : " << available; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast available attribute" << std::endl; + } + } else { + std::cout << " available : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast magnetic_field_vector data" << std::endl; + } + } else { + std::cout << std::endl << " magnetic_field_vector : n/a"; + } + + if (fDataMap.find("/run/sample/environment") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/sample/environment"]); + std::cout << std::endl << " environment : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast environment data" << std::endl; + } + } else { + std::cout << std::endl << " environment : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " instrument"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/instrument/name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/instrument/name"]); + std::cout << std::endl << " name : '" << str_data.GetData()[0] << "'"; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " detector"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/instrument/detector/number") != fDataMap.end()) { + try { + auto int_data = std::any_cast>(fDataMap["/run/instrument/detector/number"]); + std::cout << std::endl << " number : " << int_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast number data" << std::endl; + } + } else { + std::cout << std::endl << " number : n/a"; + } + + if (fDataMap.find("/run/instrument/detector/deadtimes") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/instrument/detector/deadtimes"]); + auto data = float_data.GetData(); + unsigned int end{15}; + if (data.size() < end) + end = data.size(); + std::cout << std::endl << " deadtimes: "; + for (unsigned int i=0; i>(fDataMap["/run/instrument/beam/frames_good"]); + std::cout << std::endl << " good_frames : " << fg.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast good_frames data" << std::endl; + } + } else { + std::cout << std::endl << " good_frames : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " collimator"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/instrument/collimator/type") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/instrument/collimator/type"]); + std::cout << std::endl << " type : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast type data" << std::endl; + } + } else { + std::cout << std::endl << " type : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " histogram_data_1"; + std::cout << std::endl << "----"; + std::cout << std::endl << " counts:"; + std::cout << std::endl; + try { + auto counts_data = std::any_cast>(fDataMap["/run/histogram_data_1/counts"]); + + // Check for attributes + if (counts_data.HasAttribute("units")) { + try { + auto units = std::any_cast(counts_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + + if (counts_data.HasAttribute("signal")) { + try { + auto signal = std::any_cast(counts_data.GetAttribute("signal")); + std::cout << " signal : " << signal << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast signal attribute" << std::endl; + } + } else { + std::cout << " signal : n/a" << std::endl; + } + + int noOfHistos{0}; + if (counts_data.HasAttribute("number")) { + try { + noOfHistos = std::any_cast(counts_data.GetAttribute("number")); + std::cout << " number : " << noOfHistos << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast number attribute" << std::endl; + } + } else { + std::cout << " number : n/a" << std::endl; + } + + int histoLength{0}; + if (counts_data.HasAttribute("length")) { + try { + histoLength = std::any_cast(counts_data.GetAttribute("length")); + std::cout << " length : " << histoLength << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast length attribute" << std::endl; + } + } else { + std::cout << " length : n/a" << std::endl; + } + + if (counts_data.HasAttribute("t0_bin")) { + try { + auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); + std::cout << " t0_bin : " << t0_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast t0_bin attribute" << std::endl; + } + } else { + std::cout << " t0_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("first_good_bin")) { + try { + first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); + std::cout << " first_good_bin : " << first_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast first_good_bin attribute" << std::endl; + } + } else { + std::cout << " first_good_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("last_good_bin")) { + try { + auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); + std::cout << " last_good_bin : " << last_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast last_good_bin attribute" << std::endl; + } + } else { + std::cout << " last_good_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("offset")) { + try { + auto offset = std::any_cast(counts_data.GetAttribute("offset")); + std::cout << " offset : " << offset << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast offset attribute" << std::endl; + } + } else { + std::cout << " offset : n/a" << std::endl; + } + + // dump the first couple of counts of each detector + const auto& data = counts_data.GetData(); + std::cout << std::endl << " first couple of counts of each detector:"; + for (unsigned int i=0; i>(fDataMap["/run/histogram_data_1/resolution"]); + std::cout << std::endl << " resolution : " << int_data.GetData()[0]; + + if (int_data.HasAttribute("units")) { + try { + auto units = std::any_cast(int_data.GetAttribute("units")); + std::cout << " units : " << units; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << std::endl << " units : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast resolution data" << std::endl; + } + } else { + std::cout << std::endl << " resolution : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/time_zero") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/time_zero"]); + std::cout << std::endl << " time_zero : " << float_data.GetData()[0]; + + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << std::endl << " units : " << units; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << std::endl << " units : n/a"; + } + + if (float_data.HasAttribute("available")) { + try { + auto available = std::any_cast(float_data.GetAttribute("available")); + std::cout << std::endl << " available : " << available; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast available attribute"; + } + } else { + std::cout << std::endl << " available : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast time_zero data" << std::endl; + } + } else { + std::cout << std::endl << " time_zero : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/raw_time") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/raw_time"]); + std::cout << std::endl << " raw_time : " << float_data.GetData()[0]; + if (float_data.HasAttribute("axis")) { + try { + auto axis = std::any_cast(float_data.GetAttribute("axis")); + std::cout << std::endl << " axis : " << axis; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast axis attribute" << std::endl; + } + } else { + std::cout << std::endl << " axis : n/a"; + } + + if (float_data.HasAttribute("primary")) { + try { + auto primary = std::any_cast(float_data.GetAttribute("primary")); + std::cout << std::endl << " primary : " << primary; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast primary attribute" << std::endl; + } + } else { + std::cout << std::endl << " primary : n/a"; + } + + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << std::endl << " units : " << units; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << std::endl << " units : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast raw_time data" << std::endl; + } + } else { + std::cout << std::endl << " raw_time : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/corrected_time") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/corrected_time"]); + std::cout << std::endl << " corrected_time : " << float_data.GetData()[0]; + if (float_data.HasAttribute("axis")) { + try { + auto axis = std::any_cast(float_data.GetAttribute("axis")); + std::cout << std::endl << " axis : " << axis; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast axis attribute" << std::endl; + } + } else { + std::cout << std::endl << " axis : n/a"; + } + + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << std::endl << " units : " << units; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << std::endl << " units : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast raw_time data" << std::endl; + } + } else { + std::cout << std::endl << " corrected_time : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/grouping") != fDataMap.end()) { + try { + auto int_data = std::any_cast>(fDataMap["/run/histogram_data_1/grouping"]); + auto data = int_data.GetData(); + unsigned int end{15}; + if (data.size() < end) + end = data.size(); + std::cout << std::endl << " grouping : "; + for (unsigned int i=0; i(int_data.GetAttribute("available")); + std::cout << std::endl << " available : " << available; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast available attribute" << std::endl; + } + } else { + std::cout << std::endl << " available : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast grouping data" << std::endl; + } + } else { + std::cout << std::endl << " grouping : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/alpha") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/alpha"]); + auto data = float_data.GetData(); + unsigned int end{15}; + if (data.size() < end) + end = data.size(); + std::cout << std::endl << " alpha : "; + for (unsigned int i=0; i(float_data.GetAttribute("available")); + std::cout << std::endl << " available : " << available; + } catch (const std::bad_any_cast& e) { + std::cerr << "**ERROR**: Failed to cast available attribute" << std::endl; + } + } else { + std::cout << std::endl << " alpha : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "**ERROR**: Failed to cast alpha data" << std::endl; + } + } else { + std::cout << std::endl << " available : n/a"; + } + + std::cout << std::endl; + std::cout << "========================================" << std::endl; + std::cout << std::endl; + } else { // IDF Version 2 + std::cout << std::endl; + std::cout << std::endl << "hdf5-NeXus file content of file:' " << fFileName << "'"; + std::cout << std::endl << "****"; + std::cout << std::endl << "Top Level Attributes:"; + std::cout << std::endl << " HDF4 Version : " << fHdf4Version; + std::cout << std::endl << " NeXus Version: " << fNeXusVersion; + std::cout << std::endl << " file_name : " << fFileNameNxs; + std::cout << std::endl << " file_time : " << fFileTimeNxs; + std::cout << std::endl << "++++"; + std::cout << std::endl << "raw_data_1"; + std::cout << std::endl << "----"; + std::cout << std::endl << " IDF_version: " << fIdfVersion; + if (fDataMap.find("/raw_data_1/beamline") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/beamline"]); + std::cout << std::endl << " beamline : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; + } + } else { + std::cout << std::endl << " beamline : n/a"; + } + + if (fDataMap.find("/raw_data_1/definition") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/definition"]); + std::cout << std::endl << " definition : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast definition data" << std::endl; + } + } else { + std::cout << std::endl << " definition : n/a"; + } + + if (fDataMap.find("/raw_data_1/run_number") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/run_number"]); + std::cout << std::endl << " run_number : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast run_number data" << std::endl; + } + } else { + std::cout << std::endl << " run_number : n/a"; + } + + if (fDataMap.find("/raw_data_1/title") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/title"]); + std::cout << std::endl << " title : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast title data" << std::endl; + } + } else { + std::cout << std::endl << " title : n/a"; + } + + if (fDataMap.find("/raw_data_1/start_time") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/start_time"]); + std::cout << std::endl << " start_time : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast start_time data" << std::endl; + } + } else { + std::cout << std::endl << " start_time : n/a"; + } + + if (fDataMap.find("/raw_data_1/end_time") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/end_time"]); + std::cout << std::endl << " end_time : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast end_time data" << std::endl; + } + } else { + std::cout << std::endl << " end_time : n/a"; + } + + if (fDataMap.find("/raw_data_1/good_frames") != fDataMap.end()) { + try { + auto good_frames = std::any_cast>(fDataMap["/raw_data_1/good_frames"]); + std::cout << std::endl << " good_frames: " << good_frames.GetData()[0]; + } catch(const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast good_frames data" << std::endl; + } + } else { + std::cout << std::endl << " good_frames: n/a"; + } + + if (fDataMap.find("/raw_data_1/experiment_identifier") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/experiment_identifier"]); + std::cout << std::endl << " experiment_identifier: " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast experiment_identifier data" << std::endl; + } + } else { + std::cout << std::endl << " experiment_identifier: n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " instrument"; + if (fDataMap.find("/raw_data_1/instrument/name") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/name"]); + std::cout << std::endl << " name : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " source"; + if (fDataMap.find("/raw_data_1/instrument/source/name") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/name"]); + std::cout << std::endl << " name : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/source/name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + if (fDataMap.find("/raw_data_1/instrument/source/type") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/type"]); + std::cout << std::endl << " type : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/source/type data" << std::endl; + } + } else { + std::cout << std::endl << " type : n/a"; + } + + if (fDataMap.find("/raw_data_1/instrument/source/probe") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/probe"]); + std::cout << std::endl << " probe : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/source/probe data" << std::endl; + } + } else { + std::cout << std::endl << " probe : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " detector_1"; + std::cout << std::endl << " counts:"; + std::cout << std::endl; + try { + auto counts_data = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/counts"]); + auto dims = counts_data.GetDimensions(); + std::cout << " counts dimensions: " << dims[0] << " x " + << dims[1] << " x " << dims[2] << std::endl; + std::cout << " total elements: " << counts_data.GetNumElements() << std::endl; + + // Check for attributes + if (counts_data.HasAttribute("signal")) { + try { + auto signal = std::any_cast(counts_data.GetAttribute("signal")); + std::cout << " signal : " << signal << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast signal attribute" << std::endl; + } + } else { + std::cout << std::endl << " signal : n/a"; + } + + if (counts_data.HasAttribute("axes")) { + try { + auto axes = std::any_cast(counts_data.GetAttribute("axes")); + std::cout << " axes : " << axes << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast axes attribute" << std::endl; + } + } else { + std::cout << std::endl << " axes : n/a"; + } + + if (counts_data.HasAttribute("long_name")) { + try { + auto long_name = std::any_cast(counts_data.GetAttribute("long_name")); + std::cout << " long_name : " << long_name << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast long_name attribute" << std::endl; + } + } else { + std::cout << std::endl << " long_name : n/a"; + } + + if (counts_data.HasAttribute("t0_bin")) { + try { + auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); + std::cout << " t0_bin : " << t0_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; + } + } else { + std::cout << std::endl << " t0_bin : n/a"; + } + + if (counts_data.HasAttribute("first_good_bin")) { + try { + first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); + std::cout << " first_good_bin : " << first_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; + } + } else { + std::cout << std::endl << " first_good_bin : n/a"; + } + + if (counts_data.HasAttribute("last_good_bin")) { + try { + auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); + std::cout << " last_good_bin : " << last_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; + } + } else { + std::cout << std::endl << " last_good_bin : n/a"; + } + + if (fDataMap.find("/raw_data_1/instrument/detector_1/resolution") != fDataMap.end()) { + try { + auto ivalData = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/resolution"]); + std::cout << " resolution : " << ivalData.GetData()[0]; + if (ivalData.HasAttribute("units")) { + try { + auto units = std::any_cast(ivalData.GetAttribute("units")); + std::cout << " " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << std::endl << " unit : n/a" << std::endl; + } + + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast resolution attribute" << std::endl; + } + } else { + std::cout << std::endl << " resolution : n/a"; + } + + // dump the first couple of counts of each detector + const auto& data = counts_data.GetData(); + std::cout << std::endl << " first couple of counts of each detector:"; + for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/raw_time"]); + const auto& data = raw_time.GetData(); + + // Check for attributes + if (raw_time.HasAttribute("units")) { + try { + auto units = std::any_cast(raw_time.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << std::endl << " units : n/a" << std::endl; + } + + // dump the first couple of raw_times + std::cout << " "; + for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/spectrum_index"]); + const auto& data = spectrum_index.GetData(); + + // dump the first couple of raw_times + std::cout << " "; + for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/dead_time"]); + const auto& data = dead_time.GetData(); + + // dump the first couple of raw_times + std::cout << " "; + for (unsigned int i=0; i sdsRefMap; + + for (const auto& [path, data_any] : fDataMap) { + int32 sds_ref = -1; + + // Try to write as int dataset + try { + auto data = std::any_cast>(data_any); + sds_ref = WriteIntDataset(sd_id, path, data); + } catch (...) {} + + if (sds_ref == -1) { + // Try to write as float dataset + try { + auto data = std::any_cast>(data_any); + sds_ref = WriteFloatDataset(sd_id, path, data); + } catch (...) {} + } + + if (sds_ref == -1) { + // Try to write as string dataset + try { + auto data = std::any_cast>(data_any); + sds_ref = WriteStringDataset(sd_id, path, data); + } catch (...) {} + } + + if (sds_ref != -1) { + sdsRefMap[path] = sds_ref; + } + } + + // Close SD interface before building Vgroup hierarchy + SDend(sd_id); + + // Now open the file with Hopen/Vstart to build the Vgroup hierarchy + int32 file_id = Hopen(filename.c_str(), DFACC_WRITE, 0); + if (file_id == FAIL) { + std::cerr << "**ERROR** Failed to reopen HDF4 file for Vgroup creation: " << filename << std::endl; + return 1; + } + + if (Vstart(file_id) == FAIL) { + std::cerr << "**ERROR** Failed to initialize Vgroup interface" << std::endl; + Hclose(file_id); + return 1; + } + + // Build Vgroup hierarchy and link SDS datasets into the correct groups + std::map vgroupCache; // path -> vgroup ref + + for (const auto& [path, sds_ref] : sdsRefMap) { + // Extract parent path + size_t lastSlash = path.find_last_of('/'); + if (lastSlash == std::string::npos || lastSlash == 0) { + // Dataset is at root level, no Vgroup needed + continue; + } + std::string parentPath = path.substr(0, lastSlash); + + // Create the Vgroup hierarchy for the parent path + int32 parent_vg_ref = CreateVGroupHierarchy(file_id, parentPath, vgroupCache); + if (parent_vg_ref == -1) { + std::cerr << "**ERROR** Failed to create Vgroup hierarchy for: " << parentPath << std::endl; + continue; + } + + // Attach to parent Vgroup and add the SDS dataset + int32 parent_vg_id = Vattach(file_id, parent_vg_ref, "w"); + if (parent_vg_id != FAIL) { + Vaddtagref(parent_vg_id, DFTAG_NDG, sds_ref); + Vdetach(parent_vg_id); + } + } + + // Detach all cached Vgroups (they were left attached for reuse) + // No — Vgroups were detached after creation in CreateVGroupHierarchy. + // We only re-attach briefly above to add tag/refs. + + Vend(file_id); + Hclose(file_id); + + return 0; +} + +//============================================================================= +// nxH4::PNeXus::WriteFileAttributes +//============================================================================= +void nxH4::PNeXus::WriteFileAttributes(int32 sd_id) +{ + // Write HDF4 version + if (!fHdf4Version.empty()) { + SDsetattr(sd_id, "HDF4_version", DFNT_CHAR8, + fHdf4Version.length(), fHdf4Version.c_str()); + } + + // Write NeXus version + if (!fNeXusVersion.empty()) { + SDsetattr(sd_id, "NeXus_version", DFNT_CHAR8, + fNeXusVersion.length(), fNeXusVersion.c_str()); + } + + // Write file name + if (!fFileNameNxs.empty()) { + SDsetattr(sd_id, "file_name", DFNT_CHAR8, + fFileNameNxs.length(), fFileNameNxs.c_str()); + } + + // Write file time + if (!fFileTimeNxs.empty()) { + SDsetattr(sd_id, "file_time", DFNT_CHAR8, + fFileTimeNxs.length(), fFileTimeNxs.c_str()); + } + + // Write root level attributes from fGroupAttributes + if (fGroupAttributes.find("/") != fGroupAttributes.end()) { + const auto& attrs = fGroupAttributes.at("/"); + for (const auto& [attr_name, attr_value] : attrs) { + // Handle different attribute types + try { + auto str_attr = std::any_cast(attr_value); + SDsetattr(sd_id, attr_name.c_str(), DFNT_CHAR8, + str_attr.length(), str_attr.c_str()); + } catch (...) { + // Try other types if needed + } + } + } +} + +//============================================================================= +// nxH4::PNeXus::WriteIntDataset +//============================================================================= +int32 nxH4::PNeXus::WriteIntDataset(int32 sd_id, const std::string& path, + const PNXdata& data) +{ + // Extract dataset name from path + std::vector components = SplitPath(path); + if (components.empty()) { + throw std::runtime_error("Invalid path: " + path); + } + std::string dataset_name = components.back(); + + const auto& dims = data.GetDimensions(); + int32 rank = static_cast(dims.size()); + std::vector dim_sizes(rank); + for (int32 i = 0; i < rank; i++) { + dim_sizes[i] = static_cast(dims[i]); + } + + // Create dataset + int32 sds_id = SDcreate(sd_id, dataset_name.c_str(), DFNT_INT32, rank, dim_sizes.data()); + if (sds_id == FAIL) { + throw std::runtime_error("Failed to create dataset: " + dataset_name); + } + + // Convert data to int32 + const auto& int_data = data.GetData(); + std::vector data32(int_data.begin(), int_data.end()); + + // Write data + int32 start[H4_MAX_VAR_DIMS] = {0}; + if (SDwritedata(sds_id, start, nullptr, dim_sizes.data(), data32.data()) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to write dataset: " + dataset_name); + } + + // Write attributes + WriteDatasetAttributes(sds_id, data); + + // Get the SDS reference for Vgroup linking + int32 sds_ref = SDidtoref(sds_id); + + SDendaccess(sds_id); + + return sds_ref; +} + +//============================================================================= +// nxH4::PNeXus::WriteFloatDataset +//============================================================================= +int32 nxH4::PNeXus::WriteFloatDataset(int32 sd_id, const std::string& path, + const PNXdata& data) +{ + // Extract dataset name from path + std::vector components = SplitPath(path); + if (components.empty()) { + throw std::runtime_error("Invalid path: " + path); + } + std::string dataset_name = components.back(); + + const auto& dims = data.GetDimensions(); + int32 rank = static_cast(dims.size()); + std::vector dim_sizes(rank); + for (int32 i = 0; i < rank; i++) { + dim_sizes[i] = static_cast(dims[i]); + } + + // Create dataset + int32 sds_id = SDcreate(sd_id, dataset_name.c_str(), DFNT_FLOAT32, rank, dim_sizes.data()); + if (sds_id == FAIL) { + throw std::runtime_error("Failed to create dataset: " + dataset_name); + } + + // Convert data to float32 + const auto& float_data = data.GetData(); + std::vector data32(float_data.begin(), float_data.end()); + + // Write data + int32 start[H4_MAX_VAR_DIMS] = {0}; + if (SDwritedata(sds_id, start, nullptr, dim_sizes.data(), data32.data()) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to write dataset: " + dataset_name); + } + + // Write attributes + WriteDatasetAttributes(sds_id, data); + + // Get the SDS reference for Vgroup linking + int32 sds_ref = SDidtoref(sds_id); + + SDendaccess(sds_id); + + return sds_ref; +} + +//============================================================================= +// nxH4::PNeXus::WriteStringDataset +//============================================================================= +int32 nxH4::PNeXus::WriteStringDataset(int32 sd_id, const std::string& path, + const PNXdata& data) +{ + // Extract dataset name from path + std::vector components = SplitPath(path); + if (components.empty()) { + throw std::runtime_error("Invalid path: " + path); + } + std::string dataset_name = components.back(); + + const auto& string_data = data.GetData(); + if (string_data.empty()) { + return -1; + } + + std::string str = string_data[0]; + int32 dims[1] = {static_cast(str.length())}; + + // Create dataset + int32 sds_id = SDcreate(sd_id, dataset_name.c_str(), DFNT_CHAR8, 1, dims); + if (sds_id == FAIL) { + throw std::runtime_error("Failed to create dataset: " + dataset_name); + } + + // Write data - HDF4 requires non-const pointer + std::vector str_buffer(str.begin(), str.end()); + int32 start[1] = {0}; + if (SDwritedata(sds_id, start, nullptr, dims, str_buffer.data()) == FAIL) { + SDendaccess(sds_id); + throw std::runtime_error("Failed to write dataset: " + dataset_name); + } + + // Write attributes + WriteDatasetAttributes(sds_id, data); + + // Get the SDS reference for Vgroup linking + int32 sds_ref = SDidtoref(sds_id); + + SDendaccess(sds_id); + + return sds_ref; +} + +//============================================================================= +// nxH4::PNeXus::WriteDatasetAttributes (template specializations) +//============================================================================= +template +void nxH4::PNeXus::WriteDatasetAttributes(int32 sds_id, const PNXdata& data) +{ + const auto& attributes = data.GetAttributes(); + + for (const auto& [attr_name, attr_value] : attributes) { + // Try different attribute types - primitives first (like h5nexus) + + // Try scalar int + try { + int32 value = std::any_cast(attr_value); + SDsetattr(sds_id, attr_name.c_str(), DFNT_INT32, 1, &value); + continue; + } catch (...) {} + + // Try scalar float + try { + float32 value = std::any_cast(attr_value); + SDsetattr(sds_id, attr_name.c_str(), DFNT_FLOAT32, 1, &value); + continue; + } catch (...) {} + + // Try string + try { + std::string str = std::any_cast(attr_value); + SDsetattr(sds_id, attr_name.c_str(), DFNT_CHAR8, str.length(), str.c_str()); + continue; + } catch (...) {} + + // Try vector for multi-element attributes + try { + std::vector int_vec = std::any_cast>(attr_value); + std::vector data32(int_vec.begin(), int_vec.end()); + SDsetattr(sds_id, attr_name.c_str(), DFNT_INT32, data32.size(), data32.data()); + continue; + } catch (...) {} + + // Try vector for multi-element attributes + try { + std::vector float_vec = std::any_cast>(attr_value); + std::vector data32(float_vec.begin(), float_vec.end()); + SDsetattr(sds_id, attr_name.c_str(), DFNT_FLOAT32, data32.size(), data32.data()); + continue; + } catch (...) {} + } +} + +//============================================================================= +// nxH4::PNeXus::WriteVGroupAttributes +//============================================================================= +void nxH4::PNeXus::WriteVGroupAttributes(int32 vgroup_id, + const std::map& attributes) +{ + for (const auto& [attr_name, attr_value] : attributes) { + // Try string + try { + std::string str = std::any_cast(attr_value); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_CHAR8, + static_cast(str.length()), str.c_str()); + continue; + } catch (...) {} + + // Try scalar int + try { + int32 value = std::any_cast(attr_value); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_INT32, 1, &value); + continue; + } catch (...) {} + + // Try scalar float + try { + float32 value = std::any_cast(attr_value); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_FLOAT32, 1, &value); + continue; + } catch (...) {} + + // Try vector + try { + std::vector int_vec = std::any_cast>(attr_value); + std::vector data32(int_vec.begin(), int_vec.end()); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_INT32, + static_cast(data32.size()), data32.data()); + continue; + } catch (...) {} + + // Try vector + try { + std::vector float_vec = std::any_cast>(attr_value); + std::vector data32(float_vec.begin(), float_vec.end()); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_FLOAT32, + static_cast(data32.size()), data32.data()); + continue; + } catch (...) {} + } +} + +//============================================================================= +// nxH4::PNeXus::CreateVGroupHierarchy +//============================================================================= +int32 nxH4::PNeXus::CreateVGroupHierarchy(int32 file_id, const std::string& path, + std::map& vgroupCache) +{ + std::vector components = SplitPath(path); + if (components.empty()) { + return -1; + } + + std::string currentPath; + int32 parent_vg_ref = -1; + + for (const auto& component : components) { + if (component.empty()) continue; + + currentPath += "/" + component; + + // Check if this Vgroup was already created + auto it = vgroupCache.find(currentPath); + if (it != vgroupCache.end()) { + parent_vg_ref = it->second; + continue; + } + + // Create a new Vgroup + int32 vg_id = Vattach(file_id, -1, "w"); + if (vg_id == FAIL) { + std::cerr << "**ERROR** Failed to create Vgroup for: " << currentPath << std::endl; + return -1; + } + + Vsetname(vg_id, component.c_str()); + + // Write group attributes if any exist for this path + auto attrIt = fGroupAttributes.find(currentPath); + if (attrIt != fGroupAttributes.end()) { + // Check for NX_class attribute and set it as Vgroup class + auto classIt = attrIt->second.find("NX_class"); + if (classIt != attrIt->second.end()) { + try { + std::string nxClass = std::any_cast(classIt->second); + Vsetclass(vg_id, nxClass.c_str()); + } catch (...) {} + } + WriteVGroupAttributes(vg_id, attrIt->second); + } + + // Get the reference of the newly created Vgroup + int32 vg_ref = VQueryref(vg_id); + + // If there is a parent Vgroup, add this one as a child + if (parent_vg_ref != -1) { + int32 parent_vg_id = Vattach(file_id, parent_vg_ref, "w"); + if (parent_vg_id != FAIL) { + Vinsert(parent_vg_id, vg_id); + Vdetach(parent_vg_id); + } + } + + Vdetach(vg_id); + + // Cache the reference for this path + vgroupCache[currentPath] = vg_ref; + parent_vg_ref = vg_ref; + } + + return parent_vg_ref; +} + +//============================================================================= +// Group attribute methods - manage attributes associated with HDF4 groups +//============================================================================= + +/** + * @brief Add or update an attribute for a group + * + * Stores an attribute value in the internal group attributes map. These + * attributes will be written when WriteNexusFile() is called. The root + * group "/" can be used for file-level attributes. + * + * @param groupPath HDF4 path of the group (e.g., "/raw_data_1") + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added successfully + */ +bool nxH4::PNeXus::AddGroupAttribute(const std::string& groupPath, + const std::string& attrName, + const std::any& attrValue) +{ + fGroupAttributes[groupPath][attrName] = attrValue; + return true; +} + +bool nxH4::PNeXus::RemoveGroupAttribute(const std::string& groupPath, + const std::string& attrName) +{ + auto it = fGroupAttributes.find(groupPath); + if (it != fGroupAttributes.end()) { + auto attr_it = it->second.find(attrName); + if (attr_it != it->second.end()) { + it->second.erase(attr_it); + return true; + } + } + return false; +} + +bool nxH4::PNeXus::HasGroupAttribute(const std::string& groupPath, + const std::string& attrName) const +{ + auto it = fGroupAttributes.find(groupPath); + if (it != fGroupAttributes.end()) { + return it->second.find(attrName) != it->second.end(); + } + return false; +} + +std::any nxH4::PNeXus::GetGroupAttribute(const std::string& groupPath, + const std::string& attrName) const +{ + return fGroupAttributes.at(groupPath).at(attrName); +} + +const std::map& nxH4::PNeXus::GetGroupAttributes( + const std::string& groupPath) const +{ + static std::map empty_map; + auto it = fGroupAttributes.find(groupPath); + if (it != fGroupAttributes.end()) { + return it->second; + } + return empty_map; +} + +bool nxH4::PNeXus::ClearGroupAttributes(const std::string& groupPath) +{ + auto it = fGroupAttributes.find(groupPath); + if (it != fGroupAttributes.end()) { + it->second.clear(); + return true; + } + return false; +} + +bool nxH4::PNeXus::AddRootAttribute(const std::string& attrName, const std::any& attrValue) +{ + return AddGroupAttribute("/", attrName, attrValue); +} + +//============================================================================= +// Helper methods +//============================================================================= +bool nxH4::PNeXus::CaseInsensitiveEquals(const std::string& a, const std::string& b) +{ + if (a.length() != b.length()) { + return false; + } + return std::equal(a.begin(), a.end(), b.begin(), + [](char a, char b) { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); + }); +} + +std::vector nxH4::PNeXus::SplitPath(const std::string& path) +{ + std::vector components; + std::string current; + + for (char c : path) { + if (c == '/') { + if (!current.empty()) { + components.push_back(current); + current.clear(); + } + } else { + current += c; + } + } + + if (!current.empty()) { + components.push_back(current); + } + + return components; +} + +std::string nxH4::PNeXus::FindAttributeName(int32 sd_id, const std::string& requestedName) +{ + int32 n_datasets, n_attrs; + if (SDfileinfo(sd_id, &n_datasets, &n_attrs) == FAIL) { + throw std::runtime_error("Failed to get file info"); + } + + char attr_name[H4_MAX_NC_NAME]; + int32 attr_type, attr_count; + + for (int32 i = 0; i < n_attrs; i++) { + if (SDattrinfo(sd_id, i, attr_name, &attr_type, &attr_count) != FAIL) { + if (CaseInsensitiveEquals(attr_name, requestedName)) { + return std::string(attr_name); + } + } + } + + throw std::runtime_error("Attribute not found: " + requestedName); +} + +int32 nxH4::PNeXus::FindDatasetIndex(int32 sd_id, const std::string& requestedName) +{ + // First try exact match + int32 idx = SDnametoindex(sd_id, requestedName.c_str()); + if (idx != FAIL) { + return idx; + } + + // Try case-insensitive search + int32 n_datasets, n_attrs; + if (SDfileinfo(sd_id, &n_datasets, &n_attrs) == FAIL) { + throw std::runtime_error("Failed to get file info"); + } + + for (int32 i = 0; i < n_datasets; i++) { + int32 sds_id = SDselect(sd_id, i); + if (sds_id != FAIL) { + char name[H4_MAX_NC_NAME]; + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_ds_attrs; + if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_ds_attrs) != FAIL) { + if (CaseInsensitiveEquals(name, requestedName)) { + SDendaccess(sds_id); + return i; + } + } + SDendaccess(sds_id); + } + } + + throw std::runtime_error("Dataset not found: " + requestedName); +} + +int32 nxH4::PNeXus::FindDatasetRefByPath(const std::string& path) +{ + // Navigate VGroup hierarchy to find the dataset reference + // Path format: /group1/group2/.../dataset_name + + std::vector components = SplitPath(path); + if (components.empty()) { + return -1; + } + + // Open file with V interface + if (Vstart(fFileId) == FAIL) { + return -1; + } + + int32 result_ref = -1; + int32 current_vg_ref = -1; + + // Find root VGroup (first component after /) + // For IDF version 1, root is typically "run" + if (components.size() >= 1) { + // Get all lone vgroups + int32 n_vgroups = Vlone(fFileId, nullptr, 0); + if (n_vgroups > 0) { + std::vector vgroup_refs(n_vgroups); + Vlone(fFileId, vgroup_refs.data(), n_vgroups); + + // Find the root VGroup + for (int32 i = 0; i < n_vgroups; i++) { + int32 vg_id = Vattach(fFileId, vgroup_refs[i], "r"); + if (vg_id != FAIL) { + char vg_name[VGNAMELENMAX]; + if (Vgetname(vg_id, vg_name) != FAIL) { + if (CaseInsensitiveEquals(vg_name, components[0])) { + current_vg_ref = vgroup_refs[i]; + Vdetach(vg_id); + break; + } + } + Vdetach(vg_id); + } + } + } + } + + // Navigate through intermediate VGroups + for (size_t comp_idx = 1; comp_idx < components.size() - 1; comp_idx++) { + if (current_vg_ref == -1) break; + + int32 vg_id = Vattach(fFileId, current_vg_ref, "r"); + if (vg_id == FAIL) { + current_vg_ref = -1; + break; + } + + int32 n_entries = Vntagrefs(vg_id); + bool found = false; + + for (int32 i = 0; i < n_entries; i++) { + int32 tag, ref; + if (Vgettagref(vg_id, i, &tag, &ref) != FAIL) { + // Check if it's a VGroup + if (tag == DFTAG_VG) { + int32 child_vg_id = Vattach(fFileId, ref, "r"); + if (child_vg_id != FAIL) { + char child_name[VGNAMELENMAX]; + if (Vgetname(child_vg_id, child_name) != FAIL) { + if (CaseInsensitiveEquals(child_name, components[comp_idx])) { + current_vg_ref = ref; + found = true; + Vdetach(child_vg_id); + break; + } + } + Vdetach(child_vg_id); + } + } + } + } + + Vdetach(vg_id); + + if (!found) { + current_vg_ref = -1; + break; + } + } + + // Find the dataset in the final VGroup + if (current_vg_ref != -1 && components.size() >= 1) { + int32 vg_id = Vattach(fFileId, current_vg_ref, "r"); + if (vg_id != FAIL) { + int32 n_entries = Vntagrefs(vg_id); + std::string target_name = components.back(); + + for (int32 i = 0; i < n_entries; i++) { + int32 tag, ref; + if (Vgettagref(vg_id, i, &tag, &ref) != FAIL) { + // Check if it's a Numeric Data (SDS) + if (tag == DFTAG_NDG) { + // Try to get the name by converting ref to index + int32 sds_idx = SDreftoindex(fSdId, ref); + if (sds_idx != FAIL) { + int32 sds_id = SDselect(fSdId, sds_idx); + if (sds_id != FAIL) { + char ds_name[H4_MAX_NC_NAME]; + int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; + if (SDgetinfo(sds_id, ds_name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { + if (CaseInsensitiveEquals(ds_name, target_name)) { + result_ref = ref; + SDendaccess(sds_id); + break; + } + } + SDendaccess(sds_id); + } + } + } + } + } + + Vdetach(vg_id); + } + } + + Vend(fFileId); + return result_ref; +} + +/** + * @brief Convert HDF4 numeric type constant to H4DataType enum + * + * Maps HDF4 DFNT_* type constants to the library's H4DataType enum for + * type-safe handling of dataset types. + * + * @param hdf4_type HDF4 numeric type constant (e.g., DFNT_INT32) + * @return Corresponding H4DataType enum value + * + * @note Unknown types default to H4DataType::INT32 + */ +nxH4::H4DataType nxH4::PNeXus::convertHdf4Type(int32 hdf4_type) +{ + switch (hdf4_type) { + case DFNT_INT32: return H4DataType::kINT32; + case DFNT_FLOAT32: return H4DataType::kFLOAT32; + case DFNT_FLOAT64: return H4DataType::kFLOAT64; + case DFNT_CHAR8: return H4DataType::kCHAR8; + case DFNT_UINT32: return H4DataType::kUINT32; + case DFNT_INT16: return H4DataType::kINT16; + case DFNT_UINT16: return H4DataType::kUINT16; + case DFNT_INT8: return H4DataType::kINT8; + case DFNT_UINT8: return H4DataType::kUINT8; + default: return H4DataType::kINT32; + } +} + +/** + * @brief Convert H4DataType enum to HDF4 numeric type constant + * + * Maps the library's H4DataType enum to HDF4 DFNT_* type constants + * for writing datasets to HDF4 files. + * + * @param dataType H4DataType enum value + * @return Corresponding HDF4 numeric type constant (e.g., DFNT_INT32) + * + * @note Unknown types default to DFNT_INT32 + */ +int32 nxH4::PNeXus::convertToHdf4Type(H4DataType dataType) +{ + switch (dataType) { + case H4DataType::kINT32: return DFNT_INT32; + case H4DataType::kFLOAT32: return DFNT_FLOAT32; + case H4DataType::kFLOAT64: return DFNT_FLOAT64; + case H4DataType::kCHAR8: return DFNT_CHAR8; + case H4DataType::kUINT32: return DFNT_UINT32; + case H4DataType::kINT16: return DFNT_INT16; + case H4DataType::kUINT16: return DFNT_UINT16; + case H4DataType::kINT8: return DFNT_INT8; + case H4DataType::kUINT8: return DFNT_UINT8; + default: return DFNT_INT32; + } +} +#endif // HAVE_HDF4 + +// ** HDF5 ******************************************************************** + +//============================================================================= +// nxH5::PNeXusDeadTime Constructor +//============================================================================= +nxH5::PNeXusDeadTime::PNeXusDeadTime(const PNeXus *nxs, bool debug) : fDebug(debug) +{ + std::map dataMap = nxs->GetDataMap(); + + int idfVersion = nxs->GetIdfVersion(); + if ((idfVersion != 1) && (idfVersion != 2)) { + std::stringstream err; + err << "**ERROR** Found unsupported IDF version '" << idfVersion << "'"; + std::cout << err.str() << std::endl; + fValid = false; + return; + } + + if (idfVersion == 1) { + // not yet implemented + } else { // idfVersion == 2 + // get counts + if (dataMap.find("/raw_data_1/instrument/detector_1/counts") != dataMap.end()) { + auto counts_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/counts"]); + const auto& counts = counts_data.GetData(); + fCounts = counts; + auto dims = counts_data.GetDimensions(); + if (fDebug) { + std::cout << " counts dimensions: " << dims[0] << " x " + << dims[1] << " x " << dims[2] << std::endl; + } + fDims = dims; + + // get t0 + if (counts_data.HasAttribute("t0_bin")) { + try { + auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); + fT0Bin = t0_bin; + if (fDebug) + std::cout << " fT0Bin: " << fT0Bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; + } + } + // get fgb + if (counts_data.HasAttribute("first_good_bin")) { + try { + auto first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); + fFgbBin = first_good_bin; + if (fDebug) + std::cout << " fFgbBin: " << fFgbBin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; + } + } + // get lgb + if (counts_data.HasAttribute("last_good_bin")) { + try { + auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); + fLgbBin = last_good_bin; + if (fDebug) + std::cout << " fLgbBin: " << fLgbBin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; + } + } + } + + // get dead time parameters + if (dataMap.find("/raw_data_1/instrument/detector_1/dead_time") != dataMap.end()) { + auto dead_time = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/dead_time"]); + const auto& dt_data = dead_time.GetData(); + fDeadTime = dt_data; + } + fDeadTimeEstimated.resize(fDeadTime.size()); + + // get good_frames + if (dataMap.find("/raw_data_1/good_frames") != dataMap.end()) { + auto intData = std::any_cast>(dataMap["/raw_data_1/good_frames"]); + const auto& ival = intData.GetData(); + fGoodFrames = ival[0]; + if (fDebug) + std::cout << " good_frames: " << fGoodFrames << std::endl; + } + // get resolution with units + if (dataMap.find("/raw_data_1/instrument/detector_1/resolution") != dataMap.end()) { + auto int_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/resolution"]); + const auto& ival = int_data.GetData(); + float scale{1.0}; + if (int_data.HasAttribute("units")) { + try { + auto units = std::any_cast(int_data.GetAttribute("units")); + if (units == "picoseconds") + scale = 1.0e-6; + else if (units == "nanoseconds") + scale = 1.0e-3; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } + fTimeResolution = ival[0] * scale; + if (fDebug) + std::cout << " time_resolution: " << fTimeResolution << " (us)" << std::endl; + } + } +} + +//============================================================================= +// nxH5::PNeXusDeadTime::operator() +//============================================================================= +/** + * @brief Calculate the negative log-likelihood for dead time correction + * + * This function implements the objective function for ROOT Minuit2 minimization. + * It calculates the negative log-likelihood comparing observed counts to + * the expected count rate model with dead time correction. + * + * **Model:** + * The expected count rate includes: + * - Exponential muon decay: N(t) = N0 * exp(-t/tau_mu) + N_bkg + * - Dead time correction: N_corrected = N(t) / (1 + N(t) * dtc) + * + * **Parameters:** + * - par[0] (dtc): Dead time correction parameter + * - par[1] (N0): Initial count rate amplitude + * - par[2] (N_bkg): Background count rate + * + * @param par Vector of parameters [dtc, N0, N_bkg] + * @return Negative log-likelihood value to be minimized + * + * @note Uses the muon lifetime tau_mu = 2.1969811 microseconds + */ +double nxH5::PNeXusDeadTime::operator()(const std::vector &par) const +{ + // par[0]: dtc, par[1]: N0, par[2]: N_bkg + + double nt=0, nd=0, ll=0; + const double tau_mu=2.1969811; + for (unsigned int i=fFgbBin; i::max(); + double tolerance = 0.1; + ROOT::Minuit2::FunctionMinimum min = minimize(maxfcn, tolerance); + + std::unique_ptr fcnMin; ///< Minuit2 function minimum result + + // keep FunctionMinimum object + fcnMin.reset(new ROOT::Minuit2::FunctionMinimum(min)); + + // keep user parameters + if (fcnMin) + params = fcnMin->UserParameters(); + + fDeadTimeEstimated[i] = params.Value(0); + + if (fDebug) + std::cout << "debug> " << i << ": dtc fitted=" << params.Value(0) << ", dtc from file=" << fDeadTime[i] << std::endl; +} + +//============================================================================= +// Case-insensitive string comparison helper +//============================================================================= +/** + * Compares two strings character-by-character using case-insensitive matching. + * The function first checks if the lengths match, then converts each character + * to lowercase using std::tolower before comparison. + * + * @param a First string to compare + * @param b Second string to compare + * @return true if strings are equal (ignoring case), false otherwise + * + * @note Uses static_cast to unsigned char for std::tolower to avoid undefined + * behavior with negative char values + * + * @example + * @code + * CaseInsensitiveEquals("NeXus", "nexus") // returns true + * CaseInsensitiveEquals("IDF_VERSION", "idf_version") // returns true + * @endcode + */ +bool nxH5::PNeXus::CaseInsensitiveEquals(const std::string& a, const std::string& b) +{ + if (a.length() != b.length()) { + return false; + } + + for (size_t i = 0; i < a.length(); ++i) { + if (std::tolower(static_cast(a[i])) != + std::tolower(static_cast(b[i]))) { + return false; + } + } + + return true; +} + +//============================================================================= +// Split HDF5 path into components +//============================================================================= +/** + * Splits an HDF5 path string into individual components using '/' as delimiter. + * The function preserves the information about absolute vs relative paths by + * keeping an empty first component for absolute paths. + * + * @param path HDF5 path string to split + * @return Vector of path components + * + * @note Empty components are filtered out except for the first component + * which indicates an absolute path when empty + * + * @example + * @code + * SplitPath("/raw_data_1/IDF_version") // returns ["", "raw_data_1", "IDF_version"] + * SplitPath("detector_1/counts") // returns ["detector_1", "counts"] + * SplitPath("/") // returns [""] + * @endcode + */ +std::vector nxH5::PNeXus::SplitPath(const std::string& path) +{ + std::vector components; + std::string component; + std::istringstream stream(path); + + while (std::getline(stream, component, '/')) { + // Keep empty first component to indicate absolute path + if (!component.empty() || components.empty()) { + components.push_back(component); + } + } + + return components; +} + +//============================================================================= +// Find attribute with case-insensitive name matching +//============================================================================= +/** + * Searches for an attribute by name using case-insensitive matching. + * Lists all attributes on the given HDF5 object and compares each name + * case-insensitively against the requested name. + * + * @param obj HDF5 File object to search for attributes + * @param requestedName Attribute name to find (any case) + * @return The correctly-cased attribute name as it exists in the file + * @throws H5::AttributeIException if no matching attribute is found + * + * @note Useful for handling NeXus files where attribute names may vary in case + * (e.g., "NeXus_version" vs "nexus_version") + * + * @example + * @code + * std::string actualName = FindAttributeName(file, "nexus_VERSION"); + * // actualName might be "NeXus_version" if that's how it's stored + * @endcode + */ +std::string nxH5::PNeXus::FindAttributeName(H5::H5File& obj, const std::string& requestedName) +{ + int numAttrs = obj.getNumAttrs(); + + for (int i = 0; i < numAttrs; i++) { + H5::Attribute attr = obj.openAttribute(i); + std::string attrName = attr.getName(); + attr.close(); + + if (CaseInsensitiveEquals(attrName, requestedName)) { + return attrName; + } + } + + // Not found - throw exception + std::ostringstream msg; + msg << "Could not find attribute matching case-insensitive name: '" << requestedName << "'"; + throw H5::AttributeIException("FindAttributeName", msg.str()); +} + +//============================================================================= +// Find group path with case-insensitive matching (File overload) +//============================================================================= +/** + * Finds an HDF5 group path using case-insensitive matching, starting from + * the file root. Traverses the group hierarchy by splitting the path into + * components and matching each component case-insensitively. + * + * @param parent HDF5 File object (file root) + * @param requestedPath Group path to find (any case, absolute or relative) + * @return The correctly-cased group path as it exists in the file + * @throws H5::GroupIException if group not found or path component is not a group + * + * @note Each path component is verified to be a Group using getObjectType() + * + * @example + * @code + * std::string path = FindGroupPath(file, "/RAW_DATA_1/detector_1"); + * // path might be "/raw_data_1/detector_1" if that's the actual casing + * @endcode + */ +std::string nxH5::PNeXus::FindGroupPath(H5::H5File& parent, const std::string& requestedPath) +{ + std::vector components = SplitPath(requestedPath); + + // Handle empty path + if (components.empty()) { + return "/"; + } + + // Check if absolute path + bool isAbsolute = (components[0].empty()); + std::string currentPath = isAbsolute ? "" : ""; + + // Start from index 1 if absolute path (skip empty component) + size_t startIdx = isAbsolute ? 1 : 0; + + // Traverse path components + for (size_t i = startIdx; i < components.size(); ++i) { + const std::string& requestedComponent = components[i]; + + // Build the parent path for querying + std::string parentPath = (currentPath.empty() || currentPath == "") ? "/" : currentPath; + H5::Group currentGroup = (parentPath == "/") ? parent.openGroup("/") : parent.openGroup(parentPath); + + // List object names using iteration + hsize_t numObjs = currentGroup.getNumObjs(); + std::vector objectNames; + for (hsize_t j = 0; j < numObjs; j++) { + objectNames.push_back(currentGroup.getObjnameByIdx(j)); + } + + // Find case-insensitive match + std::string matchedName; + bool found = false; + + for (const auto& objName : objectNames) { + if (CaseInsensitiveEquals(objName, requestedComponent)) { + matchedName = objName; + found = true; + break; + } + } + + if (!found) { + std::ostringstream msg; + msg << "Could not find group component matching case-insensitive path: '" + << requestedPath << "' (failed at component: '" << requestedComponent << "')"; + throw H5::GroupIException("FindGroupPath", msg.str()); + } + + // Build current path + currentPath += "/" + matchedName; + + // Verify it's a group + H5O_type_t objType = currentGroup.childObjType(matchedName); + if (objType != H5O_TYPE_GROUP) { + std::ostringstream msg; + msg << "Path component '" << matchedName << "' in '" << requestedPath + << "' is not a group"; + throw H5::GroupIException("FindGroupPath", msg.str()); + } + } + + return currentPath; +} + +//----------------------------------------------------------------------------- +// Find group path with case-insensitive matching (Group overload) +//----------------------------------------------------------------------------- +std::string nxH5::PNeXus::FindGroupPath(H5::Group& parent, const std::string& requestedPath) +{ + std::vector components = SplitPath(requestedPath); + + // Handle empty path + if (components.empty()) { + return "/"; + } + + // Check if absolute path + bool isAbsolute = (components[0].empty()); + std::string currentPath = isAbsolute ? "" : ""; + H5::Group currentGroup = parent; + + // Start from index 1 if absolute path (skip empty component) + size_t startIdx = isAbsolute ? 1 : 0; + + // Traverse path components + for (size_t i = startIdx; i < components.size(); ++i) { + const std::string& requestedComponent = components[i]; + + // List object names using iteration + hsize_t numObjs = currentGroup.getNumObjs(); + std::vector objectNames; + for (hsize_t j = 0; j < numObjs; j++) { + objectNames.push_back(currentGroup.getObjnameByIdx(j)); + } + + // Find case-insensitive match + std::string matchedName; + bool found = false; + + for (const auto& objName : objectNames) { + if (CaseInsensitiveEquals(objName, requestedComponent)) { + matchedName = objName; + found = true; + break; + } + } + + if (!found) { + std::ostringstream msg; + msg << "Could not find group component matching case-insensitive path: '" + << requestedPath << "' (failed at component: '" << requestedComponent << "')"; + throw H5::GroupIException("FindGroupPath", msg.str()); + } + + // Build current path + currentPath += "/" + matchedName; + + // Verify it's a group (unless it's the last component, which is checked by caller) + H5O_type_t objType = currentGroup.childObjType(matchedName); + if (objType != H5O_TYPE_GROUP) { + std::ostringstream msg; + msg << "Path component '" << matchedName << "' in '" << requestedPath + << "' is not a group"; + throw H5::GroupIException("FindGroupPath", msg.str()); + } + + // Open the group for next iteration + if (i < components.size() - 1) { + currentGroup = currentGroup.openGroup(matchedName); + } + } + + return currentPath; +} + +//============================================================================= +// Find dataset path with case-insensitive matching (File overload) +//============================================================================= +/** + * Finds an HDF5 dataset path using case-insensitive matching, starting from + * the file root. Traverses the group hierarchy and verifies that the final + * path component is actually a dataset. + * + * @param parent HDF5 File object (file root) + * @param requestedPath Dataset path to find (any case, absolute or relative) + * @return The correctly-cased dataset path as it exists in the file + * @throws H5::DataSetIException if dataset not found, path invalid, or + * final component is not a dataset + * + * @note Intermediate path components must be groups, and the final component + * must be a dataset, otherwise an exception is thrown + * + * @example + * @code + * std::string path = FindDatasetPath(file, "/RAW_DATA_1/idf_VERSION"); + * // path might be "/raw_data_1/IDF_version" if that's the actual casing + * std::int data = H5Easy::load(file, path); + * @endcode + */ +std::string nxH5::PNeXus::FindDatasetPath(H5::H5File& parent, const std::string& requestedPath) +{ + std::vector components = SplitPath(requestedPath); + + // Handle empty path + if (components.empty()) { + throw H5::DataSetIException("FindDatasetPath", "Empty dataset path provided"); + } + + // Check if absolute path + bool isAbsolute = (components[0].empty()); + std::string currentPath = isAbsolute ? "" : ""; + + // Start from index 1 if absolute path (skip empty component) + size_t startIdx = isAbsolute ? 1 : 0; + + // Traverse path components + for (size_t i = startIdx; i < components.size(); ++i) { + const std::string& requestedComponent = components[i]; + + // Build the parent path for querying + std::string parentPath = (currentPath.empty() || currentPath == "") ? "/" : currentPath; + H5::Group currentGroup = (parentPath == "/") ? parent.openGroup("/") : parent.openGroup(parentPath); + + // List object names using iteration + hsize_t numObjs = currentGroup.getNumObjs(); + std::vector objectNames; + for (hsize_t j = 0; j < numObjs; j++) { + objectNames.push_back(currentGroup.getObjnameByIdx(j)); + } + + // Find case-insensitive match + std::string matchedName; + bool found = false; + + for (const auto& objName : objectNames) { + if (CaseInsensitiveEquals(objName, requestedComponent)) { + matchedName = objName; + found = true; + break; + } + } + + if (!found) { + std::ostringstream msg; + msg << "Could not find dataset matching case-insensitive path: '" + << requestedPath << "' (failed at component: '" << requestedComponent << "')"; + throw H5::DataSetIException("FindDatasetPath", msg.str()); + } + + // Build current path + currentPath += "/" + matchedName; + + // Check if this is the last component (should be a dataset) + if (i == components.size() - 1) { + H5O_type_t objType = currentGroup.childObjType(matchedName); + if (objType != H5O_TYPE_DATASET) { + std::ostringstream msg; + msg << "Path '" << requestedPath << "' resolves to '" << currentPath + << "' which is not a dataset"; + throw H5::DataSetIException("FindDatasetPath", msg.str()); + } + } else { + // Intermediate component - should be a group + H5O_type_t objType = currentGroup.childObjType(matchedName); + if (objType != H5O_TYPE_GROUP) { + std::ostringstream msg; + msg << "Path component '" << matchedName << "' in '" << requestedPath + << "' is not a group"; + throw H5::DataSetIException("FindDatasetPath", msg.str()); + } + } + } + + return currentPath; +} + +//----------------------------------------------------------------------------- +// Find dataset path with case-insensitive matching (Group overload) +//----------------------------------------------------------------------------- +std::string nxH5::PNeXus::FindDatasetPath(H5::Group& parent, const std::string& requestedPath) +{ + std::vector components = SplitPath(requestedPath); + + // Handle empty path + if (components.empty()) { + throw H5::DataSetIException("FindDatasetPath", "Empty dataset path provided"); + } + + // Check if absolute path + bool isAbsolute = (components[0].empty()); + std::string currentPath = isAbsolute ? "" : ""; + H5::Group currentGroup = parent; + + // Start from index 1 if absolute path (skip empty component) + size_t startIdx = isAbsolute ? 1 : 0; + + // Traverse path components + for (size_t i = startIdx; i < components.size(); ++i) { + const std::string& requestedComponent = components[i]; + + // List object names using iteration + hsize_t numObjs = currentGroup.getNumObjs(); + std::vector objectNames; + for (hsize_t j = 0; j < numObjs; j++) { + objectNames.push_back(currentGroup.getObjnameByIdx(j)); + } + + // Find case-insensitive match + std::string matchedName; + bool found = false; + + for (const auto& objName : objectNames) { + if (CaseInsensitiveEquals(objName, requestedComponent)) { + matchedName = objName; + found = true; + break; + } + } + + if (!found) { + std::ostringstream msg; + msg << "Could not find dataset matching case-insensitive path: '" + << requestedPath << "' (failed at component: '" << requestedComponent << "')"; + throw H5::DataSetIException("FindDatasetPath", msg.str()); + } + + // Build current path + currentPath += "/" + matchedName; + + // Check if this is the last component (should be a dataset) + if (i == components.size() - 1) { + H5O_type_t objType = currentGroup.childObjType(matchedName); + if (objType != H5O_TYPE_DATASET) { + std::ostringstream msg; + msg << "Path '" << requestedPath << "' resolves to '" << currentPath + << "' which is not a dataset"; + throw H5::DataSetIException("FindDatasetPath", msg.str()); + } + } else { + // Intermediate component - should be a group + H5O_type_t objType = currentGroup.childObjType(matchedName); + if (objType != H5O_TYPE_GROUP) { + std::ostringstream msg; + msg << "Path component '" << matchedName << "' in '" << requestedPath + << "' is not a group"; + throw H5::DataSetIException("FindDatasetPath", msg.str()); + } + // Open the group for next iteration + currentGroup = currentGroup.openGroup(matchedName); + } + } + + return currentPath; +} + +//============================================================================= +// nxH5::PNeXus Constructor +//============================================================================= +nxH5::PNeXus::PNeXus() +{ + unsigned majnum, minnum, relnum; + herr_t status; + std::stringstream ss; + + status = H5get_libversion(&majnum, &minnum, &relnum); + if (status >= 0) { + ss << majnum << "." << minnum << "." << relnum; + } + fHdf5LibVersion = ss.str(); +} + +//============================================================================= +// nxH5::PNeXus Constructor +//============================================================================= +/** + * Constructs a PNeXus object and immediately reads the specified NeXus file. + * The constructor initializes the filename member and calls ReadNexusFile() + * to open and parse the HDF5 file. + * + * @param fln Path to the NeXus HDF5 file to read + * @throws H5::FileIException if the file cannot be opened + * @throws H5::AttributeIException if required attributes are missing + * @throws H5::DataSetIException if required datasets are missing + * + * @note The file is read immediately upon construction. If reading fails, + * an exception is thrown and the object is not fully constructed. + * + * @example + * @code + * try { + * PNeXus nexus("data/emu00139040.nxs"); + * std::cout << "Opened: " << nexus.GetFileName() << std::endl; + * } catch (const H5::Exception& e) { + * std::cerr << "Failed to open file: " << e.getDetailMsg() << std::endl; + * } + * @endcode + */ +nxH5::PNeXus::PNeXus(const std::string fln, const bool printDebug) : fFileName(fln), fPrintDebug(printDebug) +{ + unsigned majnum, minnum, relnum; + herr_t status; + std::stringstream ss; + + status = H5get_libversion(&majnum, &minnum, &relnum); + if (status >= 0) { + ss << majnum << "." << minnum << "." << relnum; + } + fHdf5LibVersion = ss.str(); + + ReadNexusFile(); +} + + +//============================================================================= +// Read NeXus File +//============================================================================= +/** + * Reads and parses the NeXus HDF5 file. Opens the file in read-only mode, + * loads the NeXus version attribute, and reads the IDF version dataset. + * Uses case-insensitive path lookup to handle varying path casings. + * + * @return 0 on success, 1 on error + * @throws H5::FileIException if file cannot be opened + * @throws H5::AttributeIException if NeXus_version attribute is missing + * @throws H5::DataSetIException if IDF_version dataset is missing + * + * @note This method is called automatically by the constructor. It performs + * case-insensitive lookups for both attributes and datasets to handle + * NeXus files with varying naming conventions. + * + * @internal + * Current implementation reads: + * - Attribute: NeXus_version (from root "/") + * - Dataset: /raw_data_1/IDF_version + * + * Both paths use case-insensitive lookup, so variations like + * "/RAW_DATA_1/idf_VERSION" will work correctly. + */ +int nxH5::PNeXus::ReadNexusFile() +{ + try { + // Open the HDF5/NeXus file + H5::H5File file(fFileName, H5F_ACC_RDONLY); + + // Turn off the auto-printing when failure occurs so that we can + // handle the errors appropriately + H5::Exception::dontPrint(); + + // Load NeXus version attribute (with case-insensitive lookup) + std::string version; + try { + std::string versionNeXusAttr = FindAttributeName(file, "NeXus_version"); + H5::Attribute attr = file.openAttribute(versionNeXusAttr); + H5::DataType dtype = attr.getDataType(); + attr.read(dtype, version); + attr.close(); + fNeXusVersion = version; + if (fPrintDebug) + std::cout << "debug> NeXus version=" << fNeXusVersion << std::endl; + } catch (const H5::AttributeIException& err) { + std::cerr << "Error: Failed to read NeXus_version attribute: " << err.getDetailMsg() << std::endl; + return 1; + } + + // Load IDF version from dataset (with case-insensitive lookup) + int idf_vers{-1}; + try { + std::string idfPath = FindDatasetPath(file, "/run/IDF_version"); + H5::DataSet dataset = file.openDataSet(idfPath); + dataset.read(&idf_vers, H5::PredType::NATIVE_INT); + dataset.close(); + fIdfVersion = idf_vers; + } catch (const H5::DataSetIException& err) { + if (fPrintDebug) + std::cout << "Info: not IDF version 1, will check for IDF version 2" << std::endl; + } + + if (fIdfVersion == -1) { + try { + std::string idfPath = FindDatasetPath(file, "/raw_data_1/IDF_version"); + H5::DataSet dataset = file.openDataSet(idfPath); + dataset.read(&idf_vers, H5::PredType::NATIVE_INT); + dataset.close(); + fIdfVersion = idf_vers; + } catch (const H5::DataSetIException& err) { + std::cerr << "Error: Failed to read IDF_version dataset: " << err.getDetailMsg() << std::endl; + return 1; + } + + try { + std::string versionHdf5Attr = FindAttributeName(file, "HDF5_version"); + H5::Attribute attr = file.openAttribute(versionHdf5Attr); + H5::DataType dtype = attr.getDataType(); + attr.read(dtype, version); + attr.close(); + fHdf5Version = version; + if (fPrintDebug) + std::cout << "debug> HDF5 version=" << fHdf5Version << std::endl; + } catch (const H5::AttributeIException& err) { + std::cerr << "Error: Failed to read HDF5_version attribute: " << err.getDetailMsg() << std::endl; + return 1; + } + } + + if (fPrintDebug) + std::cout << "debug> IDF_version=" << fIdfVersion << std::endl; + + if (fIdfVersion == 1) { + try { + HandleIdfV1(file); + } catch (...) { + + } + } else { // fIdfVersion == 2 + try { + HandleIdfV2(file); + } catch (...) { + + } + } + return 0; - return &fGroupedHisto[idx]; + } catch (const H5::FileIException& err) { + std::cerr << "Error: Failed to open file '" << fFileName << "': " << err.getDetailMsg() << std::endl; + return 1; + } catch (const H5::Exception& err) { + std::cerr << "Error: HDF5 exception occurred: " << err.getDetailMsg() << std::endl; + return 1; + } catch (const std::exception& err) { + std::cerr << "Error: Unexpected exception: " << err.what() << std::endl; + return 1; + } } -//------------------------------------------------------------------------------------------ -// ReadFile (public) -//------------------------------------------------------------------------------------------ -/** - *

Read the NeXus file 'fileName'. - * - * return: - * - NX_OK on successful reading - * - NX_ERROR on error. The error code/message will give the details. - * - * \param fileName file name of the nexus file to be read - */ -int PNeXus::ReadFile(const char *fileName) +//============================================================================= +// Handle NeXus IDF version 1 file +//============================================================================= +void nxH5::PNeXus::HandleIdfV1(H5::H5File &file) { - fFileName = fileName; + if (fPrintDebug) + std::cout << "debug> in HandleIdfV1 ..." << std::endl; - // open file - NXstatus status; - status = NXopen(fileName, NXACC_READ, &fFileHandle); - if (status != NX_OK) { - fErrorCode = PNEXUS_FILE_OPEN_ERROR; - fErrorMsg = "PNeXus::ReadFile() **ERROR** Couldn't open file "+std::string(fileName)+"!"; - return NX_ERROR; + std::string attrStr{""}, attrStrName{""}; + // get file_name attribute + try { + attrStrName = FindAttributeName(file, "user"); + H5::Attribute attr = file.openAttribute(attrStrName); + H5::DataType dtype = attr.getDataType(); + attr.read(dtype, attrStr); + attr.close(); + fUserV1 = attrStr; + if (fPrintDebug) + std::cout << "debug> user =" << fUserV1 << std::endl; + } catch (const H5::AttributeIException& err) { + std::cerr << "Error: Failed to read user attribute: " << err.getDetailMsg() << std::endl; } - // get idf in order to decide which read routine needs to be used. - bool found = false; - NXname nxname, nxclass; - int dataType; - // 1) get the first NXentry - if (!SearchInGroup("NXentry", "class", nxname, nxclass, dataType)) { - fErrorCode = PNEXUS_NXENTRY_NOT_FOUND; - fErrorMsg = "PNeXus::ReadFile() **ERROR** Couldn't find any NXentry!"; - return NX_ERROR; + // Read the mandatory key datasets and store them in the data map + try { + ReadStringDataset(file, "/run/program_name"); + ReadIntDataset(file, "/run/number"); + ReadStringDataset(file, "/run/title"); + ReadStringDataset(file, "/run/notes"); + ReadStringDataset(file, "/run/analysis"); + ReadStringDataset(file, "/run/lab"); + ReadStringDataset(file, "/run/beamline"); + ReadStringDataset(file, "/run/start_time"); + ReadStringDataset(file, "/run/stop_time"); + ReadIntDataset(file, "/run/switching_states"); + ReadStringDataset(file, "/run/user/name"); + ReadStringDataset(file, "/run/user/experiment_number"); + ReadStringDataset(file, "/run/sample/name"); + ReadFloatDataset(file, "/run/sample/temperature"); + ReadFloatDataset(file, "/run/sample/magnetic_field"); + ReadStringDataset(file, "/run/sample/magnetic_field_state"); + ReadFloatDataset(file, "/run/sample/magnetic_field_vector"); + ReadStringDataset(file, "/run/sample/environment"); + ReadStringDataset(file, "/run/instrument/name"); + ReadIntDataset(file, "/run/instrument/detector/number"); + ReadFloatDataset(file, "/run/instrument/detector/deadtimes"); + ReadStringDataset(file, "/run/instrument/collimator/type"); + ReadStringDataset(file, "/run/instrument/beam/beamline"); + ReadIntDataset(file, "/run/instrument/beam/frames_good"); + ReadIntDataset(file, "/run/histogram_data_1/counts"); + ReadIntDataset(file, "/run/histogram_data_1/resolution"); + ReadIntDataset(file, "/run/histogram_data_1/time_zero"); + ReadFloatDataset(file, "/run/histogram_data_1/raw_time"); + ReadFloatDataset(file, "/run/histogram_data_1/corrected_time"); + ReadIntDataset(file, "/run/histogram_data_1/grouping"); + ReadFloatDataset(file, "/run/histogram_data_1/alpha"); + } catch (const H5::Exception& err) { + std::cerr << "Error in HandleIdfV1: " << err.getDetailMsg() << std::endl; + throw; } - // 2) open the NXentry group to obtain the IDF - status = NXopengroup(fFileHandle, nxname, "NXentry"); - if (status != NX_OK) { - fErrorCode = PNEXUS_GROUP_OPEN_ERROR; - fErrorMsg = "PNeXus::ReadFile() **ERROR** Couldn't open the NeXus group '" + std::string(nxname) + "'!"; - return NX_ERROR; - } - // 3) get the IDF - // IDF - found = false; - do { - status = NXgetnextentry(fFileHandle, nxname, nxclass, &dataType); - if ((strstr(nxname, "IDF_version") != NULL) || (strstr(nxname, "idf_version") != NULL)) - found = true; - } while (!found || (status == NX_EOD)); - - if (!ErrorHandler(NXopendata(fFileHandle, nxname), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'IDF_version' nor 'idf_version' data!!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &fIdfVersion), PNEXUS_GET_DATA_ERROR, "couldn't read 'idf_version' data!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'IDF_version' data")) return NX_ERROR; - // close group NXentry - NXclosegroup(fFileHandle); - - // close file - NXclose(&fFileHandle); - - std::stringstream ss; - switch (fIdfVersion) { - case 0: - fErrorCode = PNEXUS_IDF_NOT_IMPLEMENTED; - ss << ">> **ERROR** update of the HDF4/HDF5 libs without recompilation of the NeXus lib?"; - fErrorMsg = ss.str(); - status = NX_ERROR; - break; - case 1: - status = ReadFileIdf1(); - if (status != NX_OK) { - fErrorCode = PNEXUS_VAILD_READ_IDF1_FILE; - fErrorMsg = ">> **ERROR** while reading IDF Version 1 file '" + fFileName + "'."; - } - break; - case 2: - status = ReadFileIdf2(); - if (status != NX_OK) { - fErrorCode = PNEXUS_VAILD_READ_IDF2_FILE; - fErrorMsg = ">> **ERROR** while reading IDF Version 2 file '" + fFileName + "'."; - } - break; - default: - fErrorCode = PNEXUS_IDF_NOT_IMPLEMENTED; - ss << ">> **ERROR** idf_version=" << fIdfVersion << " not yet implemented."; - fErrorMsg = ss.str(); - status = NX_ERROR; - break; - } - - return status; } -//------------------------------------------------------------------------------------------ -// WriteFile (public) -//------------------------------------------------------------------------------------------ -/** - *

Write the NeXus file 'fileName', with the 'fileType' (HDF4 | HDF5 | XML) and the instrument - * definition (1 | 2). - * - * return: - * - NX_OK on successfull reading - * - NX_ERROR on error. The error code/message will give the details. - * - * \param fileName file name of the nexus file to be written - * \param fileType = HDF4 | HDF5 | XML - * \param idf = 1 or 2 - */ -int PNeXus::WriteFile(const char *fileName, const char *fileType, const unsigned int idf) +//============================================================================= +// Handle NeXus IDF version 2 file +//============================================================================= +void nxH5::PNeXus::HandleIdfV2(H5::H5File &file) { - if (!IsValid()) { - return NX_ERROR; + if (fPrintDebug) + std::cout << "debug> in HandleIdfV2 ..." << std::endl; + + std::string attrStr{""}, attrStrName{""}; + // get file_name attribute + try { + attrStrName = FindAttributeName(file, "file_name"); + H5::Attribute attr = file.openAttribute(attrStrName); + H5::DataType dtype = attr.getDataType(); + attr.read(dtype, attrStr); + attr.close(); + fFileNameNxs = attrStr; + if (fPrintDebug) + std::cout << "debug> NXS file_name =" << fFileNameNxs << std::endl; + } catch (const H5::AttributeIException& err) { + std::cerr << "Error: Failed to read file_name attribute: " << err.getDetailMsg() << std::endl; } - int status = NX_OK; - NXaccess access=NXACC_CREATE4; - - if (!strcmp(fileType, "hdf4")) - access=NXACC_CREATE4; - else if (!strcmp(fileType, "hdf5")) - access=NXACC_CREATE5; - else if (!strcmp(fileType, "xml")) - access=NXACC_CREATEXML; - else - access=NXACC_CREATE4; - - switch (idf) { - case 1: - status = WriteFileIdf1(fileName, access); - break; - case 2: - status = WriteFileIdf2(fileName, access); - break; - default: - break; + // get file_time attribute + attrStr=""; + try { + attrStrName = FindAttributeName(file, "file_time"); + H5::Attribute attr = file.openAttribute(attrStrName); + H5::DataType dtype = attr.getDataType(); + attr.read(dtype, attrStr); + attr.close(); + fFileTimeNxs = attrStr; + if (fPrintDebug) + std::cout << "debug> NXS file_time =" << fFileNameNxs << std::endl; + } catch (const H5::AttributeIException& err) { + std::cerr << "Error: Failed to read file_time attribute: " << err.getDetailMsg() << std::endl; } - return status; + // Read the mandatory key datasets and store them in the data map + try { + ReadIntDataset(file, "/raw_data_1/IDF_version"); + ReadStringDataset(file, "/raw_data_1/beamline"); + ReadStringDataset(file, "/raw_data_1/definition"); + ReadIntDataset(file, "/raw_data_1/good_frames"); + ReadIntDataset(file, "/raw_data_1/run_number"); + ReadStringDataset(file, "/raw_data_1/title"); + ReadStringDataset(file, "/raw_data_1/start_time"); + ReadStringDataset(file, "/raw_data_1/end_time"); + ReadStringDataset(file, "/raw_data_1/experiment_identifier"); + ReadStringDataset(file, "/raw_data_1/instrument/name"); + ReadStringDataset(file, "/raw_data_1/instrument/source/name"); + ReadStringDataset(file, "/raw_data_1/instrument/source/type"); + ReadStringDataset(file, "/raw_data_1/instrument/source/probe"); + ReadIntDataset(file, "/raw_data_1/instrument/detector_1/resolution"); + ReadIntDataset(file, "/raw_data_1/instrument/detector_1/counts"); + ReadFloatDataset(file, "/raw_data_1/instrument/detector_1/raw_time"); + ReadIntDataset(file, "/raw_data_1/instrument/detector_1/spectrum_index"); + ReadFloatDataset(file, "/raw_data_1/instrument/detector_1/dead_time"); + ReadStringDataset(file, "/raw_data_1/sample/name"); + ReadFloatDataset(file, "/raw_data_1/sample/temperature"); + ReadFloatDataset(file, "/raw_data_1/sample/magnetic_field"); + ReadStringDataset(file, "/raw_data_1/sample/shape"); + } catch (const H5::Exception& err) { + std::cerr << "Error in HandleIdfV2: " << err.getDetailMsg() << std::endl; + throw; + } } -//------------------------------------------------------------------------------------------ -// Dump (public) -//------------------------------------------------------------------------------------------ -/** - *

Write the content of the NeXus file to stdout. Used for debugging purposes. - * - * \param counts flag, if true, also dump the counts for all the histograms - */ -void PNeXus::Dump(const bool counts) +//============================================================================= +// Dump hdf5-NeXus file content which was read +//============================================================================= +void nxH5::PNeXus::Dump() { - double dval; - std::string str; - bool ok; + int first_good_bin{0}; if (fIdfVersion == 1) { - std::cout << std::endl << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; - std::cout << std::endl << "NXfile:"; - std::cout << std::endl << " NeXus_version : " << fNeXusVersion; - std::cout << std::endl << " file format ver: " << fFileFormatVersion; - std::cout << std::endl << " file name : " << fFileName; - std::cout << std::endl << " file time : " << fFileTime; - std::cout << std::endl << " user : " << fCreator; - std::cout << std::endl << "NXentry:"; - std::cout << std::endl << " idf version : " << fIdfVersion; - std::cout << std::endl << " program name : " << fNxEntry1->GetProgramName() << ", version: " << fNxEntry1->GetProgramVersion(); - std::cout << std::endl << " run number : " << fNxEntry1->GetRunNumber(); - std::cout << std::endl << " title : " << fNxEntry1->GetTitle(); - std::cout << std::endl << " notes : " << fNxEntry1->GetNotes(); - std::cout << std::endl << " analysis : " << fNxEntry1->GetAnalysis(); - std::cout << std::endl << " laboratory : " << fNxEntry1->GetLaboratory(); - std::cout << std::endl << " beamline : " << fNxEntry1->GetBeamline(); - std::cout << std::endl << " start time : " << fNxEntry1->GetStartTime(); - std::cout << std::endl << " stop time : " << fNxEntry1->GetStopTime(); - std::cout << std::endl << " switching state: " << fNxEntry1->GetSwitchingState() << ", '1' normal data collection, '2' Red/Green mode"; - std::cout << std::endl << "NXuser:"; - std::cout << std::endl << " name : " << fNxEntry1->GetUser()->GetName(); - std::cout << std::endl << " exp.number : " << fNxEntry1->GetUser()->GetExperimentNumber(); - std::cout << std::endl << "NXsample:"; - std::cout << std::endl << " name : " << fNxEntry1->GetSample()->GetName(); - dval = fNxEntry1->GetSample()->GetPhysPropValue("temperature", ok); - if (ok) - std::cout << std::endl << " temperature : " << dval; - fNxEntry1->GetSample()->GetPhysPropUnit("temperature", str, ok); - if (ok) - std::cout << " (" << str << ")"; - dval = fNxEntry1->GetSample()->GetPhysPropValue("magnetic_field", ok); - if (ok) - std::cout << std::endl << " magnetic_field : " << dval; - fNxEntry1->GetSample()->GetPhysPropUnit("magnetic_field", str, ok); - if (ok) - std::cout << " (" << str << ")"; - std::cout << std::endl << " shape : " << fNxEntry1->GetSample()->GetShape(); - std::cout << std::endl << " mag.field state: " << fNxEntry1->GetSample()->GetMagneticFieldState(); - std::cout << std::endl << " environment : " << fNxEntry1->GetSample()->GetEnvironment(); - if (fNxEntry1->GetSample()->IsMagneticFieldVectorAvailable()) { - std::cout << std::endl << " magnetic field vector: "; - for (unsigned int i=0; iGetSample()->GetMagneticFieldVector().size(); i++) { - std::cout << fNxEntry1->GetSample()->GetMagneticFieldVector().at(i) << ", "; - } - std::cout << "(" << fNxEntry1->GetSample()->GetMagneticFieldVectorUnits() << "), "; - std::cout << fNxEntry1->GetSample()->GetMagneticFieldVectorCoordinateSystem(); - } - std::cout << std::endl << "NXinstrument:"; - std::cout << std::endl << " name : " << fNxEntry1->GetInstrument()->GetName(); - std::cout << std::endl << " NXdetector:"; - std::cout << std::endl << " number of detectors: " << fNxEntry1->GetInstrument()->GetDetector()->GetNumber(); - std::cout << std::endl << " NXcollimator:"; - std::cout << std::endl << " type : " << fNxEntry1->GetInstrument()->GetCollimator()->GetType(); - std::cout << std::endl << " Nxbeam:"; - std::cout << std::endl << " total_counts : " << fNxEntry1->GetInstrument()->GetBeam()->GetTotalCounts() << " (" << fNxEntry1->GetInstrument()->GetBeam()->GetUnits() << ")"; - std::cout << std::endl << "NXdata:"; - std::cout << std::endl << " number of histos : " << fNxEntry1->GetData()->GetNoOfHistos(); - std::cout << std::endl << " time resolution : " << fNxEntry1->GetData()->GetTimeResolution("ns") << " (ns)"; - if (fNxEntry1->GetData()->GetGrouping()->size() != 0) { - std::cout << std::endl << " grouping : "; - for (unsigned int i=0; iGetData()->GetGrouping()->size(); i++) { - std::cout << "(" << i << "/" << fNxEntry1->GetData()->GetGrouping()->at(i) << "), "; - } - } else { - std::cout << std::endl << " grouping : not available"; - } - if (fNxEntry1->GetData()->GetT0s()->size() == 0) { - std::cout << std::endl << " t0's : not available"; - } else { - std::cout << std::endl << " t0's : "; - for (unsigned int i=0; iGetData()->GetT0s()->size(); i++) { - std::cout << fNxEntry1->GetData()->GetT0(i) << ", "; - } - } - if (fNxEntry1->GetData()->GetFirstGoodBins()->size() == 0) { - std::cout << std::endl << " first good bins : not available"; - } else { - std::cout << std::endl << " first good bins : "; - for (unsigned int i=0; iGetData()->GetFirstGoodBins()->size(); i++) { - std::cout << fNxEntry1->GetData()->GetFirstGoodBin(i) << ", "; - } - } - if (fNxEntry1->GetData()->GetLastGoodBins()->size() == 0) { - std::cout << std::endl << " last good bins : not available"; - } else { - std::cout << std::endl << " last good bins : "; - for (unsigned int i=0; iGetData()->GetLastGoodBins()->size(); i++) { - std::cout << fNxEntry1->GetData()->GetLastGoodBin(i) << ", "; - } - } - if (fNxEntry1->GetData()->GetNoOfHistos() == 0) { - std::cout << std::endl << " historgrams : not available"; - } else { - std::cout << std::endl << " historgrams : +++++++++++++"; - for (unsigned int i=0; iGetData()->GetNoOfHistos(); i++) { - std::cout << std::endl << " histo " << i+1 << ": "; - for (unsigned int j=0; j<15; j++) { - std::cout << fNxEntry1->GetData()->GetHisto(i)->at(j) << ", "; - } - std::cout << "..."; - if (counts) - std::cout << " total no of entries: " << fNxEntry1->GetData()->GetHistoCounts(i); - } - } - if (fNxEntry1->GetData()->GetAlpha()->size() == 0) { - std::cout << std::endl << " alpha : not available"; - } else { - std::cout << std::endl << " alpha : "; - for (unsigned int i=0; iGetData()->GetAlpha()->size(); i++) - std::cout << "(" << fNxEntry1->GetData()->GetAlpha()->at(i).GetGroupFirst() << "/" << fNxEntry1->GetData()->GetAlpha()->at(i).GetGroupSecond() << "/" << fNxEntry1->GetData()->GetAlpha()->at(i).GetAlpha() << "), "; - } - if (fGroupedHisto.size() == 0) { - std::cout << std::endl << " grouped historgrams : not available"; - } else { - std::cout << std::endl << " grouped historgrams : +++++++++++++"; - for (unsigned int i=0; iGetDefinition(); - if (!fNxEntry2->GetProgramName().empty()) - std::cout << std::endl << " program name : " << fNxEntry2->GetProgramName() << ", version: " << fNxEntry2->GetProgramVersion(); - std::cout << std::endl << " run_number : " << fNxEntry2->GetRunNumber(); - std::cout << std::endl << " run_title : " << fNxEntry2->GetTitle(); - std::cout << std::endl << " start_time : " << fNxEntry2->GetStartTime(); - std::cout << std::endl << " end_time : " << fNxEntry2->GetStopTime(); - std::cout << std::endl << " exp.identifier : " << fNxEntry2->GetExperimentIdentifier(); - std::cout << std::endl << " NXuser:"; - std::cout << std::endl << " name : " << fNxEntry2->GetUser()->GetName(); - std::cout << std::endl << " NXsample:"; - std::cout << std::endl << " name : " << fNxEntry2->GetSample()->GetName(); - std::cout << std::endl << " description : " << fNxEntry2->GetSample()->GetDescription(); - std::cout << std::endl << " mag.field state : " << fNxEntry2->GetSample()->GetMagneticFieldState(); - dval = fNxEntry2->GetSample()->GetPhysPropValue("temperature_1", ok); - if (ok) - std::cout << std::endl << " temperature : " << dval; - fNxEntry2->GetSample()->GetPhysPropUnit("temperature_1", str, ok); - if (ok) - std::cout << " (" << str << ")"; - std::cout << std::endl << " temp.environment : " << fNxEntry2->GetSample()->GetEnvironmentTemp(); - dval = fNxEntry2->GetSample()->GetPhysPropValue("magnetic_field_1", ok); - if (ok) - std::cout << std::endl << " magnetic_field : " << dval; - fNxEntry2->GetSample()->GetPhysPropUnit("magnetic_field_1", str, ok); - if (ok) - std::cout << " (" << str << ")"; - std::cout << std::endl << " mag. field env. : " << fNxEntry2->GetSample()->GetEnvironmentField(); - std::cout << std::endl << " NXinstrument:"; - std::cout << std::endl << " name : " << fNxEntry2->GetInstrument()->GetName(); - std::cout << std::endl << " NXsource:"; - std::cout << std::endl << " name : " << fNxEntry2->GetInstrument()->GetSource()->GetName(); - std::cout << std::endl << " type : " << fNxEntry2->GetInstrument()->GetSource()->GetType(); - std::cout << std::endl << " probe : " << fNxEntry2->GetInstrument()->GetSource()->GetProbe(); - std::cout << std::endl << " NXbeamline:"; - std::cout << std::endl << " name : " << fNxEntry2->GetInstrument()->GetBeamline()->GetName(); - std::cout << std::endl << " NXdetector:"; - std::cout << std::endl << " description : " << fNxEntry2->GetInstrument()->GetDetector()->GetDescription(); - std::cout << std::endl << " time resolution : " << fNxEntry2->GetInstrument()->GetDetector()->GetTimeResolution("ns") << " (ns)"; - if (fNxEntry2->GetInstrument()->GetDetector()->IsT0Present()) { - if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 1) { // only one t0 for all histograms - std::cout << std::endl << " t0 : " << fNxEntry2->GetInstrument()->GetDetector()->GetT0(); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 2) { // t0[ns] - std::cout << std::endl << " t0 (idx_s/t0) : "; - for (int i=0; iGetInstrument()->GetDetector()->GetNoOfSpectra(); i++) { - std::cout << "(" << i+1 << "/" << fNxEntry2->GetInstrument()->GetDetector()->GetT0(-1, i) << "), "; - } - } else { // t0 vector of the form t0[np][ns] - std::cout << std::endl << " t0 (idx_p/idx_s/t0): "; - for (int i=0; iGetInstrument()->GetDetector()->GetNoOfPeriods(); i++) { - for (int j=0; jGetInstrument()->GetDetector()->GetNoOfSpectra(); j++) { - std::cout << "(" << i+1 << "/" << j+1 << "/" << fNxEntry2->GetInstrument()->GetDetector()->GetT0(i,j) << "), "; - } - } - } - } else { - std::cout << std::endl << " t0 : n/a"; - } - if (fNxEntry2->GetInstrument()->GetDetector()->IsFirstGoodBinPresent()) { - if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 1) { // only one fgb for all histograms - std::cout << std::endl << " first good bin : " << fNxEntry2->GetInstrument()->GetDetector()->GetFirstGoodBin(); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 2) { // fgb[ns] - std::cout << std::endl << " fgb (idx_s/fgb) : "; - for (int i=0; iGetInstrument()->GetDetector()->GetNoOfSpectra(); i++) { - std::cout << "(" << i+1 << "/" << fNxEntry2->GetInstrument()->GetDetector()->GetFirstGoodBin(-1,i) << ") , "; - } - } else { // fgb vector of the form fgb[np][ns] - std::cout << std::endl << " fgb (idx_p/idx_s/fgb): "; - for (int i=0; iGetInstrument()->GetDetector()->GetNoOfPeriods(); i++) { - for (int j=0; jGetInstrument()->GetDetector()->GetNoOfSpectra(); j++) { - std::cout << "(" << i+1 << "/" << j+1 << "/" << fNxEntry2->GetInstrument()->GetDetector()->GetFirstGoodBin(i,j); - } - } - } - } else { - std::cout << std::endl << " first good bin : n/a"; - } - if (fNxEntry2->GetInstrument()->GetDetector()->IsLastGoodBinPresent()) { - if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 1) { // only one lgb for all histograms - std::cout << std::endl << " last good bin : " << fNxEntry2->GetInstrument()->GetDetector()->GetLastGoodBin(); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 2) { // lgb[ns] - std::cout << std::endl << " lgb (idx_s/lgb) : "; - for (int i=0; iGetInstrument()->GetDetector()->GetNoOfSpectra(); i++) { - std::cout << "(" << i+1 << "/" << fNxEntry2->GetInstrument()->GetDetector()->GetLastGoodBin(-1,i) << "), "; - } - } else { // lgb vector of the form lgb[np][ns] - std::cout << std::endl << " lgb (idx_p/idx_s/lgb): "; - for (int i=0; iGetInstrument()->GetDetector()->GetNoOfPeriods(); i++) { - for (int j=0; jGetInstrument()->GetDetector()->GetNoOfSpectra(); j++) { - std::cout << "(" << i+1 << "/" << j+1 << "/" << fNxEntry2->GetInstrument()->GetDetector()->GetLastGoodBin(i,j) << "), "; - } - } - } - } else { - std::cout << std::endl << " last good bin : n/a"; - } - std::cout << std::endl << " spectrum_index : "; - for (unsigned int i=0; iGetInstrument()->GetDetector()->GetSpectrumIndexSize(); i++) - std::cout << fNxEntry2->GetInstrument()->GetDetector()->GetSpectrumIndex(i) << ", "; + std::cout << std::endl; + std::cout << std::endl << "hdf5-NeXus file content of file:' " << fFileName << "'"; std::cout << std::endl << "****"; + std::cout << std::endl << "****"; + std::cout << std::endl << "Top Level Attributes:"; + std::cout << std::endl << " NeXus Version: " << fNeXusVersion; + std::cout << std::endl << " user: " << fUserV1; + std::cout << std::endl << "++++"; + std::cout << std::endl << "run"; + std::cout << std::endl << "----"; + std::cout << std::endl << " IDF_version: " << fIdfVersion; + if (fDataMap.find("/run/program_name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/program_name"]); + std::cout << std::endl << " program_name : " << str_data.GetData()[0]; - // dump data - int maxDump = 15; - std::cout << std::endl << " counts : "; - if (fNxEntry2->GetInstrument()->GetDetector()->GetNoOfPeriods() > 0) { // counts[np][ns][ntc] - for (int i=0; iGetInstrument()->GetDetector()->GetNoOfPeriods(); i++) { - std::cout << std::endl << " period : " << i+1; - for (int j=0; jGetInstrument()->GetDetector()->GetNoOfSpectra(); j++) { - std::cout << std::endl << " spectrum : " << j+1 << ", (#bins=" << fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins() << ")"; - if (maxDump > fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins()) - maxDump = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins(); - std::cout << std::endl << " "; - for (int k=0; kGetInstrument()->GetDetector()->GetHistoValue(i,j,k) << ", "; + // Check for attributes + if (str_data.HasAttribute("version")) { + try { + auto version = std::any_cast(str_data.GetAttribute("version")); + std::cout << " version : " << version << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast version attribute" << std::endl; } - std::cout << "..."; - if (counts) - std::cout << " total number of entries: " << fNxEntry2->GetInstrument()->GetDetector()->GetHistoCounts(i,j); } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast program_name data" << std::endl; } } else { - if (fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra() > 0) { // counts[ns][ntc] - for (int j=0; jGetInstrument()->GetDetector()->GetNoOfSpectra(); j++) { - std::cout << std::endl << " spectrum : " << j+1 << ", (#bins=" << fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins() << ")"; - if (maxDump > fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins()) - maxDump = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins(); - std::cout << std::endl << " "; - for (int k=0; kGetInstrument()->GetDetector()->GetHistoValue(0,j,k) << ", "; - } - std::cout << "..."; - if (counts) - std::cout << " total number of entries: " << fNxEntry2->GetInstrument()->GetDetector()->GetHistoCounts(0,j); - } - } else { // counts[ntc] - std::cout << std::endl << " (#bins=" << fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins() << ")"; - if (maxDump > fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins()) - maxDump = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins(); - std::cout << std::endl << " "; - for (int k=0; kGetInstrument()->GetDetector()->GetHistoValue(0,0,k) << ", "; - } - std::cout << "..."; - if (counts) - std::cout << " total number of entries: " << fNxEntry2->GetInstrument()->GetDetector()->GetHistoCounts(0,0); - } + std::cout << std::endl << " program_name : n/a"; } - std::cout << std::endl << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; - std::cout << std::endl << "that's all!"; - std::cout << std::endl << std::endl; + + if (fDataMap.find("/run/number") != fDataMap.end()) { + try { + auto number = std::any_cast>(fDataMap["/run/number"]); + std::cout << std::endl << " number : " << number.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast number data" << std::endl; + } + } else { + std::cout << std::endl << " number : n/a"; + } + + if (fDataMap.find("/run/title") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/title"]); + std::cout << std::endl << " title : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast title data" << std::endl; + } + } else { + std::cout << std::endl << " title : n/a"; + } + + if (fDataMap.find("/run/notes") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/notes"]); + std::cout << std::endl << " notes : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast notes data" << std::endl; + } + } else { + std::cout << std::endl << " notes : n/a"; + } + + if (fDataMap.find("/run/analysis") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/analysis"]); + std::cout << std::endl << " analysis : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast analysis data" << std::endl; + } + } else { + std::cout << std::endl << " analysis : n/a"; + } + + if (fDataMap.find("/run/lab") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/lab"]); + std::cout << std::endl << " lab : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast lab data" << std::endl; + } + } else { + std::cout << std::endl << " lab : n/a"; + } + + if (fDataMap.find("/run/beamline") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/beamline"]); + std::cout << std::endl << " beamline : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; + } + } else { + std::cout << std::endl << " beamline : n/a"; + } + + if (fDataMap.find("/run/start_time") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/start_time"]); + std::cout << std::endl << " start_time : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast start_time data" << std::endl; + } + } else { + std::cout << std::endl << " start_time : n/a"; + } + + if (fDataMap.find("/run/end_time") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/end_time"]); + std::cout << std::endl << " end_time : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast end_time data" << std::endl; + } + } else { + std::cout << std::endl << " end_time : n/a"; + } + + if (fDataMap.find("/run/switching_state") != fDataMap.end()) { + try { + auto int_data = std::any_cast>(fDataMap["/run/switching_state"]); + std::cout << std::endl << " switching_state : " << int_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast switching_state data" << std::endl; + } + } else { + std::cout << std::endl << " switching_state : n/a"; + } + + std::cout << std::endl << " user"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/usr/name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/user/name"]); + std::cout << std::endl << " name : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + if (fDataMap.find("/run/usr/experiment_number") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/user/experiment_number"]); + std::cout << std::endl << " experiment_number : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast experiment_number data" << std::endl; + } + } else { + std::cout << std::endl << " experiment_number : n/a"; + } + + std::cout << std::endl << " sample"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/sample/name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/sample/name"]); + std::cout << std::endl << " name : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + if (fDataMap.find("/run/sample/temperature") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/sample/temperature"]); + std::cout << std::endl << " temperature : " << float_data.GetData()[0]; + + // Check for attributes + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast temperature data" << std::endl; + } + } else { + std::cout << std::endl << " temperature : n/a"; + } + + if (fDataMap.find("/run/sample/magnetic_field") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field"]); + std::cout << std::endl << " magnetic_field : " << float_data.GetData()[0]; + + // Check for attributes + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast magnetic_field data" << std::endl; + } + } else { + std::cout << std::endl << " magnetic_field : n/a"; + } + + if (fDataMap.find("/run/sample/magnetic_field_vector") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field_vector"]); + std::cout << std::endl << " magnetic_field_vector : " << float_data.GetData()[0]; + + // Check for attributes + if (float_data.HasAttribute("coordinate_system")) { + try { + auto coordinate_system = std::any_cast(float_data.GetAttribute("coordinate_system")); + std::cout << " coordinate_system : " << coordinate_system << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast coordinate_system attribute" << std::endl; + } + } + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } + if (float_data.HasAttribute("available")) { + try { + auto available = std::any_cast(float_data.GetAttribute("available")); + std::cout << " available : " << available << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast available attribute" << std::endl; + } + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast magnetic_field_vector data" << std::endl; + } + } else { + std::cout << std::endl << " magnetic_field_vector : n/a"; + } + + if (fDataMap.find("/run/sample/environment") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/sample/environment"]); + std::cout << std::endl << " environment : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast environment data" << std::endl; + } + } else { + std::cout << std::endl << " environment : n/a"; + } + + std::cout << std::endl << " instrument"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/instrument/name") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/instrument/name"]); + std::cout << std::endl << " name : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + std::cout << std::endl << " detector"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/instrument/detector/number") != fDataMap.end()) { + try { + auto int_data = std::any_cast>(fDataMap["/run/instrument/detector/number"]); + std::cout << std::endl << " number : " << int_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast number data" << std::endl; + } + } else { + std::cout << std::endl << " number : n/a"; + } + + if (fDataMap.find("/run/instrument/detector/deadtimes") != fDataMap.end()) { + try { + const auto& dead_times = std::any_cast>("/run/instrument/detector/deadtimes").GetData(); + std::cout << std::endl << " deadtimes: "; + for (unsigned int i=0; i<10; i++) + std::cout << dead_times[i] << ", "; + std::cout << "..."; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast dead time data" << std::endl; + } + } else { + std::cout << std::endl << " deadtimes: n/a"; + } + + std::cout << std::endl << " collimator"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/instrument/collimator/type") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/instrument/collimator/type"]); + std::cout << std::endl << " type : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast type data" << std::endl; + } + } else { + std::cout << std::endl << " type : n/a"; + } + + std::cout << std::endl << " beam"; + std::cout << std::endl << "----"; + if (fDataMap.find("/run/instrument/beam/beamline") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/instrument/beam/beamline"]); + std::cout << std::endl << " beamline : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; + } + } else { + std::cout << std::endl << " beamline : n/a"; + } + + if (fDataMap.find("/run/instrument/beam/frames_good") != fDataMap.end()) { + try { + auto str_data = std::any_cast>(fDataMap["/run/instrument/beam/frames_good"]); + std::cout << std::endl << " frames_good : " << str_data.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast frames_good data" << std::endl; + } + } else { + std::cout << std::endl << " frames_good : n/a"; + } + + std::cout << std::endl << " histogram_data_1"; + std::cout << std::endl << "----"; + std::cout << std::endl << " counts:"; + std::cout << std::endl; + try { + auto counts_data = std::any_cast>(fDataMap["/run/histogram_data_1/counts"]); + + // Check for attributes + if (counts_data.HasAttribute("units")) { + try { + auto units = std::any_cast(counts_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + + if (counts_data.HasAttribute("signal")) { + try { + auto signal = std::any_cast(counts_data.GetAttribute("signal")); + std::cout << " signal : " << signal << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast signal attribute" << std::endl; + } + } else { + std::cout << " signal : n/a" << std::endl; + } + + int noOfHistos{0}; + if (counts_data.HasAttribute("number")) { + try { + noOfHistos = std::any_cast(counts_data.GetAttribute("number")); + std::cout << " number : " << noOfHistos << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast number attribute" << std::endl; + } + } else { + std::cout << " number : n/a" << std::endl; + } + + int histoLength{0}; + if (counts_data.HasAttribute("length")) { + try { + histoLength = std::any_cast(counts_data.GetAttribute("length")); + std::cout << " length : " << noOfHistos << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast length attribute" << std::endl; + } + } else { + std::cout << " length : n/a" << std::endl; + } + + if (counts_data.HasAttribute("t0_bin")) { + try { + auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); + std::cout << " t0_bin : " << t0_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; + } + } else { + std::cout << " t0_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("first_good_bin")) { + try { + first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); + std::cout << " first_good_bin : " << first_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; + } + } else { + std::cout << " first_good_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("last_good_bin")) { + try { + auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); + std::cout << " last_good_bin : " << last_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; + } + } else { + std::cout << " last_good_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("offset")) { + try { + auto offset = std::any_cast(counts_data.GetAttribute("offset")); + std::cout << " offset : " << offset << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast offset attribute" << std::endl; + } + } else { + std::cout << " offset : n/a" << std::endl; + } + + // dump the first couple of counts of each detector + const auto& data = counts_data.GetData(); + std::cout << std::endl << " first couple of counts of each detector:"; + for (unsigned int i=0; i>(fDataMap["/run/histogram_data_1/resolution"]); + std::cout << std::endl << " resolution : " << int_data.GetData()[0]; + + if (int_data.HasAttribute("units")) { + try { + auto units = std::any_cast(int_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast resolution data" << std::endl; + } + } else { + std::cout << std::endl << " resolution : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/time_zero") != fDataMap.end()) { + try { + auto int_data = std::any_cast>(fDataMap["/run/histogram_data_1/time_zero"]); + std::cout << std::endl << " time_zero : " << int_data.GetData()[0]; + + if (int_data.HasAttribute("units")) { + try { + auto units = std::any_cast(int_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + + if (int_data.HasAttribute("available")) { + try { + auto available = std::any_cast(int_data.GetAttribute("available")); + std::cout << " available : " << available << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast available attribute" << std::endl; + } + } else { + std::cout << " available : n/a" << std::endl; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast time_zero data" << std::endl; + } + } else { + std::cout << std::endl << " time_zero : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/raw_time") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/raw_time"]); + std::cout << std::endl << " raw_time : " << float_data.GetData()[0]; + if (float_data.HasAttribute("axis")) { + try { + auto axis = std::any_cast(float_data.GetAttribute("axis")); + std::cout << " axis : " << axis << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast axis attribute" << std::endl; + } + } else { + std::cout << " axis : n/a" << std::endl; + } + + if (float_data.HasAttribute("primary")) { + try { + auto primary = std::any_cast(float_data.GetAttribute("primary")); + std::cout << " primary : " << primary << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast primary attribute" << std::endl; + } + } else { + std::cout << " primary : n/a" << std::endl; + } + + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast raw_time data" << std::endl; + } + } else { + std::cout << std::endl << " raw_time : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/corrected_time") != fDataMap.end()) { + try { + auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/corrected_time"]); + std::cout << std::endl << " corrected_time : " << float_data.GetData()[0]; + if (float_data.HasAttribute("axis")) { + try { + auto axis = std::any_cast(float_data.GetAttribute("axis")); + std::cout << " axis : " << axis << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast axis attribute" << std::endl; + } + } else { + std::cout << " axis : n/a" << std::endl; + } + + if (float_data.HasAttribute("units")) { + try { + auto units = std::any_cast(float_data.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast raw_time data" << std::endl; + } + } else { + std::cout << std::endl << " corrected_time : n/a"; + } + + if (fDataMap.find("/run/histogram_data_1/grouping") != fDataMap.end()) { + try { + auto int_data = std::any_cast>(fDataMap["/run/histogram_data_1/grouping"]); + std::cout << std::endl << " grouping : " << int_data.GetData()[0]; + if (int_data.HasAttribute("available")) { + try { + auto available = std::any_cast(int_data.GetAttribute("available")); + std::cout << " available : " << available << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast available attribute" << std::endl; + } + } else { + std::cout << std::endl << " available : n/a"; + } + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast grouping data" << std::endl; + } + } else { + std::cout << std::endl << " grouping : n/a"; + } + + std::cout << std::endl; + } else { // IDF Version 2 + std::cout << std::endl; + std::cout << std::endl << "hdf5-NeXus file content of file:' " << fFileName << "'"; + std::cout << std::endl << "****"; + std::cout << std::endl << "Top Level Attributes:"; + std::cout << std::endl << " HDF5 Version : " << fHdf5Version; + std::cout << std::endl << " NeXus Version: " << fNeXusVersion; + std::cout << std::endl << " file_name : " << fFileNameNxs; + std::cout << std::endl << " file_time : " << fFileTimeNxs; + std::cout << std::endl << "++++"; + std::cout << std::endl << "raw_data_1"; + std::cout << std::endl << "----"; + std::cout << std::endl << " IDF_version: " << fIdfVersion; + if (fDataMap.find("/raw_data_1/beamline") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/beamline"]); + std::cout << std::endl << " beamline : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; + } + } else { + std::cout << std::endl << " beamline : n/a"; + } + + if (fDataMap.find("/raw_data_1/definition") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/definition"]); + std::cout << std::endl << " definition : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast definition data" << std::endl; + } + } else { + std::cout << std::endl << " definition : n/a"; + } + + if (fDataMap.find("/raw_data_1/run_number") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/run_number"]); + std::cout << std::endl << " run_number : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast run_number data" << std::endl; + } + } else { + std::cout << std::endl << " run_number : n/a"; + } + + if (fDataMap.find("/raw_data_1/title") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/title"]); + std::cout << std::endl << " title : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast title data" << std::endl; + } + } else { + std::cout << std::endl << " title : n/a"; + } + + if (fDataMap.find("/raw_data_1/start_time") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/start_time"]); + std::cout << std::endl << " start_time : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast start_time data" << std::endl; + } + } else { + std::cout << std::endl << " start_time : n/a"; + } + + if (fDataMap.find("/raw_data_1/end_time") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/end_time"]); + std::cout << std::endl << " end_time : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast end_time data" << std::endl; + } + } else { + std::cout << std::endl << " end_time : n/a"; + } + + if (fDataMap.find("/raw_data_1/good_frames") != fDataMap.end()) { + try { + auto good_frames = std::any_cast>(fDataMap["/raw_data_1/good_frames"]); + std::cout << std::endl << " good_frames: " << good_frames.GetData()[0]; + } catch(const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast good_frames data" << std::endl; + } + } else { + std::cout << std::endl << " good_frames: n/a"; + } + + if (fDataMap.find("/raw_data_1/experiment_identifier") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/experiment_identifier"]); + std::cout << std::endl << " experiment_identifier: " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast experiment_identifier data" << std::endl; + } + } else { + std::cout << std::endl << " experiment_identifier: n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " instrument"; + if (fDataMap.find("/raw_data_1/instrument/name") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/name"]); + std::cout << std::endl << " name : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " source"; + if (fDataMap.find("/raw_data_1/instrument/source/name") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/name"]); + std::cout << std::endl << " name : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/source/name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + if (fDataMap.find("/raw_data_1/instrument/source/type") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/type"]); + std::cout << std::endl << " type : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/source/type data" << std::endl; + } + } else { + std::cout << std::endl << " type : n/a"; + } + + if (fDataMap.find("/raw_data_1/instrument/source/probe") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/probe"]); + std::cout << std::endl << " probe : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast instrument/source/probe data" << std::endl; + } + } else { + std::cout << std::endl << " probe : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " sample"; + if (fDataMap.find("/raw_data_1/sample/name") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/sample/name"]); + std::cout << std::endl << " name : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast sample/name data" << std::endl; + } + } else { + std::cout << std::endl << " name : n/a"; + } + + if (fDataMap.find("/raw_data_1/sample/temperature") != fDataMap.end()) { + try { + auto temp_ds = std::any_cast>(fDataMap["/raw_data_1/sample/temperature"]); + float temp = temp_ds.GetData()[0]; + if (temp_ds.HasAttribute("units")) { + try { + auto units = std::any_cast(temp_ds.GetAttribute("units")); + if (units == "Celsius") + temp += 273.16; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } + std::cout << std::endl << " temperature: " << temp << " K"; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast sample/temperature data" << std::endl; + } + } else { + std::cout << std::endl << " temperature: n/a"; + } + + if (fDataMap.find("/raw_data_1/sample/magnetic_field") != fDataMap.end()) { + try { + auto mag_field_ds = std::any_cast>(fDataMap["/raw_data_1/sample/magnetic_field"]); + float mag_field = mag_field_ds.GetData()[0]; + if (mag_field_ds.HasAttribute("units")) { + try { + auto units = std::any_cast(mag_field_ds.GetAttribute("units")); + if (units == "Tesla") + mag_field *= 1e4; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } + std::cout << std::endl << " mag. field: " << mag_field << " G"; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast sample/magnetic_field data" << std::endl; + } + } else { + std::cout << std::endl << " temperature: n/a"; + } + + if (fDataMap.find("/raw_data_1/sample/shape") != fDataMap.end()) { + try { + auto str = std::any_cast>(fDataMap["/raw_data_1/sample/shape"]); + std::cout << std::endl << " shape : " << str.GetData()[0]; + } catch (const std::bad_any_cast& e) { + std::cerr << std::endl << "Error: Failed to cast sample/shape data" << std::endl; + } + } else { + std::cout << std::endl << " shape : n/a"; + } + + std::cout << std::endl << "----"; + std::cout << std::endl << " detector_1"; + std::cout << std::endl << " counts:"; + std::cout << std::endl; + try { + auto counts_data = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/counts"]); + auto dims = counts_data.GetDimensions(); + std::cout << " counts dimensions: " << dims[0] << " x " + << dims[1] << " x " << dims[2] << std::endl; + std::cout << " total elements: " << counts_data.GetNumElements() << std::endl; + + // Check for attributes + if (counts_data.HasAttribute("signal")) { + try { + auto signal = std::any_cast(counts_data.GetAttribute("signal")); + std::cout << " signal : " << signal << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast signal attribute" << std::endl; + } + } else { + std::cout << " signal : n/a" << std::endl; + } + + if (counts_data.HasAttribute("axes")) { + try { + auto axes = std::any_cast(counts_data.GetAttribute("axes")); + std::cout << " axes : " << axes << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast axes attribute" << std::endl; + } + } else { + std::cout << " axes : n/a" << std::endl; + } + + if (counts_data.HasAttribute("long_name")) { + try { + auto long_name = std::any_cast(counts_data.GetAttribute("long_name")); + std::cout << " long_name : " << long_name << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast long_name attribute" << std::endl; + } + } else { + std::cout << " long_name : n/a" << std::endl; + } + + if (counts_data.HasAttribute("t0_bin")) { + try { + auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); + std::cout << " t0_bin : " << t0_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; + } + } else { + std::cout << " t0_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("first_good_bin")) { + try { + first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); + std::cout << " first_good_bin : " << first_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; + } + } else { + std::cout << " first_good_bin : n/a" << std::endl; + } + + if (counts_data.HasAttribute("last_good_bin")) { + try { + auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); + std::cout << " last_good_bin : " << last_good_bin << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; + } + } else { + std::cout << " last_good_bin : n/a" << std::endl; + } + + if (fDataMap.find("/raw_data_1/instrument/detector_1/resolution") != fDataMap.end()) { + try { + auto ivalData = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/resolution"]); + std::cout << " resolution : " << ivalData.GetData()[0]; + if (ivalData.HasAttribute("units")) { + try { + auto units = std::any_cast(ivalData.GetAttribute("units")); + std::cout << " " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast resolution attribute" << std::endl; + } + } else { + std::cout << " resolution : n/a"; + } + + // dump the first couple of counts of each detector + const auto& data = counts_data.GetData(); + std::cout << std::endl << " first couple of counts of each detector:"; + for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/raw_time"]); + const auto& data = raw_time.GetData(); + + // Check for attributes + if (raw_time.HasAttribute("units")) { + try { + auto units = std::any_cast(raw_time.GetAttribute("units")); + std::cout << " units : " << units << std::endl; + } catch (const std::bad_any_cast& e) { + std::cerr << "Error: Failed to cast units attribute" << std::endl; + } + } else { + std::cout << " units : n/a" << std::endl; + } + + // dump the first couple of raw_times + std::cout << " "; + for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/spectrum_index"]); + const auto& data = spectrum_index.GetData(); + + // dump the first couple of raw_times + std::cout << " "; + for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/dead_time"]); + const auto& data = dead_time.GetData(); + + // dump the first couple of raw_times + std::cout << " "; + for (unsigned int i=0; i + * Reads an integer dataset from the HDF5 file and stores it in the data map. + * Handles multi-dimensional datasets by flattening them into a vector. + * + * @param file HDF5 file object + * @param path Path to the dataset + * @throws H5::Exception if reading fails */ -void PNeXus::Init() +void nxH5::PNeXus::ReadIntDataset(H5::H5File& file, const std::string& path) { - fValid = false; - fErrorCode = PNEXUS_NO_ERROR; - fErrorMsg = "n/a"; - fNeXusVersion = NEXUS_VERSION; - fFileFormatVersion = "n/a"; - fIdfVersion = 0; - fFileName = "n/a"; - fFileTime = "n/a"; - fCreator = "n/a"; + try { + // Find the actual path (case-insensitive) + std::string actualPath = FindDatasetPath(file, path); + + // Open the dataset + H5::DataSet dataset = file.openDataSet(actualPath); + H5::DataType datatype = dataset.getDataType(); + H5::DataSpace dataspace = dataset.getSpace(); + + // Get dimensions + int rank = dataspace.getSimpleExtentNdims(); + std::vector dims(rank); + dataspace.getSimpleExtentDims(dims.data(), nullptr); + + // Calculate total number of elements + hsize_t numElements = 1; + for (int i = 0; i < rank; i++) { + numElements *= dims[i]; + } + + // Create PNXdata object + PNXdata data(datatype); + data.SetDimensions(dims); + + // Read data + std::vector buffer(numElements); + dataset.read(buffer.data(), H5::PredType::NATIVE_INT); + data.SetData(buffer); + + // Read attributes + ReadDatasetAttributes(dataset, data); + + // Store in map + fDataMap[actualPath] = data; + + dataset.close(); + + if (fPrintDebug) { + std::cout << "debug> Read integer dataset: " << actualPath + << " (dims: "; + for (size_t i = 0; i < dims.size(); i++) { + std::cout << dims[i]; + if (i < dims.size() - 1) std::cout << " x "; + } + std::cout << ")" << std::endl; + } + } catch (const H5::Exception& err) { + std::cerr << "Error reading integer dataset " << path << ": " + << err.getDetailMsg() << std::endl; + throw; + } } -//----------------------------------------------------------------------------------------------------- -// ErrorHandler (private) -//----------------------------------------------------------------------------------------------------- +//============================================================================= +// Read float dataset and store in data map +//============================================================================= /** - *

+ * Reads a float dataset from the HDF5 file and stores it in the data map. + * Handles multi-dimensional datasets by flattening them into a vector. * - * return: - * - true of no error occurred - * - false on error - * - * \param status of the calling routine - * \param errCode will set the fErrorCode of the class - * \param errMsg will set the fErrorMsg of the class + * @param file HDF5 file object + * @param path Path to the dataset + * @throws H5::Exception if reading fails */ -bool PNeXus::ErrorHandler(NXstatus status, int errCode, const std::string &errMsg) +void nxH5::PNeXus::ReadFloatDataset(H5::H5File& file, const std::string& path) { - if (status != NX_OK) { - fErrorCode = errCode; - fErrorMsg = errMsg; - std::cerr << std::endl << ">> **ERROR** " << fErrorMsg << std::endl; - if (fFileHandle != 0) { - NXclose(&fFileHandle); + try { + // Find the actual path (case-insensitive) + std::string actualPath = FindDatasetPath(file, path); + + // Open the dataset + H5::DataSet dataset = file.openDataSet(actualPath); + H5::DataType datatype = dataset.getDataType(); + H5::DataSpace dataspace = dataset.getSpace(); + + // Get dimensions + int rank = dataspace.getSimpleExtentNdims(); + std::vector dims(rank); + dataspace.getSimpleExtentDims(dims.data(), nullptr); + + // Calculate total number of elements + hsize_t numElements = 1; + for (int i = 0; i < rank; i++) { + numElements *= dims[i]; } - return false; + + // Create PNXdata object + PNXdata data(datatype); + data.SetDimensions(dims); + + // Read data + std::vector buffer(numElements); + dataset.read(buffer.data(), H5::PredType::NATIVE_FLOAT); + data.SetData(buffer); + + // Read attributes + ReadDatasetAttributes(dataset, data); + + // Store in map + fDataMap[actualPath] = data; + + dataset.close(); + + if (fPrintDebug) { + std::cout << "debug> Read float dataset: " << actualPath + << " (dims: "; + for (size_t i = 0; i < dims.size(); i++) { + std::cout << dims[i]; + if (i < dims.size() - 1) std::cout << " x "; + } + std::cout << ")" << std::endl; + } + } catch (const H5::Exception& err) { + std::cerr << "Error reading float dataset " << path << ": " + << err.getDetailMsg() << std::endl; + throw; } +} + +//============================================================================= +// Read string dataset and store in data map +//============================================================================= +/** + * Reads a string dataset from the HDF5 file and stores it in the data map. + * + * @param file HDF5 file object + * @param path Path to the dataset + * @throws H5::Exception if reading fails + */ +void nxH5::PNeXus::ReadStringDataset(H5::H5File& file, const std::string& path) +{ + try { + // Find the actual path (case-insensitive) + std::string actualPath = FindDatasetPath(file, path); + + // Open the dataset + H5::DataSet dataset = file.openDataSet(actualPath); + H5::DataType datatype = dataset.getDataType(); + H5::DataSpace dataspace = dataset.getSpace(); + + // Get dimensions + int rank = dataspace.getSimpleExtentNdims(); + std::vector dims(rank); + dataspace.getSimpleExtentDims(dims.data(), nullptr); + + // Calculate total number of elements + hsize_t numElements = 1; + for (int i = 0; i < rank; i++) { + numElements *= dims[i]; + } + + // Create PNXdata object + PNXdata data(datatype); + data.SetDimensions(dims); + + // Read data + std::vector buffer(numElements); + if (numElements == 1) { + // Single string + std::string value; + dataset.read(value, datatype); + buffer[0] = value; + } else { + // Multiple strings (if needed) + for (hsize_t i = 0; i < numElements; i++) { + dataset.read(buffer[i], datatype); + } + } + data.SetData(buffer); + + // Read attributes + ReadDatasetAttributes(dataset, data); + + // Store in map + fDataMap[actualPath] = data; + + dataset.close(); + + if (fPrintDebug) { + std::cout << "debug> Read string dataset: " << actualPath << std::endl; + } + } catch (const H5::Exception& err) { + std::cerr << "Error reading string dataset " << path << ": " + << err.getDetailMsg() << std::endl; + throw; + } +} + +//============================================================================= +// Read dataset attributes (template implementation) +//============================================================================= +/** + * Reads all attributes from an HDF5 dataset and adds them to a PNXdata object. + * Attributes can be integers, floats, or strings. + * + * @tparam T The data type of the PNXdata object + * @param dataset HDF5 dataset object + * @param data PNXdata object to add attributes to + */ +template +void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet& dataset, PNXdata& data) +{ + int numAttrs = dataset.getNumAttrs(); + + for (int i = 0; i < numAttrs; i++) { + H5::Attribute attr = dataset.openAttribute(i); + std::string attrName = attr.getName(); + H5::DataType attrType = attr.getDataType(); + H5T_class_t typeClass = attrType.getClass(); + + try { + if (typeClass == H5T_INTEGER) { + int value; + attr.read(H5::PredType::NATIVE_INT, &value); + data.AddAttribute(attrName, value); + } else if (typeClass == H5T_FLOAT) { + float value; + attr.read(H5::PredType::NATIVE_FLOAT, &value); + data.AddAttribute(attrName, value); + } else if (typeClass == H5T_STRING) { + std::string value; + attr.read(attrType, value); + data.AddAttribute(attrName, value); + } + } catch (const H5::Exception& err) { + if (fPrintDebug) { + std::cerr << "Warning: Could not read attribute " << attrName + << ": " << err.getDetailMsg() << std::endl; + } + } + + attr.close(); + } +} + +// Explicit template instantiations +template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata&); +template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata&); +template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata&); + +//============================================================================= +// WRITE METHODS +//============================================================================= + +//============================================================================= +// Create group hierarchy for a given path +//============================================================================= +/** + * Create nested group hierarchy for a given path. Groups are created recursively + * if they don't exist. If a group already exists, it is opened and returned. + * + * @param file HDF5 file object + * @param path Full path (e.g., "/raw_data_1/detector_1") + * @return H5::Group handle to the deepest created/opened group + * @throws H5::GroupIException if group creation fails + */ +H5::Group nxH5::PNeXus::CreateGroupHierarchy(H5::H5File& file, const std::string& path) +{ + std::vector components = SplitPath(path); + std::string currentPath = ""; + H5::Group currentGroup; + + for (const auto& component : components) { + if (component.empty()) continue; // Skip empty (root indicator) + + currentPath += "/" + component; + + // Check if group exists + bool exists = false; + try { + H5::Group testGroup = file.openGroup(currentPath); + testGroup.close(); + exists = true; + } catch (const H5::Exception&) { + exists = false; + } + + // Create if doesn't exist + if (!exists) { + if (fPrintDebug) { + std::cout << "debug> Creating group: " << currentPath << std::endl; + } + currentGroup = file.createGroup(currentPath); + + // Write group attributes if any exist for this path + auto attrIt = fGroupAttributes.find(currentPath); + if (attrIt != fGroupAttributes.end()) { + if (fPrintDebug) { + std::cout << "debug> Writing " << attrIt->second.size() + << " attributes to group: " << currentPath << std::endl; + } + WriteGroupAttributes(currentGroup, attrIt->second); + } + + currentGroup.close(); + + if (fPrintDebug) { + std::cout << "debug> Created group: " << currentPath << std::endl; + } + } + } + + // Return an invalid group handle (we just needed to create the hierarchy) + return currentGroup; +} + +//============================================================================= +// Write dataset attributes from PNXdata object +//============================================================================= +/** + * Writes all attributes from a PNXdata object to an HDF5 dataset. + * Supports int, float, and string attribute types. + * + * @tparam T The data type of the PNXdata object + * @param dataset HDF5 dataset object to write attributes to + * @param data PNXdata object containing attributes + */ +template +void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet& dataset, const PNXdata& data) +{ + const auto& attributes = data.GetAttributes(); + + for (const auto& [attrName, attrValue] : attributes) { + try { + // Try int + if (auto* intVal = std::any_cast(&attrValue)) { + H5::DataSpace attrSpace(H5S_SCALAR); + H5::Attribute attr = dataset.createAttribute( + attrName, H5::PredType::NATIVE_INT32, attrSpace + ); + attr.write(H5::PredType::NATIVE_INT32, intVal); + attr.close(); + attrSpace.close(); + } + // Try float + else if (auto* floatVal = std::any_cast(&attrValue)) { + H5::DataSpace attrSpace(H5S_SCALAR); + H5::Attribute attr = dataset.createAttribute( + attrName, H5::PredType::NATIVE_FLOAT, attrSpace + ); + attr.write(H5::PredType::NATIVE_FLOAT, floatVal); + attr.close(); + attrSpace.close(); + } + // Try string + else if (auto* strVal = std::any_cast(&attrValue)) { + H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); + H5::DataSpace attrSpace(H5S_SCALAR); + H5::Attribute attr = dataset.createAttribute( + attrName, strType, attrSpace + ); + attr.write(strType, *strVal); + attr.close(); + attrSpace.close(); + } + // Try vector for multi-element attributes + else if (auto* intVec = std::any_cast>(&attrValue)) { + hsize_t dims[1] = {intVec->size()}; + H5::DataSpace attrSpace(1, dims); + H5::Attribute attr = dataset.createAttribute( + attrName, H5::PredType::NATIVE_INT32, attrSpace + ); + attr.write(H5::PredType::NATIVE_INT32, intVec->data()); + attr.close(); + attrSpace.close(); + } + // Try vector for multi-element attributes + else if (auto* floatVec = std::any_cast>(&attrValue)) { + hsize_t dims[1] = {floatVec->size()}; + H5::DataSpace attrSpace(1, dims); + H5::Attribute attr = dataset.createAttribute( + attrName, H5::PredType::NATIVE_FLOAT, attrSpace + ); + attr.write(H5::PredType::NATIVE_FLOAT, floatVec->data()); + attr.close(); + attrSpace.close(); + } + else { + if (fPrintDebug) { + std::cerr << "Warning: Unsupported attribute type for " + << attrName << std::endl; + } + } + + } catch (const H5::Exception& err) { + if (fPrintDebug) { + std::cerr << "Warning: Could not write attribute " << attrName + << ": " << err.getDetailMsg() << std::endl; + } + } + } +} + +//============================================================================= +// Write integer dataset with attributes +//============================================================================= +/** + * Writes an integer dataset to the HDF5 file with all its attributes. + * The parent group hierarchy is created automatically if it doesn't exist. + * + * @param file HDF5 file object + * @param path Dataset path (groups created automatically) + * @param data PNXdata object containing data, dimensions, and attributes + * @throws H5::Exception if writing fails + */ +void nxH5::PNeXus::WriteIntDataset(H5::H5File& file, const std::string& path, + const PNXdata& data) +{ + try { + // Extract parent path and dataset name + size_t lastSlash = path.find_last_of('/'); + std::string parentPath = (lastSlash == 0) ? "/" : path.substr(0, lastSlash); + std::string datasetName = path.substr(lastSlash + 1); + + // Create parent group hierarchy + if (!parentPath.empty() && parentPath != "/") { + CreateGroupHierarchy(file, parentPath); + } + + // Get dimensions and data + const auto& dims = data.GetDimensions(); + const auto& buffer = data.GetData(); + + // Validate data consistency + if (buffer.size() != data.GetNumElements()) { + throw std::runtime_error("Data size mismatch with dimensions"); + } + + // Create dataspace + H5::DataSpace dataspace(dims.size(), dims.data()); + + // Create dataset + H5::DataSet dataset = file.createDataSet( + path, + H5::PredType::NATIVE_INT, + dataspace + ); + + // Write data + dataset.write(buffer.data(), H5::PredType::NATIVE_INT); + + // Write attributes + WriteDatasetAttributes(dataset, data); + + // Close resources + dataset.close(); + dataspace.close(); + + if (fPrintDebug) { + std::cout << "debug> Wrote integer dataset: " << path + << " (dims: "; + for (size_t i = 0; i < dims.size(); i++) { + std::cout << dims[i]; + if (i < dims.size() - 1) std::cout << " x "; + } + std::cout << ")" << std::endl; + } + + } catch (const H5::Exception& err) { + std::cerr << "Error writing integer dataset " << path << ": " + << err.getDetailMsg() << std::endl; + throw; + } +} + +//============================================================================= +// Write float dataset with attributes +//============================================================================= +/** + * Writes a float dataset to the HDF5 file with all its attributes. + * The parent group hierarchy is created automatically if it doesn't exist. + * + * @param file HDF5 file object + * @param path Dataset path (groups created automatically) + * @param data PNXdata object containing data, dimensions, and attributes + * @throws H5::Exception if writing fails + */ +void nxH5::PNeXus::WriteFloatDataset(H5::H5File& file, const std::string& path, + const PNXdata& data) +{ + try { + // Extract parent path and dataset name + size_t lastSlash = path.find_last_of('/'); + std::string parentPath = (lastSlash == 0) ? "/" : path.substr(0, lastSlash); + std::string datasetName = path.substr(lastSlash + 1); + + // Create parent group hierarchy + if (!parentPath.empty() && parentPath != "/") { + CreateGroupHierarchy(file, parentPath); + } + + // Get dimensions and data + const auto& dims = data.GetDimensions(); + const auto& buffer = data.GetData(); + + // Validate data consistency + if (buffer.size() != data.GetNumElements()) { + throw std::runtime_error("Data size mismatch with dimensions"); + } + + // Create dataspace + H5::DataSpace dataspace(dims.size(), dims.data()); + + // Create dataset + H5::DataSet dataset = file.createDataSet( + path, + H5::PredType::NATIVE_FLOAT, + dataspace + ); + + // Write data + dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT); + + // Write attributes + WriteDatasetAttributes(dataset, data); + + // Close resources + dataset.close(); + dataspace.close(); + + if (fPrintDebug) { + std::cout << "debug> Wrote float dataset: " << path + << " (dims: "; + for (size_t i = 0; i < dims.size(); i++) { + std::cout << dims[i]; + if (i < dims.size() - 1) std::cout << " x "; + } + std::cout << ")" << std::endl; + } + + } catch (const H5::Exception& err) { + std::cerr << "Error writing float dataset " << path << ": " + << err.getDetailMsg() << std::endl; + throw; + } +} + +//============================================================================= +// Write string dataset with attributes +//============================================================================= +/** + * Writes a string dataset to the HDF5 file with all its attributes. + * The parent group hierarchy is created automatically if it doesn't exist. + * Handles both scalar strings and arrays of strings. + * + * @param file HDF5 file object + * @param path Dataset path (groups created automatically) + * @param data PNXdata object containing data, dimensions, and attributes + * @throws H5::Exception if writing fails + */ +void nxH5::PNeXus::WriteStringDataset(H5::H5File& file, const std::string& path, + const PNXdata& data) +{ + try { + // Extract parent path and dataset name + size_t lastSlash = path.find_last_of('/'); + std::string parentPath = (lastSlash == 0) ? "/" : path.substr(0, lastSlash); + std::string datasetName = path.substr(lastSlash + 1); + + // Create parent group hierarchy + if (!parentPath.empty() && parentPath != "/") { + CreateGroupHierarchy(file, parentPath); + } + + // Get data + const auto& buffer = data.GetData(); + const auto& dims = data.GetDimensions(); + + /* //as35 + // Handle single string vs array of strings + if (buffer.size() == 1 && dims.size() == 1 && dims[0] == 1) { + // Single string - use variable-length string type + H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); + H5::DataSpace dataspace(H5S_SCALAR); + + H5::DataSet dataset = file.createDataSet(path, strType, dataspace); + dataset.write(buffer[0], strType); + + WriteDatasetAttributes(dataset, data); + dataset.close(); + dataspace.close(); + + } else { + */ //as35 + // Array of strings + H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); + H5::DataSpace dataspace(dims.size(), dims.data()); + + H5::DataSet dataset = file.createDataSet(path, strType, dataspace); + + // For arrays, need to create array of C strings + std::vector cStrings; + for (const auto& str : buffer) { + cStrings.push_back(str.c_str()); + } + dataset.write(cStrings.data(), strType); + + WriteDatasetAttributes(dataset, data); + dataset.close(); + dataspace.close(); + /* //as35 + } + */ //as35 + + if (fPrintDebug) { + std::cout << "debug> Wrote string dataset: " << path << std::endl; + } + + } catch (const H5::Exception& err) { + std::cerr << "Error writing string dataset " << path << ": " + << err.getDetailMsg() << std::endl; + throw; + } +} + +//============================================================================= +// Write group attributes +//============================================================================= +/** + * Writes attributes to an HDF5 group object. Supports int, float, and + * string attribute types. + * + * @param group HDF5 group object to write attributes to + * @param attributes Map of attribute names to values (stored as std::any) + */ +void nxH5::PNeXus::WriteGroupAttributes(H5::Group& group, const std::map& attributes) +{ + for (const auto& [attrName, attrValue] : attributes) { + try { + // Try int + if (auto* intVal = std::any_cast(&attrValue)) { + H5::DataSpace attrSpace(H5S_SCALAR); + H5::Attribute attr = group.createAttribute( + attrName, H5::PredType::NATIVE_INT32, attrSpace + ); + attr.write(H5::PredType::NATIVE_INT32, intVal); + attr.close(); + attrSpace.close(); + } + // Try float + else if (auto* floatVal = std::any_cast(&attrValue)) { + H5::DataSpace attrSpace(H5S_SCALAR); + H5::Attribute attr = group.createAttribute( + attrName, H5::PredType::NATIVE_FLOAT, attrSpace + ); + attr.write(H5::PredType::NATIVE_FLOAT, floatVal); + attr.close(); + attrSpace.close(); + } + // Try string + else if (auto* strVal = std::any_cast(&attrValue)) { + H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); + H5::DataSpace attrSpace(H5S_SCALAR); + H5::Attribute attr = group.createAttribute( + attrName, strType, attrSpace + ); + attr.write(strType, *strVal); + attr.close(); + attrSpace.close(); + } + // Try vector for multi-element attributes + else if (auto* intVec = std::any_cast>(&attrValue)) { + hsize_t dims[1] = {intVec->size()}; + H5::DataSpace attrSpace(1, dims); + H5::Attribute attr = group.createAttribute( + attrName, H5::PredType::NATIVE_INT32, attrSpace + ); + attr.write(H5::PredType::NATIVE_INT32, intVec->data()); + attr.close(); + attrSpace.close(); + } + // Try vector for multi-element attributes + else if (auto* floatVec = std::any_cast>(&attrValue)) { + hsize_t dims[1] = {floatVec->size()}; + H5::DataSpace attrSpace(1, dims); + H5::Attribute attr = group.createAttribute( + attrName, H5::PredType::NATIVE_FLOAT, attrSpace + ); + attr.write(H5::PredType::NATIVE_FLOAT, floatVec->data()); + attr.close(); + attrSpace.close(); + } + else { + if (fPrintDebug) { + std::cerr << "Warning: Unsupported attribute type for " + << attrName << std::endl; + } + } + + } catch (const H5::Exception& err) { + if (fPrintDebug) { + std::cerr << "Warning: Failed to write group attribute " + << attrName << ": " << err.getDetailMsg() << std::endl; + } + } + } +} + +//============================================================================= +// Write root-level file attributes +//============================================================================= +/** + * Writes root-level file attributes such as NeXus_version, HDF5_version, + * file_name, and file_time to the HDF5 file. + * + * @param file HDF5 file object + * @throws H5::Exception if writing fails + */ +void nxH5::PNeXus::WriteFileAttributes(H5::H5File& file) +{ + try { + H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); + H5::DataSpace attrSpace(H5S_SCALAR); + + // Check if custom root-level attributes exist + auto rootAttrIt = fGroupAttributes.find("/"); + bool hasCustomAttrs = (rootAttrIt != fGroupAttributes.end()); + + // NeXus_version attribute + if (!fNeXusVersion.empty() && + (!hasCustomAttrs || rootAttrIt->second.find("NeXus_version") == rootAttrIt->second.end())) { + H5::Attribute attr = file.createAttribute( + "NeXus_version", strType, attrSpace + ); + attr.write(strType, fNeXusVersion); + attr.close(); + } + + // HDF5_version attribute + if (!fHdf5Version.empty() && + (!hasCustomAttrs || rootAttrIt->second.find("HDF5_version") == rootAttrIt->second.end())) { + H5::Attribute attr = file.createAttribute( + "HDF5_version", strType, attrSpace + ); + attr.write(strType, fHdf5Version); + attr.close(); + } + + // file_name attribute + if (!fFileNameNxs.empty() && + (!hasCustomAttrs || rootAttrIt->second.find("file_name") == rootAttrIt->second.end())) { + H5::Attribute attr = file.createAttribute( + "file_name", strType, attrSpace + ); + attr.write(strType, fFileNameNxs); + attr.close(); + } + + // file_time attribute + if (!fFileTimeNxs.empty() && + (!hasCustomAttrs || rootAttrIt->second.find("file_time") == rootAttrIt->second.end())) { + H5::Attribute attr = file.createAttribute( + "file_time", strType, attrSpace + ); + attr.write(strType, fFileTimeNxs); + attr.close(); + } + + attrSpace.close(); + + // Write any custom root-level attributes from fGroupAttributes["/"] + if (hasCustomAttrs) { + if (fPrintDebug) { + std::cout << "debug> Writing " << rootAttrIt->second.size() + << " custom attributes to root level" << std::endl; + } + + for (const auto& [attrName, attrValue] : rootAttrIt->second) { + try { + // Try int + if (auto* intVal = std::any_cast(&attrValue)) { + H5::DataSpace scalarSpace(H5S_SCALAR); + H5::Attribute attr = file.createAttribute( + attrName, H5::PredType::NATIVE_INT32, scalarSpace + ); + attr.write(H5::PredType::NATIVE_INT32, intVal); + attr.close(); + scalarSpace.close(); + } + // Try float + else if (auto* floatVal = std::any_cast(&attrValue)) { + H5::DataSpace scalarSpace(H5S_SCALAR); + H5::Attribute attr = file.createAttribute( + attrName, H5::PredType::NATIVE_FLOAT, scalarSpace + ); + attr.write(H5::PredType::NATIVE_FLOAT, floatVal); + attr.close(); + scalarSpace.close(); + } + // Try string + else if (auto* strVal = std::any_cast(&attrValue)) { + H5::StrType varStrType(H5::PredType::C_S1, H5T_VARIABLE); + H5::DataSpace scalarSpace(H5S_SCALAR); + H5::Attribute attr = file.createAttribute( + attrName, varStrType, scalarSpace + ); + attr.write(varStrType, *strVal); + attr.close(); + scalarSpace.close(); + } + else { + if (fPrintDebug) { + std::cerr << "Warning: Unsupported attribute type for root-level " + << attrName << std::endl; + } + } + } catch (const H5::Exception& err) { + if (fPrintDebug) { + std::cerr << "Warning: Failed to write root-level attribute " + << attrName << ": " << err.getDetailMsg() << std::endl; + } + } + } + } + + } catch (const H5::Exception& err) { + std::cerr << "Error writing file attributes: " + << err.getDetailMsg() << std::endl; + throw; + } +} + +//============================================================================= +// Group attribute management methods +//============================================================================= + +/** + * Add or update an attribute for a group. + * + * @param groupPath HDF5 path of the group (e.g., "/raw_data_1") + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added successfully + */ +bool nxH5::PNeXus::AddGroupAttribute(const std::string& groupPath, const std::string& attrName, + const std::any& attrValue) +{ + fGroupAttributes[groupPath][attrName] = attrValue; return true; } -//----------------------------------------------------------------------------------------------------- -// GetStringData (private) -//----------------------------------------------------------------------------------------------------- /** - *

+ * Remove an attribute from a group. * - * return: - * - NX_OK on success - * - NX_ERROR otherwiese - * - * \param str std::string to be fed + * @param groupPath HDF5 path of the group + * @param attrName Attribute name to remove + * @return true if attribute was removed, false if group or attribute not found */ -NXstatus PNeXus::GetStringData(std::string &str) +bool nxH5::PNeXus::RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName) { - int i, status, rank, type, dims[32]; - char cstr[1024]; - NXname data_value; - - status = NXgetinfo(fFileHandle, &rank, dims, &type); - if (status != NX_OK) { - std::cerr << std::endl << ">> **ERROR** in NXgetinfo: couldn't get meta info!" << std::endl; - fErrorCode = PNEXUS_GET_META_INFO_ERROR; - fErrorMsg = "PNeXus::GetStringData() **ERROR** couldn't get meta info!"; - return NX_ERROR; - } - - if ((type != NX_CHAR) || (rank > 1) || (dims[0] >= (int)sizeof(cstr))) { - std::cerr << std::endl << ">> **ERROR** in NXgetinfo: found wrong meta info!" << std::endl; - fErrorCode = PNEXUS_WRONG_META_INFO; - fErrorMsg = "PNeXus::GetStringData() **ERROR** found wrong meta info!"; - return NX_ERROR; - } - - status = NXgetdata(fFileHandle, data_value); - if (status != NX_OK) { - std::cerr << std::endl << ">> **ERROR** in routine NXgetdata: couldn't get data for '" << str << "'!" << std::endl; - fErrorCode = PNEXUS_GET_DATA_ERROR; - fErrorMsg = "couldn't get data!"; - return NX_ERROR; - } - - for (i = 0; i < dims[0]; i++) - cstr[i] = *(data_value + i); - cstr[i] = '\0'; - - str = cstr; - - return NX_OK; -} - -//----------------------------------------------------------------------------------------------------- -// GetStringAttr (private) -//----------------------------------------------------------------------------------------------------- -/** - *

- * - * return: - * - NX_OK on success - * - NX_ERROR otherwiese - * - * \param attr attribute tag - * \param str std::string to be fed - */ -NXstatus PNeXus::GetStringAttr(std::string attr, std::string &str) -{ - int i, status, attlen, atttype; - char cstr[VGNAMELENMAX]; - NXname data_value; - - attlen = VGNAMELENMAX - 1; - atttype = NX_CHAR; - status = NXgetattr(fFileHandle, (char *)attr.c_str(), data_value, &attlen, &atttype); - if (status != NX_OK) { - std::cerr << std::endl << ">> **ERROR** in routine NXgetattr: couldn't get attribute '" << attr << "'! status=" << status << std::endl << std::endl; - fErrorCode = PNEXUS_GET_ATTR_ERROR; - fErrorMsg = "couldn't get std::string attribute data!"; - return NX_ERROR; - } - - for (i = 0; i < attlen; i++) - cstr[i] = *(data_value + i); - cstr[i] = '\0'; - - str = cstr; - - return NX_OK; -} - -//----------------------------------------------------------------------------------------------------- -// GetDataSize (private) -//----------------------------------------------------------------------------------------------------- -/** - *

- * - * return: - * - size in bytes of the given type - * - 0 if the type is not recognized - * - * \param type - */ -int PNeXus::GetDataSize(int type) -{ - int size; - - switch (type) { - case NX_CHAR: - size = SIZE_CHAR8; - break; - case NX_FLOAT32: - size = SIZE_FLOAT32; - break; - case NX_FLOAT64: - size = SIZE_FLOAT64; - break; - case NX_INT8: - size = SIZE_INT8; - break; - case NX_UINT8: - size = SIZE_UINT8; - break; - case NX_INT16: - size = SIZE_INT16; - break; - case NX_UINT16: - size = SIZE_UINT16; - break; - case NX_INT32: - size = SIZE_INT32; - break; - case NX_UINT32: - size = SIZE_UINT32; - break; - default: - size = 0; - break; - } - - return size; -} - -//----------------------------------------------------------------------------------------------------- -// GetDoubleVectorData (private) -//----------------------------------------------------------------------------------------------------- -/** - *

- * - * return: - * - NX_OK on success - * - NX_ERROR otherwiese - * - * \param data - */ -NXstatus PNeXus::GetDoubleVectorData(std::vector &data) -{ - // get information of the current nexus entity - int rank, type, dims[32], size, noOfElements; - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get data info!")) - return NX_ERROR; - - // calculate the needed size - size = dims[0]; - for (int i=1; i - * - * return: - * - NX_OK on success - * - NX_ERROR otherwiese - * - * \param data - */ -NXstatus PNeXus::GetIntVectorData(std::vector &data) -{ - // get information of the current nexus entity - int rank, type, dims[32], size, noOfElements; - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get data info!")) - return NX_ERROR; - - // calculate the needed size - size = dims[0]; - for (int i=1; iRead the NeXus file of type IDF Version 1. - * - * return: - * - NX_OK on successfull reading - * - NX_ERROR on error. The error code/message will give the details. - */ -int PNeXus::ReadFileIdf1() -{ - std::cout << std::endl << ">> reading NeXus IDF Version 1 file ..." << std::endl; - - // create first the necessary NXentry object for IDF Version 1 - fNxEntry1 = std::make_unique(); - if (fNxEntry1 == nullptr) { - fErrorCode = PNEXUS_OBJECT_INVOK_ERROR; - fErrorMsg = ">> **ERROR** couldn't invoke PNeXusEntry1 object."; - return NX_ERROR; - } - - std::string str; - char cstr[128]; - int ival; - float fval; - int attlen, atttype, rank, dims[32]; - NXname data_value, nxAttrName; - - // open file - NXstatus status; - status = NXopen(fFileName.c_str(), NXACC_READ, &fFileHandle); - if (status != NX_OK) { - fErrorCode = PNEXUS_FILE_OPEN_ERROR; - fErrorMsg = "PNeXus::ReadFile() **ERROR** Couldn't open file '"+fFileName+"' !"; - return NX_ERROR; - } - - // collect the NXroot attribute information - do { - status = NXgetnextattra(fFileHandle, nxAttrName, &rank, dims, &atttype); - if (status == NX_OK) { - if (!strcmp(nxAttrName, "HDF_version")) { - attlen = VGNAMELENMAX - 1; - atttype = NX_CHAR; - status = NXgetattr(fFileHandle, nxAttrName, data_value, &attlen, &atttype); - if (status == NX_OK) { - fFileFormatVersion = std::string(data_value); - } - } else if (!strcmp(nxAttrName, "HDF5_Version")) { - attlen = VGNAMELENMAX - 1; - atttype = NX_CHAR; - status = NXgetattr(fFileHandle, nxAttrName, data_value, &attlen, &atttype); - if (status == NX_OK) { - fFileFormatVersion = std::string("HDF5: ")+std::string(data_value); - } - } else if (!strcmp(nxAttrName, "XML_version")) { - attlen = VGNAMELENMAX - 1; - atttype = NX_CHAR; - status = NXgetattr(fFileHandle, nxAttrName, data_value, &attlen, &atttype); - if (status == NX_OK) { - fFileFormatVersion = std::string(data_value); - } - } else if (!strcmp(nxAttrName, "file_name")) { - if (!ErrorHandler(GetStringAttr("file_name", str), PNEXUS_GET_ATTR_ERROR, "couldn't read NXroot 'file_name' attribute!")) return NX_ERROR; - fFileName = str; - } else if (!strcmp(nxAttrName, "file_time")) { - if (!ErrorHandler(GetStringAttr("file_time", str), PNEXUS_GET_ATTR_ERROR, "couldn't read NXroot 'file_time' attribute!")) return NX_ERROR; - fFileTime = str; - } else if (!strcmp(nxAttrName, "user")) { - if (!ErrorHandler(GetStringAttr("user", str), PNEXUS_GET_ATTR_ERROR, "couldn't read NXroot 'user' attribute!")) return NX_ERROR; - fCreator = str; - } - } - } while (status == NX_OK); - - // look for the first occurring NXentry - bool found = false; - NXname nxname, nxclass; - int dataType; - do { - status = NXgetnextentry(fFileHandle, nxname, nxclass, &dataType); - if (strstr(nxclass, "NXentry") != NULL) - found = true; - } while (!found || (status == NX_EOD)); - // make sure any NXentry has been found - if (!found) { - fErrorCode = PNEXUS_NXENTRY_NOT_FOUND; - fErrorMsg = ">> **ERROR** Couldn't find any NXentry!"; - return NX_ERROR; - } - // open the NXentry group to obtain the necessary stuff - status = NXopengroup(fFileHandle, nxname, "NXentry"); - if (status != NX_OK) { - fErrorCode = PNEXUS_GROUP_OPEN_ERROR; - fErrorMsg = "PNeXus::ReadFile() **ERROR** Couldn't open the NeXus group '" + std::string(nxname) + "'!"; - return NX_ERROR; - } - - // program_name - if (!ErrorHandler(NXopendata(fFileHandle, "program_name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'program_name' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'program_name' data in NXentry!")) return NX_ERROR; - fNxEntry1->SetProgramName(str); - if (!ErrorHandler(GetStringAttr("version", str), PNEXUS_GET_ATTR_ERROR, "couldn't read 'program_name' attribute in NXentry!")) return NX_ERROR; - fNxEntry1->SetProgramVersion(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'program_name' data in NXentry!")) return NX_ERROR; - - // run number - if (!ErrorHandler(NXopendata(fFileHandle, "number"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'number' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &ival), PNEXUS_GET_DATA_ERROR, "couldn't read 'number' data in NXentry!")) return NX_ERROR; - fNxEntry1->SetRunNumber(ival); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'number' data in NXentry!")) return NX_ERROR; - - // title - if (!ErrorHandler(NXopendata(fFileHandle, "title"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'title' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'title' data in NXentry!")) return NX_ERROR; - fNxEntry1->SetTitle(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'title' data in NXentry!")) return NX_ERROR; - - // notes - if (!ErrorHandler(NXopendata(fFileHandle, "notes"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'notes' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'notes' data in NXentry!")) return NX_ERROR; - fNxEntry1->SetNotes(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'notes' data in NXentry!")) return NX_ERROR; - - // analysis tag - if (!ErrorHandler(NXopendata(fFileHandle, "analysis"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'analysis' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'analysis' data in NXentry!")) return NX_ERROR; - fNxEntry1->SetAnalysis(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'analysis' data in NXentry!")) return NX_ERROR; - - // lab - if (!ErrorHandler(NXopendata(fFileHandle, "lab"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'lab' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'lab' data in NXentry!")) return NX_ERROR; - fNxEntry1->SetLaboratory(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'lab' data in NXentry!")) return NX_ERROR; - - // beamline - if (!ErrorHandler(NXopendata(fFileHandle, "beamline"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'beamline' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'beamline' data in NXentry!")) return NX_ERROR; - fNxEntry1->SetBeamline(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'beamline' data in NXentry!")) return NX_ERROR; - - // start time - if (!ErrorHandler(NXopendata(fFileHandle, "start_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'start_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'start_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'start_time' data in NXentry!")) return NX_ERROR; - if (fNxEntry1->SetStartTime(str) != NX_OK) { - fErrorCode = PNEXUS_TIME_FORMAT_ERROR; - fErrorMsg = "start time format is not ISO 8601 conform."; - } - - // stop time - if (!ErrorHandler(NXopendata(fFileHandle, "stop_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'start_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'start_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'start_time' data in NXentry!")) return NX_ERROR; - if (fNxEntry1->SetStopTime(str) != NX_OK) { - fErrorCode = PNEXUS_TIME_FORMAT_ERROR; - fErrorMsg = "stop time format is not ISO 8601 conform.."; - } - - // switching state (red/green mode) - if (!ErrorHandler(NXopendata(fFileHandle, "switching_states"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'switching_states' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &ival), PNEXUS_GET_DATA_ERROR, "couldn't read 'switching_states' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'switching_states' data in NXentry!")) return NX_ERROR; - if (fNxEntry1->SetSwitchingState(ival)) { - fErrorCode = PNEXUS_DATA_ERROR; - fErrorMsg = "unkown switching state found."; - } - - // open group NXuser - if (!ErrorHandler(NXopengroup(fFileHandle, "user", "NXuser"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup 'user'!")) return NX_ERROR; - - // change to subgroup - if (!ErrorHandler(NXinitgroupdir(fFileHandle), PNEXUS_INIT_GROUPDIR_ERROR, "couldn't init group directory")) return NX_ERROR; - int numItems = 0; - NXname groupName, className; - if (!ErrorHandler(NXgetgroupinfo(fFileHandle, &numItems, groupName, className), PNEXUS_GET_GROUP_INFO_ERROR, "couldn't get user group info")) return NX_ERROR; - - // get all the user info - // go through the user list and filter out the required items - NXname nx_label; - int nx_dataType; - for (int i=0; iGetUser()->SetName(str); - } - if (!strcmp(nx_label, "experiment_number")) { - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't get user name")) return NX_ERROR; - fNxEntry1->GetUser()->SetExperimentNumber(str); - } - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close data")) return NX_ERROR; - } - - // close group NXuser - NXclosegroup(fFileHandle); - - // open group NXsample - if (SearchInGroup("NXsample", "class", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopengroup(fFileHandle, "sample", "NXsample"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup 'sample'!")) return NX_ERROR; - } else { - std::cout << std::endl << ">> **WARNING** unable to open subgroup NXsample, will try NXSample." << std::endl; - if (!ErrorHandler(NXopengroup(fFileHandle, "sample", "NXSample"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup 'sample'!")) return NX_ERROR; - } - - // read sample name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in sample group")) return NX_ERROR; - fNxEntry1->GetSample()->SetName(str); - - // read sample temperature - if (!ErrorHandler(NXopendata(fFileHandle, "temperature"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'temperature' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &fval), PNEXUS_GET_DATA_ERROR, "couldn't read 'temperature' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read temperature units!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'temperature' data in sample group")) return NX_ERROR; - fNxEntry1->GetSample()->SetPhysProp("temperature", (double)fval, str); - - // read sample magnetic field - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'magnetic_field' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &fval), PNEXUS_GET_DATA_ERROR, "couldn't read 'magnetic_field' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read magnetic_field units!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'magnetic_field' data in sample group")) return NX_ERROR; - fNxEntry1->GetSample()->SetPhysProp("magnetic_field", (double)fval, str); - - // read sample shape, e.g. powder, single crystal, etc (THIS IS AN OPTIONAL ENTRY) - if (SearchInGroup("shape", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "shape"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'shape' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'shape' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'shape' data in sample group")) return NX_ERROR; - fNxEntry1->GetSample()->SetShape(str); - } - - // read magnetic field state, e.g. TF, LF, ZF - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_state"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'magnetic_field_state' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'magnetic_field_state' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'magnetic_field_state' data in sample group")) return NX_ERROR; - fNxEntry1->GetSample()->SetMagneticFieldState(str); - - // read 'magnetic_field_vector' and 'coordinate_system' attribute - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_vector"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'magnetic_field_vector' data in sample group!")) return NX_ERROR; - int attLen = 1, attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "available", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &ival, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'magnetic_field_vector available' data in sample group!")) return NX_ERROR; - fNxEntry1->GetSample()->SetMagneticFieldVectorAvailable(ival); - if (fNxEntry1->GetSample()->IsMagneticFieldVectorAvailable()) { - std::vector magVec; - if (!ErrorHandler(GetDoubleVectorData(magVec), PNEXUS_GET_DATA_ERROR, "couldn't get 'magnetic_field_vector' data!")) return NX_ERROR; - fNxEntry1->GetSample()->SetMagneticFieldVector(magVec); - magVec.clear(); - if (!ErrorHandler(GetStringAttr("coordinate_system", str), PNEXUS_GET_ATTR_ERROR, "couldn't read magnetic field coordinate_system!")) return NX_ERROR; - fNxEntry1->GetSample()->SetMagneticFieldVectorCoordinateSystem(str); - - // workaround since not all ISIS IDF 1 files have the 'units' entry!! - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "units", sizeof(cstr)); - if (SearchAttrInData(cstr, attLen, attType)) { - status = NXgetattr(fFileHandle, cstr, data_value, &attLen, &attType); - if (status == NX_OK) { - strncpy(cstr, data_value, sizeof(cstr)); - str = cstr; - } else { - str = std::string("Gauss"); - } - } else { - str = std::string("Gauss"); - } - fNxEntry1->GetSample()->SetMagneticFieldUnits(str); - } - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'magnetic_field_vector' data in sample group")) return NX_ERROR; - - // read sample environment, e.g. CCR, LowTemp-2, etc. - if (!ErrorHandler(NXopendata(fFileHandle, "environment"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'environment' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'environment' data in sample group!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'environment' data in sample group")) return NX_ERROR; - fNxEntry1->GetSample()->SetEnvironment(str); - - // close group NXsample - NXclosegroup(fFileHandle); - - // get required instrument information - // open subgroup NXinstrument with subgroups detector, collimator, beam - if (!ErrorHandler(NXopengroup(fFileHandle, "instrument", "NXinstrument"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup instrument!")) return NX_ERROR; - - // get instrument name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in instrument group!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in instrument group!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in instrument group")) return NX_ERROR; - fNxEntry1->GetInstrument()->SetName(str); - - // open subgroup NXdetector - if (!ErrorHandler(NXopengroup(fFileHandle, "detector", "NXdetector"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup detector!")) return NX_ERROR; - - // get number of detectors - if (!ErrorHandler(NXopendata(fFileHandle, "number"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'number' data in detector group!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &ival), PNEXUS_GET_DATA_ERROR, "couldn't read 'number' data in detector group!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'number' data in detector group")) return NX_ERROR; - fNxEntry1->GetInstrument()->GetDetector()->SetNumber(ival); - - // close subgroup NXdetector - NXclosegroup(fFileHandle); - - // open subgroup NXcollimator - if (!ErrorHandler(NXopengroup(fFileHandle, "collimator", "NXcollimator"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup collimator!")) return NX_ERROR; - - // get collimator type - if (!ErrorHandler(NXopendata(fFileHandle, "type"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'type' data in collimator group!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'type' data in collimator group!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'type' data in collimator group")) return NX_ERROR; - fNxEntry1->GetInstrument()->GetCollimator()->SetType(str); - - // close subgroup NXcollimator - NXclosegroup(fFileHandle); - - // open subgroup NXbeam - if (!ErrorHandler(NXopengroup(fFileHandle, "beam", "NXbeam"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup beam!")) return NX_ERROR; - - // get the total counts - if (!ErrorHandler(NXopendata(fFileHandle, "total_counts"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'total_counts' data in beam group!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &fval), PNEXUS_GET_DATA_ERROR, "couldn't read 'total_counts' data in beam group!")) return NX_ERROR; - fNxEntry1->GetInstrument()->GetBeam()->SetTotalCounts((double)fval); - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read total_counts units!")) return NX_ERROR; - fNxEntry1->GetInstrument()->GetBeam()->SetUnits(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'total_counts' data in beam group")) return NX_ERROR; - - // close subgroup NXbeam - NXclosegroup(fFileHandle); - - // close subgroup NXinstrument - NXclosegroup(fFileHandle); - - // find the first occuring NXdata class - found = false; - do { - status = NXgetnextentry(fFileHandle, nxname, nxclass, &dataType); - if (strstr(nxclass, "NXdata") != NULL) - found = true; - } while (!found || (status == NX_EOD)); - // make sure any NXentry has been found - if (!found) { - fErrorCode = PNEXUS_GROUP_OPEN_ERROR; - fErrorMsg = ">> **ERROR** Couldn't find any NXdata!"; - return NX_ERROR; - } - - // open subgroup NXdata (this is for Version 1, only and is subject to change in the near future!) - memset(cstr, '\0', sizeof(cstr)); - snprintf(cstr, sizeof(cstr), "couldn't open NeXus subgroup %s!", nxname); - if (!ErrorHandler(NXopengroup(fFileHandle, nxname, "NXdata"), PNEXUS_GROUP_OPEN_ERROR, cstr)) return NX_ERROR; - - // get time resolution - if (!ErrorHandler(NXopendata(fFileHandle, "resolution"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'resolution' data in NXdata group!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &ival), PNEXUS_GET_DATA_ERROR, "couldn't read 'resolution' data in NXdata group!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read 'resolution' units!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'resolution' data in NXdata group")) return NX_ERROR; - if (!strcmp(str.c_str(), "picoseconds")) { - fNxEntry1->GetData()->SetTimeResolution((double)ival, "ps"); - } else if (!strcmp(str.c_str(), "femtoseconds")) { - fNxEntry1->GetData()->SetTimeResolution((double)ival, "fs"); - } - - // get data, t0, first good bin, last good bin, data - if (!ErrorHandler(NXopendata(fFileHandle, "counts"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'counts' data in NXdata group!")) return NX_ERROR; - int histoLength=0; - int noOfHistos=0; - - // get number of histos - attLen = 1; - attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "number", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &noOfHistos, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'number' data in NXdata group!")) return NX_ERROR; - - // get histo length - attLen = 1; - attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "length", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &histoLength, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'length' data in NXdata group!")) return NX_ERROR; - - // get t0 - attLen = 1; - attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "T0_bin", sizeof(cstr)); - if (SearchAttrInData(cstr, attLen, attType)) { - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &ival, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'T0_bin' data in NXdata group!")) return NX_ERROR; - } else { - std::cout << std::endl << ">> **WARNING** didn't find attribute 'T0_bin' in NXdata/counts, will try 't0_bin'." << std::endl; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "t0_bin", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &ival, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 't0_bin' data in NXdata group!")) return NX_ERROR; - } - fNxEntry1->GetData()->SetT0(ival); - - // get first good bin - attLen = 1; - attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "first_good_bin", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &ival, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'first_good_bin' data in NXdata group!")) return NX_ERROR; - fNxEntry1->GetData()->SetFirstGoodBin(ival); - - // get last good bin - attLen = 1; - attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "last_good_bin", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &ival, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'last_good_bin' data in NXdata group!")) return NX_ERROR; - fNxEntry1->GetData()->SetLastGoodBin(ival); - - // get data - - // get information of the current nexus entity - int type, size, noOfElements; - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get data info!")) return NX_ERROR; - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetData()->FlushHistos(); - std::vector data; - for (int i=0; i0)) { - fNxEntry1->GetData()->SetHisto(data); - data.clear(); - data.push_back(*(i_data_ptr+i)); - } else { - data.push_back(*(i_data_ptr+i)); - } - } - fNxEntry1->GetData()->SetHisto(data); - data.clear(); - - // clean up - if (data_ptr) { - delete [] data_ptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'counts' data in NXdata group")) return NX_ERROR; - - // get grouping - if (!ErrorHandler(NXopendata(fFileHandle, "grouping"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'grouping' data in NXdata group!")) return NX_ERROR; - attLen = 1; - attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "available", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &ival, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'grouping available' data in NXdata group!")) return NX_ERROR; - if (ival) { - std::vector grouping; - if (!ErrorHandler(GetIntVectorData(grouping), PNEXUS_GET_DATA_ERROR, "couldn't read 'grouping' data in NXdata group!")) return NX_ERROR; - fNxEntry1->GetData()->SetGrouping(grouping); - grouping.clear(); - } - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'grouping' data in NXdata group")) return NX_ERROR; - - // if grouping has been available check for consistency - bool ok=true; - if (ival) { // i.e. grouping is available (see a few lines further up) - // check grouping vector for consistency - if (fNxEntry1->GetData()->GetGrouping()->size() != fNxEntry1->GetData()->GetNoOfHistos()) { - fNxEntry1->GetData()->FlushGrouping(); - std::cerr << std::endl << ">> **WARNING** grouping vector size (" << fNxEntry1->GetData()->GetGrouping()->size()<< ") != no of histograms (" << fNxEntry1->GetData()->GetNoOfHistos() << ") which doesn't make sence, hence grouping will be ignored." << std::endl; - } - // check that the grouping values do make sense, i.e. allowed range is grouping value > 0 and grouping value <= # of histos - for (unsigned int i=0; iGetData()->GetGrouping()->size(); i++) { - if ((fNxEntry1->GetData()->GetGrouping()->at(i) == 0) || (fNxEntry1->GetData()->GetGrouping()->at(i) > (int)fNxEntry1->GetData()->GetNoOfHistos())) { - std::cerr << std::endl << ">> **WARNING** found grouping entry '" << fNxEntry1->GetData()->GetGrouping()->at(i) << "' which doesn't make sense, hence grouping will be ignored." << std::endl; - ok = false; - break; - } - } - if (!ok) - fNxEntry1->GetData()->FlushGrouping(); - } - - // get alpha - if (!ErrorHandler(NXopendata(fFileHandle, "alpha"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'alpha' data in NXdata group!")) return NX_ERROR; - attLen = 1; - attType = NX_INT32; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "available", sizeof(cstr)); - if (!ErrorHandler(NXgetattr(fFileHandle, cstr, &ival, &attLen, &attType), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'alpha available' data in NXdata group!")) return NX_ERROR; - if (ival) { - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get alpha info!")) return NX_ERROR; - // calculate the needed size - size = dims[0]; - for (int i=1; i> **ERROR** " << fErrorMsg << std::endl; - return NX_ERROR; - } - - // allocate locale memory to get the data - char *data_ptr = new char[size]; - if (data_ptr == nullptr) { - return NX_ERROR; - } - - // get the data - float *f_data_ptr = (float*) data_ptr; - status = NXgetdata(fFileHandle, f_data_ptr); - if (status != NX_OK) { - return NX_ERROR; - } - - // copy the data into the vector - fNxEntry1->GetData()->FlushAlpha(); - PNeXusAlpha1 alpha; - std::vector alphaVec; - for (int i=0; iGetData()->SetAlpha(alphaVec); - - // clean up - alphaVec.clear(); - if (data_ptr) { - delete [] data_ptr; - } - } - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'alpha' data in NXdata group")) return NX_ERROR; - - // close subgroup NXdata - NXclosegroup(fFileHandle); - - // close group NXentry - NXclosegroup(fFileHandle); - - // close file - NXclose(&fFileHandle); - - GroupHistoData(); - - fValid = true; - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// ReadFileIdf2 (private) -//------------------------------------------------------------------------------------------ -/** - *

Read the NeXus file of type IDF Version 2. - * - * return: - * - NX_OK on successfull reading - * - NX_ERROR on error. The error code/message will give the details. - */ -int PNeXus::ReadFileIdf2() -{ - std::cout << std::endl << ">> reading NeXus IDF Version 2 file ..." << std::endl; - - // create first the necessary NXentry object for IDF Version 2 - fNxEntry2 = std::make_unique(); - if (fNxEntry2 == nullptr) { - fErrorCode = PNEXUS_OBJECT_INVOK_ERROR; - fErrorMsg = ">> **ERROR** couldn't invoke PNeXusEntry2 object."; - return NX_ERROR; - } - - std::string str; - int ival, attlen, atttype; - float fval; - NXname data_value, nxAttrName; - int rank, type, dims[32], size, noOfElements; - - // open file - NXstatus status; - status = NXopen(fFileName.c_str(), NXACC_READ, &fFileHandle); - if (status != NX_OK) { - fErrorCode = PNEXUS_FILE_OPEN_ERROR; - fErrorMsg = "PNeXus::ReadFileIdf2() **ERROR** Couldn't open file '"+fFileName+"' !"; - return NX_ERROR; - } - - // collect the NXroot attribute information - do { - status = NXgetnextattra(fFileHandle, nxAttrName, &rank, dims, &atttype); - if (status == NX_OK) { - if (!strcmp(nxAttrName, "HDF_version")) { - attlen = VGNAMELENMAX - 1; - atttype = NX_CHAR; - status = NXgetattr(fFileHandle, nxAttrName, data_value, &attlen, &atttype); - if (status == NX_OK) { - fFileFormatVersion = std::string(data_value); - } - } else if (!strcmp(nxAttrName, "HDF5_Version")) { - attlen = VGNAMELENMAX - 1; - atttype = NX_CHAR; - status = NXgetattr(fFileHandle, nxAttrName, data_value, &attlen, &atttype); - if (status == NX_OK) { - fFileFormatVersion = std::string("HDF5: ")+std::string(data_value); - } - } else if (!strcmp(nxAttrName, "XML_version")) { - attlen = VGNAMELENMAX - 1; - atttype = NX_CHAR; - status = NXgetattr(fFileHandle, nxAttrName, data_value, &attlen, &atttype); - if (status == NX_OK) { - fFileFormatVersion = std::string(data_value); - } - } else if (!strcmp(nxAttrName, "file_name")) { - if (!ErrorHandler(GetStringAttr("file_name", str), PNEXUS_GET_ATTR_ERROR, "couldn't read NXroot 'file_name' attribute!")) return NX_ERROR; - fFileName = str; - } else if (!strcmp(nxAttrName, "file_time")) { - if (!ErrorHandler(GetStringAttr("file_time", str), PNEXUS_GET_ATTR_ERROR, "couldn't read NXroot 'file_time' attribute!")) return NX_ERROR; - fFileTime = str; - } else if (!strcmp(nxAttrName, "creator")) { - if (!ErrorHandler(GetStringAttr("creator", str), PNEXUS_GET_ATTR_ERROR, "couldn't read NXroot 'creator' attribute!")) return NX_ERROR; - fCreator = str; - } - } - } while (status == NX_OK); - - // look for the first occurring NXentry which name ends on "_1" - NXname nxname, nxclass; - int dataType; - bool found = false; - size_t pos; - do { - status = NXgetnextentry(fFileHandle, nxname, nxclass, &dataType); - if (!strcmp(nxclass, "NXentry")) { - str = nxname; - pos = str.find_last_of("_1"); - if (pos != str.npos) - found = true; - } - } while (!found && (status == NX_OK)); - if (!found) { - fErrorCode = PNEXUS_NXENTRY_NOT_FOUND; - fErrorMsg = ">> **ERROR** Couldn't find any NXentry on NXroot level!"; - return NX_ERROR; - } - - // open the NXentry group to obtain the necessary stuff - status = NXopengroup(fFileHandle, nxname, "NXentry"); - if (status != NX_OK) { - fErrorCode = PNEXUS_GROUP_OPEN_ERROR; - fErrorMsg = "PNeXus::ReadFileIdf2() **ERROR** Couldn't open the NeXus group '" + std::string(nxname) + "'!"; - return NX_ERROR; - } - - // beamline - if (SearchInGroup("beamline", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "beamline"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'beamline' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'beamline' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'beamline' data in NXentry!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetBeamline()->SetName(str); - } - - // definition - if (!ErrorHandler(NXopendata(fFileHandle, "definition"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'definition' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'definition' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'definition' data in NXentry!")) return NX_ERROR; - fNxEntry2->SetDefinition(str); - - // program_name and version - if (SearchInGroup("program_name", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "program_name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'program_name' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'program_name' data in NXentry!")) return NX_ERROR; - fNxEntry2->SetProgramName(str); - if (!ErrorHandler(GetStringAttr("version", str), PNEXUS_GET_ATTR_ERROR, "couldn't read 'program_name' attribute in NXentry!")) return NX_ERROR; - fNxEntry2->SetProgramVersion(str); - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'program_name' data in NXentry!")) return NX_ERROR; - } - - // run number - if (!ErrorHandler(NXopendata(fFileHandle, "run_number"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'run_number' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &ival), PNEXUS_GET_DATA_ERROR, "couldn't read 'run_number' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'run_number' data in NXentry!")) return NX_ERROR; - fNxEntry2->SetRunNumber(ival); - - // title - if (!ErrorHandler(NXopendata(fFileHandle, "title"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'title' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'title' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'title' data in NXentry!")) return NX_ERROR; - fNxEntry2->SetTitle(str); - - // start time - if (!ErrorHandler(NXopendata(fFileHandle, "start_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'start_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'start_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'start_time' data in NXentry!")) return NX_ERROR; - if (fNxEntry2->SetStartTime(str) != NX_OK) { - fErrorCode = PNEXUS_TIME_FORMAT_ERROR; - fErrorMsg = "start time format is not ISO 8601 conform."; - } - - // end time - if (!ErrorHandler(NXopendata(fFileHandle, "end_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'end_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'end_time' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'end_time' data in NXentry!")) return NX_ERROR; - if (fNxEntry2->SetStopTime(str) != NX_OK) { - fErrorCode = PNEXUS_TIME_FORMAT_ERROR; - fErrorMsg = "stop time format is not ISO 8601 conform.."; - } - - // experiment identifier - if (!ErrorHandler(NXopendata(fFileHandle, "experiment_identifier"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'experiment_identifier' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'experiment_identifier' data in NXentry!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'experiment_identifier' data in NXentry!")) return NX_ERROR; - fNxEntry2->SetExperimentIdentifier(str); - - // find entry for NXuser - if (SearchInGroup("NXuser", "class", nxname, nxclass, dataType)) { - // open the NXuser - status = NXopengroup(fFileHandle, nxname, "NXuser"); - if (status != NX_OK) { - fErrorCode = PNEXUS_GROUP_OPEN_ERROR; - fErrorMsg = "PNeXus::ReadFileIdf2() **ERROR** Couldn't open NXuser '" + std::string(nxname) + "' in NXentry!"; - return NX_ERROR; - } - - // user name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in NXuser!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in NXuser!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in NXuser!")) return NX_ERROR; - fNxEntry2->GetUser()->SetName(str); - - // close group NXuser - NXclosegroup(fFileHandle); - } - - // find entry for NXsample - if (SearchInGroup("NXsample", "class", nxname, nxclass, dataType)) { - // open group NXsample - if (!ErrorHandler(NXopengroup(fFileHandle, "sample", "NXsample"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup sample!")) return NX_ERROR; - - // sample name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in NXsample!")) return NX_ERROR; - fNxEntry2->GetSample()->SetName(str); - - // sample description - if (SearchInGroup("description", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "description"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'description' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'description' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'description' data in NXsample!")) return NX_ERROR; - fNxEntry2->GetSample()->SetDescription(str); - } - - // temperature - if (SearchInGroup("temperature_1", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "temperature_1"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'temperature_1' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &fval), PNEXUS_GET_DATA_ERROR, "couldn't read 'temperature_1' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read temperature units in NXsample!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'temperature_1' data in NXsample!")) return NX_ERROR; - fNxEntry2->GetSample()->SetPhysProp("temperature_1", fval, str); - } - - // temperature environment - if (SearchInGroup("temperature_1_env", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopengroup(fFileHandle, "temperature_1_env", "NXenvironment"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup NXenvironment!")) return NX_ERROR; - // temperature environment name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in NXenvironment!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in NXenvironment!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in NXenvironment!")) return NX_ERROR; - fNxEntry2->GetSample()->SetEnvironmentTemp(str); - // close group NXenvironment - NXclosegroup(fFileHandle); - } - - // magnetic_field - if (SearchInGroup("magnetic_field_1", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_1"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'magnetic_field_1' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &fval), PNEXUS_GET_DATA_ERROR, "couldn't read 'magnetic_field_1' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read magnetic field units in NXsample!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'magnetic_field_1' data in NXsample!")) return NX_ERROR; - fNxEntry2->GetSample()->SetPhysProp("magnetic_field_1", fval, str); - } - - // magnetic field state - if (SearchInGroup("magnetic_field_state", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_state"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'magnetic_field_state' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'magnetic_field_state' data in NXsample!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'magnetic_field_state' data in NXsample!")) return NX_ERROR; - fNxEntry2->GetSample()->SetMagneticFieldState(str); - } - - // magnetic field environment - if (SearchInGroup("magnetic_field_1_env", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopengroup(fFileHandle, "magnetic_field_1_env", "NXenvironment"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup NXenvironment!")) return NX_ERROR; - // magnetic field environment name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in NXenvironment!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in NXenvironment!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in NXenvironment!")) return NX_ERROR; - fNxEntry2->GetSample()->SetEnvironmentField(str); - // close group NXenvironment - NXclosegroup(fFileHandle); - } - - // close group NXsample - NXclosegroup(fFileHandle); - } - - // open group NXinstrument - if (!ErrorHandler(NXopengroup(fFileHandle, "instrument", "NXinstrument"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup instrument in NXentry!")) return NX_ERROR; - - // name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in NXinstrument!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in NXinstrument!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in NXinstrument!")) return NX_ERROR; - fNxEntry2->GetInstrument()->SetName(str); - - // open group NXsource - if (!ErrorHandler(NXopengroup(fFileHandle, "source", "NXsource"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup instrument in NXinstrument!")) return NX_ERROR; - - // name - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'name' data in NXsource!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'name' data in NXsource!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'name' data in NXsource!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetSource()->SetName(str); - - // type - if (!ErrorHandler(NXopendata(fFileHandle, "type"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'type' data in NXsource!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'type' data in NXsource!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'type' data in NXsource!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetSource()->SetType(str); - - // probe - if (!ErrorHandler(NXopendata(fFileHandle, "probe"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'probe' data in NXsource!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'probe' data in NXsource!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'probe' data in NXsource!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetSource()->SetProbe(str); - - // close group NXsource - NXclosegroup(fFileHandle); - - - // open group NXbeamline - if (SearchInGroup("beamline", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopengroup(fFileHandle, "beamline", "NXbeamline"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup beamline in NXinstrument!")) return NX_ERROR; - - // beamline name - if (!ErrorHandler(NXopendata(fFileHandle, "beamline"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'beamline' data in NXbeamline!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'beamline' data in NXbeamline!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'beamline' data in NXbeamline!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetBeamline()->SetName(str); - - // close group NXbeamline - NXclosegroup(fFileHandle); - } else { - std::cerr << "**WARNING** in class NXinstrument the object NXbeamline with name 'beamline' is missing!" << std::endl; - std::cerr << " Complain at the facility where you got this data file from!" << std::endl; - } - - // open group NXdetector in instrument (NXinstrument) - if (!ErrorHandler(NXopengroup(fFileHandle, "detector_1", "NXdetector"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open NeXus subgroup detector_1 in NXinstrument!")) return NX_ERROR; - - // description - if (SearchInGroup("description", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "description"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'description' data in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(GetStringData(str), PNEXUS_GET_DATA_ERROR, "couldn't read 'description' data in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'description' data in NXdetector!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetDetector()->SetDescription(str); - } else { - std::cerr << "**WARNING** in class NXdetector to object NX_CHAR with name 'description' is missing!" << std::endl; - std::cerr << " Complain at the facility where you got this data file from!" << std::endl; - } - - // get the time resolution. This is a little bit complicated since it is either present as 'histogram_resolution' or needs to be extracted from the 'raw_time' vector - // 1st check if 'histogram_resolution' is found - if (SearchInGroup("histogram_resolution", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "histogram_resolution"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'histogram_resolution' data in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &fval), PNEXUS_GET_DATA_ERROR, "couldn't read 'histogram_resolution' data in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read time resolution units in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'histogram_resolution' data in NXdetector!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetDetector()->SetTimeResolution((double)fval, str); - } else if (SearchInGroup("resolution", "name", nxname, nxclass, dataType)) { // 2nd check if 'resolution' is found - if (!ErrorHandler(NXopendata(fFileHandle, "resolution"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'resolution' data in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(NXgetdata(fFileHandle, &ival), PNEXUS_GET_DATA_ERROR, "couldn't read 'resolution' data in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read time resolution units in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'resolution' data in NXdetector!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetDetector()->SetTimeResolution((double)ival, str); - } else { // 3nd 'histogram_resolution' is not present, hence extract the time resolution from the 'raw_time' vector - if (!ErrorHandler(NXopendata(fFileHandle, "raw_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'raw_time' data in NXdetector!")) return NX_ERROR; - std::vector rawTime; - if (!ErrorHandler(GetDoubleVectorData(rawTime), PNEXUS_GET_DATA_ERROR, "couldn't get 'raw_time' data in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(GetStringAttr("units", str), PNEXUS_GET_ATTR_ERROR, "couldn't read 'raw_time' units in NXdetector!")) return NX_ERROR; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'raw_time' data in NXdetector!")) return NX_ERROR; - fNxEntry2->GetInstrument()->GetDetector()->SetTimeResolution(rawTime[1]-rawTime[0], str); - rawTime.clear(); - } - - // get counts - if (!ErrorHandler(NXopendata(fFileHandle, "counts"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'counts' data in NXdetector!")) return NX_ERROR; - // check the dimensions of 'counts' - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'counts' info in NXdetector!")) return NX_ERROR; - - // check if last_good_bin, first_good_bin, and t0_bins are defined as attributes - int attLen = 1, attType = NX_INT32; - if (SearchAttrInData("first_good_bin", attLen, attType)) { - char cstr[1204]; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "first_good_bin", sizeof(cstr)); - status = NXgetattr(fFileHandle, cstr, (void*)&ival, &attLen, &attType); - - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(1); // a single set for t0, fgb, lgb is likely given - int *i_data_ptr = new int; - *i_data_ptr = ival; - if (!fNxEntry2->GetInstrument()->GetDetector()->SetFirstGoodBin(i_data_ptr)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - } - if (SearchAttrInData("last_good_bin", attLen, attType)) { - char cstr[1204]; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "last_good_bin", sizeof(cstr)); - status = NXgetattr(fFileHandle, cstr, (void*)&ival, &attLen, &attType); - - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(1); // a single set for t0, fgb, lgb is likely given - int *i_data_ptr = new int; - *i_data_ptr = ival; - if (!fNxEntry2->GetInstrument()->GetDetector()->SetLastGoodBin(i_data_ptr)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - } - if (SearchAttrInData("t0_bin", attLen, attType)) { - char cstr[1204]; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "t0_bin", sizeof(cstr)); - status = NXgetattr(fFileHandle, cstr, (void*)&ival, &attLen, &attType); - - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(1); // a single set for t0, fgb, lgb is likely given - int *i_data_ptr = new int; - *i_data_ptr = ival; - if (!fNxEntry2->GetInstrument()->GetDetector()->SetT0(i_data_ptr)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - } - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetInstrument()->GetDetector()->SetNoOfPeriods(dims[0]); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfSpectra(dims[1]); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfBins(dims[2]); - } else if (rank == 2) { // i.e. ns, ntc - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfPeriods(-1); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfSpectra(dims[0]); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfBins(dims[1]); - } else if (rank == 1) { // i.e. ntc only - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfPeriods(-1); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfSpectra(1); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfBins(dims[0]); - } else { - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfPeriods(-1); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfSpectra(-1); - fNxEntry2->GetInstrument()->GetDetector()->SetNoOfBins(-1); - std::cerr << std::endl << ">> **ERROR** found rank=" << rank << " for NXinstrument:NXdetector:counts! Allowed ranks are 1, 2, or 3." << std::endl; - return NX_ERROR; - } - - if (!fNxEntry2->GetInstrument()->GetDetector()->SetHistos(i_data_ptr)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - - // clean up - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'counts' data in NXdetector!")) return NX_ERROR; - - // handle spectrum_index - if (!ErrorHandler(NXopendata(fFileHandle, "spectrum_index"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'spectrum_index' data in NXdetector!")) return NX_ERROR; - // check the dimensions of 'spectrum_index' - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'spectrum_index' info in NXdetector!")) return NX_ERROR; - if (rank > 1) { - std::cerr << std::endl << ">> **ERROR** found rank=" << rank << " of spectrum_index in NXdetector. Rank needs to be == 1!" << std::endl; - return NX_ERROR; - } - if (dims[0] != fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra()) { - std::cerr << std::endl << ">> **ERROR** found spectrum_index dimension=" << dims[0] << " inconsistent with counts (" << fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra() << "!" << std::endl; - return NX_ERROR; - } - // allocate locale memory to get the data - data_ptr = new char[dims[0]*GetDataSize(type)]; - if (data_ptr == nullptr) { - return NX_ERROR; - } - // get the data - i_data_ptr = (int*) data_ptr; - status = NXgetdata(fFileHandle, i_data_ptr); - if (status != NX_OK) { - return NX_ERROR; - } - for (int i=0; iGetInstrument()->GetDetector()->SetSpectrumIndex(*(i_data_ptr+i), i); - - // clean up - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'spectrum_index' data in NXdetector!")) return NX_ERROR; - - // only handle t0, fgb, lgb, if they are not already set as an attribute of /raw_data_1/instrument/detector_1/counts - if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == -1) { - // handle t0's - if (SearchInGroup("time_zero_bin", "name", nxname, nxclass, dataType)) { // check for 'time_zero_bin' - if (!ErrorHandler(NXopendata(fFileHandle, "time_zero_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'time_zero_bin' data in NXdetector!")) return NX_ERROR; - - // check the dimensions of the 'time_zero_bin' vector - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'time_zero_bin' info in NXdetector!")) return NX_ERROR; - - if ((rank == 1) && (dims[0] == 1)) { // single t0 entry - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(1); - } else if ((rank == 1) && (dims[0] > 1)) { // t0 of the form t0[ns] - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(2); - } else if (rank == 2) { // t0 of the form t0[np][ns] - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(3); - } else { - std::cerr << std::endl << ">> **ERROR** found 'time_zero_bin' info in NXdetector with rank=" << rank << ". Do not know how to handle." << std::endl; - return NX_ERROR; - } - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetInstrument()->GetDetector()->SetT0(i_data_ptr)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - - // clean up - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'time_zero_bin' data in NXdetector!")) return NX_ERROR; - } else if (SearchInGroup("time_zero", "name", nxname, nxclass, dataType)) { // check for 'time_zero' - if (!ErrorHandler(NXopendata(fFileHandle, "time_zero"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'time_zero' data in NXdetector!")) return NX_ERROR; - // check the dimensions of the 'time_zero' vector - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'time_zero' info in NXdetector!")) return NX_ERROR; - - if ((rank == 1) && (dims[0] == 1)) { // single t0 entry - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(1); - } else if ((rank == 1) && (dims[0] > 1)) { // t0 of the form t0[ns] - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(2); - } else if (rank == 2) { // t0 of the form t0[np][ns] - fNxEntry2->GetInstrument()->GetDetector()->SetT0Tag(3); - } else { - std::cerr << std::endl << ">> **ERROR** found 'time_zero' info in NXdetector with rank=" << rank << ". Do not know how to handle." << std::endl; - return NX_ERROR; - } - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetInstrument()->GetDetector()->GetTimeResolution(str) == 0.0) { - std::cerr << std::endl << ">> **ERROR** trying to set T0's based on 'time_zero'. Need a valid time resolution to do so, but this is not given." << std::endl; - return NX_ERROR; - } - // set the t0's based on the t0 time stamp and the time resolution - int *pt0 = nullptr; - if (rank == 1) { - pt0 = new int; - *pt0 = (int)(*f_data_ptr / (float)fNxEntry2->GetInstrument()->GetDetector()->GetTimeResolution(str)); - } else { // rank == 2 - pt0 = new int[noOfElements]; - for (int i=0; iGetInstrument()->GetDetector()->GetTimeResolution(str)); - } - } - - if (!fNxEntry2->GetInstrument()->GetDetector()->SetT0(pt0)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - - // clean up - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - std::cerr << std::endl << ">> **WARNING** found only 'time_zero' will convert it to 'time_zero_bin' values" << std::endl; - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'time_zero' data in NXdetector!")) return NX_ERROR; - } else { - std::cerr << std::endl << ">> **WARNING** found neither 'time_zero_bin' nor 'time_zero' values ..." << std::endl; - } - - // handle first good bin - if (SearchInGroup("first_good_bin", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "first_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'first_good_bin' data in NXdetector!")) return NX_ERROR; - - // check the dimensions of the 'first_good_bin' vector - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'first_good_bin' info in NXdetector!")) return NX_ERROR; - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetInstrument()->GetDetector()->SetFirstGoodBin(i_data_ptr)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'first_good_bin' data in NXdetector!")) return NX_ERROR; - } else if (SearchInGroup("first_good_time", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "first_good_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'first_good_time' data in NXdetector!")) return NX_ERROR; - - // check the dimensions of the 'first_good_time' vector - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'first_good_time' info in NXdetector!")) return NX_ERROR; - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetInstrument()->GetDetector()->GetTimeResolution(str)); - } else { // rank == 2 - p_fgb = new int[noOfElements]; - for (int i=0; iGetInstrument()->GetDetector()->GetTimeResolution(str)); - } - } - - if (!fNxEntry2->GetInstrument()->GetDetector()->SetFirstGoodBin(p_fgb)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - - // clean up - if (p_fgb) { - delete [] p_fgb; - p_fgb = 0; - } - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'first_good_time' data in NXdetector!")) return NX_ERROR; - } else { - std::cerr << std::endl << ">> **WARNING** found neither 'first_good_bin' nor 'first_good_time' values ..." << std::endl; - } - - // handle last good bin - if (SearchInGroup("last_good_bin", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "last_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'last_good_bin' data in NXdetector!")) return NX_ERROR; - - // check the dimensions of the 'last_good_bin' vector - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'last_good_bin' info in NXdetector!")) return NX_ERROR; - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetInstrument()->GetDetector()->SetLastGoodBin(i_data_ptr)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - - // clean up - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'last_good_bin' data in NXdetector!")) return NX_ERROR; - } else if (SearchInGroup("last_good_time", "name", nxname, nxclass, dataType)) { - if (!ErrorHandler(NXopendata(fFileHandle, "last_good_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open 'last_good_time' data in NXdetector!")) return NX_ERROR; - - // check the dimensions of the 'last_good_time' vector - if (!ErrorHandler(NXgetinfo(fFileHandle, &rank, dims, &type), PNEXUS_GET_META_INFO_ERROR, "couldn't get 'last_good_time' info in NXdetector!")) return NX_ERROR; - - // calculate the needed size - size = dims[0]; - for (int i=1; iGetInstrument()->GetDetector()->GetTimeResolution(str)); - } else { // rank == 2 - p_lgb = new int[noOfElements]; - for (int i=0; iGetInstrument()->GetDetector()->GetTimeResolution(str)); - } - } - - if (fNxEntry2->GetInstrument()->GetDetector()->SetFirstGoodBin(p_lgb)) { - std::cerr << std::endl << ">> **ERROR** " << fNxEntry2->GetInstrument()->GetDetector()->GetErrorMsg() << std::endl; - return NX_ERROR; - } - - // clean up - if (p_lgb) { - delete [] p_lgb; - p_lgb = 0; - } - if (data_ptr) { - delete [] data_ptr; - data_ptr = nullptr; - } - - if (!ErrorHandler(NXclosedata(fFileHandle), PNEXUS_CLOSE_DATA_ERROR, "couldn't close 'last_good_time' data in NXdetector!")) return NX_ERROR; - } else { - std::cerr << std::endl << ">> **WARNING** found neither 'last_good_bin' nor 'last_good_time' values ..." << std::endl; - } - } - - // close group NXdetector - NXclosegroup(fFileHandle); - - // close group NXinstrument - NXclosegroup(fFileHandle); - - // GET DATA : STILL MISSING : The real data are found under NXentry:NXinstrument:NXdetector - // CHECK if it is necessary to read ANY of these data - - // close group NXentry - NXclosegroup(fFileHandle); - - // close file - NXclose(&fFileHandle); - - fValid = true; - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// GroupHistoData (private) -//------------------------------------------------------------------------------------------ -/** - *

Feed the grouped histo data, based on the grouping vector and the raw histo data. - */ -NXstatus PNeXus::GroupHistoData() -{ - if (fIdfVersion == 1) { - // check if NO grouping is whished - if (fNxEntry1->GetData()->GetGrouping()->size() == 0) { - return NX_OK; - } - - // check that the grouping size is equal to the number of histograms - if (fNxEntry1->GetData()->GetGrouping()->size() != fNxEntry1->GetData()->GetNoOfHistos()) { - fErrorCode = PNEXUS_HISTO_ERROR; - fErrorMsg = "grouping vector size is unequal to the number of histos present!"; - return NX_ERROR; - } - - // make a vector of all grouping present - std::vector groupingValue; - bool newGroup = true; - for (unsigned int i=0; iGetData()->GetGrouping()->size(); i++) { - newGroup = true; - for (unsigned int j=0; jGetData()->GetGrouping()->at(i)) { - newGroup = false; - break; - } - } - if (newGroup) - groupingValue.push_back(fNxEntry1->GetData()->GetGrouping()->at(i)); - } - - // check that none of the grouping values is outside of the valid range - for (unsigned int i=0; i= fNxEntry1->GetData()->GetNoOfHistos()) { - fErrorCode = PNEXUS_HISTO_ERROR; - fErrorMsg = "grouping values out of range"; - return NX_ERROR; - } - } - - // set fGroupedHisto to the proper size - fGroupedHisto.clear(); - fGroupedHisto.resize(groupingValue.size()); - for (unsigned int i=0; iGetData()->GetHisto(0)->size()); - } - - for (unsigned int i=0; iGetData()->GetNoOfHistos(); i++) { - for (unsigned int j=0; jGetData()->GetHisto(i)->size(); j++) { - fGroupedHisto[fNxEntry1->GetData()->GetGrouping()->at(i)-1][j] += fNxEntry1->GetData()->GetHisto(i)->at(j); - } - } - - // cleanup - groupingValue.clear(); - } else if (fIdfVersion == 2) { - // will probably do nothing here - } else { - fErrorCode = PNEXUS_HISTO_ERROR; - fErrorMsg = "unsupported IDF"; - return NX_ERROR; - } - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// WriteFileIdf1 (private) -//------------------------------------------------------------------------------------------ -/** - *

Write the NeXus file of type IDF Version 1. - * - * return: - * - NX_OK on successfull writting - * - NX_ERROR on error. The error code/message will give the details. - * - * \param fileName file name - * \param access flag needed to know in which format (HDF4, HDF5, or XML) the file shall be written. - */ -int PNeXus::WriteFileIdf1(const char* fileName, const NXaccess access) -{ - std::string str; - char cstr[1204]; - bool ok = false; - int size, idata; - float fdata; - - memset(cstr, '\0', sizeof(cstr)); - snprintf(cstr, sizeof(cstr), "couldn't open file '%s' for writing", fileName); - if (!ErrorHandler(NXopen(fileName, access, &fFileHandle), PNEXUS_FILE_OPEN_ERROR, cstr)) return NX_ERROR; - - // write NXfile attribute NeXus_version - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, NEXUS_VERSION, sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "NeXus_version", cstr, strlen(cstr), NX_CHAR), PNEXUS_SET_ATTR_ERROR, "couldn't set NXfile attribute 'NeXus_version'")) return NX_ERROR; - - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetUser()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "user", cstr, strlen(cstr), NX_CHAR), PNEXUS_SET_ATTR_ERROR, "couldn't set NXfile attributes")) return NX_ERROR; - - // make group 'run' - if (!ErrorHandler(NXmakegroup(fFileHandle, "run", "NXentry"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXfile/run'.")) return NX_ERROR; - // open group 'run' - if (!ErrorHandler(NXopengroup(fFileHandle, "run", "NXentry"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXfile/run' for writting.")) return NX_ERROR; - - // write IDF_version - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "IDF_version", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/IDF_version'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "IDF_version"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/IDF_version' for writting.")) return NX_ERROR; - idata = fIdfVersion; - if (!ErrorHandler(NXputdata(fFileHandle, &idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/IDF_version'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write program_name, and attribute version - size = fNxEntry1->GetProgramName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "program_name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/program_name'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "program_name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/program_name' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetProgramName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/program_name'.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetProgramVersion().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "version", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'version' for 'NXentry/program_name'")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'number' - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "number", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/number'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "number"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/number' for writting.")) return NX_ERROR; - idata = fNxEntry1->GetRunNumber(); - if (!ErrorHandler(NXputdata(fFileHandle, &idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/number'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'title' - size = fNxEntry1->GetTitle().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "title", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/title'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "title"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/title' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetTitle().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/title'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'notes' - size = fNxEntry1->GetNotes().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "notes", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/notes'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "notes"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/notes' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetNotes().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/notes'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'analysis' - size = fNxEntry1->GetAnalysis().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "analysis", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/analysis'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "analysis"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/analysis' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetAnalysis().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/analysis'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'lab' - size = fNxEntry1->GetLaboratory().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "lab", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/lab'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "lab"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/lab' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetLaboratory().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/lab'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'beamline' - size = fNxEntry1->GetBeamline().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "beamline", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/beamline'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "beamline"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/beamline' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetBeamline().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/beamline'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'start_time' - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetStartTime().c_str(), sizeof(cstr)); - size = strlen(cstr); - if (!ErrorHandler(NXmakedata(fFileHandle, "start_time", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/start_time'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "start_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/start_time' for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/start_time'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'stop_time' - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetStopTime().c_str(), sizeof(cstr)); - size = strlen(cstr); - if (!ErrorHandler(NXmakedata(fFileHandle, "stop_time", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/stop_time'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "stop_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/stop_time' for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/stop_time'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'switching_states' - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "switching_states", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXentry/switching_states'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "switching_states"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXentry/switching_states' for writting.")) return NX_ERROR; - idata = fNxEntry1->GetSwitchingState(); - if (!ErrorHandler(NXputdata(fFileHandle, &idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXentry/switching_states'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // make group 'user' - if (!ErrorHandler(NXmakegroup(fFileHandle, "user", "NXuser"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXuser/user'.")) return NX_ERROR; - // open group 'user' - if (!ErrorHandler(NXopengroup(fFileHandle, "user", "NXuser"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXuser/user' for writting.")) return NX_ERROR; - - // write user 'name' - size = fNxEntry1->GetUser()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXuser/name'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXuser/name' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetUser()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXuser/name'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write user 'experiment_number' - size = fNxEntry1->GetUser()->GetExperimentNumber().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "experiment_number", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXuser/experiment_number'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "experiment_number"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXuser/experiment_number' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetUser()->GetExperimentNumber().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXuser/experiment_number'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'user' - NXclosegroup(fFileHandle); - - // make group 'sample' - if (!ErrorHandler(NXmakegroup(fFileHandle, "sample", "NXsample"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXsample/sample'.")) return NX_ERROR; - // open group 'sample' - if (!ErrorHandler(NXopengroup(fFileHandle, "sample", "NXsample"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXsample/sample' for writting.")) return NX_ERROR; - - // write sample 'name' - size = fNxEntry1->GetSample()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXsample/name'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXsample/name' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetSample()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXsample/name'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write sample 'temperature' - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "temperature", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXsample/temperature'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "temperature"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXsample/temperature' for writting.")) return NX_ERROR; - fdata = (float)fNxEntry1->GetSample()->GetPhysPropValue(std::string("temperature"), ok); - if (!ErrorHandler(NXputdata(fFileHandle, &fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXsample/temperature'.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - str = std::string("n/a"); - fNxEntry1->GetSample()->GetPhysPropUnit(std::string("temperature"), str, ok); - strncpy(cstr, str.c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXsample/temperature'")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write sample 'magnetic_field' - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "magnetic_field", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXsample/magnetic_field'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXsample/magnetic_field' for writting.")) return NX_ERROR; - fdata = (float)fNxEntry1->GetSample()->GetPhysPropValue(std::string("magnetic_field"), ok); - if (!ErrorHandler(NXputdata(fFileHandle, &fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXsample/magnetic_field'.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - str = std::string("n/a"); - fNxEntry1->GetSample()->GetPhysPropUnit(std::string("magnetic_field"), str, ok); - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, str.c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXsample/magnetic_field'")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write sample 'shape' only if populated with something different than 'n/a' - if (fNxEntry1->GetSample()->GetShape() != "n/a") { - size = fNxEntry1->GetSample()->GetShape().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "shape", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXsample/shape'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "shape"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXsample/shape' for writting.")) return NX_ERROR; - strncpy(cstr, fNxEntry1->GetSample()->GetShape().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXsample/shape'.")) return NX_ERROR; - NXclosedata(fFileHandle); - } - - // write sample 'magnetic_field_state' - size = fNxEntry1->GetSample()->GetMagneticFieldState().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "magnetic_field_state", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXsample/magnetic_field_state'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_state"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXsample/magnetic_field_state' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetSample()->GetMagneticFieldState().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXsample/magnetic_field_state'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write sample 'magnetic_field_vector' - float *magFieldVec; - if (fNxEntry1->GetSample()->IsMagneticFieldVectorAvailable()) { - idata = 1; - size = fNxEntry1->GetSample()->GetMagneticFieldVector().size(); - magFieldVec = new float[size]; - for (int i=0; iGetSample()->GetMagneticFieldVector().at(i); - } else { - idata = 0; - size = 3; - magFieldVec = new float[size]; - for (int i=0; iGetSample()->GetMagneticFieldVectorCoordinateSystem().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "coordinate_system", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'coordinate_system' for 'NXsample/magnetic_field_vector'")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetSample()->GetMagneticFieldVectorUnits().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXsample/magnetic_field_vector'")) return NX_ERROR; - if (!ErrorHandler(NXputattr(fFileHandle, "available", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'available' for 'NXsample/magnetic_field_vector'")) return NX_ERROR; - NXclosedata(fFileHandle); - if (magFieldVec) { - delete [] magFieldVec; - magFieldVec = 0; - } - - // write sample 'environment' - size = fNxEntry1->GetSample()->GetEnvironment().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "environment", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXsample/environment'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "environment"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXsample/environment' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetSample()->GetEnvironment().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXsample/environment'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'sample' - NXclosegroup(fFileHandle); - - // make group 'instrument' - if (!ErrorHandler(NXmakegroup(fFileHandle, "instrument", "NXinstrument"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXinstrument/instrument'.")) return NX_ERROR; - // open group 'instrument' - if (!ErrorHandler(NXopengroup(fFileHandle, "instrument", "NXinstrument"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXinstrument/instrument' for writting.")) return NX_ERROR; - - // write instrument 'name' - size = fNxEntry1->GetInstrument()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXinstrument/name'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXinstrument/name' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetInstrument()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXinstrument/name'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // make group 'detector' - if (!ErrorHandler(NXmakegroup(fFileHandle, "detector", "NXdetector"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXdetector/detector'.")) return NX_ERROR; - // open group 'detector' - if (!ErrorHandler(NXopengroup(fFileHandle, "detector", "NXdetector"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXdetector/detector' for writting.")) return NX_ERROR; - - // write detector 'number' - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "number", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXdetector/number'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "number"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXdetector/number' for writting.")) return NX_ERROR; - idata = fNxEntry1->GetInstrument()->GetDetector()->GetNumber(); - if (!ErrorHandler(NXputdata(fFileHandle, &idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXdetector/number'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'detector' - NXclosegroup(fFileHandle); - - // make group 'collimator' - if (!ErrorHandler(NXmakegroup(fFileHandle, "collimator", "NXcollimator"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXcollimator/collimator'.")) return NX_ERROR; - // open group 'collimator' - if (!ErrorHandler(NXopengroup(fFileHandle, "collimator", "NXcollimator"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXcollimator/collimator' for writting.")) return NX_ERROR; - - // write collimator 'type' - size = fNxEntry1->GetInstrument()->GetCollimator()->GetType().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "type", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXcollimator/type'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "type"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXcollimator/type' for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetInstrument()->GetCollimator()->GetType().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXcollimator/type'.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'collimator' - NXclosegroup(fFileHandle); - - // make group 'beam' - if (!ErrorHandler(NXmakegroup(fFileHandle, "beam", "NXbeam"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXbeam/beam'.")) return NX_ERROR; - // open group 'beam' - if (!ErrorHandler(NXopengroup(fFileHandle, "beam", "NXbeam"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXbeam/beam' for writting.")) return NX_ERROR; - - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "total_counts", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXbeam/total_counts'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "total_counts"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXbeam/total_counts' for writting.")) return NX_ERROR; - fdata = (float)fNxEntry1->GetInstrument()->GetBeam()->GetTotalCounts(); - if (!ErrorHandler(NXputdata(fFileHandle, &fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXbeam/total_counts'.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry1->GetInstrument()->GetBeam()->GetUnits().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXbeam/total_counts'")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'beam' - NXclosegroup(fFileHandle); - - // close group 'instrument' - NXclosegroup(fFileHandle); - - // make group 'histogram_data_1' - if (!ErrorHandler(NXmakegroup(fFileHandle, "histogram_data_1", "NXdata"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'NXdata/histogram_data_1'.")) return NX_ERROR; - // open group 'histogram_data_1' - if (!ErrorHandler(NXopengroup(fFileHandle, "histogram_data_1", "NXdata"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXdata/histogram_data_1' for writting.")) return NX_ERROR; - - // write data 'counts' - int *histo_data=0; - int histo_size[2]; - if (fNxEntry1->GetData()->GetNoOfHistos() == 0) { - histo_data = new int[1]; - histo_data[0] = -1; - histo_size[0] = 1; - histo_size[1] = 1; - } else { - int noOfHistos = fNxEntry1->GetData()->GetNoOfHistos(); - int histoLength = fNxEntry1->GetData()->GetHisto(0)->size(); - histo_data = new int[noOfHistos*histoLength]; - for (int i=0; iGetData()->GetHisto(i)->at(j); - histo_size[0] = noOfHistos; - histo_size[1] = histoLength; - } - - if (!ErrorHandler(NXcompmakedata(fFileHandle, "counts", NX_INT32, 2, histo_size, NX_COMP_LZW, histo_size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXdata/counts'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "counts"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXdata/counts' for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)histo_data), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXdata/counts'.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "counts", sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXdata/counts'")) return NX_ERROR; - idata = 1; - if (!ErrorHandler(NXputattr(fFileHandle, "signal", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'signal' for 'NXdata/counts'")) return NX_ERROR; - idata = fNxEntry1->GetData()->GetNoOfHistos(); - if (!ErrorHandler(NXputattr(fFileHandle, "number", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'number' for 'NXdata/counts'")) return NX_ERROR; - idata = fNxEntry1->GetData()->GetHistoLength(); - if (!ErrorHandler(NXputattr(fFileHandle, "length", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'length' for 'NXdata/counts'")) return NX_ERROR; - idata = fNxEntry1->GetData()->GetT0(0); - if (!ErrorHandler(NXputattr(fFileHandle, "t0_bin", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'T0_bin' for 'NXdata/counts'")) return NX_ERROR; - idata = fNxEntry1->GetData()->GetFirstGoodBin(0); - if (!ErrorHandler(NXputattr(fFileHandle, "first_good_bin", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'first_good_bin' for 'NXdata/counts'")) return NX_ERROR; - idata = fNxEntry1->GetData()->GetLastGoodBin(0); - if (!ErrorHandler(NXputattr(fFileHandle, "last_good_bin", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'last_good_bin' for 'NXdata/counts'")) return NX_ERROR; - fdata = (float)fNxEntry1->GetData()->GetTimeResolution("ps")/2.0; - if (!ErrorHandler(NXputattr(fFileHandle, "offset", &fdata, 1, NX_FLOAT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'offset' for 'NXdata/counts'")) return NX_ERROR; - NXclosedata(fFileHandle); - - if (histo_data) { - delete [] histo_data; - } - - // write data 'resolution' - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "resolution", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXdata/resolution'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "resolution"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXdata/resolution' for writting.")) return NX_ERROR; - fdata = fNxEntry1->GetData()->GetTimeResolution("fs"); - idata = (int)fdata; - if (!ErrorHandler(NXputdata(fFileHandle, &idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXdata/resolution'.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "femtoseconds", sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXdata/resolution'")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write data 'time_zero' based on t0_bin which is the master! - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "time_zero", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXdata/time_zero'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "time_zero"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXdata/time_zero' for writting.")) return NX_ERROR; - fdata = (float)fNxEntry1->GetData()->GetT0(0) * (float)fNxEntry1->GetData()->GetTimeResolution("us"); - if (!ErrorHandler(NXputdata(fFileHandle, &fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXdata/time_zero'.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "microseconds", sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXdata/time_zero'")) return NX_ERROR; - idata = 1; - if (!ErrorHandler(NXputattr(fFileHandle, "available", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'available' for 'NXdata/time_zero'")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write data 'raw_time' - int noOfHistos = fNxEntry1->GetData()->GetNoOfHistos(); - int histoLength = fNxEntry1->GetData()->GetHisto(0)->size(); - if (noOfHistos == 0) { - fErrorCode = PNEXUS_HISTO_ERROR; - fErrorMsg = "no data for writing present."; - return NX_ERROR; - } - float *raw_time = new float[histoLength]; - for (int i=0; iGetData()->GetTimeResolution("us") * (float)i; // raw time in (us) - } - size = histoLength; - if (!ErrorHandler(NXmakedata(fFileHandle, "raw_time", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXdata/raw_time'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "raw_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXdata/raw_time' for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, raw_time), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXdata/raw_time'.")) return NX_ERROR; - idata = 1; - if (!ErrorHandler(NXputattr(fFileHandle, "axis", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'axis' for 'NXdata/raw_time'")) return NX_ERROR; - idata = 1; - if (!ErrorHandler(NXputattr(fFileHandle, "primary", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'primary' for 'NXdata/raw_time'")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "microseconds", sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXdata/raw_time'")) return NX_ERROR; - NXclosedata(fFileHandle); - if (raw_time) { - delete [] raw_time; - raw_time = 0; - } - - // write data 'corrected_time' - float *corrected_time = new float[histoLength]; - for (int i=0; iGetData()->GetTimeResolution("us") * (float)((int)i-(int)fNxEntry1->GetData()->GetT0(0)+1); // raw time in (us) - } - size = histoLength; - if (!ErrorHandler(NXmakedata(fFileHandle, "corrected_time", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXdata/corrected_time'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "corrected_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXdata/corrected_time' for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, corrected_time), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXdata/corrected_time'.")) return NX_ERROR; - idata = 1; - if (!ErrorHandler(NXputattr(fFileHandle, "axis", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'axis' for 'NXdata/corrected_time'")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "microseconds", sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'NXdata/corrected_time'")) return NX_ERROR; - NXclosedata(fFileHandle); - if (corrected_time) { - delete [] corrected_time; - corrected_time = 0; - } - - // write data 'grouping' - int *grouping = new int[noOfHistos]; - std::vector groupNo; // keep the number of different groupings - if (noOfHistos == (int)fNxEntry1->GetData()->GetGrouping()->size()) { // grouping vector seems to be properly defined - bool found; - groupNo.push_back(fNxEntry1->GetData()->GetGrouping()->at(0)); - for (int i=0; iGetData()->GetGrouping()->at(i); - found = false; - for (unsigned int j=0; jGetData()->GetGrouping()->at(i) == groupNo[j]) { - found = true; - break; - } - } - if (!found) { - groupNo.push_back(fNxEntry1->GetData()->GetGrouping()->at(i)); - } - } - } else { // grouping vector not available - for (int i=0; iGetData()->GetAlpha()->size() == 0) { - alpha = new float[3]; - alpha[0] = -1.0; - alpha[1] = -1.0; - alpha[2] = -1.0; - array_size[0] = 1; - array_size[1] = 3; - } else { - alpha = new float[fNxEntry1->GetData()->GetAlpha()->size()*3]; - for (unsigned int i=0; iGetData()->GetAlpha()->size(); i++) { - alpha[i] = (float)fNxEntry1->GetData()->GetAlpha()->at(i).GetGroupFirst(); - alpha[i+1] = (float)fNxEntry1->GetData()->GetAlpha()->at(i).GetGroupSecond(); - alpha[i+2] = (float)(float)fNxEntry1->GetData()->GetAlpha()->at(i).GetAlpha(); - } - array_size[0] = fNxEntry1->GetData()->GetAlpha()->size(); - array_size[1] = 3; - } - if (!ErrorHandler(NXmakedata(fFileHandle, "alpha", NX_FLOAT32, 2, array_size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'NXdata/alpha'.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "alpha"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'NXdata/alpha' for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)alpha), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'NXdata/alpha'.")) return NX_ERROR; - idata = fNxEntry1->GetData()->GetAlpha()->size(); - if (!ErrorHandler(NXputattr(fFileHandle, "available", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'available' for 'NXdata/alpha'")) return NX_ERROR; - if (alpha) { - delete [] alpha; - } - - // close group 'histogram_data_1' - NXclosegroup(fFileHandle); - - // close group 'run' - NXclosegroup(fFileHandle); - - NXclose(&fFileHandle); - - return NX_OK; -} - -//------------------------------------------------------------------------------------------ -// WriteFileIdf2 (private) -//------------------------------------------------------------------------------------------ -/** - *

Write the NeXus file of type IDF Version 2. - * - * return: - * - NX_OK on successfull writting - * - NX_ERROR on error. The error code/message will give the details. - * - * \param fileName file name - * \param access flag needed to know in which format (HDF4, HDF5, or XML) the file shall be written. - */ -int PNeXus::WriteFileIdf2(const char* fileName, const NXaccess access) -{ - std::string str; - char cstr[1204]; - bool ok = false; - int size, idata; - float fdata; - double dval; - NXlink nxLink; - std::vector nxLinkVec; - - memset(cstr, '\0', sizeof(cstr)); - snprintf(cstr, sizeof(cstr), "couldn't open file '%s' for writing", fileName); - if (!ErrorHandler(NXopen(fileName, access, &fFileHandle), PNEXUS_FILE_OPEN_ERROR, cstr)) return NX_ERROR; - - // write NXroot attribute 'file_name' - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fileName, sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "file_name", cstr, strlen(cstr), NX_CHAR), PNEXUS_SET_ATTR_ERROR, "couldn't set NXroot attribute 'file_name'")) return NX_ERROR; - - // write NXroot attribute 'creator' - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fCreator.c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "creator", cstr, strlen(cstr), NX_CHAR), PNEXUS_SET_ATTR_ERROR, "couldn't set NXroot attribute 'creator'")) return NX_ERROR; - - // make group 'raw_data_1' - if (!ErrorHandler(NXmakegroup(fFileHandle, "raw_data_1", "NXentry"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'raw_data_1' in NXroot.")) return NX_ERROR; - // open group 'raw_data_1' - if (!ErrorHandler(NXopengroup(fFileHandle, "raw_data_1", "NXentry"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'raw_data_1' in NXroot for writting.")) return NX_ERROR; - - // write idf_version - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "idf_version", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'idf_version' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "idf_version"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'idf_version' in NXentry for writting.")) return NX_ERROR; - idata = fIdfVersion; - if (!ErrorHandler(NXputdata(fFileHandle, &idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'idf_version' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'definition' - size = fNxEntry2->GetDefinition().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "definition", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'definition' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "definition"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'definition' in NXentry for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetDefinition().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'definition' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write 'program_name' if present - if (!fNxEntry2->GetProgramName().empty()) { - size = fNxEntry2->GetProgramName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "program_name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'program_name' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "program_name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'program_name' in NXentry for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetProgramName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'program_name' in NXentry.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetProgramVersion().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "version", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'version' for 'program_name' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - } - - // write run 'run_number' - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "run_number", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'run_number' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "run_number"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'run_number' in NXentry for writting.")) return NX_ERROR; - idata = fNxEntry2->GetRunNumber(); - if (!ErrorHandler(NXputdata(fFileHandle, &idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'run_number' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'title' - size = fNxEntry2->GetTitle().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "title", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'title' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "title"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'title' in NXentry for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetTitle().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'title' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'start_time' - size = fNxEntry2->GetStartTime().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "start_time", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'start_time' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "start_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'start_time' in NXentry for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetStartTime().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'start_time' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'end_time' - size = fNxEntry2->GetStopTime().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "end_time", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'end_time' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "end_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'end_time' in NXentry for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetStopTime().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'end_time' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write run 'experiment_identifier' - size = fNxEntry2->GetExperimentIdentifier().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "experiment_identifier", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'experiment_identifier' in NXentry.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "experiment_identifier"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'experiment_identifier' in NXentry for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetExperimentIdentifier().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'experiment_identifier' in NXentry.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // user_1 (NXuser) will only be written if there is at least a user name present - if (fNxEntry2->GetUser()->GetName() != "n/a") { - // make group 'user_1' - if (!ErrorHandler(NXmakegroup(fFileHandle, "user_1", "NXuser"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'user_1' in NXentry.")) return NX_ERROR; - // open group 'user' - if (!ErrorHandler(NXopengroup(fFileHandle, "user_1", "NXuser"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'user_1' in NXentry for writting.")) return NX_ERROR; - - // write user 'name' - size = fNxEntry2->GetUser()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'name' in NXuser.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'name' in NXuser for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetUser()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'name' in NXuser.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'user_1' - NXclosegroup(fFileHandle); - } - - // make group 'sample' - if (!ErrorHandler(NXmakegroup(fFileHandle, "sample", "NXsample"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'sample' in NXentry.")) return NX_ERROR; - // open group 'sample' - if (!ErrorHandler(NXopengroup(fFileHandle, "sample", "NXsample"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'sample' in NXentry for writting.")) return NX_ERROR; - - // write sample 'name' - size = fNxEntry2->GetSample()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'name' in NXsample.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'name' in NXsample for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetSample()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'name' in NXsample.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // check if description is present - if (fNxEntry2->GetSample()->GetDescription().compare("n/a")) { - // write sample 'description' - size = fNxEntry2->GetSample()->GetDescription().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "description", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'description' in NXsample.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "description"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'description' in NXsample for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetSample()->GetDescription().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'description' in NXsample.")) return NX_ERROR; - NXclosedata(fFileHandle); - } - - // check if temperature is present and if yes, write it into the file - ok=false; - dval = fNxEntry2->GetSample()->GetPhysPropValue("temperature_1", ok); - if (ok) { - fdata = (float)dval; - fNxEntry2->GetSample()->GetPhysPropUnit("temperature_1", str, ok); - } - if (ok) { - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "temperature_1", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'temperature_1' in NXsample.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "temperature_1"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'temperature_1' in NXsample for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)&fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'temperature_1' in NXsample.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, str.c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'temperature_1' in NXsample.")) return NX_ERROR; - NXclosedata(fFileHandle); - } - - // check if temperature environment info is present - if (fNxEntry2->GetSample()->GetEnvironmentTemp().compare("n/a")) { - // make group 'temperature_1_env' - if (!ErrorHandler(NXmakegroup(fFileHandle, "temperature_1_env", "NXenvironment"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'temperature_1_env' in NXsample.")) return NX_ERROR; - // open group 'temperature_1_env' - if (!ErrorHandler(NXopengroup(fFileHandle, "temperature_1_env", "NXenvironment"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXenvironment' in NXsample for writting.")) return NX_ERROR; - // write sample 'temperature_1_env' - size = fNxEntry2->GetSample()->GetEnvironmentTemp().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'name' in 'temperature_1_env' in NXsample.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'name' in 'temperature_1_env' in NXsample for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetSample()->GetEnvironmentTemp().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'temperature_1_env' in NXsample.")) return NX_ERROR; - NXclosedata(fFileHandle); - // close group 'temperature_1_env' - NXclosegroup(fFileHandle); - } - - // check if magnetic field is present and if yes, write it into the file - ok=false; - dval = fNxEntry2->GetSample()->GetPhysPropValue("magnetic_field_1", ok); - if (ok) { - fdata = (float)dval; - fNxEntry2->GetSample()->GetPhysPropUnit("magnetic_field_1", str, ok); - } - if (ok) { - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "magnetic_field_1", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'magnetic_field_1' in NXsample.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_1"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'magnetic_field_1' in NXsample for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)&fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'magnetic_field_1' in NXsample.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, str.c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'magnetic_field_1' in NXsample.")) return NX_ERROR; - NXclosedata(fFileHandle); - } - - // check if magnetic field environment info is present - if (fNxEntry2->GetSample()->GetEnvironmentField().compare("n/a")) { - // make group 'magnetic_field_1_env' - if (!ErrorHandler(NXmakegroup(fFileHandle, "magnetic_field_1_env", "NXenvironment"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'magnetic_field_1_env' in NXsample.")) return NX_ERROR; - // open group 'magnetic_field_1_env' - if (!ErrorHandler(NXopengroup(fFileHandle, "magnetic_field_1_env", "NXenvironment"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'NXenvironment' in NXsample for writting.")) return NX_ERROR; - // write sample 'magnetic_field_1_env' - size = fNxEntry2->GetSample()->GetEnvironmentField().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "magnetic_field_1_env", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'magnetic_field_1_env' in NXsample.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_1_env"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'magnetic_field_1_env' in NXsample for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetSample()->GetEnvironmentField().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'magnetic_field_1_env' in NXsample.")) return NX_ERROR; - NXclosedata(fFileHandle); - // close group 'magnetic_field_1_env' - NXclosegroup(fFileHandle); - } - - // check if magnetic field state info is present - if (fNxEntry2->GetSample()->GetMagneticFieldState().compare("n/a")) { - // write sample 'magnetic_field_state' - size = fNxEntry2->GetSample()->GetMagneticFieldState().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "magnetic_field_state", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'magnetic_field_state' in NXsample.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "magnetic_field_state"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'magnetic_field_state' in NXsample for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetSample()->GetMagneticFieldState().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'magnetic_field_state' in NXsample.")) return NX_ERROR; - NXclosedata(fFileHandle); - } - - // close group 'sample' - NXclosegroup(fFileHandle); - - - // make group 'instrument' - if (!ErrorHandler(NXmakegroup(fFileHandle, "instrument", "NXinstrument"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'instrument' in NXentry.")) return NX_ERROR; - // open group 'instrument' - if (!ErrorHandler(NXopengroup(fFileHandle, "instrument", "NXinstrument"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'instrument' in NXentry for writting.")) return NX_ERROR; - - // write instrument 'name' - size = fNxEntry2->GetInstrument()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'name' in NXinstrument.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'name' in NXinstrument for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetInstrument()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'name' in NXinstrument.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // make group 'source' - if (!ErrorHandler(NXmakegroup(fFileHandle, "source", "NXsource"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'source' in NXinstrument.")) return NX_ERROR; - // open group 'source' - if (!ErrorHandler(NXopengroup(fFileHandle, "source", "NXsource"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'source' in NXentry for NXinstrument.")) return NX_ERROR; - - // write instrument 'name' - size = fNxEntry2->GetInstrument()->GetSource()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "name", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'name' in NXsource.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "name"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'name' in NXsource for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetInstrument()->GetSource()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'name' in NXsource.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write instrument 'type' - size = fNxEntry2->GetInstrument()->GetSource()->GetType().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "type", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'type' in NXsource.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "type"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'type' in NXsource for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetInstrument()->GetSource()->GetType().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'type' in NXsource.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write instrument 'probe' - size = fNxEntry2->GetInstrument()->GetSource()->GetProbe().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "probe", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'probe' in NXsource.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "probe"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'probe' in NXsource for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetInstrument()->GetSource()->GetProbe().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'probe' in NXsource.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'source' - NXclosegroup(fFileHandle); - - // make group 'beamline' - if (!ErrorHandler(NXmakegroup(fFileHandle, "beamline", "NXbeamline"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'beamline' in NXinstrument.")) return NX_ERROR; - // open group 'beamline' - if (!ErrorHandler(NXopengroup(fFileHandle, "beamline", "NXbeamline"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'beamline' in NXentry for NXinstrument.")) return NX_ERROR; - - // write beamline 'beamline' - size = fNxEntry2->GetInstrument()->GetBeamline()->GetName().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "beamline", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'beamline' in NXbeamline.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "beamline"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'beamline' in NXbeamline for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetInstrument()->GetBeamline()->GetName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'beamline' in NXbeamline.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // close group 'beamline' - NXclosegroup(fFileHandle); - - // make group 'detector_1' - if (!ErrorHandler(NXmakegroup(fFileHandle, "detector_1", "NXdetector"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'detector_1' in NXinstrument.")) return NX_ERROR; - // open group 'detector_1' - if (!ErrorHandler(NXopengroup(fFileHandle, "detector_1", "NXdetector"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'detector_1' in NXinstrument.")) return NX_ERROR; - - // write detector_1 'description' - size = fNxEntry2->GetInstrument()->GetDetector()->GetDescription().length(); - if (!ErrorHandler(NXmakedata(fFileHandle, "description", NX_CHAR, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'description' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "description"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'description' in NXdetector for writting.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, fNxEntry2->GetInstrument()->GetDetector()->GetDescription().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputdata(fFileHandle, cstr), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'description' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - - // write detector_1 'counts' - int dims[3]; - if (fNxEntry2->GetInstrument()->GetDetector()->GetNoOfPeriods() > 0) { // counts[][][] - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfPeriods(); - dims[1] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - dims[2] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins(); - if (!ErrorHandler(NXcompmakedata(fFileHandle, "counts", NX_INT32, 3, dims, NX_COMP_LZW, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'counts' in NXdetector.")) return NX_ERROR; - } else { - if (fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra() > 0) { // counts[][] - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - dims[1] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins(); - if (!ErrorHandler(NXcompmakedata(fFileHandle, "counts", NX_INT32, 2, dims, NX_COMP_LZW, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'counts' in NXdetector.")) return NX_ERROR; - } else { // counts[] - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfBins(); - if (!ErrorHandler(NXcompmakedata(fFileHandle, "counts", NX_INT32, 1, dims, NX_COMP_LZW, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'counts' in NXdetector.")) return NX_ERROR; - } - } - if (!ErrorHandler(NXopendata(fFileHandle, "counts"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'counts' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)fNxEntry2->GetInstrument()->GetDetector()->GetHistos()), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'counts' in NXdetector.")) return NX_ERROR; - - // write 'counts' attributes - idata = 1; - if (!ErrorHandler(NXputattr(fFileHandle, "signal", &idata, 1, NX_INT32), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'signal' for 'counts' in NXdetector.")) return NX_ERROR; - - memset(cstr, '\0', sizeof(cstr)); - if (fNxEntry2->GetInstrument()->GetDetector()->GetNoOfPeriods() > 0) { // counts[][][] - strncpy(cstr, "[period_index, spectrum_index, raw_time_bin]", sizeof(cstr)); - } else { - if (fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra() > 0) { // counts[][] - strncpy(cstr, "[spectrum_index, raw_time_bin]", sizeof(cstr)); - } else { // counts[] - strncpy(cstr, "[raw_time_bin]", sizeof(cstr)); - } - } - if (!ErrorHandler(NXputattr(fFileHandle, "axes", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'axes' for 'counts' in NXdetector.")) return NX_ERROR; - - memset(cstr, '\0', sizeof(cstr)); - if (!fNxEntry2->GetInstrument()->GetSource()->GetProbe().compare("positive muons")) { - strncpy(cstr, "positron counts", sizeof(cstr)); - } else if (!fNxEntry2->GetInstrument()->GetSource()->GetProbe().compare("negative muons")) { - strncpy(cstr, "electron counts", sizeof(cstr)); - } else { - strncpy(cstr, "n/a", sizeof(cstr)); - } - if (!ErrorHandler(NXputattr(fFileHandle, "long_name", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'long_name' for 'counts' in NXdetector.")) return NX_ERROR; - // create link of 'counts' for NXdata - if (!ErrorHandler(NXgetdataID(fFileHandle, &nxLink), PNEXUS_LINKING_ERROR, "couldn't obtain link of 'counts' in NXdetector.")) return NX_ERROR; - nxLinkVec.push_back(nxLink); - NXclosedata(fFileHandle); - - // write time resolution - fdata = (float)fNxEntry2->GetInstrument()->GetDetector()->GetTimeResolution("ns"); - size = 1; - if (!ErrorHandler(NXmakedata(fFileHandle, "histogram_resolution", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'histogram_resolution' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "histogram_resolution"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'histogram_resolution' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)&fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'histogram_resolution' in NXdetector.")) return NX_ERROR; - memset(cstr, '\0', sizeof(cstr)); - strncpy(cstr, "nano.second", sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'histogram_resolution' in NXdetector.")) return NX_ERROR; - // create link of 'histogram_resolution' for NXdata - if (!ErrorHandler(NXgetdataID(fFileHandle, &nxLink), PNEXUS_LINKING_ERROR, "couldn't obtain link of 'histogram_resolution' in NXdetector.")) return NX_ERROR; - nxLinkVec.push_back(nxLink); - NXclosedata(fFileHandle); - - // write detector_1 'raw_time' - size = (int)fNxEntry2->GetInstrument()->GetDetector()->GetRawTime()->size(); - float *p_fdata = new float[size]; - assert(p_fdata); - for (int i=0; iGetInstrument()->GetDetector()->GetRawTime()->at(i); - if (!ErrorHandler(NXmakedata(fFileHandle, "raw_time", NX_FLOAT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'raw_time' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "raw_time"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'raw_time' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)p_fdata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'raw_time' in NXdetector.")) return NX_ERROR; - strncpy(cstr, fNxEntry2->GetInstrument()->GetDetector()->GetRawTimeUnit().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "units", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'units' for 'raw_time' in NXdetector.")) return NX_ERROR; - strncpy(cstr, fNxEntry2->GetInstrument()->GetDetector()->GetRawTimeName().c_str(), sizeof(cstr)); - if (!ErrorHandler(NXputattr(fFileHandle, "long_name", cstr, strlen(cstr), NX_CHAR), PNEXUS_PUT_ATTR_ERROR, "couldn't put attribute 'long_name' for 'raw_time' in NXdetector.")) return NX_ERROR; - // create link of 'raw_time' for NXdata - if (!ErrorHandler(NXgetdataID(fFileHandle, &nxLink), PNEXUS_LINKING_ERROR, "couldn't obtain link of 'raw_time' in NXdetector.")) return NX_ERROR; - nxLinkVec.push_back(nxLink); - NXclosedata(fFileHandle); - // clean up - if (p_fdata) { - delete [] p_fdata; - p_fdata = 0; - } - - // write detector_1 'spectrum_index' - size = fNxEntry2->GetInstrument()->GetDetector()->GetSpectrumIndexSize(); - int *p_idata = new int[size]; - assert(p_idata); - for (int i=0; iGetInstrument()->GetDetector()->GetSpectrumIndex(i); - if (!ErrorHandler(NXmakedata(fFileHandle, "spectrum_index", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'spectrum_index' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "spectrum_index"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'spectrum_index' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)p_idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'spectrum_index' in NXdetector.")) return NX_ERROR; - // create link of 'spectrum_index' for NXdata - if (!ErrorHandler(NXgetdataID(fFileHandle, &nxLink), PNEXUS_LINKING_ERROR, "couldn't obtain link of 'spectrum_index' in NXdetector.")) return NX_ERROR; - nxLinkVec.push_back(nxLink); - NXclosedata(fFileHandle); - if (p_idata) { - delete [] p_idata; - p_idata = 0; - } - - // write detector_1 'time_zero_bin' if present - if (fNxEntry2->GetInstrument()->GetDetector()->IsT0Present()) { - if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 1) { - size = 1; - idata = fNxEntry2->GetInstrument()->GetDetector()->GetT0(); - if (!ErrorHandler(NXmakedata(fFileHandle, "time_zero_bin", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'time_zero_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "time_zero_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'time_zero_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)&idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'time_zero_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 2) { - int dims[1]; - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - if (!ErrorHandler(NXmakedata(fFileHandle, "time_zero_bin", NX_INT32, 1, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'time_zero_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "time_zero_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'time_zero_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)fNxEntry2->GetInstrument()->GetDetector()->GetT0s()), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'time_zero_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 3) { - int dims[2]; - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfPeriods(); - dims[1] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - if (!ErrorHandler(NXmakedata(fFileHandle, "time_zero_bin", NX_INT32, 2, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'time_zero_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "time_zero_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'time_zero_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)fNxEntry2->GetInstrument()->GetDetector()->GetT0s()), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'time_zero_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else { - std::cerr << std::endl << ">> **WARNING** time_zero_bin with rank " << fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() << " requested. Do not know how to handle." << std::endl; - } - } - - // write detector_1 'first_good_bin' if present - if (fNxEntry2->GetInstrument()->GetDetector()->IsFirstGoodBinPresent()) { - if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 1) { - size = 1; - idata = fNxEntry2->GetInstrument()->GetDetector()->GetFirstGoodBin(); - if (!ErrorHandler(NXmakedata(fFileHandle, "first_good_bin", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'first_good_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "first_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'first_good_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)&idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'first_good_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 2) { - int dims[1]; - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - if (!ErrorHandler(NXmakedata(fFileHandle, "first_good_bin", NX_INT32, 1, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'first_good_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "first_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'first_good_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)fNxEntry2->GetInstrument()->GetDetector()->GetFirstGoodBins()), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'first_good_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 3) { - int dims[2]; - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfPeriods(); - dims[1] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - if (!ErrorHandler(NXmakedata(fFileHandle, "first_good_bin", NX_INT32, 2, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'first_good_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "first_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'first_good_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)fNxEntry2->GetInstrument()->GetDetector()->GetFirstGoodBins()), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'first_good_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else { - std::cerr << std::endl << ">> **WARNING** first_good_bin with rank " << fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() << " requested. Do not know how to handle." << std::endl; - } - } - - // write detector_1 'last_good_bin' if present - if (fNxEntry2->GetInstrument()->GetDetector()->IsLastGoodBinPresent()) { - if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 1) { - size = 1; - idata = fNxEntry2->GetInstrument()->GetDetector()->GetLastGoodBin(); - if (!ErrorHandler(NXmakedata(fFileHandle, "last_good_bin", NX_INT32, 1, &size), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'last_good_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "last_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'last_good_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)&idata), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'last_good_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 2) { - int dims[1]; - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - if (!ErrorHandler(NXmakedata(fFileHandle, "last_good_bin", NX_INT32, 1, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'last_good_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "last_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'last_good_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)fNxEntry2->GetInstrument()->GetDetector()->GetLastGoodBins()), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'last_good_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else if (fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() == 3) { - int dims[2]; - dims[0] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfPeriods(); - dims[1] = fNxEntry2->GetInstrument()->GetDetector()->GetNoOfSpectra(); - if (!ErrorHandler(NXmakedata(fFileHandle, "last_good_bin", NX_INT32, 2, dims), PNEXUS_MAKE_DATA_ERROR, "couldn't create data entry 'last_good_bin' in NXdetector.")) return NX_ERROR; - if (!ErrorHandler(NXopendata(fFileHandle, "last_good_bin"), PNEXUS_OPEN_DATA_ERROR, "couldn't open data 'last_good_bin' in NXdetector for writting.")) return NX_ERROR; - if (!ErrorHandler(NXputdata(fFileHandle, (void*)fNxEntry2->GetInstrument()->GetDetector()->GetLastGoodBins()), PNEXUS_PUT_DATA_ERROR, "couldn't put data 'last_good_bin' in NXdetector.")) return NX_ERROR; - NXclosedata(fFileHandle); - } else { - std::cerr << std::endl << ">> **WARNING** last_good_bin with rank " << fNxEntry2->GetInstrument()->GetDetector()->GetT0Tag() << " requested. Do not know how to handle." << std::endl; - } - } - - // close group 'detector_1' - NXclosegroup(fFileHandle); - - // close group 'instrument' - NXclosegroup(fFileHandle); - - // make group 'detector_1' NXdata - if (!ErrorHandler(NXmakegroup(fFileHandle, "detector_1", "NXdata"), PNEXUS_CREATE_GROUP_ERROR, "couldn't create group 'detector_1' in NXroot.")) return NX_ERROR; - // open group 'detector_1' NXdata - if (!ErrorHandler(NXopengroup(fFileHandle, "detector_1", "NXdata"), PNEXUS_GROUP_OPEN_ERROR, "couldn't open group 'detector_1' in NXroot.")) return NX_ERROR; - - for (unsigned int i=0; iChecks if the given data are representing a valid IDF Version 1. - * - * return: - * - true if a valid IDF Version 1 representation is present - * - false otherwise - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXus::IsValidIdf1(bool strict) -{ - bool valid = true; - - if (fIdfVersion != 1) { - std::cerr << std::endl << ">> **ERROR** wrong IDF version found, namely IDF " << fIdfVersion << ", instead of IDF 1" << std::endl; + auto groupIt = fGroupAttributes.find(groupPath); + if (groupIt == fGroupAttributes.end()) { return false; } - if (!fNxEntry1->IsValid(strict)) - valid = false; - - return valid; -} - -//------------------------------------------------------------------------------------------ -// IsValidIdf2 (private) -//------------------------------------------------------------------------------------------ -/** - *

Checks if the given data are representing a valid IDF Version 2. - * - * return: - * - true if a valid IDF Version 2 representation is present - * - false otherwise - * - * \param strict flag if true a strict NeXus validation is performed. - */ -bool PNeXus::IsValidIdf2(bool strict) -{ - if (fIdfVersion != 2) { - std::cerr << std::endl << ">> **ERROR** wrong IDF version found, namely IDF " << fIdfVersion << ", instead of IDF 2" << std::endl; + auto attrIt = groupIt->second.find(attrName); + if (attrIt == groupIt->second.end()) { return false; } - if (!fCreator.compare("n/a")) { - if (strict) { - std::cerr << std::endl << ">> **ERROR** creator not set." << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** creator not set." << std::endl; - } - } + groupIt->second.erase(attrIt); - if (!fFileName.compare("n/a")) { - if (strict) { - std::cerr << std::endl << ">> **ERROR** file name not given." << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** file name not given." << std::endl; - } - } - - if (!fFileTime.compare("n/a")) { - if (strict) { - std::cerr << std::endl << ">> **ERROR** no file creation time is given." << std::endl; - return false; - } else { - std::cerr << std::endl << ">> **WARNING** no file creation time is given." << std::endl; - } - } - - if (!fNxEntry2->IsValid(strict)) { - return false; + // Clean up empty group entry + if (groupIt->second.empty()) { + fGroupAttributes.erase(groupIt); } return true; } -//------------------------------------------------------------------------------------------ -// SearchInGroup (private) -//------------------------------------------------------------------------------------------ /** - *

Searches an entry (labelled by str) in the currently open group. If the tag=='name' - * a label is looked for, if the tag=='class' a class is looked for. + * Check if a group has a specific attribute. * - * return: - * - true if entry 'str' is found in the currently open group. nxname, nxclass keeps than the entry information. - * - false otherwise - * - * \param str label/class of the entry to be looked for - * \param tag either 'name' or 'class' - * \param nxname 'name' of the entry if entry is found - * \param nxclass 'class name' of the entry if entry is found - * \param dataType of the entry if entry is found + * @param groupPath HDF5 path of the group + * @param attrName Attribute name to check + * @return true if attribute exists, false otherwise */ -bool PNeXus::SearchInGroup(std::string str, std::string tag, NXname &nxname, NXname &nxclass, int &dataType) +bool nxH5::PNeXus::HasGroupAttribute(const std::string& groupPath, const std::string& attrName) const { - bool found = false; - int status; + auto groupIt = fGroupAttributes.find(groupPath); + if (groupIt == fGroupAttributes.end()) { + return false; + } - NXinitgroupdir(fFileHandle); - do { - status = NXgetnextentry(fFileHandle, nxname, nxclass, &dataType); - if (!tag.compare("name")) { - if (!str.compare(nxname)) { - found = true; - break; + return groupIt->second.find(attrName) != groupIt->second.end(); +} + +/** + * Get an attribute value from a group. + * + * @param groupPath HDF5 path of the group + * @param attrName Attribute name + * @return The attribute value as std::any + * @throws std::out_of_range if group or attribute doesn't exist + */ +std::any nxH5::PNeXus::GetGroupAttribute(const std::string& groupPath, const std::string& attrName) const +{ + return fGroupAttributes.at(groupPath).at(attrName); +} + +/** + * Get all attributes for a group. + * + * @param groupPath HDF5 path of the group + * @return Map of attribute names to values + */ +const std::map& nxH5::PNeXus::GetGroupAttributes(const std::string& groupPath) const +{ + static const std::map emptyMap; + auto it = fGroupAttributes.find(groupPath); + if (it == fGroupAttributes.end()) { + return emptyMap; + } + return it->second; +} + +/** + * Clear all attributes from a group. + * + * @param groupPath HDF5 path of the group + * @return true if group was found and attributes cleared, false otherwise + */ +bool nxH5::PNeXus::ClearGroupAttributes(const std::string& groupPath) +{ + auto it = fGroupAttributes.find(groupPath); + if (it == fGroupAttributes.end()) { + return false; + } + + fGroupAttributes.erase(it); + return true; +} + +/** + * Add or update an attribute at the root level "/". + * This is a convenience method that calls AddGroupAttribute("/", attrName, attrValue). + * + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added successfully + */ +bool nxH5::PNeXus::AddRootAttribute(const std::string& attrName, const std::any& attrValue) +{ + return AddGroupAttribute("/", attrName, attrValue); +} + +//============================================================================= +// Write NeXus file (main entry point) +//============================================================================= +/** + * Writes the data map contents to a new NeXus HDF5 file. + * This is the main public API for writing NeXus files. + * + * @param filename Path to the output NeXus HDF5 file + * @param idfVersion IDF version to write (default: 2) + * @return 0 on success, 1 on error + * @throws H5::FileIException if file cannot be created + * @throws H5::GroupIException if group creation fails + * @throws H5::DataSetIException if dataset writing fails + */ +int nxH5::PNeXus::WriteNexusFile(const std::string& filename, int idfVersion) +{ + try { + if (fPrintDebug) { + std::cout << std::endl; + std::cout << "debug> Creating NeXus file: " << filename << std::endl; + } + + // Validate that we have data to write + if (fDataMap.empty()) { + std::cerr << "Error: No data to write (data map is empty)" << std::endl; + return 1; + } + + // Create new HDF5 file (truncate if exists) + H5::H5File file(filename, H5F_ACC_TRUNC); + + // Turn off auto-printing for exceptions + H5::Exception::dontPrint(); + + // Write root-level file attributes + WriteFileAttributes(file); + + // Write structure based on IDF version + if ((idfVersion == 1) || (idfVersion == 2)) { + if (fPrintDebug) { + std::cout << "debug> Writing IDF v2 structure..." << std::endl; } - } else if (!tag.compare("class")) { - if (!str.compare(nxclass)) { - found = true; - break; + + // Iterate through all datasets in fDataMap + for (const auto& [path, anyData] : fDataMap) { + try { + // Try PNXdata + try { + auto intData = std::any_cast>(anyData); + WriteIntDataset(file, path, intData); + continue; + } catch (const std::bad_any_cast&) {} + + // Try PNXdata + try { + auto floatData = std::any_cast>(anyData); + WriteFloatDataset(file, path, floatData); + continue; + } catch (const std::bad_any_cast&) {} + + // Try PNXdata + try { + auto strData = std::any_cast>(anyData); + WriteStringDataset(file, path, strData); + continue; + } catch (const std::bad_any_cast&) {} + + // Unknown type + if (fPrintDebug) { + std::cerr << "Warning: Unknown data type for path " << path + << std::endl; + } + + } catch (const H5::Exception& err) { + std::cerr << "Error writing dataset " << path << ": " + << err.getDetailMsg() << std::endl; + throw; + } } } else { - std::cerr << std::endl << ">> **ERROR** found tag='" << tag << "' which is not handled!" << std::endl; - break; + std::cerr << "Error: Unsupported IDF version " << idfVersion + << std::endl; + file.close(); + return 1; } - } while (!found && (status == NX_OK)); - return found; + // Close file + file.close(); + + if (fPrintDebug) { + std::cout << "debug> Successfully wrote NeXus file: " << filename + << std::endl; + } + + return 0; + + } catch (const H5::FileIException& err) { + std::cerr << "Error: Failed to create file '" << filename << "': " + << err.getDetailMsg() << std::endl; + return 1; + } catch (const H5::Exception& err) { + std::cerr << "Error: HDF5 exception occurred: " + << err.getDetailMsg() << std::endl; + return 1; + } catch (const std::exception& err) { + std::cerr << "Error: Unexpected exception: " << err.what() << std::endl; + return 1; + } } -//------------------------------------------------------------------------------------------ -// SearchAttrInData (private) -//------------------------------------------------------------------------------------------ -/** - *

Searches an attribute (labelled by str) in the currently open data set. - * - * return: - * - true if attribute 'str' is found in the currently open data set. nxname, nxclass keeps than the entry information. - * - false otherwise - * - * \param str label of the attribute to be looked for - * \param length of the attribute data if entry is found - * \param dataType of the entry if entry is found - */ -bool PNeXus::SearchAttrInData(std::string str, int &length, int &dataType) -{ - bool found = false; - int status, rank, dims[32]; - char name[128]; - - memset(name, 0, sizeof(name)); - - NXinitattrdir(fFileHandle); - do { - status = NXgetnextattra(fFileHandle, name, &rank, dims, &dataType); - if (!str.compare(name)) { - if (rank == 1) - length = dims[0]; - found = true; - break; - } - } while (!found && (status == NX_OK)); - - return found; -} +// Explicit template instantiations for write methods +template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata&); +template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata&); +template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata&); diff --git a/src/external/nexus/PNeXus.h b/src/external/nexus/PNeXus.h index 768120a26..69729cc85 100644 --- a/src/external/nexus/PNeXus.h +++ b/src/external/nexus/PNeXus.h @@ -27,611 +27,2069 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +/** + * @file PNeXus.h + * @brief NeXus HDF4/HDF5 file reader and writer for muon spin rotation data + * + * This header file defines the PNeXus library for reading and writing NeXus + * files in both HDF4 and HDF5 formats. The library is specifically designed + * for ISIS muon NeXus files but can be adapted for other NeXus data. + * + * The library provides: + * - **Dual format support**: Both HDF4 (nxH4 namespace) and HDF5 (nxH5 namespace) + * - **Case-insensitive path lookup**: Handles varying path casings in NeXus files + * - **Type-safe data handling**: Template-based PNXdata class with type preservation + * - **IDF version support**: Handles both IDF version 1 and version 2 file structures + * - **Dead time correction**: Built-in muon spectrometry dead time analysis + * + * ## Architecture + * + * The library is organized into three namespaces: + * - **nxs**: Common utilities (HDFType enum, checkHDFType function) + * - **nxH4**: HDF4-specific implementation (conditional, requires HAVE_HDF4) + * - **nxH5**: HDF5-specific implementation (always available) + * + * ## Data Organization + * + * NeXus files follow the ISIS muon format with structure: + * @verbatim + * /raw_data_1/ (NXentry) + * ├── IDF_version + * ├── beamline, definition, run_number, title + * ├── start_time, end_time, good_frames + * ├── detector_1/ (NXdata) + * │ ├── counts (multi-dimensional) + * │ ├── raw_time + * │ └── attributes (signal, axes, units, etc.) + * └── instrument/ (NXinstrument) + * ├── name, source/ + * └── detector_1/ (NXdetector) + * ├── counts, raw_time + * ├── resolution, spectrum_index + * └── dead_time + * @endverbatim + * + * ## Usage Example + * + * @code + * #include "PNeXus.h" + * + * // Check file type + * nxs::HDFType type = nxs::checkHDFType("file.nxs"); + * + * if (type == nxs::HDFType::HDF5) { + * // Read HDF5 NeXus file + * nxH5::PNeXus nexus("file.nxs"); + * nexus.Dump(); + * + * // Access data + * auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + * auto dims = counts.GetDimensions(); + * } + * @endcode + * + * @author Andreas Suter + * @date 2007-2026 + * @copyright GNU General Public License v2 + * @version 1.0 + * + * @see nxs::checkHDFType() + * @see nxH4::PNeXus + * @see nxH5::PNeXus + */ + #ifndef _PNEXUS_H_ #define _PNEXUS_H_ #include #include -#include +#include +#include +#include +#include +#include -#include "napi.h" +#include "Minuit2/FCNBase.h" -#ifndef VGNAMELENMAX -#define VGNAMELENMAX 64 -#endif /* VGNAMELENMAX */ +#ifdef HAVE_HDF4 +#include +#include +#endif // HAVE_HDF4 -#define PNEXUS_NO_ERROR 0 -#define PNEXUS_NXENTRY_NOT_FOUND 1 -#define PNEXUS_FILE_OPEN_ERROR 2 -#define PNEXUS_GROUP_OPEN_ERROR 3 -#define PNEXUS_OPEN_DATA_ERROR 4 -#define PNEXUS_GET_DATA_ERROR 5 -#define PNEXUS_GET_ATTR_ERROR 6 -#define PNEXUS_CLOSE_DATA_ERROR 7 -#define PNEXUS_GET_META_INFO_ERROR 8 -#define PNEXUS_WRONG_META_INFO 9 -#define PNEXUS_WRONG_DATE_FORMAT 10 -#define PNEXUS_WRONG_TIME_FORMAT 11 -#define PNEXUS_INIT_GROUPDIR_ERROR 12 -#define PNEXUS_GET_GROUP_INFO_ERROR 13 -#define PNEXUS_GET_NEXT_ENTRY_ERROR 14 -#define PNEXUS_HISTO_ERROR 15 -#define PNEXUS_SET_ATTR_ERROR 16 -#define PNEXUS_CREATE_GROUP_ERROR 17 -#define PNEXUS_MAKE_DATA_ERROR 18 -#define PNEXUS_PUT_DATA_ERROR 19 -#define PNEXUS_PUT_ATTR_ERROR 20 -#define PNEXUS_IDF_NOT_IMPLEMENTED 21 -#define PNEXUS_VAILD_READ_IDF1_FILE 22 -#define PNEXUS_VAILD_READ_IDF2_FILE 23 -#define PNEXUS_OBJECT_INVOK_ERROR 24 -#define PNEXUS_TIME_FORMAT_ERROR 25 -#define PNEXUS_DATA_ERROR 26 -#define PNEXUS_NXUSER_NOT_FOUND 27 -#define PNEXUS_LINKING_ERROR 28 +#include -class PNeXusProp { - public: - PNeXusProp(); - virtual ~PNeXusProp() {} +/** + * @namespace nxs + * @brief Common utilities for NeXus file handling + * + * The nxs namespace provides utility functions that are common to both + * HDF4 and HDF5 implementations. This includes file type detection and + * shared enumerations. + * + * @see nxH4 for HDF4-specific implementation + * @see nxH5 for HDF5-specific implementation + */ +namespace nxs { - virtual std::string GetName() { return fName; } - virtual double GetValue() { return fValue; } - virtual std::string GetUnit() { return fUnit; } - - virtual void SetName(std::string name) { fName = name; } - virtual void SetValue(double val) { fValue = val; } - virtual void SetUnit(std::string unit) { fUnit = unit; } - - private: - std::string fName; - double fValue; - std::string fUnit; +/** + * @enum HDFType + * @brief Enumeration of supported HDF file types + * + * Used to identify whether a file is HDF4, HDF5, or of unknown format. + * This is determined by reading the file's magic bytes header. + */ +enum class HDFType { + HDF4, ///< HDF4 format (magic: 0x0e 0x03 0x13 0x01) + HDF5, ///< HDF5 format (magic: 0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a) + Unknown ///< Unrecognized file format }; -class PNeXusBeam1 { - public: - PNeXusBeam1() { fTotalCounts = 0; fUnits = "n/a"; } - virtual ~PNeXusBeam1() {} +/** + * @brief Determine the HDF format type of a file by reading its header + * + * Reads the first 8 bytes of the file and compares them against known + * HDF4 and HDF5 magic byte signatures to determine the file format. + * + * **Magic Signatures:** + * - HDF5: `0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a` (8 bytes) + * - HDF4: `0x0e 0x03 0x13 0x01` (4 bytes) + * + * @param filename Path to the file to check + * @return HDFType indicating the file format (HDF4, HDF5, or Unknown) + * + * @note Returns Unknown if the file cannot be opened or is too small + * + * @example + * @code + * std::string file = "experiment.nxs"; + * nxs::HDFType type = nxs::checkHDFType(file); + * + * switch (type) { + * case nxs::HDFType::HDF4: + * // Use nxH4::PNeXus + * break; + * case nxs::HDFType::HDF5: + * // Use nxH5::PNeXus + * break; + * default: + * std::cerr << "Unknown file format" << std::endl; + * } + * @endcode + * + * @see nxH4::PNeXus for HDF4 file handling + * @see nxH5::PNeXus for HDF5 file handling + */ +HDFType checkHDFType(const std::string& filename); - virtual bool IsValid(bool strict); +/** + * @brief get the current time and return it as na IOS8601 + * time stamp. + * + * @return ISO 8601 time stamp string + */ +std::string getIso8601TimestampLocal(); - virtual double GetTotalCounts() { return fTotalCounts; } - virtual std::string GetUnits() { return fUnits; } +} // end namespace nxs - virtual void SetTotalCounts(double counts) { fTotalCounts = counts; } - virtual void SetUnits(std::string units) { fUnits = units; } +#ifdef HAVE_HDF4 +/** + * @namespace nxH4 + * @brief HDF4 implementation of the NeXus file reader/writer + * + * The nxH4 namespace provides classes and functions for reading and writing + * NeXus files in HDF4 format. This implementation uses the HDF4 C API + * (mfhdf.h, hdf.h) and provides case-insensitive path lookup. + * + * **Key Classes:** + * - PNeXus: Main file reader/writer class + * - PNXdata: Template class for storing dataset content + * - PNeXusDeadTime: Dead time correction calculator + * + * **Requirements:** + * - HDF4 library (libmfhdf, libhdf) + * - HAVE_HDF4 must be defined at compile time + * + * @note This namespace is only available if compiled with HAVE_HDF4 defined + * + * @see nxH5 for the HDF5 implementation + * @see nxs::checkHDFType() to determine file format before reading + */ +namespace nxH4 { - private: - double fTotalCounts; ///< total number of counts - std::string fUnits; ///< 'units' in which total counts is given, e.g. 'Mev' +class PNeXus; + +/** + * @class PNeXusDeadTime + * @brief Dead time correction calculator for muon detector data + * + * The PNeXusDeadTime class calculates dead time corrections for muon detector + * count data using ROOT Minuit2 minimization. It inherits from ROOT::Minuit2::FCNBase + * to implement a chi-square minimization function. + * + * Dead time is the period after detecting an event during which the detector + * cannot register another event. This class estimates the dead time parameter + * for each detector spectrum by minimizing the deviation from expected count rates. + * + * **Typical Usage:** + * @code + * nxH4::PNeXus nexus("file.nxs"); + * nxH4::PNeXusDeadTime ndt(&nexus, false); // false = no debug output + * + * if (ndt.IsValid()) { + * auto dims = ndt.GetDimensions(); + * // Minimize for each spectrum + * for (unsigned int i = 0; i < dims[1]; i++) { + * ndt.minimize(i); + * } + * } + * @endcode + * + * @note Requires a valid PNeXus object with count data + * @note Uses ROOT Minuit2 for minimization + * + * @see ROOT::Minuit2::FCNBase + * @see nxH4::PNeXus + */ +class PNeXusDeadTime : public ROOT::Minuit2::FCNBase +{ +public: + /** + * @brief Constructor - initializes dead time calculator from NeXus data + * * Reads count data, time resolution, and bin information from the NeXus file + * to set up the dead time calculation. + * * @param nxs Pointer to PNeXus object containing the detector data + * @param debug If true, print additional debug information during calculations + * * @note After construction, check IsValid() to ensure data was loaded successfully + */ + PNeXusDeadTime(const PNeXus *nxs, bool debug=false); + + /** + * @brief Check if the dead time calculator was initialized successfully + * * @return true if required datasets were found and loaded, false otherwise + */ + bool IsValid() { return fValid; } + + /** + * @brief Get the error definition for the minimizer (FCNBase requirement) + * * Returns the UP parameter which defines how the minimizer estimates errors. + * For chi-square minimization, UP = 0.5 corresponds to 1-sigma errors. + * * @return Error definition parameter (0.5 for chi-square) + */ + double Up() const { return fUp; } + + /** + * @brief Function call operator - calculates chi-square for given parameters + * * This is the objective function that ROOT::Minuit2 minimizes. It calculates + * the chi-square deviation between observed counts and expected counts + * corrected for dead time. + * * @param par Vector of parameters (dead time values in microseconds) + * @return Chi-square value for the given parameters + */ + double operator()(const std::vector &par) const; + + /** + * @brief Minimize dead time for a specific detector spectrum + * * Performs Minuit2 minimization to find the optimal dead time parameter + * for the specified spectrum index. Results are printed to stdout. + * * @param i Spectrum index to minimize (0-based index into second dimension) + * * @note Prints minimization results including dead time estimate and errors + * @note The index i must be less than dims[1] (number of spectra) + */ + void Minimize(const int i); + + /** + * @brief Get the dimensions of the count dataset + * * Returns the dimensions [periods, spectra, bins] of the count data + * being used for dead time calculation. + * * @return Vector of dimension sizes (typically 3D: [periods, spectra, bins]) + */ + const std::vector& GetDimensions() const { return fDims; } + +private: + bool fDebug{false}; ///< Debug flag - if true, print additional diagnostic information + bool fValid; ///< Validity flag - true if required data was loaded successfully + int fGoodFrames{-1}; ///< Number of good time frames for analysis + float fTimeResolution{0.0}; ///< Time resolution in picoseconds + std::vectorfDims = {0, 0, 0}; ///< Dimensions of count data [periods, spectra, bins] + int fT0Bin{-1}; ///< T0 bin number (time zero reference) + int fFgbBin{-1}; ///< First good bin for analysis + int fLgbBin{-1}; ///< Last good bin for analysis + std::vector fDeadTime; ///< Dead time values per detector (microseconds) + std::vector fCounts; ///< Count data for minimization + double fUp{0.5}; ///< UP parameter for Minuit2 (0.5 for chi-square) + unsigned int fIdx{0}; ///< Current spectrum index being minimized }; -class PNeXusCollimator1 { - public: - PNeXusCollimator1() { fType = "n/a"; } - virtual ~PNeXusCollimator1() {} - virtual bool IsValid(bool strict) { return true; } // currently only a dummy - - virtual std::string GetType() { return fType; } - virtual void SetType(std::string type) { fType = type; } - - private: - std::string fType; +/** + * @brief HDF4 data type enumeration + * + * This enum maps to HDF4 numeric types (DFNT_*) for type-safe dataset handling + */ +enum class H4DataType { + kINT32, ///< 32-bit signed integer (DFNT_INT32) + kFLOAT32, ///< 32-bit floating point (DFNT_FLOAT32) + kFLOAT64, ///< 64-bit floating point (DFNT_FLOAT64) + kCHAR8, ///< 8-bit character (DFNT_CHAR8) + kUINT32, ///< 32-bit unsigned integer (DFNT_UINT32) + kINT16, ///< 16-bit signed integer (DFNT_INT16) + kUINT16, ///< 16-bit unsigned integer (DFNT_UINT16) + kINT8, ///< 8-bit signed integer (DFNT_INT8) + kUINT8 ///< 8-bit unsigned integer (DFNT_UINT8) }; -class PNeXusDetector1 { - public: - PNeXusDetector1() { fNumber = 0; } - virtual ~PNeXusDetector1() {} +/** + * @class PNXdata + * @brief Template class for storing HDF4 dataset content with attributes + * + * The PNXdata class stores data read from an HDF4 dataset along with metadata + * such as dimensions, datatype, and attributes. It is designed to be stored in + * std::map where the key is the HDF4 path. + * + * @tparam T The element type of the dataset (int, float, std::string, etc.) + * + * Key features: + * - Stores multi-dimensional data as flattened std::vector + * - Preserves dimension information (shape) + * - Stores HDF4 DataType for type safety + * - Supports attributes as nested PNXdata objects + * - Compatible with std::any for type-erased storage + * + * @example + * @code + * // Create PNXdata for a 3D integer dataset (1 x 96 x 2048) + * PNXdata counts; + * counts.SetDimensions({1, 96, 2048}); + * counts.SetData(dataVector); + * + * // Store in map + * std::map dataMap; + * dataMap["/raw_data_1/detector_1/counts"] = counts; + * @endcode + */ +template class PNXdata { +public: + /** + * @brief Default constructor + */ + PNXdata() : fDataType(H4DataType::kINT32) {} - virtual bool IsValid(bool strict); + /** + * @brief Constructor with datatype + * @param dataType HDF4 datatype for this dataset + */ + PNXdata(const H4DataType& dataType) : fDataType(dataType) {} - virtual int GetNumber() { return fNumber; } - virtual void SetNumber(int number) { fNumber = number; } + /** + * @brief Get the HDF4 DataType + * @return The HDF4 DataType for this dataset + */ + H4DataType GetDataType() const { return fDataType; } - private: - int fNumber; -}; - -class PNeXusInstrument1 { - public: - PNeXusInstrument1() { fName = "n/a"; } - virtual ~PNeXusInstrument1() {} - - virtual bool IsValid(bool strict); - - virtual std::string GetName() { return fName; } - virtual PNeXusDetector1* GetDetector() { return &fDetector; } - virtual PNeXusCollimator1* GetCollimator() { return &fCollimator; } - virtual PNeXusBeam1* GetBeam() { return &fBeam; } - - virtual void SetName(std::string name) { fName = name; } - virtual void SetDetector(PNeXusDetector1 &detector) { fDetector = detector; } - virtual void SetCollimator(PNeXusCollimator1 &collimator) { fCollimator = collimator; } - virtual void SetBeam(PNeXusBeam1 &beam) { fBeam = beam; } - - private: - std::string fName; ///< instrument name - PNeXusDetector1 fDetector; - PNeXusCollimator1 fCollimator; - PNeXusBeam1 fBeam; -}; - -class PNeXusSample1 { - public: - PNeXusSample1(); - virtual ~PNeXusSample1(); - - virtual bool IsValid(bool strict); - - virtual std::string GetName() { return fName; } - virtual std::string GetShape() { return fShape; } - virtual std::string GetMagneticFieldState() { return fMagneticFieldState; } - virtual std::string GetEnvironment() { return fEnvironment; } - virtual double GetPhysPropValue(std::string name, bool &ok); - virtual void GetPhysPropUnit(std::string name, std::string &unit, bool &ok); - virtual int IsMagneticFieldVectorAvailable() { return fMagneticFieldVectorAvailable; } - virtual std::vector GetMagneticFieldVector() { return fMagneticFieldVector; } - virtual std::string GetMagneticFieldVectorUnits() { return fMagneticFieldVectorUnits; } - virtual std::string GetMagneticFieldVectorCoordinateSystem() { return fMagneticFieldVectorCoordinateSystem; } - - virtual void SetName(std::string name) { fName = name; } - virtual void SetShape(std::string shape) { fShape = shape; } - virtual void SetMagneticFieldState(std::string magFieldState) { fMagneticFieldState = magFieldState; } - virtual void SetEnvironment(std::string env) { fEnvironment = env; } - virtual void SetPhysProp(std::string name, double value, std::string unit, int idx=-1); - virtual void SetMagneticFieldVectorAvailable(int avail) { fMagneticFieldVectorAvailable = avail; } - virtual void SetMagneticFieldVector(std::vector &magVec) { fMagneticFieldVector = magVec; } - virtual void SetMagneticFieldVectorCoordinateSystem(std::string coord) { fMagneticFieldVectorCoordinateSystem = coord; } - virtual void SetMagneticFieldUnits(std::string units) { fMagneticFieldVectorUnits = units; } - - private: - std::string fName; ///< sample name - std::string fShape; ///< sample orientation - std::string fMagneticFieldState; ///< magnetic field state, e.g. TF, ZF, ... - std::string fEnvironment; ///< sample environment, e.g. CCR, Konti-1, ... - std::vector fPhysProp; ///< collects the temperature, magnetic field - - int fMagneticFieldVectorAvailable; ///< flag '0' magnetic field vector not available, '1' magnetic field vector available - std::vector fMagneticFieldVector; ///< magnetic field vector - std::string fMagneticFieldVectorUnits; ///< units in which the magnetic field vector is given - std::string fMagneticFieldVectorCoordinateSystem; ///< coordinate system, e.g. 'cartesian' -}; - -class PNeXusUser1 { - public: - PNeXusUser1(); - virtual ~PNeXusUser1() {} - - virtual bool IsValid(bool strict) { return true; } // currently only a dummy - - virtual std::string GetName() { return fName; } - virtual std::string GetExperimentNumber() { return fExperimentNumber; } - - virtual void SetName(std::string name) { fName = name; } - virtual void SetExperimentNumber(std::string expNum) { fExperimentNumber = expNum; } - - private: - std::string fName; ///< user name - std::string fExperimentNumber; ///< experiment number, RB number at ISIS -}; - -class PNeXusAlpha1 { - public: - PNeXusAlpha1(); - virtual ~PNeXusAlpha1() {} - - virtual unsigned int GetGroupFirst() { return fGroupFirst; } - virtual unsigned int GetGroupSecond() { return fGroupSecond; } - virtual double GetAlpha() { return fAlphaVal; } - - virtual void SetGroupFirst(unsigned int val) { fGroupFirst = val; } - virtual void SetGroupSecond(unsigned int val) { fGroupSecond = val; } - virtual void SetAlpha(double val) { fAlphaVal = val; } - - private: - unsigned int fGroupFirst; - unsigned int fGroupSecond; - double fAlphaVal; -}; - -class PNeXusData1 { - public: - PNeXusData1() { fTimeResolution = 0.0; } - virtual ~PNeXusData1(); - - virtual bool IsValid(bool strict); - - virtual double GetTimeResolution(std::string units); - virtual std::vector *GetT0s() { return &fT0; } - virtual int GetT0(unsigned int idx); - virtual std::vector *GetFirstGoodBins() { return &fFirstGoodBin; } - virtual int GetFirstGoodBin(unsigned int idx); - virtual std::vector *GetLastGoodBins() { return &fLastGoodBin; } - virtual int GetLastGoodBin(unsigned int idx); - virtual std::vector *GetHistoNames() { return &fHistoName; } - virtual void GetHistoName(unsigned int idx, std::string &name, bool &ok); - virtual unsigned int GetNoOfHistos() { return fHisto.size(); } - virtual unsigned int GetHistoLength(unsigned int histoNo=0); - virtual unsigned int GetHistoCounts(unsigned int histoNo=0); - virtual std::vector *GetHisto(unsigned int histoNo); - virtual std::vector *GetGrouping() { return &fGrouping; } - virtual std::vector *GetAlpha() { return &fAlpha; } - - virtual void SetTimeResolution(double val, std::string units); - virtual void SetT0(unsigned int t0, int idx=-1); - virtual void SetFirstGoodBin(unsigned int fgb, int idx=-1); - virtual void SetLastGoodBin(unsigned int lgb, int idx=-1); - virtual void FlushHistos(); - virtual void SetHisto(std::vector &data, int histoNo=-1); - virtual void FlushGrouping() { fGrouping.clear(); } - virtual void SetGrouping(std::vector &grouping) { fGrouping = grouping; } - virtual void FlushAlpha() { fAlpha.clear(); } - virtual void SetAlpha(std::vector &alpha) { fAlpha = alpha; } - - private: - double fTimeResolution; ///< time resolution in (ps) - std::vector fT0; - std::vector fFirstGoodBin; - std::vector fLastGoodBin; - std::vector fHistoName; - std::vector< std::vector > fHisto; - std::vector fGrouping; - std::vector fAlpha; -}; - -class PNeXusEntry1 { - public: - PNeXusEntry1(); - virtual ~PNeXusEntry1() {} - - virtual bool IsValid(bool strict); - - virtual std::string GetProgramName() { return fProgramName; } - virtual std::string GetProgramVersion() { return fProgramVersion; } - virtual int GetRunNumber() { return fRunNumber; } - virtual std::string GetTitle() { return fTitle; } - virtual std::string GetNotes() { return fNotes; } - virtual std::string GetAnalysis() { return fAnalysis; } - virtual std::string GetLaboratory() { return fLaboratory; } - virtual std::string GetBeamline() { return fBeamline; } - virtual std::string GetStartTime() { return fStartTime; } - virtual std::string GetStopTime() { return fStopTime; } - virtual int GetSwitchingState() { return fSwitchingState; } - virtual PNeXusUser1* GetUser() { return &fUser; } - virtual PNeXusSample1* GetSample() { return &fSample; } - virtual PNeXusInstrument1* GetInstrument() { return &fInstrument; } - virtual PNeXusData1* GetData() { return &fData; } - - virtual void SetProgramName(std::string name) { fProgramName = name; } - virtual void SetProgramVersion(std::string version) { fProgramVersion = version; } - virtual void SetRunNumber(int number) { fRunNumber = number; } - virtual void SetTitle(std::string title) { fTitle = title; } - virtual void SetNotes(std::string notes) { fNotes = notes; } - virtual void SetAnalysis(std::string analysis) { fAnalysis = analysis; } - virtual void SetLaboratory(std::string lab) { fLaboratory = lab; } - virtual void SetBeamline(std::string beamline) { fBeamline = beamline; } - virtual int SetStartTime(std::string time); - virtual int SetStopTime(std::string time); - virtual int SetSwitchingState(int state); - virtual void SetUser(PNeXusUser1 &user) { fUser = user; } - virtual void SetSample(PNeXusSample1 &sample) { fSample = sample; } - virtual void SetInstrument(PNeXusInstrument1 &instrument) { fInstrument = instrument; } - virtual void SetData(PNeXusData1 &data) { fData = data; } - - private: - std::string fProgramName; ///< name of the creating program - std::string fProgramVersion; ///< version of the creating program - int fRunNumber; ///< run number - std::string fTitle; ///< string containing the run title - std::string fNotes; ///< comments - std::string fAnalysis; ///< type of muon experiment "muonTD", "ALC", ... - std::string fLaboratory; ///< name of the laboratory where the data are taken, e.g. PSI, triumf, ISIS, J-Parc - std::string fBeamline; ///< name of the beamline used for the experiment, e.g. muE4 - std::string fStartTime; ///< start date/time of the run - std::string fStopTime; ///< stop date/time of the run - int fSwitchingState; ///< '1' normal data collection, '2' Red/Green mode - PNeXusUser1 fUser; ///< NXuser info IDF Version 1 - PNeXusSample1 fSample; ///< NXsample info IDF Version 1 - PNeXusInstrument1 fInstrument; ///< NXinstrument info IDF Version 1 - PNeXusData1 fData; ///< NXdata info IDF Version 1 -}; - -class PNeXusSource2 { - public: - PNeXusSource2(); - virtual ~PNeXusSource2() {} - - virtual bool IsValid(bool strict); - - virtual std::string GetName() { return fName; } - virtual std::string GetType() { return fType; } - virtual std::string GetProbe() { return fProbe; } - - virtual void SetName(std::string name) { fName = name; } - virtual void SetType(std::string type) { fType = type; } - virtual void SetProbe(std::string probe) { fProbe = probe; } - - private: - std::string fName; ///< facility name - std::string fType; ///< continous muon source, pulsed muon source, low energy muon source, ... - std::string fProbe; ///< positive muon, negative muon -}; - -class PNeXusBeamline2 { - public: - PNeXusBeamline2() { fName = "n/a"; } - virtual ~PNeXusBeamline2() {} - - virtual bool IsValid(bool strict); - - virtual std::string GetName() { return fName; } - - virtual void SetName(std::string name) { fName = name; } - - private: - std::string fName; -}; - -class PNeXusDetector2 { - public: - PNeXusDetector2(); - virtual ~PNeXusDetector2(); - - virtual bool IsValid(bool strict); - virtual std::string GetErrorMsg() { return fErrorMsg; } - - virtual std::string GetDescription() { return fDescription; } - virtual double GetTimeResolution(std::string units); - virtual std::vector *GetRawTime() { return &fRawTime; } - virtual std::string GetRawTimeName() { return fRawTimeName; } - virtual std::string GetRawTimeUnit() { return fRawTimeUnit; } - virtual bool IsT0Present() { return (fT0 == nullptr) ? false : true; } - virtual int GetT0Tag() { return fT0Tag; } - virtual int GetT0(int idxp=-1, int idxs=-1); - virtual int* GetT0s() { return fT0; } - virtual bool IsFirstGoodBinPresent() { return (fFirstGoodBin == nullptr) ? false : true; } - virtual int GetFirstGoodBin(int idxp=-1, int idxs=-1); - virtual int* GetFirstGoodBins() { return fFirstGoodBin; } - virtual bool IsLastGoodBinPresent() { return (fLastGoodBin == nullptr) ? false : true; } - virtual int GetLastGoodBin(int idxp=-1, int idxs=-1); - virtual int* GetLastGoodBins() { return fLastGoodBin; } - virtual int GetNoOfPeriods() { return fNoOfPeriods; } - virtual int GetNoOfSpectra() { return fNoOfSpectra; } - virtual int GetNoOfBins() { return fNoOfBins; } - virtual unsigned int GetHistoCounts(int idx_p, int idx_s); - virtual int GetHistoValue(int idx_p, int idx_s, int idx_b); - virtual int* GetHistos() { return fHisto; } - virtual unsigned int GetSpectrumIndexSize() { return fSpectrumIndex.size(); } - virtual std::vector *GetSpectrumIndex() { return &fSpectrumIndex; } - virtual int GetSpectrumIndex(unsigned int idx); - - virtual void SetDescription(std::string description) { fDescription = description; } - virtual void SetTimeResolution(double val, std::string units); - virtual void SetRawTime(std::vector &rawTime); - virtual void SetRawTimeName(std::string rawTimeName) { fRawTimeName = rawTimeName; } - virtual void SetRawTimeUnit(std::string rawTimeUnit) { fRawTimeUnit = rawTimeUnit; } - virtual void SetT0Tag(int tag) { fT0Tag = tag; } - virtual int SetT0(int *t0); - virtual int SetFirstGoodBin(int *fgb); - virtual int SetLastGoodBin(int *lgb); - virtual void SetNoOfPeriods(int val) { fNoOfPeriods = val; } - virtual void SetNoOfSpectra(int val) { fNoOfSpectra = val; } - virtual void SetNoOfBins(int val) { fNoOfBins = val; } - virtual int SetHistos(int *histo); - virtual void SetSpectrumIndex(std::vector spectIdx) { fSpectrumIndex = spectIdx; } - virtual void SetSpectrumIndex(int spectIdx, int idx=-1); - - private: - std::string fErrorMsg; ///< internal error message - std::string fDescription; ///< description of the detector - double fTimeResolution; ///< keeps the time resolution in (ps) - std::vector fSpectrumIndex; ///< list of global spectra - std::vector fRawTime; ///< keeps a raw time vector - std::string fRawTimeName; ///< name of the raw time vector - std::string fRawTimeUnit; ///< unit of the raw time vector - - int fNoOfPeriods; ///< number of periods or -1 if not defined - int fNoOfSpectra; ///< number of spectra or -1 if not defined - int fNoOfBins; ///< number of bins of a spectrum or -1 if not defined - - int fT0Tag; ///< tag for t0, fgb, lgb structure. -1==not present, 1==NX_INT, 2==NX_INT[ns], 3==NX_INT[np][ns] - int *fT0; - int *fFirstGoodBin; - int *fLastGoodBin; - int *fHisto; -}; - -class PNeXusInstrument2 { - public: - PNeXusInstrument2() { fName = "n/a"; } - virtual ~PNeXusInstrument2() {} - - virtual bool IsValid(bool strict); - - virtual std::string GetName() { return fName; } - virtual PNeXusSource2* GetSource() { return &fSource; } - virtual PNeXusBeamline2* GetBeamline() { return &fBeamline; } - virtual PNeXusDetector2* GetDetector() { return &fDetector; } - - virtual void SetName(std::string name) { fName = name; } - virtual void SetSource(PNeXusSource2 &source) { fSource = source; } - virtual void SetBeamline(PNeXusBeamline2 &beamline) { fBeamline = beamline; } - virtual void SetDetector(PNeXusDetector2 &detector) { fDetector = detector; } - - private: - std::string fName; ///< name of the instrument - PNeXusSource2 fSource; ///< details of the muon source used - PNeXusBeamline2 fBeamline; ///< beamline description - PNeXusDetector2 fDetector; ///< details of the detectors which also includes the data!! -}; - -class PNeXusSample2 { - public: - PNeXusSample2(); - virtual ~PNeXusSample2(); - - virtual bool IsValid(bool strict); - - virtual std::string GetName() { return fName; } - virtual std::string GetDescription() { return fDescription; } - virtual std::string GetMagneticFieldState() { return fMagneticFieldState; } - virtual std::string GetEnvironmentTemp() { return fEnvironmentTemp; } - virtual std::string GetEnvironmentField() { return fEnvironmentField; } - virtual double GetPhysPropValue(std::string name, bool &ok); - virtual void GetPhysPropUnit(std::string name, std::string &unit, bool &ok); - - virtual void SetName(std::string name) { fName = name; } - virtual void SetDescription(std::string description) { fDescription = description; } - virtual void SetMagneticFieldState(std::string magFieldState) { fMagneticFieldState = magFieldState; } - virtual void SetEnvironmentTemp(std::string env) { fEnvironmentTemp = env; } - virtual void SetEnvironmentField(std::string env) { fEnvironmentField = env; } - virtual void SetPhysProp(std::string name, double value, std::string unit, int idx=-1); - - private: - std::string fName; ///< sample name - std::string fDescription; ///< sample description - std::string fMagneticFieldState; ///< magnetic field state, e.g. TF, ZF, ... - std::string fEnvironmentTemp; ///< sample environment related to temperature, e.g. CCR, Konti-1, ... - std::string fEnvironmentField; ///< sample environment related to field, e.g. WEW-Bruker - std::vector fPhysProp; ///< collects the temperature, magnetic field -}; - -class PNeXusUser2 { - public: - PNeXusUser2() { fName = "n/a"; } - virtual ~PNeXusUser2() {} - - virtual bool IsValid(bool strict) { return true; } // currently only a dummy - - virtual std::string GetName() { return fName; } - - virtual void SetName(std::string name) { fName = name; } - - private: - std::string fName; ///< user name -}; - -class PNeXusEntry2 { - public: - PNeXusEntry2(); - virtual ~PNeXusEntry2() {} - - virtual bool IsValid(bool strict); - - virtual std::string GetErrorMsg() { return fErrorMsg; } - virtual std::string GetDefinition() { return fDefinition; } - virtual std::string GetProgramName() { return fProgramName; } - virtual std::string GetProgramVersion() { return fProgramVersion; } - virtual int GetRunNumber() { return fRunNumber; } - virtual std::string GetTitle() { return fTitle; } - virtual std::string GetStartTime() { return fStartTime; } - virtual std::string GetStopTime() { return fStopTime; } - virtual std::string GetExperimentIdentifier() { return fExperimentIdentifier; } - virtual PNeXusUser2* GetUser() { return &fUser; } - virtual PNeXusSample2* GetSample() { return &fSample; } - virtual PNeXusInstrument2* GetInstrument() { return &fInstrument; } - - virtual void SetDefinition(std::string def) { fDefinition = def; } - virtual void SetProgramName(std::string name) { fProgramName = name; } - virtual void SetProgramVersion(std::string version) { fProgramVersion = version; } - virtual void SetRunNumber(int number) { fRunNumber = number; } - virtual void SetTitle(std::string title) { fTitle = title; } - virtual int SetStartTime(std::string time); - virtual int SetStopTime(std::string time); - virtual void SetExperimentIdentifier(std::string expId) { fExperimentIdentifier = expId; } - virtual void SetUser(PNeXusUser2 &user) { fUser = user; } - virtual void SetSample(PNeXusSample2 &sample) { fSample = sample; } - virtual void SetInstrument(PNeXusInstrument2 &instrument) { fInstrument = instrument; } - - private: - std::string fErrorMsg; ///< internal error message - std::string fDefinition; ///< the template (DTD name) on which the entry was based, e.g. 'pulsedTD' - std::string fProgramName; ///< name of the creating program - std::string fProgramVersion; ///< version of the creating program - int fRunNumber; ///< run number - std::string fTitle; ///< string containing the run title - std::string fStartTime; ///< start date/time of the run - std::string fStopTime; ///< stop date/time of the run - std::string fExperimentIdentifier; ///< experiment number, (for ISIS, the RB number) - PNeXusUser2 fUser; ///< NXuser info IDF Version 2 - PNeXusSample2 fSample; ///< NXsample info IDF Version 2 - PNeXusInstrument2 fInstrument; ///< NXinstrument inf IDF Version 2 + /** + * @brief Set the HDF4 DataType + * @param dataType The HDF4 DataType to set + */ + void SetDataType(const H4DataType& dataType) { fDataType = dataType; } + + /** + * @brief Get the data as a vector + * @return Reference to the data vector + */ + const std::vector& GetData() const { return fData; } + + /** + * @brief Get mutable reference to the data vector + * @return Reference to the data vector + */ + std::vector& GetData() { return fData; } + + /** + * @brief Set the data vector + * @param data Vector of data to store + */ + void SetData(const std::vector& data) { fData = data; } + + /** + * @brief Get the dimensions of the dataset + * @return Vector of dimension sizes + */ + const std::vector& GetDimensions() const { return fDimensions; } + + /** + * @brief Set the dimensions of the dataset + * @param dims Vector of dimension sizes + */ + void SetDimensions(const std::vector& dims) { fDimensions = dims; } + + /** + * @brief Get the number of dimensions + * @return Number of dimensions (rank) + */ + size_t GetRank() const { return fDimensions.size(); } + + /** + * @brief Get total number of elements + * @return Product of all dimensions + */ + size_t GetNumElements() const { + if (fDimensions.empty()) return 0; + size_t total = 1; + for (auto dim : fDimensions) { + total *= dim; + } + return total; + } + + /** + * @brief Add an attribute to this dataset + * @param name Attribute name + * @param value Attribute value (stored as std::any to support different types) + */ + void AddAttribute(const std::string& name, const std::any& value) { + fAttributes[name] = value; + } + + /** + * @brief Get an attribute by name + * @param name Attribute name + * @return The attribute value as std::any + * @throws std::out_of_range if attribute doesn't exist + */ + std::any GetAttribute(const std::string& name) const { + return fAttributes.at(name); + } + + /** + * @brief Check if an attribute exists + * @param name Attribute name + * @return true if attribute exists, false otherwise + */ + bool HasAttribute(const std::string& name) const { + return fAttributes.find(name) != fAttributes.end(); + } + + /** + * @brief Get all attributes + * @return Map of attribute names to values + */ + const std::map& GetAttributes() const { + return fAttributes; + } + + /** + * @brief Get mutable reference to all attributes + * @return Mutable map of attribute names to values + */ + std::map& GetAttributes() { + return fAttributes; + } + +private: + H4DataType fDataType; ///< HDF4 datatype of the dataset + std::vector fData; ///< Data storage (flattened multi-dimensional array) + std::vector fDimensions; ///< Dimensions of the dataset + std::map fAttributes; ///< Attributes associated with this dataset }; +/** + * @class PNeXus + * @brief NeXus HDF4 file reader with case-insensitive path lookup + * + * The PNeXus class provides functionality for reading ISIS muon NeXus HDF4 files + * using the HDF4 C API. It implements case-insensitive path lookup for + * datasets, groups, and attributes to handle files with varying path casings. + * + * Key features: + * - Automatic file reading upon construction + * - Case-insensitive path resolution for datasets and groups + * - Case-insensitive attribute name matching + * - Comprehensive exception handling + * + * @example + * @code + * PNeXus nexus("data/experiment.nxs"); + * // Case-insensitive paths work: "/RAW_DATA_1/IDF_version" -> "/raw_data_1/IDF_version" + * @endcode + * + * @note All path lookups throw appropriate exceptions on failure + */ class PNeXus { - public: - PNeXus(); - PNeXus(const char* fileName); - virtual ~PNeXus(); +public: + /** + * @brief Default constructor - creates an empty PNeXus object + * + * Creates a PNeXus object without opening any file. Use this constructor + * when you want to build a NeXus file from scratch by adding datasets + * programmatically. + * + * @example + * @code + * nxH4::PNeXus nexus; + * nexus.AddDataset("/raw_data_1/run_number", {12345}, {1}, nxH4::H4DataType::INT32); + * nexus.WriteNexusFile("output.nxs"); + * @endcode + */ + PNeXus(); - virtual int GetIdfVersion() { return fIdfVersion; } - virtual std::string GetFileName() { return fFileName; } - virtual std::string GetFileTime() { return fFileTime; } + /** + * @brief Constructor - creates PNeXus object and reads the NeXus file + * @param fln Filename of the NeXus HDF4 file to read + * @param printDebug Enable debug output + * @throws std::runtime_error if file cannot be opened or read + */ + PNeXus(const std::string fln, const bool printDebug=false); - virtual void SetIdfVersion(unsigned int idf); - virtual void SetFileName(std::string name) { fFileName = name; } - virtual void SetFileTime(std::string time) { fFileTime = time; } + /** + * @brief Destructor - closes HDF4 file if open + */ + ~PNeXus(); - virtual PNeXusEntry1* GetEntryIdf1() { return fNxEntry1.get(); } - virtual PNeXusEntry2* GetEntryIdf2() { return fNxEntry2.get(); } + /** + * @brief Get the filename of the NeXus file + * @return The filename as a string + */ + std::string GetFileName() const { return fFileName; } - virtual bool IsValid(bool strict=false); - virtual int GetErrorCode() { return fErrorCode; } - virtual std::string GetErrorMsg() { return fErrorMsg; } + /** + * @brief Get the hdf4 library version + * @return The hdf4 version as a string + */ + std::string GetHdf4LibVersion() const { return fHdf4LibVersion; } - virtual std::vector* GetGroupedHisto(unsigned int idx); + /** + * @brief Get the hdf4 version of the NeXus file + * @return The hdf4 version as a string + */ + std::string GetHdf4Version() const { return fHdf4Version; } - virtual int ReadFile(const char *fileName); - virtual int WriteFile(const char *fileName, const char *fileType="hdf4", const unsigned int idf=2); + /** + * @brief Get the NeXus version of the file + * @return The NeXus version as a string + */ + std::string GetNeXusVersion() const { return fNeXusVersion; } - virtual void SetCreator(std::string str) { fCreator = str; } + /** + * @brief Get the Idf version of the file + * @return The Idf version tag + */ + int GetIdfVersion() const { return fIdfVersion; } - virtual void Dump(const bool counts); + /** + * @brief Read and parse the NeXus HDF4 file + * @return 0 on success, 1 on error + * @throws std::runtime_error if file cannot be opened or read + */ + int ReadNexusFile(); - private: - bool fValid; - int fErrorCode; - std::string fErrorMsg; + /** + * @brief Get the data map containing all datasets + * @return Reference to the data map (path -> PNXdata stored in std::any) + */ + const std::map& GetDataMap() const { return fDataMap; } - std::string fNeXusVersion; ///< version of the NeXus API used in writing the file - std::string fFileFormatVersion; ///< version of the HDF, HDF5, or XML library used to create the file (IDF 2 only) + /** + * @brief Get mutable reference to the data map + * @return Reference to the data map + */ + std::map& GetDataMap() { return fDataMap; } - unsigned int fIdfVersion; ///< version of the instrument definition - std::string fFileName; ///< file name of the original NeXus file to assist identification if the external name has been changed - std::string fFileTime; ///< date and time of file creating (IDF 2 only) - NXhandle fFileHandle; + /** + * @brief Check if a dataset path exists in the data map + * @param path HDF4 path to check + * @return true if path exists, false otherwise + */ + bool HasDataset(const std::string& path) const { return fDataMap.find(path) != fDataMap.end(); } - std::string fCreator; ///< facility of program where the file originated + /** + * @brief Add or update a dataset in the data map + * @tparam T Data type of the PNXdata object + * @param path HDF4 path for the dataset + * @param data PNXdata object to store + */ + template + void SetDataset(const std::string& path, const PNXdata& data) { + fDataMap[path] = data; + } - std::unique_ptr fNxEntry1; ///< NXentry for IDF 1 - std::unique_ptr fNxEntry2; ///< NXentry for IDF 2 + /** + * @brief Get a dataset from the data map + * @tparam T Data type of the PNXdata object + * @param path HDF4 path of the dataset + * @return PNXdata object + * @throws std::out_of_range if path doesn't exist + * @throws std::bad_any_cast if type T doesn't match stored type + */ + template + PNXdata GetDataset(const std::string& path) const { + return std::any_cast>(fDataMap.at(path)); + } - std::vector< std::vector > fGroupedHisto; + /** + * @brief Get a mutable reference to a dataset in the data map + * @tparam T Data type of the PNXdata object + * @param path HDF4 path of the dataset + * @return Reference to PNXdata object + * @throws std::out_of_range if path doesn't exist + * @throws std::bad_any_cast if type T doesn't match stored type + */ + template + PNXdata& GetDatasetRef(const std::string& path) { + return std::any_cast&>(fDataMap.at(path)); + } - virtual void Init(); - virtual bool ErrorHandler(NXstatus status, int errCode, const std::string &errMsg); - virtual NXstatus GetStringData(std::string &str); - virtual NXstatus GetStringAttr(std::string attr, std::string &str); - virtual int GetDataSize(int type); - virtual NXstatus GetDoubleVectorData(std::vector &data); - virtual NXstatus GetIntVectorData(std::vector &data); + /** + * @brief Remove a dataset from the data map + * @param path HDF4 path of the dataset to remove + * @return true if dataset was removed, false if path didn't exist + */ + bool RemoveDataset(const std::string& path) { + auto it = fDataMap.find(path); + if (it != fDataMap.end()) { + fDataMap.erase(it); + return true; + } + return false; + } - virtual int ReadFileIdf1(); - virtual int ReadFileIdf2(); + /** + * @brief Clear all datasets from the data map + */ + void ClearDataMap() { fDataMap.clear(); } - virtual int WriteFileIdf1(const char* fileName, const NXaccess access); - virtual int WriteFileIdf2(const char* fileName, const NXaccess access); + /** + * @brief Get the number of datasets in the data map + * @return Number of datasets stored + */ + size_t GetNumDatasets() const { return fDataMap.size(); } - virtual NXstatus GroupHistoData(); + /** + * @brief Update the data of an existing dataset + * @tparam T Data type of the PNXdata object + * @param path HDF4 path of the dataset + * @param newData New data vector to set + * @return true if update succeeded, false if path doesn't exist + */ + template + bool UpdateDatasetData(const std::string& path, const std::vector& newData) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.SetData(newData); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } - virtual bool IsValidIdf1(bool strict); - virtual bool IsValidIdf2(bool strict); + /** + * @brief Update the dimensions of an existing dataset + * @tparam T Data type of the PNXdata object + * @param path HDF4 path of the dataset + * @param newDimensions New dimensions vector to set + * @return true if update succeeded, false if path doesn't exist + */ + template + bool UpdateDatasetDimensions(const std::string& path, const std::vector& newDimensions) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.SetDimensions(newDimensions); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } - virtual bool SearchInGroup(std::string str, std::string tag, NXname &nxname, NXname &nxclass, int &dataType); - virtual bool SearchAttrInData(std::string str, int &length, int &dataType); + /** + * @brief Add or update an attribute for a dataset + * @tparam T Data type of the PNXdata object + * @param path HDF4 path of the dataset + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added, false if dataset doesn't exist + */ + template + bool AddDatasetAttribute(const std::string& path, const std::string& attrName, + const std::any& attrValue) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.AddAttribute(attrName, attrValue); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Remove an attribute from a dataset + * @tparam T Data type of the PNXdata object + * @param path HDF4 path of the dataset + * @param attrName Attribute name to remove + * @return true if attribute was removed, false if dataset doesn't exist or attribute not found + */ + template + bool RemoveDatasetAttribute(const std::string& path, const std::string& attrName) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + auto& attrs = dataset.GetAttributes(); + auto it = attrs.find(attrName); + if (it != attrs.end()) { + attrs.erase(it); + return true; + } + return false; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Create and add a new dataset with data, dimensions, and optional attributes + * @tparam T Data type of the PNXdata object + * @param path HDF4 path for the new dataset + * @param data Data vector + * @param dimensions Dimensions vector + * @param dataType HDF4 DataType (optional, uses default if not provided) + * @return true if dataset was created, false if path already exists + */ + template + bool AddDataset(const std::string& path, const std::vector& data, + const std::vector& dimensions, + const H4DataType& dataType = H4DataType::kINT32) { + if (HasDataset(path)) return false; + PNXdata dataset(dataType); + dataset.SetData(data); + dataset.SetDimensions(dimensions); + fDataMap[path] = dataset; + return true; + } + + /** + * @brief Modify an existing dataset's data and dimensions + * @tparam T Data type of the PNXdata object + * @param path HDF4 path of the dataset + * @param newData New data vector + * @param newDimensions New dimensions vector + * @return true if modification succeeded, false if path doesn't exist + */ + template + bool ModifyDataset(const std::string& path, const std::vector& newData, + const std::vector& newDimensions) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.SetData(newData); + dataset.SetDimensions(newDimensions); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Print a human-readable dump of the NeXus file contents + * + * Outputs the contents of the loaded NeXus file to stdout in a + * hierarchical format. The output includes: + * - File metadata (HDF4 version, NeXus version, file name, file time) + * - IDF version + * - Run information (run number, title, start/stop time) + * - Sample information (name, temperature, magnetic field) + * - Instrument information + * - Detector counts with dimensions and first few values + * + * The output format differs based on IDF version (1 or 2). + * + * @note This method is primarily useful for debugging and verification + */ + void Dump(); + + /** + * @brief Write the data map contents to a NeXus HDF4 file + * @param filename Path to the output NeXus HDF4 file + * @param idfVersion IDF version to write (default: 2) + * @return 0 on success, 1 on error + * @throws std::runtime_error if file cannot be created or written + */ + int WriteNexusFile(const std::string& filename, int idfVersion = 2); + + /** + * @brief Add or update an attribute for a group + * @param groupPath HDF4 path of the group (e.g., "/raw_data_1") + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added successfully + * @example + * @code + * nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + * @endcode + */ + bool AddGroupAttribute(const std::string& groupPath, const std::string& attrName, + const std::any& attrValue); + + /** + * @brief Remove an attribute from a group + * @param groupPath HDF4 path of the group + * @param attrName Attribute name to remove + * @return true if attribute was removed, false if group or attribute not found + */ + bool RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName); + + /** + * @brief Check if a group has a specific attribute + * @param groupPath HDF4 path of the group + * @param attrName Attribute name to check + * @return true if attribute exists, false otherwise + */ + bool HasGroupAttribute(const std::string& groupPath, const std::string& attrName) const; + + /** + * @brief Get an attribute value from a group + * @param groupPath HDF4 path of the group + * @param attrName Attribute name + * @return The attribute value as std::any + * @throws std::out_of_range if group or attribute doesn't exist + */ + std::any GetGroupAttribute(const std::string& groupPath, const std::string& attrName) const; + + /** + * @brief Get all attributes for a group + * @param groupPath HDF4 path of the group + * @return Map of attribute names to values + */ + const std::map& GetGroupAttributes(const std::string& groupPath) const; + + /** + * @brief Clear all attributes from a group + * @param groupPath HDF4 path of the group + * @return true if group was found and attributes cleared, false otherwise + */ + bool ClearGroupAttributes(const std::string& groupPath); + + /** + * @brief Add or update an attribute at the root level "/" + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added successfully + * @note This is a convenience method that calls AddGroupAttribute("/", attrName, attrValue) + * @example + * @code + * nexus.AddRootAttribute("experiment_id", std::string("EXP-2024-001")); + * nexus.AddRootAttribute("temperature", 293.15f); + * nexus.AddRootAttribute("scan_number", static_cast(42)); + * @endcode + */ + bool AddRootAttribute(const std::string& attrName, const std::any& attrValue); + +private: + bool fPrintDebug{false}; ///< if true print additional debug information + std::string fFileName{""}; ///< NeXus HDF4 filename + int fIdfVersion{-1}; ///< IDF version of the NeXus file + std::string fHdf4LibVersion{""}; ///< HDF4 library version used when calling PNeXus + std::string fHdf4Version{""}; ///< HDF4 version of the file + std::string fNeXusVersion{""}; ///< NeXus version of the file + std::string fFileNameNxs{""}; + std::string fFileTimeNxs{""}; + std::string fCreatorNxs{""}; + std::string fUserV1{""}; + int32 fSdId{-1}; ///< HDF4 SD interface identifier + int32 fFileId{-1}; ///< HDF4 file identifier + std::map fDataMap; ///< Map of HDF4 paths to PNXdata objects + std::map> fGroupAttributes; ///< Map of group paths to their attributes + + /** + * @brief Read datasets for IDF version 1 file structure + * + * Reads datasets specific to the IDF version 1 NeXus format, which uses + * a "/run/" group hierarchy. Datasets are organized under paths like: + * - /run/IDF_version, /run/number, /run/title + * - /run/sample/temperature, /run/sample/magnetic_field + * - /run/histogram_data_1/counts, /run/histogram_data_1/raw_time + * + * @param sd_id HDF4 SD interface ID + * + * @note IDF v1 is an older format primarily used by ISIS facilities + */ + void HandleIdfV1(int32 sd_id); + + /** + * @brief Read datasets for IDF version 2 file structure + * + * Reads datasets specific to the IDF version 2 NeXus format, which uses + * a "/raw_data_1/" group hierarchy. Datasets are organized under paths like: + * - /raw_data_1/IDF_version, /raw_data_1/run_number, /raw_data_1/title + * - /raw_data_1/instrument/detector_1/counts + * - /raw_data_1/instrument/detector_1/resolution + * + * @param sd_id HDF4 SD interface ID + * + * @note IDF v2 is the current standard format for ISIS muon NeXus files + */ + void HandleIdfV2(int32 sd_id); + + // ======================================================================== + // Write methods for HDF4 file creation + // ======================================================================== + + /** + * @brief Write dataset attributes from PNXdata object + * @tparam T The data type of the PNXdata object + * @param sds_id HDF4 dataset ID to write attributes to + * @param data PNXdata object containing attributes + */ + template + void WriteDatasetAttributes(int32 sds_id, const PNXdata& data); + + /** + * @brief Write attributes to a Vgroup + * @param vgroup_id HDF4 Vgroup ID to write attributes to + * @param attributes Map of attribute names to values + */ + void WriteVGroupAttributes(int32 vgroup_id, + const std::map& attributes); + + /** + * @brief Create the Vgroup hierarchy for a given path + * + * Given a path like "/run/sample", creates Vgroups "run" and "sample" + * (nested), writing any associated group attributes from fGroupAttributes. + * Uses a cache to avoid creating duplicate Vgroups. + * + * @param file_id HDF4 file ID (from Hopen) + * @param path The group path to create (e.g., "/run/sample") + * @param vgroupCache Map of already-created group paths to their Vgroup references + * @return The Vgroup reference of the deepest group in the path + */ + int32 CreateVGroupHierarchy(int32 file_id, const std::string& path, + std::map& vgroupCache); + + /** + * @brief Write an integer dataset with attributes and return its SDS reference + * @param sd_id HDF4 SD interface ID + * @param path Dataset path (leaf name used for SDS name) + * @param data PNXdata object containing data, dimensions, and attributes + * @return SDS reference number for linking into Vgroups + * @throws std::runtime_error if writing fails + */ + int32 WriteIntDataset(int32 sd_id, const std::string& path, + const PNXdata& data); + + /** + * @brief Write a float dataset with attributes and return its SDS reference + * @param sd_id HDF4 SD interface ID + * @param path Dataset path (leaf name used for SDS name) + * @param data PNXdata object containing data, dimensions, and attributes + * @return SDS reference number for linking into Vgroups + * @throws std::runtime_error if writing fails + */ + int32 WriteFloatDataset(int32 sd_id, const std::string& path, + const PNXdata& data); + + /** + * @brief Write a string dataset with attributes and return its SDS reference + * @param sd_id HDF4 SD interface ID + * @param path Dataset path (leaf name used for SDS name) + * @param data PNXdata object containing data, dimensions, and attributes + * @return SDS reference number for linking into Vgroups, or -1 for empty strings + * @throws std::runtime_error if writing fails + */ + int32 WriteStringDataset(int32 sd_id, const std::string& path, + const PNXdata& data); + + /** + * @brief Write root-level file attributes + * @param sd_id HDF4 SD interface ID + * @throws std::runtime_error if writing fails + */ + void WriteFileAttributes(int32 sd_id); + + // ======================================================================== + // Case-insensitive lookup helper methods + // ======================================================================== + + /** + * @brief Compare two strings case-insensitively + * @param a First string to compare + * @param b Second string to compare + * @return true if strings are equal (ignoring case), false otherwise + * @note Uses std::tolower for character-by-character comparison + */ + static bool CaseInsensitiveEquals(const std::string& a, const std::string& b); + + /** + * @brief Split an HDF4 path into components + * @param path HDF4 path string (e.g., "/raw_data_1/IDF_version") + * @return Vector of path components + * @note Empty first component indicates absolute path + * @example "/raw_data_1/IDF_version" -> ["", "raw_data_1", "IDF_version"] + */ + static std::vector SplitPath(const std::string& path); + + /** + * @brief Find attribute name with case-insensitive matching + * @param sd_id HDF4 SD interface ID + * @param requestedName Requested attribute name (any case) + * @return Correctly-cased attribute name as it exists in the file + * @throws std::runtime_error if attribute not found + * @example "nexus_version" might resolve to "NeXus_version" + */ + std::string FindAttributeName(int32 sd_id, const std::string& requestedName); + + /** + * @brief Find dataset index with case-insensitive matching + * @param sd_id HDF4 SD interface ID + * @param requestedName Requested dataset name (any case) + * @return Dataset index + * @throws std::runtime_error if dataset not found + */ + int32 FindDatasetIndex(int32 sd_id, const std::string& requestedName); + + /** + * @brief Find dataset reference by navigating VGroup hierarchy + * @param path Full hierarchical path (e.g., "/run/sample/name") + * @return Dataset reference number, or -1 if not found + * @note Uses VGroup navigation to resolve paths in HDF4 files with duplicate dataset names + */ + int32 FindDatasetRefByPath(const std::string& path); + + // ======================================================================== + // Dataset reading helper methods + // ======================================================================== + + /** + * @brief Read an integer dataset and store in data map + * @param sd_id HDF4 SD interface ID + * @param path Path to the dataset + * @throws std::runtime_error if reading fails + */ + void ReadIntDataset(int32 sd_id, const std::string& path); + + /** + * @brief Read a float dataset and store in data map + * @param sd_id HDF4 SD interface ID + * @param path Path to the dataset + * @throws std::runtime_error if reading fails + */ + void ReadFloatDataset(int32 sd_id, const std::string& path); + + /** + * @brief Read a string dataset and store in data map + * @param sd_id HDF4 SD interface ID + * @param path Path to the dataset + * @throws std::runtime_error if reading fails + */ + void ReadStringDataset(int32 sd_id, const std::string& path); + + /** + * @brief Read dataset attributes and add to PNXdata object + * @tparam T The data type of the PNXdata object + * @param sds_id HDF4 dataset ID + * @param data PNXdata object to add attributes to + */ + template + void ReadDatasetAttributes(int32 sds_id, PNXdata& data); + + /** + * @brief Convert HDF4 data type to H4DataType enum + * @param hdf4_type HDF4 numeric type constant (DFNT_*) + * @return H4DataType enum value + */ + static H4DataType convertHdf4Type(int32 hdf4_type); + + /** + * @brief Convert H4DataType enum to HDF4 data type + * @param dataType H4DataType enum value + * @return HDF4 numeric type constant (DFNT_*) + */ + static int32 convertToHdf4Type(H4DataType dataType); }; +} // end namespace nxH4 +#endif // HAVE_HDF4 + +/** + * @namespace nxH5 + * @brief HDF5 implementation of the NeXus file reader/writer + * + * The nxH5 namespace provides classes and functions for reading and writing + * NeXus files in HDF5 format. This implementation uses the HDF5 C++ API + * (H5Cpp.h) and provides case-insensitive path lookup. + * + * **Key Classes:** + * - PNeXus: Main file reader/writer class + * - PNXdata: Template class for storing dataset content with HDF5 DataTypes + * - PNeXusDeadTime: Dead time correction calculator + * + * **Requirements:** + * - HDF5 C++ library (libhdf5_cpp) + * + * **Key Features:** + * - Case-insensitive path resolution for datasets and groups + * - Multi-dimensional array support with dimension tracking + * - Attribute management for datasets and groups + * - Type-safe operations using PNXdata template class + * - IDF version 1 and 2 support + * + * @example + * @code + * // Read an HDF5 NeXus file + * nxH5::PNeXus nexus("experiment.nxs"); + * + * // Access data with type safety + * if (nexus.HasDataset("/raw_data_1/detector_1/counts")) { + * auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + * auto dims = counts.GetDimensions(); // [periods, spectra, bins] + * auto data = counts.GetData(); // flattened data vector + * } + * + * // Calculate dead time corrections + * nxH5::PNeXusDeadTime dtCalc(&nexus, true); + * if (dtCalc.IsValid()) { + * for (size_t i = 0; i < dtCalc.GetDimensions()[1]; i++) { + * dtCalc.minimize(i); + * } + * } + * @endcode + * + * @note This is the primary implementation and is always available + * + * @see nxH4 for the HDF4 implementation + * @see nxs::checkHDFType() to determine file format before reading + */ +namespace nxH5 { + +class PNeXus; + +/** + * @class PNeXusDeadTime + * @brief Dead time correction calculator for muon detector data + * + * The PNeXusDeadTime class calculates dead time corrections for muon detector + * count data using ROOT Minuit2 minimization. It inherits from ROOT::Minuit2::FCNBase + * to implement a chi-square minimization function. + * + * Dead time is the period after detecting an event during which the detector + * cannot register another event. This class estimates the dead time parameter + * for each detector spectrum by minimizing the deviation from expected count rates. + * + * **Typical Usage:** + * @code + * nxH5::PNeXus nexus("file.nxs"); + * nxH5::PNeXusDeadTime ndt(&nexus, false); // false = no debug output + * + * if (ndt.IsValid()) { + * auto dims = ndt.GetDimensions(); + * // Minimize for each spectrum + * for (unsigned int i = 0; i < dims[1]; i++) { + * ndt.minimize(i); + * } + * } + * @endcode + * + * @note Requires a valid PNeXus object with count data + * @note Uses ROOT Minuit2 for minimization + * + * @see ROOT::Minuit2::FCNBase + * @see nxH5::PNeXus + */ +class PNeXusDeadTime : public ROOT::Minuit2::FCNBase +{ +public: + /** + * @brief Constructor - initializes dead time calculator from NeXus data + * * Reads count data, time resolution, and bin information from the NeXus file + * to set up the dead time calculation. + * * @param nxs Pointer to PNeXus object containing the detector data + * @param debug If true, print additional debug information during calculations + * * @note After construction, check IsValid() to ensure data was loaded successfully + */ + PNeXusDeadTime(const PNeXus *nxs, bool debug=false); + + /** + * @brief Check if the dead time calculator was initialized successfully + * * @return true if required datasets were found and loaded, false otherwise + */ + bool IsValid() { return fValid; } + + /** + * @brief Get the error definition for the minimizer (FCNBase requirement) + * * Returns the UP parameter which defines how the minimizer estimates errors. + * For chi-square minimization, UP = 0.5 corresponds to 1-sigma errors. + * * @return Error definition parameter (0.5 for chi-square) + */ + double Up() const { return fUp; } + + /** + * @brief Function call operator - calculates chi-square for given parameters + * * This is the objective function that ROOT::Minuit2 minimizes. It calculates + * the chi-square deviation between observed counts and expected counts + * corrected for dead time. + * * @param par Vector of parameters (dead time values in microseconds) + * @return Chi-square value for the given parameters + */ + double operator()(const std::vector &par) const; + + /** + * @brief Minimize dead time for a specific detector spectrum + * * Performs Minuit2 minimization to find the optimal dead time parameter + * for the specified spectrum index. Results are printed to stdout. + * * @param i Spectrum index to minimize (0-based index into second dimension) + * * @note Prints minimization results including dead time estimate and errors + * @note The index i must be less than dims[1] (number of spectra) + */ + void Minimize(const int i); + + /** + * @brief Get the dimensions of the count dataset + * * Returns the dimensions [periods, spectra, bins] of the count data + * being used for dead time calculation. + * * @return Vector of dimension sizes (typically 3D: [periods, spectra, bins]) + */ + const std::vector& GetDimensions() const { return fDims; } + + /** + * @brief Get the estimated dead time values from minimization + * + * Returns the dead time values calculated by the minimize() function + * for each spectrum. These values represent the dead time parameter + * that best fits the observed count data when accounting for dead + * time losses. + * + * @return Vector of estimated dead time values (in microseconds) per spectrum + * + * @note Call minimize() for each spectrum before calling this method + * to populate the estimated values + * + * @see minimize() for the calculation method + */ + std::vector GetDeadTimeEstimated() { return fDeadTimeEstimated; } + +private: + bool fDebug{false}; ///< Debug flag - if true, print additional diagnostic information + bool fValid; ///< Validity flag - true if required data was loaded successfully + int fGoodFrames{-1}; ///< Number of good time frames for analysis + float fTimeResolution{0.0}; ///< Time resolution in picoseconds + std::vectorfDims = {0, 0, 0}; ///< Dimensions of count data [periods, spectra, bins] + int fT0Bin{-1}; ///< T0 bin number (time zero reference) + int fFgbBin{-1}; ///< First good bin for analysis + int fLgbBin{-1}; ///< Last good bin for analysis + std::vector fDeadTime; ///< Dead time values per detector (microseconds) from the file + std::vector fDeadTimeEstimated; ///< Dead time values per detector (microseconds) as estimated from the data + std::vector fCounts; ///< Count data for minimization + double fUp{0.5}; ///< UP parameter for Minuit2 (0.5 for chi-square) + unsigned int fIdx{0}; ///< Current spectrum index being minimized +}; + + +/** + * @class PNXdata + * @brief Template class for storing HDF5 dataset content with attributes + * + * The PNXdata class stores data read from an HDF5 dataset along with metadata + * such as dimensions, datatype, and attributes. It is designed to be stored in + * std::map where the key is the HDF5 path. + * + * @tparam T The element type of the dataset (int, float, std::string, etc.) + * + * Key features: + * - Stores multi-dimensional data as flattened std::vector + * - Preserves dimension information (shape) + * - Stores HDF5 DataType for type safety + * - Supports attributes as nested PNXdata objects + * - Compatible with std::any for type-erased storage + * + * @example + * @code + * // Create PNXdata for a 3D integer dataset (1 x 96 x 2048) + * PNXdata counts; + * counts.SetDimensions({1, 96, 2048}); + * counts.SetData(dataVector); + * + * // Store in map + * std::map dataMap; + * dataMap["/raw_data_1/detector_1/counts"] = counts; + * @endcode + */ +template class PNXdata { +public: + /** + * @brief Default constructor + */ + PNXdata() : fDataType(H5::PredType::NATIVE_INT) {} + + /** + * @brief Constructor with datatype + * @param dataType HDF5 datatype for this dataset + */ + PNXdata(const H5::DataType& dataType) : fDataType(dataType) {} + + /** + * @brief Get the HDF5 DataType + * @return The HDF5 DataType for this dataset + */ + H5::DataType GetDataType() const { return fDataType; } + + /** + * @brief Set the HDF5 DataType + * @param dataType The HDF5 DataType to set + */ + void SetDataType(const H5::DataType& dataType) { fDataType = dataType; } + + /** + * @brief Get the data as a vector + * @return Reference to the data vector + */ + const std::vector& GetData() const { return fData; } + + /** + * @brief Get mutable reference to the data vector + * @return Reference to the data vector + */ + std::vector& GetData() { return fData; } + + /** + * @brief Set the data vector + * @param data Vector of data to store + */ + void SetData(const std::vector& data) { fData = data; } + + /** + * @brief Get the dimensions of the dataset + * @return Vector of dimension sizes + */ + const std::vector& GetDimensions() const { return fDimensions; } + + /** + * @brief Set the dimensions of the dataset + * @param dims Vector of dimension sizes + */ + void SetDimensions(const std::vector& dims) { fDimensions = dims; } + + /** + * @brief Get the number of dimensions + * @return Number of dimensions (rank) + */ + size_t GetRank() const { return fDimensions.size(); } + + /** + * @brief Get total number of elements + * @return Product of all dimensions + */ + size_t GetNumElements() const { + if (fDimensions.empty()) return 0; + size_t total = 1; + for (auto dim : fDimensions) { + total *= dim; + } + return total; + } + + /** + * @brief Add an attribute to this dataset + * @param name Attribute name + * @param value Attribute value (stored as std::any to support different types) + */ + void AddAttribute(const std::string& name, const std::any& value) { + fAttributes[name] = value; + } + + /** + * @brief Get an attribute by name + * @param name Attribute name + * @return The attribute value as std::any + * @throws std::out_of_range if attribute doesn't exist + */ + std::any GetAttribute(const std::string& name) const { + return fAttributes.at(name); + } + + /** + * @brief Check if an attribute exists + * @param name Attribute name + * @return true if attribute exists, false otherwise + */ + bool HasAttribute(const std::string& name) const { + return fAttributes.find(name) != fAttributes.end(); + } + + /** + * @brief Get all attributes + * @return Map of attribute names to values + */ + const std::map& GetAttributes() const { + return fAttributes; + } + + /** + * @brief Get mutable reference to all attributes + * @return Mutable map of attribute names to values + */ + std::map& GetAttributes() { + return fAttributes; + } + +private: + H5::DataType fDataType; ///< HDF5 datatype of the dataset + std::vector fData; ///< Data storage (flattened multi-dimensional array) + std::vector fDimensions; ///< Dimensions of the dataset + std::map fAttributes; ///< Attributes associated with this dataset +}; + +/** + * @class PNeXus + * @brief NeXus HDF5 file reader with case-insensitive path lookup + * + * The PNeXus class provides functionality for reading ISIS muon NeXus HDF5 files + * using the HDF5 C++ API. It implements case-insensitive path lookup for + * datasets, groups, and attributes to handle files with varying path casings. + * + * Key features: + * - Automatic file reading upon construction + * - Case-insensitive path resolution for datasets and groups + * - Case-insensitive attribute name matching + * - Comprehensive exception handling with HDF5 C++ exceptions + * + * @example + * @code + * PNeXus nexus("data/experiment.nxs"); + * // Case-insensitive paths work: "/RAW_DATA_1/IDF_version" -> "/raw_data_1/IDF_version" + * @endcode + * + * @note All path lookups throw appropriate HDF5 C++ exceptions on failure + */ +class PNeXus { +public: + /** + * @brief Default constructor - creates an empty PNeXus object + * + * Creates a PNeXus object without opening any file. Use this constructor + * when you want to build a NeXus file from scratch by adding datasets + * programmatically. + * + * @example + * @code + * nxH5::PNeXus nexus; + * + * // Add datasets programmatically + * std::vector counts(16 * 66000, 0); + * nexus.AddDataset("/raw_data_1/detector_1/counts", counts, + * {1, 16, 66000}, H5::PredType::NATIVE_INT); + * + * // Add attributes + * nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + * + * // Write to file + * nexus.WriteNexusFile("output.nxs"); + * @endcode + */ + PNeXus(); + + /** + * @brief Constructor - creates PNeXus object and reads the NeXus file + * @param fln Filename of the NeXus HDF5 file to read + * @param printDebug Enable debug output during reading (default: false) + * @throws H5::FileIException if file cannot be opened + * @throws H5::AttributeIException if required attributes are missing + * @throws H5::DataSetIException if required datasets are missing + * + * @example + * @code + * try { + * nxH5::PNeXus nexus("experiment.nxs", true); // Enable debug output + * nexus.Dump(); // Print file contents + * } catch (const H5::Exception& e) { + * std::cerr << "HDF5 error: " << e.getDetailMsg() << std::endl; + * } + * @endcode + */ + PNeXus(const std::string fln, const bool printDebug=false); + + /** + * @brief Get the filename of the NeXus file + * @return The filename as a string + */ + std::string GetFileName() const { return fFileName; } + + /** + * @brief Get the HDF5 library version string + * @return The HDF5 version as a string (e.g., "1.10.4") + */ + std::string GetHdf5LibVersion() const { return fHdf5LibVersion; } + + /** + * @brief Get the HDF5 version string from the file + * @return The HDF5 version as a string (e.g., "1.10.4") + */ + std::string GetHdf5Version() const { return fHdf5Version; } + + /** + * @brief Get the NeXus version of the file + * @return The NeXus version as a string + */ + std::string GetNeXusVersion() const { return fNeXusVersion; } + + /** + * @brief Get the Idf version of the file + * @return The Idf version tag + */ + int GetIdfVersion() const { return fIdfVersion; } + + /** + * @brief Read and parse the NeXus HDF5 file + * @return 0 on success, 1 on error + * @throws H5::FileIException if file cannot be opened + * @throws H5::AttributeIException if required attributes are missing + * @throws H5::DataSetIException if required datasets are missing + */ + int ReadNexusFile(); + + /** + * @brief Get the data map containing all datasets + * @return Reference to the data map (path -> PNXdata stored in std::any) + */ + const std::map& GetDataMap() const { return fDataMap; } + + /** + * @brief Get mutable reference to the data map + * @return Reference to the data map + */ + std::map& GetDataMap() { return fDataMap; } + + /** + * @brief Check if a dataset path exists in the data map + * @param path HDF5 path to check + * @return true if path exists, false otherwise + */ + bool HasDataset(const std::string& path) const { return fDataMap.find(path) != fDataMap.end(); } + + /** + * @brief Add or update a dataset in the data map + * @tparam T Data type of the PNXdata object + * @param path HDF5 path for the dataset + * @param data PNXdata object to store + */ + template + void SetDataset(const std::string& path, const PNXdata& data) { + fDataMap[path] = data; + } + + /** + * @brief Get a dataset from the data map + * @tparam T Data type of the PNXdata object + * @param path HDF5 path of the dataset + * @return PNXdata object + * @throws std::out_of_range if path doesn't exist + * @throws std::bad_any_cast if type T doesn't match stored type + */ + template + PNXdata GetDataset(const std::string& path) const { + return std::any_cast>(fDataMap.at(path)); + } + + /** + * @brief Get a mutable reference to a dataset in the data map + * @tparam T Data type of the PNXdata object + * @param path HDF5 path of the dataset + * @return Reference to PNXdata object + * @throws std::out_of_range if path doesn't exist + * @throws std::bad_any_cast if type T doesn't match stored type + */ + template + PNXdata& GetDatasetRef(const std::string& path) { + return std::any_cast&>(fDataMap.at(path)); + } + + /** + * @brief Remove a dataset from the data map + * @param path HDF5 path of the dataset to remove + * @return true if dataset was removed, false if path didn't exist + */ + bool RemoveDataset(const std::string& path) { + auto it = fDataMap.find(path); + if (it != fDataMap.end()) { + fDataMap.erase(it); + return true; + } + return false; + } + + /** + * @brief Clear all datasets from the data map + */ + void ClearDataMap() { fDataMap.clear(); } + + /** + * @brief Get the number of datasets in the data map + * @return Number of datasets stored + */ + size_t GetNumDatasets() const { return fDataMap.size(); } + + /** + * @brief Update the data of an existing dataset + * @tparam T Data type of the PNXdata object + * @param path HDF5 path of the dataset + * @param newData New data vector to set + * @return true if update succeeded, false if path doesn't exist + */ + template + bool UpdateDatasetData(const std::string& path, const std::vector& newData) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.SetData(newData); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Update the dimensions of an existing dataset + * @tparam T Data type of the PNXdata object + * @param path HDF5 path of the dataset + * @param newDimensions New dimensions vector to set + * @return true if update succeeded, false if path doesn't exist + */ + template + bool UpdateDatasetDimensions(const std::string& path, const std::vector& newDimensions) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.SetDimensions(newDimensions); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Add or update an attribute for a dataset + * @tparam T Data type of the PNXdata object + * @param path HDF5 path of the dataset + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added, false if dataset doesn't exist + */ + template + bool AddDatasetAttribute(const std::string& path, const std::string& attrName, + const std::any& attrValue) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.AddAttribute(attrName, attrValue); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Remove an attribute from a dataset + * @tparam T Data type of the PNXdata object + * @param path HDF5 path of the dataset + * @param attrName Attribute name to remove + * @return true if attribute was removed, false if dataset doesn't exist or attribute not found + */ + template + bool RemoveDatasetAttribute(const std::string& path, const std::string& attrName) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + auto& attrs = dataset.GetAttributes(); + auto it = attrs.find(attrName); + if (it != attrs.end()) { + attrs.erase(it); + return true; + } + return false; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Create and add a new dataset with data, dimensions, and optional attributes + * @tparam T Data type of the PNXdata object + * @param path HDF5 path for the new dataset + * @param data Data vector + * @param dimensions Dimensions vector + * @param dataType HDF5 DataType (optional, uses default if not provided) + * @return true if dataset was created, false if path already exists + */ + template + bool AddDataset(const std::string& path, const std::vector& data, + const std::vector& dimensions, + const H5::DataType& dataType = H5::PredType::NATIVE_INT) { + if (HasDataset(path)) return false; + PNXdata dataset(dataType); + dataset.SetData(data); + dataset.SetDimensions(dimensions); + fDataMap[path] = dataset; + return true; + } + + /** + * @brief Modify an existing dataset's data and dimensions + * @tparam T Data type of the PNXdata object + * @param path HDF5 path of the dataset + * @param newData New data vector + * @param newDimensions New dimensions vector + * @return true if modification succeeded, false if path doesn't exist + */ + template + bool ModifyDataset(const std::string& path, const std::vector& newData, + const std::vector& newDimensions) { + if (!HasDataset(path)) return false; + try { + auto& dataset = std::any_cast&>(fDataMap.at(path)); + dataset.SetData(newData); + dataset.SetDimensions(newDimensions); + return true; + } catch (const std::bad_any_cast&) { + return false; + } + } + + /** + * @brief Print a human-readable dump of the NeXus file contents + * + * Outputs the contents of the loaded NeXus file to stdout in a + * hierarchical format. The output includes: + * - File metadata (HDF5 version, NeXus version, file name, file time) + * - IDF version + * - Run information (run number, title, start/end time, good frames) + * - Instrument and source information + * - Detector counts with dimensions, attributes, and first few values + * - Raw time and spectrum index data + * + * The output format differs based on IDF version (1 or 2). + * + * @note This method is primarily useful for debugging and verification + * + * @example + * @code + * nxH5::PNeXus nexus("experiment.nxs"); + * nexus.Dump(); // Prints formatted output to stdout + * @endcode + */ + void Dump(); + + /** + * @brief Write the data map contents to a NeXus HDF5 file + * + * Creates a new HDF5 file and writes all datasets from the internal + * data map along with their attributes. Group hierarchy is created + * automatically based on dataset paths. + * + * @param filename Path to the output NeXus HDF5 file + * @param idfVersion IDF version to write (default: 2) + * @return 0 on success, 1 on error + * @throws H5::FileIException if file cannot be created + * @throws H5::GroupIException if group creation fails + * @throws H5::DataSetIException if dataset writing fails + * + * @note Currently only IDF version 2 is fully supported for writing + * + * @example + * @code + * nxH5::PNeXus nexus("input.nxs"); + * // Modify data... + * nexus.WriteNexusFile("output.nxs", 2); + * @endcode + */ + int WriteNexusFile(const std::string& filename, int idfVersion = 2); + + /** + * @brief Add or update an attribute for a group + * @param groupPath HDF5 path of the group (e.g., "/raw_data_1") + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added successfully + * @example + * @code + * nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + * @endcode + */ + bool AddGroupAttribute(const std::string& groupPath, const std::string& attrName, + const std::any& attrValue); + + /** + * @brief Remove an attribute from a group + * @param groupPath HDF5 path of the group + * @param attrName Attribute name to remove + * @return true if attribute was removed, false if group or attribute not found + */ + bool RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName); + + /** + * @brief Check if a group has a specific attribute + * @param groupPath HDF5 path of the group + * @param attrName Attribute name to check + * @return true if attribute exists, false otherwise + */ + bool HasGroupAttribute(const std::string& groupPath, const std::string& attrName) const; + + /** + * @brief Get an attribute value from a group + * @param groupPath HDF5 path of the group + * @param attrName Attribute name + * @return The attribute value as std::any + * @throws std::out_of_range if group or attribute doesn't exist + */ + std::any GetGroupAttribute(const std::string& groupPath, const std::string& attrName) const; + + /** + * @brief Get all attributes for a group + * @param groupPath HDF5 path of the group + * @return Map of attribute names to values + */ + const std::map& GetGroupAttributes(const std::string& groupPath) const; + + /** + * @brief Clear all attributes from a group + * @param groupPath HDF5 path of the group + * @return true if group was found and attributes cleared, false otherwise + */ + bool ClearGroupAttributes(const std::string& groupPath); + + /** + * @brief Add or update an attribute at the root level "/" + * @param attrName Attribute name + * @param attrValue Attribute value (stored as std::any) + * @return true if attribute was added successfully + * @note This is a convenience method that calls AddGroupAttribute("/", attrName, attrValue) + * @example + * @code + * nexus.AddRootAttribute("experiment_id", std::string("EXP-2024-001")); + * nexus.AddRootAttribute("temperature", 293.15f); + * nexus.AddRootAttribute("scan_number", static_cast(42)); + * @endcode + */ + bool AddRootAttribute(const std::string& attrName, const std::any& attrValue); + +private: + bool fPrintDebug{false}; ///< if true print additional debug information + std::string fFileName{""}; ///< NeXus HDF5 filename + int fIdfVersion{-1}; ///< IDF version of the NeXus file + std::string fHdf5LibVersion{"n/a"}; ///< HDF5 library version used when calling PNeXus + std::string fHdf5Version{""}; ///< HDF5 version of the file + std::string fNeXusVersion{""}; ///< NeXus version of the file + std::string fFileNameNxs{""}; + std::string fFileTimeNxs{""}; + std::string fCreatorNxs{""}; + std::string fUserV1{""}; + std::map fDataMap; ///< Map of HDF5 paths to PNXdata objects + std::map> fGroupAttributes; ///< Map of group paths to their attributes + + /** + * @brief Read datasets for IDF version 1 file structure + * + * Reads datasets specific to the IDF version 1 NeXus format, which uses + * a "/run/" group hierarchy. Datasets are organized under paths like: + * - /run/IDF_version, /run/number, /run/title + * - /run/sample/temperature, /run/sample/magnetic_field + * - /run/histogram_data_1/counts, /run/histogram_data_1/raw_time + * + * @param file HDF5 file object to read from + * + * @note IDF v1 is an older format primarily used by ISIS facilities + */ + void HandleIdfV1(H5::H5File &file); + + /** + * @brief Read datasets for IDF version 2 file structure + * + * Reads datasets specific to the IDF version 2 NeXus format, which uses + * a "/raw_data_1/" group hierarchy. Datasets are organized under paths like: + * - /raw_data_1/IDF_version, /raw_data_1/run_number, /raw_data_1/title + * - /raw_data_1/instrument/detector_1/counts + * - /raw_data_1/instrument/detector_1/resolution + * + * @param file HDF5 file object to read from + * + * @note IDF v2 is the current standard format for ISIS muon NeXus files + */ + void HandleIdfV2(H5::H5File &file); + + // ======================================================================== + // Write methods for HDF5 file creation + // ======================================================================== + + /** + * @brief Create nested group hierarchy for a given path + * @param file HDF5 file object + * @param path Full path (e.g., "/raw_data_1/detector_1") + * @return H5::Group handle to the deepest created/opened group + * @throws H5::GroupIException if group creation fails + */ + H5::Group CreateGroupHierarchy(H5::H5File& file, const std::string& path); + + /** + * @brief Write dataset attributes from PNXdata object + * @tparam T The data type of the PNXdata object + * @param dataset HDF5 dataset object to write attributes to + * @param data PNXdata object containing attributes + */ + template + void WriteDatasetAttributes(H5::DataSet& dataset, const PNXdata& data); + + /** + * @brief Write attributes to a group + * @param group HDF5 group object to write attributes to + * @param attributes Map of attribute names to values + */ + void WriteGroupAttributes(H5::Group& group, const std::map& attributes); + + /** + * @brief Write an integer dataset with attributes + * @param file HDF5 file object + * @param path Dataset path (groups created automatically) + * @param data PNXdata object containing data, dimensions, and attributes + * @throws H5::Exception if writing fails + */ + void WriteIntDataset(H5::H5File& file, const std::string& path, + const PNXdata& data); + + /** + * @brief Write a float dataset with attributes + * @param file HDF5 file object + * @param path Dataset path (groups created automatically) + * @param data PNXdata object containing data, dimensions, and attributes + * @throws H5::Exception if writing fails + */ + void WriteFloatDataset(H5::H5File& file, const std::string& path, + const PNXdata& data); + + /** + * @brief Write a string dataset with attributes + * @param file HDF5 file object + * @param path Dataset path (groups created automatically) + * @param data PNXdata object containing data, dimensions, and attributes + * @throws H5::Exception if writing fails + */ + void WriteStringDataset(H5::H5File& file, const std::string& path, + const PNXdata& data); + + /** + * @brief Write root-level file attributes + * @param file HDF5 file object + * @throws H5::Exception if writing fails + */ + void WriteFileAttributes(H5::H5File& file); + + // ======================================================================== + // Case-insensitive lookup helper methods + // ======================================================================== + + /** + * @brief Compare two strings case-insensitively + * @param a First string to compare + * @param b Second string to compare + * @return true if strings are equal (ignoring case), false otherwise + * @note Uses std::tolower for character-by-character comparison + */ + static bool CaseInsensitiveEquals(const std::string& a, const std::string& b); + + /** + * @brief Split an HDF5 path into components + * @param path HDF5 path string (e.g., "/raw_data_1/IDF_version") + * @return Vector of path components + * @note Empty first component indicates absolute path + * @example "/raw_data_1/IDF_version" -> ["", "raw_data_1", "IDF_version"] + */ + static std::vector SplitPath(const std::string& path); + + /** + * @brief Find attribute name with case-insensitive matching + * @param obj HDF5 File object to search + * @param requestedName Requested attribute name (any case) + * @return Correctly-cased attribute name as it exists in the file + * @throws H5::AttributeIException if attribute not found + * @example "nexus_version" might resolve to "NeXus_version" + */ + std::string FindAttributeName(H5::H5File& obj, const std::string& requestedName); + + /** + * @brief Find group path with case-insensitive matching (File version) + * @param parent HDF5 File object (root) + * @param requestedPath Requested group path (any case) + * @return Correctly-cased group path as it exists in the file + * @throws H5::GroupIException if group not found or path invalid + * @example "/RAW_DATA_1" might resolve to "/raw_data_1" + */ + std::string FindGroupPath(H5::H5File& parent, const std::string& requestedPath); + + /** + * @brief Find group path with case-insensitive matching (Group version) + * @param parent HDF5 Group object to start search from + * @param requestedPath Requested group path (any case) + * @return Correctly-cased group path as it exists in the file + * @throws H5::GroupIException if group not found or path invalid + */ + std::string FindGroupPath(H5::Group& parent, const std::string& requestedPath); + + /** + * @brief Find dataset path with case-insensitive matching (File version) + * @param parent HDF5 File object (root) + * @param requestedPath Requested dataset path (any case) + * @return Correctly-cased dataset path as it exists in the file + * @throws H5::DataSetIException if dataset not found or path invalid + * @example "/RAW_DATA_1/idf_VERSION" might resolve to "/raw_data_1/IDF_version" + * @note Validates that final path component is actually a dataset + */ + std::string FindDatasetPath(H5::H5File& parent, const std::string& requestedPath); + + /** + * @brief Find dataset path with case-insensitive matching (Group version) + * @param parent HDF5 Group object to start search from + * @param requestedPath Requested dataset path (any case) + * @return Correctly-cased dataset path as it exists in the file + * @throws H5::DataSetIException if dataset not found or path invalid + * @note Validates that final path component is actually a dataset + */ + std::string FindDatasetPath(H5::Group& parent, const std::string& requestedPath); + + // ======================================================================== + // Dataset reading helper methods + // ======================================================================== + + /** + * @brief Read an integer dataset and store in data map + * @param file HDF5 file object + * @param path Path to the dataset + * @throws H5::Exception if reading fails + */ + void ReadIntDataset(H5::H5File& file, const std::string& path); + + /** + * @brief Read a float dataset and store in data map + * @param file HDF5 file object + * @param path Path to the dataset + * @throws H5::Exception if reading fails + */ + void ReadFloatDataset(H5::H5File& file, const std::string& path); + + /** + * @brief Read a string dataset and store in data map + * @param file HDF5 file object + * @param path Path to the dataset + * @throws H5::Exception if reading fails + */ + void ReadStringDataset(H5::H5File& file, const std::string& path); + + /** + * @brief Read dataset attributes and add to PNXdata object + * @tparam T The data type of the PNXdata object + * @param dataset HDF5 dataset object + * @param data PNXdata object to add attributes to + */ + template + void ReadDatasetAttributes(H5::DataSet& dataset, PNXdata& data); +}; + +} // end namespace nxH5 + #endif // _PNEXUS_H_