From 2b7670af43f14d8e723414047d0fe90821c40572 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Tue, 26 Sep 2023 11:21:34 +0200 Subject: [PATCH 01/19] Adding method to apply a calibration with gain correction. --- .../analysis/inc/TRestCalibrationCorrection.h | 206 ++++ .../src/TRestCalibrationCorrection.cxx | 1090 +++++++++++++++++ 2 files changed, 1296 insertions(+) create mode 100644 source/framework/analysis/inc/TRestCalibrationCorrection.h create mode 100644 source/framework/analysis/src/TRestCalibrationCorrection.cxx diff --git a/source/framework/analysis/inc/TRestCalibrationCorrection.h b/source/framework/analysis/inc/TRestCalibrationCorrection.h new file mode 100644 index 000000000..35cd74239 --- /dev/null +++ b/source/framework/analysis/inc/TRestCalibrationCorrection.h @@ -0,0 +1,206 @@ +/************************************************************************* + * This file is part of the REST software framework. * + * * + * Copyright (C) 2016 GIFNA/TREX (University of Zaragoza) * + * For more information see https://gifna.unizar.es/trex * + * * + * REST is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * REST is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have a copy of the GNU General Public License along with * + * REST in $REST_PATH/LICENSE. * + * If not, see https://www.gnu.org/licenses/. * + * For the list of contributors see $REST_PATH/CREDITS. * + *************************************************************************/ + +#ifndef REST_TRestCalibrationCorrection +#define REST_TRestCalibrationCorrection + +#include "TRestMetadata.h" +#include "TRestDataSet.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// Metadata class to calculate,store and apply the gain corrected calibration of a group of detectors. +class TRestCalibrationCorrection : public TRestMetadata { +public: + class Module; +private: + std::string fObservable = ""; //"rawAna_ThresholdIntegral"; //< + std::string fSpatialObservableX = ""; //"hitsAna_xMean"; //< + std::string fSpatialObservableY = ""; //"hitsAna_yMean"; //< + + std::vector + fModulesCal = {}; + std::string fCalibFileName = ""; + std::string fOutputFileName = ""; + + void Initialize() override; + void InitFromConfigFile() override; + +public: + std::set GetPlaneIDs() const; + std::set GetModuleIDs(const int planeId) const; + std::map> GetModuleIDs() const; + Int_t GetNumberOfPlanes() const { return GetPlaneIDs().size();} + Int_t GetNumberOfModules() const { int sum=0; for (auto pID : GetPlaneIDs()) for (auto n : GetModuleIDs(pID)) sum += n; return sum;} + //Int_t GetNumberOfModulesOfPlane(const int planeID) const { return fNModules.at(planeID);} + std::string GetCalibrationFileName() const { return fCalibFileName;} + std::string GetOutputFileName() const { return fOutputFileName;} + std::string GetObservable() const { return fObservable;} + std::string GetSpatialObservableX() const { return fSpatialObservableX;} + std::string GetSpatialObservableY() const { return fSpatialObservableY;} + + Module* GetModuleCalibration(const int planeID, const int moduleID); + double GetSlopeParameter(const int planeID, const int moduleID, const double x, const double y); + double GetInterceptParameter(const int planeID, const int moduleID, const double x, const double y); + + void SetCalibrationFileName(const std::string &fileName) { fCalibFileName = fileName;} + void SetOutputFileName(const std::string &fileName) { fOutputFileName = fileName;} + void SetModuleCalibration(const Module& moduleCal); + void SetObservable( const std::string &observable) { fObservable = observable;} + void SetSpatialObservableX( const std::string &spatialObservableX) { fSpatialObservableX = spatialObservableX;} + void SetSpatialObservableY( const std::string &spatialObservableY) { fSpatialObservableY = spatialObservableY;} + + + void Import(const std::string& fileName); + void Export(const std::string& fileName = ""); + + TRestCalibrationCorrection& operator=(TRestCalibrationCorrection& src); + +public: + void PrintMetadata() override; + + void Calibrate(); + void CalibrateDataSet(const std::string& dataSetFileName, std::string outputFileName = ""); + + TRestCalibrationCorrection(); + TRestCalibrationCorrection(const char* configFilename, std::string name = ""); + ~TRestCalibrationCorrection(); + + // REMOVE COMMENT. ROOT class definition helper. Increase the number in it every time + // you add/rename/remove the metadata members + ClassDefOverride(TRestCalibrationCorrection, 1); + + class Module { + private: + const TRestCalibrationCorrection* p = nullptr; // fEnergyPeaks = {};//{22.5, 8.0}; + std::vector fRangePeaks = {};//{TVector2(230000, 650000), TVector2(40000, 230000)}; //in development... + TVector2 fCalibRange = TVector2(0,0); //< // Calibration range + Int_t fNBins = 100; //< // Number of bins for the spectrum histograms + + /*std::string fObservable = ""; //"rawAna_ThresholdIntegral"; //< + std::string fSpatialObservableX = ""; //"hitsAna_xMean"; //< + std::string fSpatialObservableY = ""; //"hitsAna_yMean"; //<*/ + std::string fDefinitionCut = ""; //"TREXsides_tagId == 2"; //< + + Int_t fNumberOfSegmentsX = 1; //< + Int_t fNumberOfSegmentsY = 1; //< + TVector2 fReadoutRange = TVector2(0, 246); //< // Readout dimensions + std::set fSplitX = {}; //< + std::set fSplitY = {}; //< + + std::string fDataSetFileName = ""; //< // File name for the dataset + + std::vector> fSlope = {}; //< + std::vector> fIntercept = {}; //< + + bool fZeroPoint = false; //< Zero point will be automatically added if there are less than 2 peaks + bool fAutoRangePeaks = true; //< Automatic range peaks + std::vector< std::vector > fSegSpectra = {}; //fSegmentedSpectra + std::vector< std::vector > fSegLinearFit = {}; //fSegmentedLinearFit + + public: + void SetSplitX(); + void SetSplitY(); + void SetSplits(); + + void AddPeak(const double &energyPeak, const TVector2 &rangePeak=TVector2(0,0)) { + fEnergyPeaks.push_back(energyPeak); fRangePeaks.push_back(rangePeak);} + + void LoadConfigFromTiXmlElement(const TiXmlElement* module); + + std::pair GetIndexMatrix(const double x, const double y) const; + double GetSlope(const double x, const double y) const; + double GetIntercept(const double x, const double y) const; + + Int_t GetPlaneId() const { return fPlaneId;} + Int_t GetModuleId() const { return fModuleId;} + std::string GetObservable() const { return p->fObservable;} + std::string GetSpatialObservableX() const { return p->fSpatialObservableX;} + std::string GetSpatialObservableY() const { return p->fSpatialObservableY;} + inline std::string GetModuleDefinitionCut() const { return fDefinitionCut;} + Int_t GetNumberOfSegmentsX() const { return fNumberOfSegmentsX;} + Int_t GetNumberOfSegmentsY() const { return fNumberOfSegmentsY;} + + std::set GetSplitX() const { return fSplitX;} + std::set GetSplitY() const { return fSplitY;} + std::string GetDataSetFileName() const { return fDataSetFileName;} + TVector2 GetReadoutRangeVar() const { return fReadoutRange;} + + void DrawSpectrum(); + void DrawSpectrum(const double x, const double y, TCanvas *c=nullptr); + void DrawSpectrum(const int index_x, const int index_y, TCanvas *c=nullptr); + void DrawFullSpectrum(); + + void DrawLinearFit(); + void DrawLinearFit(const double x, const double y, TCanvas *c=nullptr); + void DrawLinearFit(const int index_x, const int index_y, TCanvas *c=nullptr); + + void DrawGainMap(const int peakNumber = 0); + + void Refit(const double x, const double y, const double energy, const TVector2& range); + void Refit(const int x, const int y, const int peakNumber,const TVector2& range); + + void SetPlaneId( const Int_t &planeId) { fPlaneId = planeId;} + void SetModuleId( const Int_t &moduleId) { fModuleId = moduleId;} + void SetModuleDefinitionCut( const std::string &moduleDefinitionCut) { fDefinitionCut = moduleDefinitionCut;} + void SetCalibrationRange( const TVector2 &calibrationRange) { fCalibRange = calibrationRange;} + void SetNBins( const Int_t &nBins) { fNBins = nBins;} + + void SetNumberOfSegmentsX( const Int_t &numberOfSegmentsX) { fNumberOfSegmentsX = numberOfSegmentsX; SetSplitX();} + void SetNumberOfSegmentsY( const Int_t &numberOfSegmentsY) { fNumberOfSegmentsY = numberOfSegmentsY; SetSplitY();} + //void SetInputFile( const std::string &inputFile) { fInputFile = inputFile;} + //void SetOutputFile( const std::string &outputFile) { fOutputFile = outputFile;} + void SetDataSetFileName( const std::string &dataSetFileName) { fDataSetFileName = dataSetFileName;} + void SetReadoutRange( const TVector2 &readoutRange) { fReadoutRange = readoutRange;} + void SetZeroPoint( const bool &ZeroPoint) { fZeroPoint = ZeroPoint;} + void SetAutoRangePeaks( const bool &autoRangePeaks) { fAutoRangePeaks = autoRangePeaks;} + + + void Print(); + + void CalculateCalibrationParameters(); + void Initialize(); + + Module() {} + Module(const TRestCalibrationCorrection& parent) : p(&parent) {}; + Module(const TRestCalibrationCorrection& parent, const Int_t planeId, const Int_t moduleId) : p(&parent) { + SetPlaneId(planeId); SetModuleId(moduleId); }; + ~Module() {}; + + + + }; + +}; +#endif diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx new file mode 100644 index 000000000..994b85f6d --- /dev/null +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -0,0 +1,1090 @@ +/************************************************************************* + * This file is part of the REST software framework. * + * * + * Copyright (C) 2016 GIFNA/TREX (University of Zaragoza) * + * For more information see https://gifna.unizar.es/trex * + * * + * REST is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * REST is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have a copy of the GNU General Public License along with * + * REST in $REST_PATH/LICENSE. * + * If not, see https://www.gnu.org/licenses/. * + * For the list of contributors see $REST_PATH/CREDITS. * + *************************************************************************/ + +///////////////////////////////////////////////////////////////////////// +/// TRestCalibrationCorrection calculates and stores the calibration +/// parameters for a given detector with multiple (or just one) modules. +/// This modules are defined in the Module class. It performs a gain correction +/// based on a spatial segmentation of the detector module. This is useful for +/// big modules such as the ones used in TREX-DM experiment. +/// +/// ### Parameters +/// * **calibFileName**: name of the file to use for the calibration. It should be a DataSet +/// * **outputFileName**: name of the file to save the this calibration metadata +/// * **observable**: name of the observable to be calibrated. It must be a branch of the calibration DataSet +/// * **spatialObservableX**: name of the observable to be used for the spatial segmentation along the X axis. +/// It must be a branch of the calibration DataSet +/// * **spatialObservableY**: name of the observable to be used for the spatial segmentation along the Y axis. +/// It must be a branch of the calibration DataSet +/// * **modulesCal**: vector of Module objects +/// +/// ### Examples +/// Give examples of usage and RML descriptions that can be tested. +/// \code +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// \endcode +/// +/// Example to calculate the calibration parameters over a calibration dataSet using restRoot: +/// \code +/// [0] TRestCalibrationCorrection cal ("makeCalibration.rml"); +/// [1] cal.SetCalibrationFileName("myDataSet.root"); //if not already defined in rml file +/// [2] cal.SetOutputFileName("myCalibration.root"); //if not already defined in rml file +/// [3] cal.Calibrate(); +/// [4] cal.Export(); +/// \endcode +/// +/// Example to calibrate a dataSet with the previously calculated calibration parameters +/// and draw the result (using restRoot): +/// \code +/// [0] TRestCalibrationCorrection cal; +/// [1] cal.Import("myCalibration.root"); +/// [2] cal.CalibrateDataSet("dataSetToCalibrate.root", "calibratedDataSet.root"); +/// [3] TRestDataSet ds("calibratedDataSet.root"); +/// [4] auto h = ds->GetDataFrame().Histo1D({"hname", "",100,-1,40.}, "calib_ThresholdIntegral"); +/// [5] h->Draw(); +/// \endcode +///---------------------------------------------------------------------- +/// +/// REST-for-Physics - Software for Rare Event Searches Toolkit +/// +/// History of developments: +/// +/// 2023-May: First implementation of TRestCalibrationCorrection +/// Álvaro Ezquerro +/// +/// \class TRestCalibrationCorrection +/// \author: Álvaro Ezquerro aezquerro@unizar.es +/// +///
+/// + +#include "TRestCalibrationCorrection.h" + +ClassImp(TRestCalibrationCorrection); +/////////////////////////////////////////////// +/// \brief Default constructor +/// +TRestCalibrationCorrection::TRestCalibrationCorrection() { + Initialize(); +} + +///////////////////////////////////////////// +/// \brief Constructor loading data from a config file +/// +/// If no configuration path is defined using TRestMetadata::SetConfigFilePath +/// the path to the config file must be specified using full path, absolute or +/// relative. +/// +/// The default behaviour is that the config file must be specified with +/// full path, absolute or relative. +/// +/// \param configFilename A const char* that defines the RML filename. +/// \param name The name of the metadata section. It will be used to find the +/// corresponding TRestCalibrationCorrection section inside the RML. +/// +TRestCalibrationCorrection::TRestCalibrationCorrection(const char* configFilename, std::string name) : TRestMetadata(configFilename) { + LoadConfigFromFile(fConfigFileName, name); + if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info) PrintMetadata(); +} + +/////////////////////////////////////////////// +/// \brief Default destructor +/// +TRestCalibrationCorrection::~TRestCalibrationCorrection() { +} + + +void TRestCalibrationCorrection::Initialize() { SetSectionName(this->ClassName()); } +/////////////////////////////////////////////// +/// \brief Initialization of TRestCalibrationCorrection +/// members through a RML file +/// +void TRestCalibrationCorrection::InitFromConfigFile() { + this->Initialize(); + TRestMetadata::InitFromConfigFile(); + + //Load Module from RML + TiXmlElement* moduleDefinition = GetElement("module"); + while (moduleDefinition != nullptr) { + fModulesCal.push_back( Module(*this) ); + fModulesCal.back().LoadConfigFromTiXmlElement(moduleDefinition); + + moduleDefinition = GetNextElement(moduleDefinition); + } + + if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Debug) + PrintMetadata(); +} + +///////////////////////////////////////////// +/// \brief Function to calculate the calibration parameters of all modules +/// +void TRestCalibrationCorrection::Calibrate(){ + for (auto& i : fModulesCal){ + RESTInfo << "Calibrating plane " << i.GetPlaneId() << " module " << i.GetModuleId() << RESTendl; + i.CalculateCalibrationParameters(); + if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info){ + i.DrawSpectrum(); + i.DrawGainMap(); + } + } +} + +///////////////////////////////////////////// +/// \brief Function to calibrate a dataSet +/// +void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFileName, std::string outputFileName){ + if (fModulesCal.size() == 0) { + RESTError << "TRestCalibrationCorrection::CalibrateDataSet: No modules defined." << RESTendl; + return; + } + + TRestDataSet dataSet; + dataSet.Import(dataSetFileName); + auto dataFrame = dataSet.GetDataFrame(); + + //Define a new column with the identifier (pmID) of the module for each row (event) + std::string modCut = fModulesCal[0].GetModuleDefinitionCut(); + std::string pmIDname = (std::string)GetName() + "_pmID"; + int pmID = fModulesCal[0].GetPlaneId()*10 + fModulesCal[0].GetModuleId(); + dataFrame = dataFrame.Define(pmIDname, modCut + " ? " + std::to_string(pmID) + " : -1"); + for (int n=1; n::quiet_NaN(); + }; + std::string calibObsName = (std::string)GetName() + "_"; + calibObsName += GetObservable().erase(0, GetObservable().find("_")+1); //remove the "rawAna_" part + dataFrame = dataFrame.Define(calibObsName, calibrate, + {fObservable, fSpatialObservableX, + fSpatialObservableY, pmIDname}); + + dataSet.SetDataFrame(dataFrame); + + //Format the output file name and export the dataSet + if (outputFileName.empty()) { + outputFileName = dataSetFileName; + RESTWarning << "TRestCalibrationCorrection::CalibrateDataSet: No output file name defined. " + << "Using input file name " << outputFileName << RESTendl; + } + else if (TRestTools::GetFileNameExtension(outputFileName) != "root") + outputFileName = outputFileName+".root"; + + RESTInfo << "Exporting calibrated dataSet to " << outputFileName << RESTendl; + dataSet.Export(outputFileName); + + //Add this TRestCalibrationCorrection metadata to the output file + TFile* f = TFile::Open(outputFileName.c_str(), "UPDATE"); + this->Write(); + f->Close(); + delete f; +} + +///////////////////////////////////////////// +/// \brief Function to retrieve the module calibration with planeID and moduleID +/// +/// +TRestCalibrationCorrection::Module* TRestCalibrationCorrection::GetModuleCalibration(const int planeID, const int moduleID){ + for (auto& i : fModulesCal){ + if (i.GetPlaneId() == planeID && i.GetModuleId() == moduleID) return &i; + } + RESTError << "No ModuleCalibration with planeID " << planeID << " and moduleID " << moduleID << RESTendl; + return nullptr; +} + + +///////////////////////////////////////////// +/// \brief Function to get the slope parameter of the module with +/// planeID and moduleID at physical position (x,y) +/// +/// +double TRestCalibrationCorrection::GetSlopeParameter(const int planeID, const int moduleID, const double x, const double y){ + Module* moduleCal = GetModuleCalibration(planeID, moduleID); + if (moduleCal == nullptr) return 0; //return numeric_limits::quiet_NaN() + return moduleCal->GetSlope(x, y); +} + +///////////////////////////////////////////// +/// \brief Function to get the intercept parameter of the module with +/// planeID and moduleID at physical position (x,y) +/// +/// +double TRestCalibrationCorrection::GetInterceptParameter(const int planeID, const int moduleID, const double x, const double y){ + Module* moduleCal = GetModuleCalibration(planeID, moduleID); + if (moduleCal == nullptr) return 0; //return numeric_limits::quiet_NaN() + return moduleCal->GetIntercept(x, y); +} + +///////////////////////////////////////////// +/// \brief Function to get a list (set) of the plane IDs +/// +/// +std::set TRestCalibrationCorrection::GetPlaneIDs() const { + std::set planeIDs; + for (const auto& mc : fModulesCal) + planeIDs.insert(mc.GetPlaneId()); + return planeIDs; +} + +///////////////////////////////////////////// +/// \brief Function to get a list (set) of the module IDs +/// of the plane with planeID +/// +std::set TRestCalibrationCorrection::GetModuleIDs(const int planeId) const { + std::set moduleIDs; + for (const auto& mc : fModulesCal) + if (mc.GetPlaneId()==planeId) + moduleIDs.insert(mc.GetModuleId()); + return moduleIDs; + +} + +///////////////////////////////////////////// +/// \brief Function to get the map of the module IDs for each plane ID +/// +std::map> TRestCalibrationCorrection::GetModuleIDs() const { + std::map> moduleIds; + for (const int planeId : GetPlaneIDs()) + moduleIds.insert(std::pair>(planeId,GetModuleIDs(planeId))); + return moduleIds; +} + +TRestCalibrationCorrection& TRestCalibrationCorrection::operator=(TRestCalibrationCorrection& src) { + SetName(src.GetName()); + fOutputFileName = src.GetOutputFileName(); + fObservable = src.GetObservable(); + fSpatialObservableX = src.GetSpatialObservableX(); + fSpatialObservableY = src.GetSpatialObservableY(); + fModulesCal.clear(); + for (auto pID : src.GetPlaneIDs()) + for (auto mID : src.GetModuleIDs(pID)) + fModulesCal.push_back(*src.GetModuleCalibration(pID,mID)); + return *this; +} + +///////////////////////////////////////////// +/// \brief Function to set a module calibration. If the module calibration +/// already exists (same planeId and moduleId), it will be replaced. +/// +void TRestCalibrationCorrection::SetModuleCalibration(const Module& moduleCal){ + for (auto& i : fModulesCal){ + if (i.GetPlaneId() == moduleCal.GetPlaneId() && i.GetModuleId() == moduleCal.GetModuleId()) { + i = moduleCal; + return; + } + } + fModulesCal.push_back(moduleCal); +} + +///////////////////////////////////////////// +/// \brief Function to import the calibration parameters +/// from the root file fileName. +/// +void TRestCalibrationCorrection::Import(const std::string& fileName){ + if (fileName.empty()) { + RESTError << "No input calibration file defined" << RESTendl; + return; + } + + if (TRestTools::isRootFile(fileName)){ + RESTInfo << "Opening " << fileName << RESTendl; + TFile* f = TFile::Open(fileName.c_str(), "READ"); + if (f == nullptr) { + RESTError << "Cannot open calibration file " << fileName << RESTendl; + return; + } + + TRestCalibrationCorrection* cal = nullptr; + if (f != nullptr) { + TIter nextkey(f->GetListOfKeys()); + TKey* key; + while ((key = (TKey*)nextkey())) { + std::string kName = key->GetClassName(); + if (REST_Reflection::GetClassQuick(kName.c_str()) != nullptr && + REST_Reflection::GetClassQuick(kName.c_str())->InheritsFrom("TRestCalibrationCorrection")) { + cal = f->Get(key->GetName()); + *this = *cal; + } + } + } + } + else + RESTError << "File extension not supported for " << fileName << RESTendl; + if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Debug) PrintMetadata(); +} + +///////////////////////////////////////////// +/// \brief Function to export the calibration +/// to the file fileName. +/// +void TRestCalibrationCorrection::Export(const std::string& fileName){ + if (!fileName.empty()) fOutputFileName = fileName; + if (fOutputFileName.empty()) { + RESTError << "No output file defined" << RESTendl; + return; + } + + if (TRestTools::GetFileNameExtension(fOutputFileName) == "root") { + TFile* f = TFile::Open(fOutputFileName.c_str(), "UPDATE"); + this->Write(GetName()); + f->Close(); + delete f; + RESTInfo << "Calibration saved to " << fOutputFileName << RESTendl; + } else + RESTError << "File extension for " << fOutputFileName << "is not supported." << RESTendl; +} + +///////////////////////////////////////////// +/// \brief Prints on screen the information about the metadata members +/// +void TRestCalibrationCorrection::PrintMetadata() { + TRestMetadata::PrintMetadata(); + RESTMetadata << " Calibration file: " << fCalibFileName << RESTendl; + RESTMetadata << " Output file: " << fOutputFileName << RESTendl; + RESTMetadata << " Number of planes: " << GetNumberOfPlanes() << RESTendl; + RESTMetadata << " Number of modules: " << GetNumberOfModules() << RESTendl; + RESTMetadata << " Calibration observable: " << fObservable << RESTendl; + RESTMetadata << " Spatial observable X: " << fSpatialObservableX << RESTendl; + RESTMetadata << " Spatial observable Y: " << fSpatialObservableY << RESTendl; + RESTMetadata << "-----------------------------------------------" << RESTendl; + for (auto& i : fModulesCal) + i.Print(); + RESTMetadata << "***********************************************" << RESTendl; + RESTMetadata << RESTendl; +} + +///////////////////////////////////////////// +/// \brief Function to get the index of the matrix of calibration parameters +/// for a given x and y position on the detector plane. +/// +/// \param x A const double that defines the x position on the detector plane. +/// \param y A const double that defines the y position on the detector plane. +/// +std::pair TRestCalibrationCorrection::Module::GetIndexMatrix(const double x, const double y) const { + int index_x=-1, index_y=-1; + + if (fSplitX.upper_bound(x) != fSplitX.end()){ + index_x = std::distance(fSplitX.begin(), fSplitX.upper_bound(x)) -1; + if (index_x < 0){ + RESTWarning << "index_x < 0 for x = "<< x <<" and fSplitX[0]="<<*fSplitX.begin()<RESTendl; + index_x=0; + } + } + else {RESTWarning << "x is out of split for x = "<< x <RESTendl; index_x=fSplitX.size()-2;} + + if (fSplitY.upper_bound(y) != fSplitY.end()){ + index_y = std::distance(fSplitY.begin(), fSplitY.upper_bound(y)) -1; + if (index_y < 0){ + RESTWarning << "index_y < 0 for y = "<< y <<" and fSplitY[0]="<<*fSplitY.begin()<RESTendl; + index_y=0; + } + } + else {RESTWarning << "y is out of split for y = "<< y <RESTendl; index_y=fSplitY.size()-2;} + + return std::make_pair(index_x,index_y); +} +///////////////////////////////////////////// +/// \brief Function to get the calibration parameter slope for a +/// given x and y position on the detector plane. +/// +/// \param x A const double that defines the x position on the detector plane. +/// \param y A const double that defines the y position on the detector plane. +/// +double TRestCalibrationCorrection::Module::GetSlope(const double x, const double y) const { + auto [index_x, index_y] = GetIndexMatrix(x,y); + if (fSlope.size() == 0) { + RESTError << "Calibration slope matrix is empty. Returning 0" << p->RESTendl; + return 0; + } + + if (index_x > fSlope.size() || index_y > fSlope.at(0).size()){ + RESTError << "Index out of range. Returning 0" << p->RESTendl; + return 0; + } + + return fSlope[index_x][index_y]; +} + +///////////////////////////////////////////// +/// \brief Function to get the calibration parameter intercept for a +/// given x and y position on the detector plane. +/// +/// \param x A const double that defines the x position on the detector plane. +/// \param y A const double that defines the y position on the detector plane. +/// +double TRestCalibrationCorrection::Module::GetIntercept(const double x, const double y) const { + auto [index_x, index_y] = GetIndexMatrix(x,y); + if (fIntercept.size() == 0) { + RESTError << "Calibration constant matrix is empty. Returning 0" << p->RESTendl; + return 0; + } + + if (index_x > fIntercept.size() || index_y > fIntercept.at(0).size()){ + RESTError << "Index out of range. Returning 0" << p->RESTendl; + return 0; + } + + return fIntercept[index_x][index_y]; +} + + +///////////////////////////////////////////// +/// \brief Function to set the class members for segmentation of +/// the detector plane along the X and Y axis. +void TRestCalibrationCorrection::Module::SetSplits() { + SetSplitX(); + SetSplitY(); +} +///////////////////////////////////////////// +/// \brief Function to set the class members for segmentation of +/// the detector plane along the X axis. +/// +/// It uses the number of segments and the readout range to define the +/// edges of the segments. +void TRestCalibrationCorrection::Module::SetSplitX() { + fSplitX.clear(); + for ( int i=0; i<=fNumberOfSegmentsX; i++) { // <= so that the last segment is included + double x = fReadoutRange.X()+ ( (fReadoutRange.Y()-fReadoutRange.X())/(float)fNumberOfSegmentsX )*i; + fSplitX.insert(x); + } +} +///////////////////////////////////////////// +/// \brief Function to set the class members for segmentation of +/// the detector plane along the Y axis. +/// +/// It uses the number of segments and the readout range to define the +/// edges of the segments. +void TRestCalibrationCorrection::Module::SetSplitY() { + fSplitY.clear(); + for ( int i=0; i<=fNumberOfSegmentsY; i++) { // <= so that the last segment is included + double y = fReadoutRange.X()+ ( (fReadoutRange.Y()-fReadoutRange.X())/(float)fNumberOfSegmentsY )*i; + fSplitY.insert(y); + } +} + +///////////////////////////////////////////// +/// \brief Function calculate the calibration parameters for each segment +/// defined at fSplitX and fSplitY. +/// +/// It uses the data of the observable fObservable from the TRestDataSet +/// at fDataSetFileName (or fCalibrationFileName if first is empty). +/// The segmentation is given by the splits. +/// +/// Fitting ranges logic is as follows: +/// 1. If fRangePeaks is defined and fAutoRangePeaks is false: fRangePeaks is used. +/// 2. If fRangePeaks is defined and fAutoRangePeaks is true: the fitting range +/// is calculated by the peak position found by TSpectrum inside fRangePeaks. +/// 3. If fRangePeaks is not defined and fAutoRangePeaks is false: use the peak position found by TSpectrum +/// and the peak position of the next peak to define the range. +/// 4. If fRangePeaks is not defined and fAutoRangePeaks is true: same as 3. +/// +void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { + //--- Initial checks and settings --- + if ( fDataSetFileName.empty() ) fDataSetFileName = p->GetCalibrationFileName(); + if ( fDataSetFileName.empty() ) { + RESTError << "No calibration file defined" << p->RESTendl; + return; + } + + if ( !TRestTools::isDataSet(fDataSetFileName) ) + RESTWarning << fDataSetFileName << " is not a dataset." << p->RESTendl; + TRestDataSet dataSet; + dataSet.Import(fDataSetFileName); + SetSplits(); + + //--- Get the calibration range if not provided (default is 0,0) --- + if ( fCalibRange.X() >= fCalibRange.Y() ){ + //Get spectrum for this file + std::string cut = fDefinitionCut; + if ( cut.empty()) cut = "1"; + auto histo = dataSet.GetDataFrame().Filter(cut).Histo1D( + {"temp", "", fNBins, 0, 0}, GetObservable()); + std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); + double xMin = hpunt->GetXaxis()->GetXmin(); + double xMax = hpunt->GetXaxis()->GetXmax(); + + // Reduce the range to avoid the possible empty (nCounts<1%) end part of the spectrum + double fraction=1, nAtEndSpc=0, step=0.66; + while (nAtEndSpc*1. / hpunt->Integral() < 0.01 && fraction>0.001){ + fraction *= step; + nAtEndSpc = hpunt->Integral( hpunt->FindFixBin(hpunt->GetXaxis()->GetXmax()*fraction), + hpunt->FindFixBin(hpunt->GetXaxis()->GetXmax()) ); + } + xMax = hpunt->GetXaxis()->GetXmax() * fraction/step; //previous step is the last one that nAtEndSpc<1% + //Set the calibration range if needed + //fCalibRange.SetX(xMin); + fCalibRange.SetY(xMax); + hpunt.reset(); //delete hpunt; + RESTDebug << "Calibration range (auto)set to (" << fCalibRange.X() << "," << fCalibRange.Y() << ")" << p->RESTendl; + } + + //--- Definition of histogram matrix --- + std::vector< std::vector > h + (fNumberOfSegmentsX, std::vector(fNumberOfSegmentsY, nullptr)); + for ( int i=0; i > calParSlope + (fNumberOfSegmentsX, std::vector (fNumberOfSegmentsY,0)); + std::vector > calParIntercept + (fNumberOfSegmentsX, std::vector (fNumberOfSegmentsY,0)); + + // build the spectrum for each segment + auto itX = fSplitX.begin(); + for ( int i=0; i=" + std::to_string(xLower) + "&&" + + GetSpatialObservableX() +"<" + std::to_string(xUpper); + if ( !GetSpatialObservableY().empty()) + segment_cut += "&&"+GetSpatialObservableY()+">=" + std::to_string(yLower) + "&&" + + GetSpatialObservableY() +"<" + std::to_string(yUpper); + if ( !fDefinitionCut.empty()) + segment_cut += "&&"+fDefinitionCut; + if ( segment_cut.empty() ) segment_cut = "1"; + RESTExtreme << "Segment["<RESTendl; + auto histo = dataSet.GetDataFrame().Filter(segment_cut).Histo1D( + {"temp", "", h[i][j]->GetNbinsX(), + h[i][j]->GetXaxis()->GetXmin(), + h[i][j]->GetXaxis()->GetXmax()}, + GetObservable()); + std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); + h[i][j] -> Add(hpunt.get()); + hpunt.reset(); //delete hpunt; + itY++; + } + itX++; + } + + //--- Fit every peak energy for every segment --- + fSegLinearFit = std::vector(h.size(), std::vector(h.at(0).size(), nullptr)); + for ( int i=0; iRESTendl; + //Search for peaks --> peakPos + std::unique_ptr s (new TSpectrum(2*fEnergyPeaks.size()+1)); + std::vector peakPos; + s->Search(h[i][j], 2, "goff", 0.1); + for (int k=0; kGetNPeaks(); k++) peakPos.push_back(s->GetPositionX()[k]); + std::sort(peakPos.begin(), peakPos.end(), std::greater()); + const double ratio = peakPos.size()==0 ? + 1 : peakPos.front() / fEnergyPeaks.front(); //to estimate peak position + + //Initialize graph for linear fit + std::shared_ptr gr; + gr = std::shared_ptr(new TGraph()); + gr->SetName("grFit"); + gr->SetTitle((";"+GetObservable()+";energy").c_str()); + + //Fit every energy peak + int c = 0; double mu = 0; + for (const auto& energy : fEnergyPeaks) { + RESTExtreme << "\t fitting energy " << DoubleToString(energy, "%g") << p->RESTendl; + // estimation of the peak position is between start and end + double pos = energy * ratio; + double start = pos * 0.8; + double end = pos * 1.2; + if (fRangePeaks.at(c).X() < fRangePeaks.at(c).Y()){ //if range is defined use it + start = fRangePeaks.at(c).X(); + end = fRangePeaks.at(c).Y(); + } + + do { + if (fAutoRangePeaks){ + if ( peakPos.size() > 0 ) { + //Find the peak position that is between start and end + pos = peakPos.at(0); + while ( !(start < pos && pos < end) ){ + // if none of the peak position is + // between start and end, use the greater one. + if (pos == peakPos.back()){ + pos = peakPos.at(0); + break; + } + pos = *std::next(std::find(peakPos.begin(),peakPos.end(), pos)); //get next peak position + } + peakPos.erase(std::find(peakPos.begin(),peakPos.end(), pos)); //remove this peak position from the list + // final estimation of the peak range (idem fitting window) with this peak position pos + start = pos * 0.8; + end = pos * 1.2; + const double relDist = peakPos.size()>0 ? (pos-peakPos.front())/pos : 999; + if (relDist < 0.2) { //if the next peak is too close reduce the window width + start = pos * (1-relDist/2); + end = pos * (1+relDist/2); + } + } + } + + std::string name = "g" + std::to_string(c); + TF1* g = new TF1(name.c_str(), "gaus", start, end); + RESTExtreme << "\t\tat " << DoubleToString(pos, "%.3g") + << ". Range(" << DoubleToString(start, "%.3g") << ", " + << DoubleToString(end, "%.3g") << ")"<< p->RESTendl; + + if ( h[i][j]->GetFunction(name.c_str()) ) //remove previous fit + h[i][j]->GetListOfFunctions()->Remove(h[i][j]->GetFunction(name.c_str())); + + h[i][j]->Fit(g,"R+Q0"); //use 0 to not draw the fit but save it + mu = g->GetParameter(1); + RESTExtreme << "\t\tgaus mean " << DoubleToString(mu, "%g") << p->RESTendl; + } while (fAutoRangePeaks && peakPos.size()>0 && + !(startSetPoint(c++, mu, energy); + } + s.reset(); //delete s; + + if (fZeroPoint) + gr->SetPoint(c++, 0, 0); + while (gr->GetN()<2){ //minimun 2 points needed for linear fit + gr->SetPoint(c++, 0, 0); + SetZeroPoint(true); + RESTDebug << "Not enough points for linear fit. Adding and setting zero point to true" << p->RESTendl; + } + + //Linear fit + std::unique_ptr linearFit; + linearFit = std::unique_ptr(new TF1("linearFit", "pol1")); + gr->Fit("linearFit", "SQ"); //Q for quiet mode + + fSegLinearFit.at(i).at(j) = (TGraph*) gr->Clone(); + + const double slope = linearFit->GetParameter(1); + const double intercept = linearFit->GetParameter(0); + calParSlope.at(i).at(j) = slope; + calParIntercept.at(i).at(j) = intercept; + } + } + fSegSpectra = h; + fSlope = calParSlope; + fIntercept = calParIntercept; +} + +///////////////////////////////////////////// +/// \brief Function to fit again manually a peak for a given segment of the module. +/// +/// \param x position along X-axis at the detector module (in physical units). +/// \param y position along Y-axis at the detector module (in physical units). +/// \param energyPeak The energy of the peak to be fitted (in physical units). +/// \param range The range for the fitting of the peak (in the observables corresponding units). +/// +void TRestCalibrationCorrection::Module::Refit(const double x, const double y, + const double energyPeak, const TVector2& range) { + auto [index_x, index_y] = GetIndexMatrix(x,y); + int peakNumber = -1; + for (int i=0; iRESTendl; + return; + } + Refit(index_x, index_y, peakNumber, range); +} + +///////////////////////////////////////////// +/// \brief Function to fit again manually a peak for a given segment of the module. +/// +/// \param x index along X-axis of the corresponding segment. +/// \param y index along Y-axis of the corresponding segment. +/// \param peakNumber The index of the peak to be fitted. +/// \param range The range for the fitting of the peak (in the observables corresponding units). +/// +void TRestCalibrationCorrection::Module::Refit(const int x, const int y, + const int peakNumber,const TVector2& range){ + //Refit the desired peak + std::string name = "g" + std::to_string(peakNumber); + TF1* g = new TF1(name.c_str(), "gaus", range.X(), range.Y()); + TH1F* h = fSegSpectra.at(x).at(y); + if ( h->GetFunction(name.c_str()) ) //remove previous fit + h->GetListOfFunctions()->Remove(h->GetFunction(name.c_str())); + h->Fit(g,"R+Q0"); //use 0 to not draw the fit but save it + const double mu = g->GetParameter(1); + + //Change the point of the graph + TGraph* gr = fSegLinearFit.at(x).at(y); + gr->SetPoint(peakNumber, mu, fEnergyPeaks.at(peakNumber)); + + //Refit the calibration curve + TF1* lf = nullptr; + if ( gr->GetFunction("linearFit") ) + lf = gr->GetFunction("linearFit"); + else + lf = new TF1("linearFit", "pol1"); + gr->Fit(lf, "SQ"); //Q for quiet mode + fSlope.at(x).at(y) = lf->GetParameter(1); + fIntercept.at(x).at(y) = lf->GetParameter(0); +} + +///////////////////////////////////////////// +/// \brief Function to read the parameters from the RML element (TiXmlElement) +/// and set those class members. +/// +/// \param module A const TiXmlElement pointer that contains the information. +/// Example of this RML element: +/// +/// +/// +/// +void TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement(const TiXmlElement* module){ + + if (module == nullptr) { + RESTError << "TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement: module is nullptr" << p->RESTendl; + return; + } + + std::string el = !module->Attribute("planeId") ? "Not defined" : module->Attribute("planeId") ; + if ( !(el.empty() || el == "Not defined")) + this->SetPlaneId(StringToInteger(el)); + el = !module->Attribute("moduleId") ? "Not defined" : module->Attribute("moduleId") ; + if ( !(el.empty() || el == "Not defined")) + this->SetModuleId(StringToInteger(el)); + + el = !module->Attribute("moduleDefinitionCut") ? "Not defined" : module->Attribute("moduleDefinitionCut") ; + if ( !(el.empty() || el == "Not defined")) + this->SetModuleDefinitionCut(el); + + el = !module->Attribute("numberOfSegmentsX") ? "Not defined" : module->Attribute("numberOfSegmentsX") ; + if ( !(el.empty() || el == "Not defined")) + this->SetNumberOfSegmentsX(StringToInteger(el)); + el = !module->Attribute("numberOfSegmentsY") ? "Not defined" : module->Attribute("numberOfSegmentsY") ; + if ( !(el.empty() || el == "Not defined")) + this->SetNumberOfSegmentsY(StringToInteger(el)); + el = !module->Attribute("readoutRange") ? "Not defined" : module->Attribute("readoutRange") ; + if ( !(el.empty() || el == "Not defined")) + this->SetReadoutRange(StringTo2DVector(el)); + + el = !module->Attribute("calibRange") ? "Not defined" : module->Attribute("calibRange") ; + if ( !(el.empty() || el == "Not defined")) + this->SetCalibrationRange(StringTo2DVector(el)); + el = !module->Attribute("nBins") ? "Not defined" : module->Attribute("nBins") ; + if ( !(el.empty() || el == "Not defined")) + this->SetNBins(StringToInteger(el)); + + el = !module->Attribute("dataSetFileName") ? "Not defined" : module->Attribute("dataSetFileName") ; + if ( !(el.empty() || el == "Not defined")) + this->SetDataSetFileName(el); + + el = !module->Attribute("zeroPoint") ? "Not defined" : module->Attribute("zeroPoint") ; + if ( !(el.empty() || el == "Not defined")) + this->SetZeroPoint(ToLower(el)=="true"); + el = !module->Attribute("autoRangePeaks") ? "Not defined" : module->Attribute("autoRangePeaks") ; + if ( !(el.empty() || el == "Not defined")) + this->SetAutoRangePeaks(ToLower(el)=="true"); + + //Get peaks energy and range + TiXmlElement* peakDefinition = (TiXmlElement*) module->FirstChildElement("peak"); + while (peakDefinition != nullptr) { + double energy = 0; + TVector2 range = TVector2(0,0); + + std::string ell = !peakDefinition->Attribute("energy") ? "Not defined" : peakDefinition->Attribute("energy") ; + if (ell.empty() || ell == "Not defined") { + RESTError << "< peak variable key does not contain energy!" << p->RESTendl; + exit(1); + } + energy = StringToDouble(ell); + + ell = !peakDefinition->Attribute("range") ? "Not defined" : peakDefinition->Attribute("range") ; + if ( !(ell.empty() || ell == "Not defined")) + range = StringTo2DVector(ell); + + this->AddPeak(energy,range); + peakDefinition = (TiXmlElement*) peakDefinition->NextSiblingElement(); + } +} + + +void TRestCalibrationCorrection::Module::DrawSpectrum(const double x, const double y, TCanvas *c){ + auto [index_x, index_y] = GetIndexMatrix(x,y); + DrawSpectrum(index_x, index_y, c); +} + +void TRestCalibrationCorrection::Module::DrawSpectrum(const int index_x, const int index_y, TCanvas *c){ + if (fSegSpectra.size() == 0) { + RESTError << "Spectra matrix is empty." << p->RESTendl; + return; + } + if (index_x >= fSegSpectra.size() || index_y >= fSegSpectra.at(index_x).size()){ + RESTError << "Index out of range." << p->RESTendl; + return; + } + + if ( !c ){ + std::string t = "spectrum_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId) + +"_"+std::to_string(index_x)+"_"+std::to_string(index_y); + c = new TCanvas(t.c_str(),t.c_str()); + } + + auto xLower = *std::next(fSplitX.begin(), index_x); + auto xUpper = *std::next(fSplitX.begin(), index_x+1); + auto yLower = *std::next(fSplitY.begin(), index_y); + auto yUpper = *std::next(fSplitY.begin(), index_y+1); + std::string tH = "Spectrum x=["+DoubleToString(xLower,"%g")+", "+DoubleToString(xUpper,"%g") + +") y=["+DoubleToString(yLower,"%g")+", "+DoubleToString(yUpper,"%g")+");" + +GetObservable()+";counts"; + fSegSpectra[index_x][index_y]->SetTitle(tH.c_str()); + fSegSpectra[index_x][index_y]->Draw(); + for (int c=0; cGetFunction(("g"+std::to_string(c)).c_str()); + if (!fit) RESTError << "Fit for energy peak" << fEnergyPeaks[c] <<" not found." << p->RESTendl; + if (!fit) break; + fit->SetLineColor(c+2); //skipe 0 which is white and 1 which is blue as histogram + fit->Draw("same"); + } + +} + +void TRestCalibrationCorrection::Module::DrawSpectrum(){ + if (fSegSpectra.size() == 0) { + RESTError << "Spectra matrix is empty." << p->RESTendl; + return; + } + std::string t = "spectra_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); + TCanvas *myCanvas = new TCanvas(t.c_str(),t.c_str()); + myCanvas->Divide(fSegSpectra.size(),fSegSpectra.at(0).size()); + for (int i=0; icd(i+1+fSegSpectra[i].size()*j); + DrawSpectrum(i, fSegSpectra[i].size()-1-j, myCanvas); + } + } +} +void TRestCalibrationCorrection::Module::DrawFullSpectrum(){ + if (fSegSpectra.size() == 0) { + RESTError << "Spectra matrix is empty." << p->RESTendl; + return; + } + TH1F* sumHist = new TH1F("fullSpc", "", fSegSpectra[0][0]->GetNbinsX(), + fSegSpectra[0][0]->GetXaxis()->GetXmin(), + fSegSpectra[0][0]->GetXaxis()->GetXmax() ); + + sumHist->SetTitle(("Full spectrum;"+GetObservable()+";counts").c_str()); + for (int i=0; iAdd(fSegSpectra[i][j]); + } + } + std::string t = "fullSpc_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); + TCanvas *c = new TCanvas(t.c_str(),t.c_str()); + sumHist->Draw(); +} + + +void TRestCalibrationCorrection::Module::DrawLinearFit(const double x, const double y, TCanvas *c){ + auto [index_x, index_y] = GetIndexMatrix(x,y); + DrawLinearFit(index_x, index_y, c); +} + +void TRestCalibrationCorrection::Module::DrawLinearFit(const int index_x, const int index_y, TCanvas *c){ + if (fSegLinearFit.size() == 0) { + RESTError << "Spectra matrix is empty." << p->RESTendl; + return; + } + if (index_x >= fSegLinearFit.size() || index_y >= fSegLinearFit.at(index_x).size()){ + RESTError << "Index out of range." << p->RESTendl; + return; + } + if ( !c ){ + std::string t = "linearFit_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId) + +"_"+std::to_string(index_x)+"_"+std::to_string(index_y); + c = new TCanvas(t.c_str(),t.c_str()); + } + auto xLower = *std::next(fSplitX.begin(), index_x); + auto xUpper = *std::next(fSplitX.begin(), index_x+1); + auto yLower = *std::next(fSplitY.begin(), index_y); + auto yUpper = *std::next(fSplitY.begin(), index_y+1); + std::string tH = "Linear Fit x=["+DoubleToString(xLower,"%g")+", "+DoubleToString(xUpper,"%g") + +") y=["+DoubleToString(yLower,"%g")+", "+DoubleToString(yUpper,"%g")+");" + +GetObservable()+";energy"; + fSegLinearFit[index_x][index_y]->SetTitle(tH.c_str()); + fSegLinearFit[index_x][index_y]->Draw("AL*"); +} + +void TRestCalibrationCorrection::Module::DrawLinearFit(){ + std::string t = "linearFits_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); + TCanvas *myCanvas = new TCanvas(t.c_str(),t.c_str()); + myCanvas->Divide(fSegLinearFit.size(),fSegLinearFit.at(0).size()); + for (int i=0; icd(i+1+fSegLinearFit[i].size()*j); + //fSegLinearFit[i][j]->Draw("AL*"); + DrawLinearFit(i, fSegSpectra[i].size()-1-j, myCanvas); + } + } +} + +void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber){ + if (peakNumber < 0 || peakNumber >= fEnergyPeaks.size()){ + RESTError << "Peak number out of range (peakNumber should be between 0 and " + << fEnergyPeaks.size()-1 << " )" << p->RESTendl; + return; + } + double peakEnergy = fEnergyPeaks[peakNumber]; + std::string title = "Gain map for energy " + DoubleToString(peakEnergy,"%g") + +";"+GetSpatialObservableX()+";"+GetSpatialObservableY();// + " keV"; + std::string t = "gainMap"+std::to_string(peakNumber) + +"_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); + TCanvas *gainMap = new TCanvas(t.c_str(),t.c_str()); + TH2F *hGainMap= new TH2F(("h"+t).c_str(), title.c_str(), + fNumberOfSegmentsY, fReadoutRange.X(), fReadoutRange.Y(), + fNumberOfSegmentsX, fReadoutRange.X(), fReadoutRange.Y()); + + const double peakPosRef = + fSegLinearFit[(fNumberOfSegmentsX-1)/2][(fNumberOfSegmentsY-1)/2]->GetPointX(peakNumber); + + auto itX = fSplitX.begin(); + for ( int i=0; iFill((xUpper+xLower)/2., (yUpper+yLower)/2., + fSegLinearFit[i][j]->GetPointX(peakNumber)/peakPosRef); + itY++; + } + itX++; + } + hGainMap->SetStats(0); + hGainMap->Draw("colz"); + hGainMap->SetBarOffset(0.2); + hGainMap->Draw("TEXT SAME"); +} + +///////////////////////////////////////////// +/// \brief Prints on screen the information about the +/// members of Module +/// +void TRestCalibrationCorrection::Module::Print() { + RESTMetadata << "-----------------------------------------------" << p->RESTendl; + RESTMetadata << " Plane ID: " << fPlaneId << p->RESTendl; + RESTMetadata << " Module ID: " << fModuleId << p->RESTendl; + RESTMetadata << " Definition cut: " << fDefinitionCut << p->RESTendl; + RESTMetadata << p->RESTendl; + + RESTMetadata << " DataSet: " << fDataSetFileName << p->RESTendl; + RESTMetadata << p->RESTendl; + + RESTMetadata << " Energy peaks: "; + for (const auto& peak : fEnergyPeaks) RESTMetadata << peak << ", "; + RESTMetadata << p->RESTendl; + bool anyRange = false; + for (const auto& r : fRangePeaks) if (r.X() < r.Y()){RESTMetadata<<" Range peaks: ";anyRange=true;break;} + if (anyRange) for (const auto& r : fRangePeaks) RESTMetadata << "(" << r.X() << ", " << r.Y() << ") "; + if (anyRange) RESTMetadata << p->RESTendl; + RESTMetadata << " Auto range peaks: " << (fAutoRangePeaks ? "true" : "false") << p->RESTendl; + RESTMetadata << " Zero point: " << (fZeroPoint ? "true" : "false") << p->RESTendl; + RESTMetadata << " Calibration range: (" << fCalibRange.X() << ", " << fCalibRange.Y() << " )" << p->RESTendl; + RESTMetadata << " Number of bins: " << fNBins << p->RESTendl; + RESTMetadata << p->RESTendl; + + RESTMetadata << " Number of segments X: " << fNumberOfSegmentsX << p->RESTendl; + RESTMetadata << " Number of segments Y: " << fNumberOfSegmentsY << p->RESTendl; + //RESTMetadata << " Draw: " << (fDrawVar ? "true" : "false") << p->RESTendl; + RESTMetadata << " Readout range (" << fReadoutRange.X() << ", " << fReadoutRange.Y() << " )" << p->RESTendl; + RESTMetadata << "SplitX: "; + for (auto &i : fSplitX){ + RESTMetadata << " " << i; + } + RESTMetadata << p->RESTendl; + RESTMetadata << "SplitY: "; + for (auto &i : fSplitY){ + RESTMetadata << " " << i; + } + RESTMetadata << p->RESTendl; + RESTMetadata << p->RESTendl; + + RESTMetadata << " Slope: " << p->RESTendl; + int maxSize = 0; + for (auto& x : fSlope) if (maxSize < x.size()) maxSize = x.size(); + for (int j = 0; j < maxSize; j++) { + for (int k = 0; k < fSlope.size(); k++){ + if (j < fSlope[k].size()) + RESTMetadata << DoubleToString(fSlope[k][fSlope[k].size()-1-j],"%.3e") + << " "; + else RESTMetadata << " "; + } + RESTMetadata << p->RESTendl; + } + RESTMetadata << " Intercept: " << p->RESTendl; + maxSize = 0; + for (auto& x : fIntercept) if (maxSize < x.size()) maxSize = x.size(); + for (int j = 0; j < maxSize; j++) { + for (int k = 0; k < fIntercept.size(); k++){ + if (j < fIntercept[k].size()) + RESTMetadata << DoubleToString(fIntercept[k][fIntercept[k].size()-1-j],"%+.3e") + << " "; + else RESTMetadata << " "; + } + RESTMetadata << p->RESTendl; + } + RESTMetadata << "-----------------------------------------------" << p->RESTendl; +} + From b858a12e82a5bb1de50cc3e32d464714a1c739cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 09:30:32 +0000 Subject: [PATCH 02/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../analysis/inc/TRestCalibrationCorrection.h | 228 ++--- .../src/TRestCalibrationCorrection.cxx | 812 +++++++++--------- 2 files changed, 529 insertions(+), 511 deletions(-) diff --git a/source/framework/analysis/inc/TRestCalibrationCorrection.h b/source/framework/analysis/inc/TRestCalibrationCorrection.h index 35cd74239..df78cb230 100644 --- a/source/framework/analysis/inc/TRestCalibrationCorrection.h +++ b/source/framework/analysis/inc/TRestCalibrationCorrection.h @@ -23,66 +23,75 @@ #ifndef REST_TRestCalibrationCorrection #define REST_TRestCalibrationCorrection -#include "TRestMetadata.h" -#include "TRestDataSet.h" -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include + +#include "TRestDataSet.h" +#include "TRestMetadata.h" /// Metadata class to calculate,store and apply the gain corrected calibration of a group of detectors. class TRestCalibrationCorrection : public TRestMetadata { -public: + public: class Module; -private: - std::string fObservable = ""; //"rawAna_ThresholdIntegral"; //< - std::string fSpatialObservableX = ""; //"hitsAna_xMean"; //< - std::string fSpatialObservableY = ""; //"hitsAna_yMean"; //< - std::vector - fModulesCal = {}; + private: + std::string fObservable = ""; //"rawAna_ThresholdIntegral"; //< + std::string fSpatialObservableX = ""; //"hitsAna_xMean"; //< + std::string fSpatialObservableY = ""; //"hitsAna_yMean"; //< + + std::vector fModulesCal = {}; std::string fCalibFileName = ""; std::string fOutputFileName = ""; - + void Initialize() override; void InitFromConfigFile() override; - -public: + + public: std::set GetPlaneIDs() const; std::set GetModuleIDs(const int planeId) const; std::map> GetModuleIDs() const; - Int_t GetNumberOfPlanes() const { return GetPlaneIDs().size();} - Int_t GetNumberOfModules() const { int sum=0; for (auto pID : GetPlaneIDs()) for (auto n : GetModuleIDs(pID)) sum += n; return sum;} - //Int_t GetNumberOfModulesOfPlane(const int planeID) const { return fNModules.at(planeID);} - std::string GetCalibrationFileName() const { return fCalibFileName;} - std::string GetOutputFileName() const { return fOutputFileName;} - std::string GetObservable() const { return fObservable;} - std::string GetSpatialObservableX() const { return fSpatialObservableX;} - std::string GetSpatialObservableY() const { return fSpatialObservableY;} - + Int_t GetNumberOfPlanes() const { return GetPlaneIDs().size(); } + Int_t GetNumberOfModules() const { + int sum = 0; + for (auto pID : GetPlaneIDs()) + for (auto n : GetModuleIDs(pID)) sum += n; + return sum; + } + // Int_t GetNumberOfModulesOfPlane(const int planeID) const { return fNModules.at(planeID);} + std::string GetCalibrationFileName() const { return fCalibFileName; } + std::string GetOutputFileName() const { return fOutputFileName; } + std::string GetObservable() const { return fObservable; } + std::string GetSpatialObservableX() const { return fSpatialObservableX; } + std::string GetSpatialObservableY() const { return fSpatialObservableY; } + Module* GetModuleCalibration(const int planeID, const int moduleID); double GetSlopeParameter(const int planeID, const int moduleID, const double x, const double y); double GetInterceptParameter(const int planeID, const int moduleID, const double x, const double y); - void SetCalibrationFileName(const std::string &fileName) { fCalibFileName = fileName;} - void SetOutputFileName(const std::string &fileName) { fOutputFileName = fileName;} + void SetCalibrationFileName(const std::string& fileName) { fCalibFileName = fileName; } + void SetOutputFileName(const std::string& fileName) { fOutputFileName = fileName; } void SetModuleCalibration(const Module& moduleCal); - void SetObservable( const std::string &observable) { fObservable = observable;} - void SetSpatialObservableX( const std::string &spatialObservableX) { fSpatialObservableX = spatialObservableX;} - void SetSpatialObservableY( const std::string &spatialObservableY) { fSpatialObservableY = spatialObservableY;} - + void SetObservable(const std::string& observable) { fObservable = observable; } + void SetSpatialObservableX(const std::string& spatialObservableX) { + fSpatialObservableX = spatialObservableX; + } + void SetSpatialObservableY(const std::string& spatialObservableY) { + fSpatialObservableY = spatialObservableY; + } void Import(const std::string& fileName); void Export(const std::string& fileName = ""); TRestCalibrationCorrection& operator=(TRestCalibrationCorrection& src); - -public: + + public: void PrintMetadata() override; void Calibrate(); @@ -97,110 +106,119 @@ class TRestCalibrationCorrection : public TRestMetadata { ClassDefOverride(TRestCalibrationCorrection, 1); class Module { - private: - const TRestCalibrationCorrection* p = nullptr; // fEnergyPeaks = {};//{22.5, 8.0}; - std::vector fRangePeaks = {};//{TVector2(230000, 650000), TVector2(40000, 230000)}; //in development... - TVector2 fCalibRange = TVector2(0,0); //< // Calibration range - Int_t fNBins = 100; //< // Number of bins for the spectrum histograms + private: + const TRestCalibrationCorrection* p = nullptr; // fEnergyPeaks = {}; //{22.5, 8.0}; + std::vector fRangePeaks = + {}; //{TVector2(230000, 650000), TVector2(40000, 230000)}; //in development... + TVector2 fCalibRange = TVector2(0, 0); //< // Calibration range + Int_t fNBins = 100; //< // Number of bins for the spectrum histograms /*std::string fObservable = ""; //"rawAna_ThresholdIntegral"; //< std::string fSpatialObservableX = ""; //"hitsAna_xMean"; //< std::string fSpatialObservableY = ""; //"hitsAna_yMean"; //<*/ - std::string fDefinitionCut = ""; //"TREXsides_tagId == 2"; //< + std::string fDefinitionCut = ""; //"TREXsides_tagId == 2"; //< - Int_t fNumberOfSegmentsX = 1; //< - Int_t fNumberOfSegmentsY = 1; //< - TVector2 fReadoutRange = TVector2(0, 246); //< // Readout dimensions - std::set fSplitX = {}; //< - std::set fSplitY = {}; //< + Int_t fNumberOfSegmentsX = 1; //< + Int_t fNumberOfSegmentsY = 1; //< + TVector2 fReadoutRange = TVector2(0, 246); //< // Readout dimensions + std::set fSplitX = {}; //< + std::set fSplitY = {}; //< - std::string fDataSetFileName = ""; //< // File name for the dataset + std::string fDataSetFileName = ""; //< // File name for the dataset - std::vector> fSlope = {}; //< - std::vector> fIntercept = {}; //< + std::vector> fSlope = {}; //< + std::vector> fIntercept = {}; //< - bool fZeroPoint = false; //< Zero point will be automatically added if there are less than 2 peaks - bool fAutoRangePeaks = true; //< Automatic range peaks - std::vector< std::vector > fSegSpectra = {}; //fSegmentedSpectra - std::vector< std::vector > fSegLinearFit = {}; //fSegmentedLinearFit + bool fZeroPoint = false; //< Zero point will be automatically added if there are less than 2 peaks + bool fAutoRangePeaks = true; //< Automatic range peaks + std::vector> fSegSpectra = {}; // fSegmentedSpectra + std::vector> fSegLinearFit = {}; // fSegmentedLinearFit - public: + public: void SetSplitX(); void SetSplitY(); void SetSplits(); - void AddPeak(const double &energyPeak, const TVector2 &rangePeak=TVector2(0,0)) { - fEnergyPeaks.push_back(energyPeak); fRangePeaks.push_back(rangePeak);} - + void AddPeak(const double& energyPeak, const TVector2& rangePeak = TVector2(0, 0)) { + fEnergyPeaks.push_back(energyPeak); + fRangePeaks.push_back(rangePeak); + } + void LoadConfigFromTiXmlElement(const TiXmlElement* module); std::pair GetIndexMatrix(const double x, const double y) const; double GetSlope(const double x, const double y) const; double GetIntercept(const double x, const double y) const; - Int_t GetPlaneId() const { return fPlaneId;} - Int_t GetModuleId() const { return fModuleId;} - std::string GetObservable() const { return p->fObservable;} - std::string GetSpatialObservableX() const { return p->fSpatialObservableX;} - std::string GetSpatialObservableY() const { return p->fSpatialObservableY;} - inline std::string GetModuleDefinitionCut() const { return fDefinitionCut;} - Int_t GetNumberOfSegmentsX() const { return fNumberOfSegmentsX;} - Int_t GetNumberOfSegmentsY() const { return fNumberOfSegmentsY;} + Int_t GetPlaneId() const { return fPlaneId; } + Int_t GetModuleId() const { return fModuleId; } + std::string GetObservable() const { return p->fObservable; } + std::string GetSpatialObservableX() const { return p->fSpatialObservableX; } + std::string GetSpatialObservableY() const { return p->fSpatialObservableY; } + inline std::string GetModuleDefinitionCut() const { return fDefinitionCut; } + Int_t GetNumberOfSegmentsX() const { return fNumberOfSegmentsX; } + Int_t GetNumberOfSegmentsY() const { return fNumberOfSegmentsY; } - std::set GetSplitX() const { return fSplitX;} - std::set GetSplitY() const { return fSplitY;} - std::string GetDataSetFileName() const { return fDataSetFileName;} - TVector2 GetReadoutRangeVar() const { return fReadoutRange;} + std::set GetSplitX() const { return fSplitX; } + std::set GetSplitY() const { return fSplitY; } + std::string GetDataSetFileName() const { return fDataSetFileName; } + TVector2 GetReadoutRangeVar() const { return fReadoutRange; } void DrawSpectrum(); - void DrawSpectrum(const double x, const double y, TCanvas *c=nullptr); - void DrawSpectrum(const int index_x, const int index_y, TCanvas *c=nullptr); + void DrawSpectrum(const double x, const double y, TCanvas* c = nullptr); + void DrawSpectrum(const int index_x, const int index_y, TCanvas* c = nullptr); void DrawFullSpectrum(); void DrawLinearFit(); - void DrawLinearFit(const double x, const double y, TCanvas *c=nullptr); - void DrawLinearFit(const int index_x, const int index_y, TCanvas *c=nullptr); - + void DrawLinearFit(const double x, const double y, TCanvas* c = nullptr); + void DrawLinearFit(const int index_x, const int index_y, TCanvas* c = nullptr); + void DrawGainMap(const int peakNumber = 0); void Refit(const double x, const double y, const double energy, const TVector2& range); - void Refit(const int x, const int y, const int peakNumber,const TVector2& range); - - void SetPlaneId( const Int_t &planeId) { fPlaneId = planeId;} - void SetModuleId( const Int_t &moduleId) { fModuleId = moduleId;} - void SetModuleDefinitionCut( const std::string &moduleDefinitionCut) { fDefinitionCut = moduleDefinitionCut;} - void SetCalibrationRange( const TVector2 &calibrationRange) { fCalibRange = calibrationRange;} - void SetNBins( const Int_t &nBins) { fNBins = nBins;} - - void SetNumberOfSegmentsX( const Int_t &numberOfSegmentsX) { fNumberOfSegmentsX = numberOfSegmentsX; SetSplitX();} - void SetNumberOfSegmentsY( const Int_t &numberOfSegmentsY) { fNumberOfSegmentsY = numberOfSegmentsY; SetSplitY();} - //void SetInputFile( const std::string &inputFile) { fInputFile = inputFile;} - //void SetOutputFile( const std::string &outputFile) { fOutputFile = outputFile;} - void SetDataSetFileName( const std::string &dataSetFileName) { fDataSetFileName = dataSetFileName;} - void SetReadoutRange( const TVector2 &readoutRange) { fReadoutRange = readoutRange;} - void SetZeroPoint( const bool &ZeroPoint) { fZeroPoint = ZeroPoint;} - void SetAutoRangePeaks( const bool &autoRangePeaks) { fAutoRangePeaks = autoRangePeaks;} - - + void Refit(const int x, const int y, const int peakNumber, const TVector2& range); + + void SetPlaneId(const Int_t& planeId) { fPlaneId = planeId; } + void SetModuleId(const Int_t& moduleId) { fModuleId = moduleId; } + void SetModuleDefinitionCut(const std::string& moduleDefinitionCut) { + fDefinitionCut = moduleDefinitionCut; + } + void SetCalibrationRange(const TVector2& calibrationRange) { fCalibRange = calibrationRange; } + void SetNBins(const Int_t& nBins) { fNBins = nBins; } + + void SetNumberOfSegmentsX(const Int_t& numberOfSegmentsX) { + fNumberOfSegmentsX = numberOfSegmentsX; + SetSplitX(); + } + void SetNumberOfSegmentsY(const Int_t& numberOfSegmentsY) { + fNumberOfSegmentsY = numberOfSegmentsY; + SetSplitY(); + } + // void SetInputFile( const std::string &inputFile) { fInputFile = inputFile;} + // void SetOutputFile( const std::string &outputFile) { fOutputFile = outputFile;} + void SetDataSetFileName(const std::string& dataSetFileName) { fDataSetFileName = dataSetFileName; } + void SetReadoutRange(const TVector2& readoutRange) { fReadoutRange = readoutRange; } + void SetZeroPoint(const bool& ZeroPoint) { fZeroPoint = ZeroPoint; } + void SetAutoRangePeaks(const bool& autoRangePeaks) { fAutoRangePeaks = autoRangePeaks; } + void Print(); void CalculateCalibrationParameters(); void Initialize(); Module() {} - Module(const TRestCalibrationCorrection& parent) : p(&parent) {}; - Module(const TRestCalibrationCorrection& parent, const Int_t planeId, const Int_t moduleId) : p(&parent) { - SetPlaneId(planeId); SetModuleId(moduleId); }; - ~Module() {}; - - - + Module(const TRestCalibrationCorrection& parent) : p(&parent){}; + Module(const TRestCalibrationCorrection& parent, const Int_t planeId, const Int_t moduleId) + : p(&parent) { + SetPlaneId(planeId); + SetModuleId(moduleId); + }; + ~Module(){}; }; - }; #endif diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index 994b85f6d..5dc39c94a 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -25,20 +25,20 @@ /// parameters for a given detector with multiple (or just one) modules. /// This modules are defined in the Module class. It performs a gain correction /// based on a spatial segmentation of the detector module. This is useful for -/// big modules such as the ones used in TREX-DM experiment. -/// +/// big modules such as the ones used in TREX-DM experiment. +/// /// ### Parameters /// * **calibFileName**: name of the file to use for the calibration. It should be a DataSet /// * **outputFileName**: name of the file to save the this calibration metadata /// * **observable**: name of the observable to be calibrated. It must be a branch of the calibration DataSet -/// * **spatialObservableX**: name of the observable to be used for the spatial segmentation along the X axis. +/// * **spatialObservableX**: name of the observable to be used for the spatial segmentation along the X axis. /// It must be a branch of the calibration DataSet -/// * **spatialObservableY**: name of the observable to be used for the spatial segmentation along the Y axis. +/// * **spatialObservableY**: name of the observable to be used for the spatial segmentation along the Y axis. /// It must be a branch of the calibration DataSet /// * **modulesCal**: vector of Module objects /// /// ### Examples -/// Give examples of usage and RML descriptions that can be tested. +/// Give examples of usage and RML descriptions that can be tested. /// \code /// /// @@ -72,7 +72,7 @@ /// [2] cal.SetOutputFileName("myCalibration.root"); //if not already defined in rml file /// [3] cal.Calibrate(); /// [4] cal.Export(); -/// \endcode +/// \endcode /// /// Example to calibrate a dataSet with the previously calculated calibration parameters /// and draw the result (using restRoot): @@ -83,31 +83,29 @@ /// [3] TRestDataSet ds("calibratedDataSet.root"); /// [4] auto h = ds->GetDataFrame().Histo1D({"hname", "",100,-1,40.}, "calib_ThresholdIntegral"); /// [5] h->Draw(); -/// \endcode +/// \endcode ///---------------------------------------------------------------------- -/// -/// REST-for-Physics - Software for Rare Event Searches Toolkit -/// -/// History of developments: -/// +/// +/// REST-for-Physics - Software for Rare Event Searches Toolkit +/// +/// History of developments: +/// /// 2023-May: First implementation of TRestCalibrationCorrection /// Álvaro Ezquerro -/// -/// \class TRestCalibrationCorrection +/// +/// \class TRestCalibrationCorrection /// \author: Álvaro Ezquerro aezquerro@unizar.es -/// -///
-/// +/// +///
+/// #include "TRestCalibrationCorrection.h" ClassImp(TRestCalibrationCorrection); -/////////////////////////////////////////////// -/// \brief Default constructor -/// -TRestCalibrationCorrection::TRestCalibrationCorrection() { - Initialize(); -} +/////////////////////////////////////////////// +/// \brief Default constructor +/// +TRestCalibrationCorrection::TRestCalibrationCorrection() { Initialize(); } ///////////////////////////////////////////// /// \brief Constructor loading data from a config file @@ -123,18 +121,17 @@ TRestCalibrationCorrection::TRestCalibrationCorrection() { /// \param name The name of the metadata section. It will be used to find the /// corresponding TRestCalibrationCorrection section inside the RML. /// -TRestCalibrationCorrection::TRestCalibrationCorrection(const char* configFilename, std::string name) : TRestMetadata(configFilename) { +TRestCalibrationCorrection::TRestCalibrationCorrection(const char* configFilename, std::string name) + : TRestMetadata(configFilename) { LoadConfigFromFile(fConfigFileName, name); - if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info) PrintMetadata(); + if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info) PrintMetadata(); } -/////////////////////////////////////////////// -/// \brief Default destructor -/// -TRestCalibrationCorrection::~TRestCalibrationCorrection() { -} +/////////////////////////////////////////////// +/// \brief Default destructor +/// +TRestCalibrationCorrection::~TRestCalibrationCorrection() {} - void TRestCalibrationCorrection::Initialize() { SetSectionName(this->ClassName()); } /////////////////////////////////////////////// /// \brief Initialization of TRestCalibrationCorrection @@ -144,27 +141,26 @@ void TRestCalibrationCorrection::InitFromConfigFile() { this->Initialize(); TRestMetadata::InitFromConfigFile(); - //Load Module from RML + // Load Module from RML TiXmlElement* moduleDefinition = GetElement("module"); - while (moduleDefinition != nullptr) { - fModulesCal.push_back( Module(*this) ); + while (moduleDefinition != nullptr) { + fModulesCal.push_back(Module(*this)); fModulesCal.back().LoadConfigFromTiXmlElement(moduleDefinition); - + moduleDefinition = GetNextElement(moduleDefinition); } - if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Debug) - PrintMetadata(); + if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Debug) PrintMetadata(); } ///////////////////////////////////////////// /// \brief Function to calculate the calibration parameters of all modules /// -void TRestCalibrationCorrection::Calibrate(){ - for (auto& i : fModulesCal){ +void TRestCalibrationCorrection::Calibrate() { + for (auto& i : fModulesCal) { RESTInfo << "Calibrating plane " << i.GetPlaneId() << " module " << i.GetModuleId() << RESTendl; i.CalculateCalibrationParameters(); - if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info){ + if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info) { i.DrawSpectrum(); i.DrawGainMap(); } @@ -174,57 +170,57 @@ void TRestCalibrationCorrection::Calibrate(){ ///////////////////////////////////////////// /// \brief Function to calibrate a dataSet /// -void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFileName, std::string outputFileName){ +void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFileName, + std::string outputFileName) { if (fModulesCal.size() == 0) { RESTError << "TRestCalibrationCorrection::CalibrateDataSet: No modules defined." << RESTendl; return; } - + TRestDataSet dataSet; dataSet.Import(dataSetFileName); auto dataFrame = dataSet.GetDataFrame(); - //Define a new column with the identifier (pmID) of the module for each row (event) + // Define a new column with the identifier (pmID) of the module for each row (event) std::string modCut = fModulesCal[0].GetModuleDefinitionCut(); std::string pmIDname = (std::string)GetName() + "_pmID"; - int pmID = fModulesCal[0].GetPlaneId()*10 + fModulesCal[0].GetModuleId(); + int pmID = fModulesCal[0].GetPlaneId() * 10 + fModulesCal[0].GetModuleId(); dataFrame = dataFrame.Define(pmIDname, modCut + " ? " + std::to_string(pmID) + " : -1"); - for (int n=1; n::quiet_NaN(); }; std::string calibObsName = (std::string)GetName() + "_"; - calibObsName += GetObservable().erase(0, GetObservable().find("_")+1); //remove the "rawAna_" part - dataFrame = dataFrame.Define(calibObsName, calibrate, - {fObservable, fSpatialObservableX, - fSpatialObservableY, pmIDname}); + calibObsName += GetObservable().erase(0, GetObservable().find("_") + 1); // remove the "rawAna_" part + dataFrame = dataFrame.Define(calibObsName, calibrate, + {fObservable, fSpatialObservableX, fSpatialObservableY, pmIDname}); dataSet.SetDataFrame(dataFrame); - //Format the output file name and export the dataSet + // Format the output file name and export the dataSet if (outputFileName.empty()) { outputFileName = dataSetFileName; RESTWarning << "TRestCalibrationCorrection::CalibrateDataSet: No output file name defined. " << "Using input file name " << outputFileName << RESTendl; - } - else if (TRestTools::GetFileNameExtension(outputFileName) != "root") - outputFileName = outputFileName+".root"; + } else if (TRestTools::GetFileNameExtension(outputFileName) != "root") + outputFileName = outputFileName + ".root"; RESTInfo << "Exporting calibrated dataSet to " << outputFileName << RESTendl; dataSet.Export(outputFileName); - //Add this TRestCalibrationCorrection metadata to the output file + // Add this TRestCalibrationCorrection metadata to the output file TFile* f = TFile::Open(outputFileName.c_str(), "UPDATE"); this->Write(); f->Close(); @@ -235,34 +231,36 @@ void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFile /// \brief Function to retrieve the module calibration with planeID and moduleID /// /// -TRestCalibrationCorrection::Module* TRestCalibrationCorrection::GetModuleCalibration(const int planeID, const int moduleID){ - for (auto& i : fModulesCal){ +TRestCalibrationCorrection::Module* TRestCalibrationCorrection::GetModuleCalibration(const int planeID, + const int moduleID) { + for (auto& i : fModulesCal) { if (i.GetPlaneId() == planeID && i.GetModuleId() == moduleID) return &i; } RESTError << "No ModuleCalibration with planeID " << planeID << " and moduleID " << moduleID << RESTendl; return nullptr; } - ///////////////////////////////////////////// -/// \brief Function to get the slope parameter of the module with +/// \brief Function to get the slope parameter of the module with /// planeID and moduleID at physical position (x,y) /// /// -double TRestCalibrationCorrection::GetSlopeParameter(const int planeID, const int moduleID, const double x, const double y){ +double TRestCalibrationCorrection::GetSlopeParameter(const int planeID, const int moduleID, const double x, + const double y) { Module* moduleCal = GetModuleCalibration(planeID, moduleID); - if (moduleCal == nullptr) return 0; //return numeric_limits::quiet_NaN() + if (moduleCal == nullptr) return 0; // return numeric_limits::quiet_NaN() return moduleCal->GetSlope(x, y); } ///////////////////////////////////////////// -/// \brief Function to get the intercept parameter of the module with +/// \brief Function to get the intercept parameter of the module with /// planeID and moduleID at physical position (x,y) /// /// -double TRestCalibrationCorrection::GetInterceptParameter(const int planeID, const int moduleID, const double x, const double y){ +double TRestCalibrationCorrection::GetInterceptParameter(const int planeID, const int moduleID, + const double x, const double y) { Module* moduleCal = GetModuleCalibration(planeID, moduleID); - if (moduleCal == nullptr) return 0; //return numeric_limits::quiet_NaN() + if (moduleCal == nullptr) return 0; // return numeric_limits::quiet_NaN() return moduleCal->GetIntercept(x, y); } @@ -272,8 +270,7 @@ double TRestCalibrationCorrection::GetInterceptParameter(const int planeID, cons /// std::set TRestCalibrationCorrection::GetPlaneIDs() const { std::set planeIDs; - for (const auto& mc : fModulesCal) - planeIDs.insert(mc.GetPlaneId()); + for (const auto& mc : fModulesCal) planeIDs.insert(mc.GetPlaneId()); return planeIDs; } @@ -282,21 +279,19 @@ std::set TRestCalibrationCorrection::GetPlaneIDs() const { /// of the plane with planeID /// std::set TRestCalibrationCorrection::GetModuleIDs(const int planeId) const { - std::set moduleIDs; - for (const auto& mc : fModulesCal) - if (mc.GetPlaneId()==planeId) - moduleIDs.insert(mc.GetModuleId()); + std::set moduleIDs; + for (const auto& mc : fModulesCal) + if (mc.GetPlaneId() == planeId) moduleIDs.insert(mc.GetModuleId()); return moduleIDs; - } ///////////////////////////////////////////// /// \brief Function to get the map of the module IDs for each plane ID /// std::map> TRestCalibrationCorrection::GetModuleIDs() const { - std::map> moduleIds; - for (const int planeId : GetPlaneIDs()) - moduleIds.insert(std::pair>(planeId,GetModuleIDs(planeId))); + std::map> moduleIds; + for (const int planeId : GetPlaneIDs()) + moduleIds.insert(std::pair>(planeId, GetModuleIDs(planeId))); return moduleIds; } @@ -308,8 +303,7 @@ TRestCalibrationCorrection& TRestCalibrationCorrection::operator=(TRestCalibrati fSpatialObservableY = src.GetSpatialObservableY(); fModulesCal.clear(); for (auto pID : src.GetPlaneIDs()) - for (auto mID : src.GetModuleIDs(pID)) - fModulesCal.push_back(*src.GetModuleCalibration(pID,mID)); + for (auto mID : src.GetModuleIDs(pID)) fModulesCal.push_back(*src.GetModuleCalibration(pID, mID)); return *this; } @@ -317,8 +311,8 @@ TRestCalibrationCorrection& TRestCalibrationCorrection::operator=(TRestCalibrati /// \brief Function to set a module calibration. If the module calibration /// already exists (same planeId and moduleId), it will be replaced. /// -void TRestCalibrationCorrection::SetModuleCalibration(const Module& moduleCal){ - for (auto& i : fModulesCal){ +void TRestCalibrationCorrection::SetModuleCalibration(const Module& moduleCal) { + for (auto& i : fModulesCal) { if (i.GetPlaneId() == moduleCal.GetPlaneId() && i.GetModuleId() == moduleCal.GetModuleId()) { i = moduleCal; return; @@ -328,16 +322,16 @@ void TRestCalibrationCorrection::SetModuleCalibration(const Module& moduleCal){ } ///////////////////////////////////////////// -/// \brief Function to import the calibration parameters -/// from the root file fileName. +/// \brief Function to import the calibration parameters +/// from the root file fileName. /// -void TRestCalibrationCorrection::Import(const std::string& fileName){ +void TRestCalibrationCorrection::Import(const std::string& fileName) { if (fileName.empty()) { RESTError << "No input calibration file defined" << RESTendl; return; } - - if (TRestTools::isRootFile(fileName)){ + + if (TRestTools::isRootFile(fileName)) { RESTInfo << "Opening " << fileName << RESTendl; TFile* f = TFile::Open(fileName.c_str(), "READ"); if (f == nullptr) { @@ -352,36 +346,36 @@ void TRestCalibrationCorrection::Import(const std::string& fileName){ while ((key = (TKey*)nextkey())) { std::string kName = key->GetClassName(); if (REST_Reflection::GetClassQuick(kName.c_str()) != nullptr && - REST_Reflection::GetClassQuick(kName.c_str())->InheritsFrom("TRestCalibrationCorrection")) { + REST_Reflection::GetClassQuick(kName.c_str()) + ->InheritsFrom("TRestCalibrationCorrection")) { cal = f->Get(key->GetName()); *this = *cal; } } } - } - else + } else RESTError << "File extension not supported for " << fileName << RESTendl; if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Debug) PrintMetadata(); } ///////////////////////////////////////////// /// \brief Function to export the calibration -/// to the file fileName. +/// to the file fileName. /// -void TRestCalibrationCorrection::Export(const std::string& fileName){ +void TRestCalibrationCorrection::Export(const std::string& fileName) { if (!fileName.empty()) fOutputFileName = fileName; if (fOutputFileName.empty()) { RESTError << "No output file defined" << RESTendl; return; } - + if (TRestTools::GetFileNameExtension(fOutputFileName) == "root") { TFile* f = TFile::Open(fOutputFileName.c_str(), "UPDATE"); this->Write(GetName()); f->Close(); delete f; RESTInfo << "Calibration saved to " << fOutputFileName << RESTendl; - } else + } else RESTError << "File extension for " << fOutputFileName << "is not supported." << RESTendl; } @@ -398,8 +392,7 @@ void TRestCalibrationCorrection::PrintMetadata() { RESTMetadata << " Spatial observable X: " << fSpatialObservableX << RESTendl; RESTMetadata << " Spatial observable Y: " << fSpatialObservableY << RESTendl; RESTMetadata << "-----------------------------------------------" << RESTendl; - for (auto& i : fModulesCal) - i.Print(); + for (auto& i : fModulesCal) i.Print(); RESTMetadata << "***********************************************" << RESTendl; RESTMetadata << RESTendl; } @@ -407,48 +400,54 @@ void TRestCalibrationCorrection::PrintMetadata() { ///////////////////////////////////////////// /// \brief Function to get the index of the matrix of calibration parameters /// for a given x and y position on the detector plane. -/// +/// /// \param x A const double that defines the x position on the detector plane. /// \param y A const double that defines the y position on the detector plane. -/// +/// std::pair TRestCalibrationCorrection::Module::GetIndexMatrix(const double x, const double y) const { - int index_x=-1, index_y=-1; - - if (fSplitX.upper_bound(x) != fSplitX.end()){ - index_x = std::distance(fSplitX.begin(), fSplitX.upper_bound(x)) -1; - if (index_x < 0){ - RESTWarning << "index_x < 0 for x = "<< x <<" and fSplitX[0]="<<*fSplitX.begin()<RESTendl; - index_x=0; + int index_x = -1, index_y = -1; + + if (fSplitX.upper_bound(x) != fSplitX.end()) { + index_x = std::distance(fSplitX.begin(), fSplitX.upper_bound(x)) - 1; + if (index_x < 0) { + RESTWarning << "index_x < 0 for x = " << x << " and fSplitX[0]=" << *fSplitX.begin() + << p->RESTendl; + index_x = 0; } + } else { + RESTWarning << "x is out of split for x = " << x << p->RESTendl; + index_x = fSplitX.size() - 2; } - else {RESTWarning << "x is out of split for x = "<< x <RESTendl; index_x=fSplitX.size()-2;} - if (fSplitY.upper_bound(y) != fSplitY.end()){ - index_y = std::distance(fSplitY.begin(), fSplitY.upper_bound(y)) -1; - if (index_y < 0){ - RESTWarning << "index_y < 0 for y = "<< y <<" and fSplitY[0]="<<*fSplitY.begin()<RESTendl; - index_y=0; + if (fSplitY.upper_bound(y) != fSplitY.end()) { + index_y = std::distance(fSplitY.begin(), fSplitY.upper_bound(y)) - 1; + if (index_y < 0) { + RESTWarning << "index_y < 0 for y = " << y << " and fSplitY[0]=" << *fSplitY.begin() + << p->RESTendl; + index_y = 0; } + } else { + RESTWarning << "y is out of split for y = " << y << p->RESTendl; + index_y = fSplitY.size() - 2; } - else {RESTWarning << "y is out of split for y = "<< y <RESTendl; index_y=fSplitY.size()-2;} - return std::make_pair(index_x,index_y); + return std::make_pair(index_x, index_y); } ///////////////////////////////////////////// -/// \brief Function to get the calibration parameter slope for a +/// \brief Function to get the calibration parameter slope for a /// given x and y position on the detector plane. /// /// \param x A const double that defines the x position on the detector plane. /// \param y A const double that defines the y position on the detector plane. -/// +/// double TRestCalibrationCorrection::Module::GetSlope(const double x, const double y) const { - auto [index_x, index_y] = GetIndexMatrix(x,y); + auto [index_x, index_y] = GetIndexMatrix(x, y); if (fSlope.size() == 0) { RESTError << "Calibration slope matrix is empty. Returning 0" << p->RESTendl; return 0; } - if (index_x > fSlope.size() || index_y > fSlope.at(0).size()){ + if (index_x > fSlope.size() || index_y > fSlope.at(0).size()) { RESTError << "Index out of range. Returning 0" << p->RESTendl; return 0; } @@ -457,20 +456,20 @@ double TRestCalibrationCorrection::Module::GetSlope(const double x, const double } ///////////////////////////////////////////// -/// \brief Function to get the calibration parameter intercept for a +/// \brief Function to get the calibration parameter intercept for a /// given x and y position on the detector plane. /// /// \param x A const double that defines the x position on the detector plane. /// \param y A const double that defines the y position on the detector plane. -/// +/// double TRestCalibrationCorrection::Module::GetIntercept(const double x, const double y) const { - auto [index_x, index_y] = GetIndexMatrix(x,y); + auto [index_x, index_y] = GetIndexMatrix(x, y); if (fIntercept.size() == 0) { RESTError << "Calibration constant matrix is empty. Returning 0" << p->RESTendl; return 0; } - if (index_x > fIntercept.size() || index_y > fIntercept.at(0).size()){ + if (index_x > fIntercept.size() || index_y > fIntercept.at(0).size()) { RESTError << "Index out of range. Returning 0" << p->RESTendl; return 0; } @@ -478,7 +477,6 @@ double TRestCalibrationCorrection::Module::GetIntercept(const double x, const do return fIntercept[index_x][index_y]; } - ///////////////////////////////////////////// /// \brief Function to set the class members for segmentation of /// the detector plane along the X and Y axis. @@ -494,8 +492,9 @@ void TRestCalibrationCorrection::Module::SetSplits() { /// edges of the segments. void TRestCalibrationCorrection::Module::SetSplitX() { fSplitX.clear(); - for ( int i=0; i<=fNumberOfSegmentsX; i++) { // <= so that the last segment is included - double x = fReadoutRange.X()+ ( (fReadoutRange.Y()-fReadoutRange.X())/(float)fNumberOfSegmentsX )*i; + for (int i = 0; i <= fNumberOfSegmentsX; i++) { // <= so that the last segment is included + double x = + fReadoutRange.X() + ((fReadoutRange.Y() - fReadoutRange.X()) / (float)fNumberOfSegmentsX) * i; fSplitX.insert(x); } } @@ -507,8 +506,9 @@ void TRestCalibrationCorrection::Module::SetSplitX() { /// edges of the segments. void TRestCalibrationCorrection::Module::SetSplitY() { fSplitY.clear(); - for ( int i=0; i<=fNumberOfSegmentsY; i++) { // <= so that the last segment is included - double y = fReadoutRange.X()+ ( (fReadoutRange.Y()-fReadoutRange.X())/(float)fNumberOfSegmentsY )*i; + for (int i = 0; i <= fNumberOfSegmentsY; i++) { // <= so that the last segment is included + double y = + fReadoutRange.X() + ((fReadoutRange.Y() - fReadoutRange.X()) / (float)fNumberOfSegmentsY) * i; fSplitY.insert(y); } } @@ -520,10 +520,10 @@ void TRestCalibrationCorrection::Module::SetSplitY() { /// It uses the data of the observable fObservable from the TRestDataSet /// at fDataSetFileName (or fCalibrationFileName if first is empty). /// The segmentation is given by the splits. -/// +/// /// Fitting ranges logic is as follows: /// 1. If fRangePeaks is defined and fAutoRangePeaks is false: fRangePeaks is used. -/// 2. If fRangePeaks is defined and fAutoRangePeaks is true: the fitting range +/// 2. If fRangePeaks is defined and fAutoRangePeaks is true: the fitting range /// is calculated by the peak position found by TSpectrum inside fRangePeaks. /// 3. If fRangePeaks is not defined and fAutoRangePeaks is false: use the peak position found by TSpectrum /// and the peak position of the next peak to define the range. @@ -531,189 +531,194 @@ void TRestCalibrationCorrection::Module::SetSplitY() { /// void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { //--- Initial checks and settings --- - if ( fDataSetFileName.empty() ) fDataSetFileName = p->GetCalibrationFileName(); - if ( fDataSetFileName.empty() ) { + if (fDataSetFileName.empty()) fDataSetFileName = p->GetCalibrationFileName(); + if (fDataSetFileName.empty()) { RESTError << "No calibration file defined" << p->RESTendl; return; } - if ( !TRestTools::isDataSet(fDataSetFileName) ) + if (!TRestTools::isDataSet(fDataSetFileName)) RESTWarning << fDataSetFileName << " is not a dataset." << p->RESTendl; TRestDataSet dataSet; dataSet.Import(fDataSetFileName); SetSplits(); //--- Get the calibration range if not provided (default is 0,0) --- - if ( fCalibRange.X() >= fCalibRange.Y() ){ - //Get spectrum for this file - std::string cut = fDefinitionCut; - if ( cut.empty()) cut = "1"; - auto histo = dataSet.GetDataFrame().Filter(cut).Histo1D( - {"temp", "", fNBins, 0, 0}, GetObservable()); - std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); - double xMin = hpunt->GetXaxis()->GetXmin(); - double xMax = hpunt->GetXaxis()->GetXmax(); - - // Reduce the range to avoid the possible empty (nCounts<1%) end part of the spectrum - double fraction=1, nAtEndSpc=0, step=0.66; - while (nAtEndSpc*1. / hpunt->Integral() < 0.01 && fraction>0.001){ - fraction *= step; - nAtEndSpc = hpunt->Integral( hpunt->FindFixBin(hpunt->GetXaxis()->GetXmax()*fraction), - hpunt->FindFixBin(hpunt->GetXaxis()->GetXmax()) ); - } - xMax = hpunt->GetXaxis()->GetXmax() * fraction/step; //previous step is the last one that nAtEndSpc<1% - //Set the calibration range if needed - //fCalibRange.SetX(xMin); - fCalibRange.SetY(xMax); - hpunt.reset(); //delete hpunt; - RESTDebug << "Calibration range (auto)set to (" << fCalibRange.X() << "," << fCalibRange.Y() << ")" << p->RESTendl; - } - + if (fCalibRange.X() >= fCalibRange.Y()) { + // Get spectrum for this file + std::string cut = fDefinitionCut; + if (cut.empty()) cut = "1"; + auto histo = dataSet.GetDataFrame().Filter(cut).Histo1D({"temp", "", fNBins, 0, 0}, GetObservable()); + std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); + double xMin = hpunt->GetXaxis()->GetXmin(); + double xMax = hpunt->GetXaxis()->GetXmax(); + + // Reduce the range to avoid the possible empty (nCounts<1%) end part of the spectrum + double fraction = 1, nAtEndSpc = 0, step = 0.66; + while (nAtEndSpc * 1. / hpunt->Integral() < 0.01 && fraction > 0.001) { + fraction *= step; + nAtEndSpc = hpunt->Integral(hpunt->FindFixBin(hpunt->GetXaxis()->GetXmax() * fraction), + hpunt->FindFixBin(hpunt->GetXaxis()->GetXmax())); + } + xMax = hpunt->GetXaxis()->GetXmax() * fraction / + step; // previous step is the last one that nAtEndSpc<1% + // Set the calibration range if needed + // fCalibRange.SetX(xMin); + fCalibRange.SetY(xMax); + hpunt.reset(); // delete hpunt; + RESTDebug << "Calibration range (auto)set to (" << fCalibRange.X() << "," << fCalibRange.Y() << ")" + << p->RESTendl; + } + //--- Definition of histogram matrix --- - std::vector< std::vector > h - (fNumberOfSegmentsX, std::vector(fNumberOfSegmentsY, nullptr)); - for ( int i=0; i> h(fNumberOfSegmentsX, std::vector(fNumberOfSegmentsY, nullptr)); + for (int i = 0; i < h.size(); i++) { + for (int j = 0; j < h.at(0).size(); j++) { + h[i][j] = new TH1F("", "", fNBins, fCalibRange.X(), + fCalibRange.Y()); // h[column][row] equivalent to h[x][y] + } } - std::vector > calParSlope - (fNumberOfSegmentsX, std::vector (fNumberOfSegmentsY,0)); - std::vector > calParIntercept - (fNumberOfSegmentsX, std::vector (fNumberOfSegmentsY,0)); - + std::vector> calParSlope(fNumberOfSegmentsX, + std::vector(fNumberOfSegmentsY, 0)); + std::vector> calParIntercept(fNumberOfSegmentsX, + std::vector(fNumberOfSegmentsY, 0)); + // build the spectrum for each segment auto itX = fSplitX.begin(); - for ( int i=0; i=" + std::to_string(xLower) + "&&" - + GetSpatialObservableX() +"<" + std::to_string(xUpper); - if ( !GetSpatialObservableY().empty()) - segment_cut += "&&"+GetSpatialObservableY()+">=" + std::to_string(yLower) + "&&" - + GetSpatialObservableY() +"<" + std::to_string(yUpper); - if ( !fDefinitionCut.empty()) - segment_cut += "&&"+fDefinitionCut; - if ( segment_cut.empty() ) segment_cut = "1"; - RESTExtreme << "Segment["<RESTendl; - auto histo = dataSet.GetDataFrame().Filter(segment_cut).Histo1D( - {"temp", "", h[i][j]->GetNbinsX(), - h[i][j]->GetXaxis()->GetXmin(), - h[i][j]->GetXaxis()->GetXmax()}, - GetObservable()); - std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); - h[i][j] -> Add(hpunt.get()); - hpunt.reset(); //delete hpunt; + if (!GetSpatialObservableX().empty()) + segment_cut += GetSpatialObservableX() + ">=" + std::to_string(xLower) + "&&" + + GetSpatialObservableX() + "<" + std::to_string(xUpper); + if (!GetSpatialObservableY().empty()) + segment_cut += "&&" + GetSpatialObservableY() + ">=" + std::to_string(yLower) + "&&" + + GetSpatialObservableY() + "<" + std::to_string(yUpper); + if (!fDefinitionCut.empty()) segment_cut += "&&" + fDefinitionCut; + if (segment_cut.empty()) segment_cut = "1"; + RESTExtreme << "Segment[" << i << "][" << j << "] cut: " << segment_cut << p->RESTendl; + auto histo = dataSet.GetDataFrame() + .Filter(segment_cut) + .Histo1D({"temp", "", h[i][j]->GetNbinsX(), h[i][j]->GetXaxis()->GetXmin(), + h[i][j]->GetXaxis()->GetXmax()}, + GetObservable()); + std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); + h[i][j]->Add(hpunt.get()); + hpunt.reset(); // delete hpunt; itY++; } itX++; } - + //--- Fit every peak energy for every segment --- fSegLinearFit = std::vector(h.size(), std::vector(h.at(0).size(), nullptr)); - for ( int i=0; iRESTendl; - //Search for peaks --> peakPos - std::unique_ptr s (new TSpectrum(2*fEnergyPeaks.size()+1)); - std::vector peakPos; + for (int i = 0; i < h.size(); i++) { + for (int j = 0; j < h.at(0).size(); j++) { + RESTExtreme << "Segment[" << i << "][" << j << "]" << p->RESTendl; + // Search for peaks --> peakPos + std::unique_ptr s(new TSpectrum(2 * fEnergyPeaks.size() + 1)); + std::vector peakPos; s->Search(h[i][j], 2, "goff", 0.1); - for (int k=0; kGetNPeaks(); k++) peakPos.push_back(s->GetPositionX()[k]); + for (int k = 0; k < s->GetNPeaks(); k++) peakPos.push_back(s->GetPositionX()[k]); std::sort(peakPos.begin(), peakPos.end(), std::greater()); - const double ratio = peakPos.size()==0 ? - 1 : peakPos.front() / fEnergyPeaks.front(); //to estimate peak position + const double ratio = peakPos.size() == 0 + ? 1 + : peakPos.front() / fEnergyPeaks.front(); // to estimate peak position - //Initialize graph for linear fit + // Initialize graph for linear fit std::shared_ptr gr; gr = std::shared_ptr(new TGraph()); gr->SetName("grFit"); - gr->SetTitle((";"+GetObservable()+";energy").c_str()); - - //Fit every energy peak - int c = 0; double mu = 0; + gr->SetTitle((";" + GetObservable() + ";energy").c_str()); + + // Fit every energy peak + int c = 0; + double mu = 0; for (const auto& energy : fEnergyPeaks) { RESTExtreme << "\t fitting energy " << DoubleToString(energy, "%g") << p->RESTendl; // estimation of the peak position is between start and end double pos = energy * ratio; double start = pos * 0.8; double end = pos * 1.2; - if (fRangePeaks.at(c).X() < fRangePeaks.at(c).Y()){ //if range is defined use it + if (fRangePeaks.at(c).X() < fRangePeaks.at(c).Y()) { // if range is defined use it start = fRangePeaks.at(c).X(); end = fRangePeaks.at(c).Y(); } do { - if (fAutoRangePeaks){ - if ( peakPos.size() > 0 ) { - //Find the peak position that is between start and end - pos = peakPos.at(0); - while ( !(start < pos && pos < end) ){ + if (fAutoRangePeaks) { + if (peakPos.size() > 0) { + // Find the peak position that is between start and end + pos = peakPos.at(0); + while (!(start < pos && pos < end)) { // if none of the peak position is - // between start and end, use the greater one. - if (pos == peakPos.back()){ - pos = peakPos.at(0); + // between start and end, use the greater one. + if (pos == peakPos.back()) { + pos = peakPos.at(0); break; } - pos = *std::next(std::find(peakPos.begin(),peakPos.end(), pos)); //get next peak position + pos = *std::next(std::find(peakPos.begin(), peakPos.end(), + pos)); // get next peak position } - peakPos.erase(std::find(peakPos.begin(),peakPos.end(), pos)); //remove this peak position from the list - // final estimation of the peak range (idem fitting window) with this peak position pos + peakPos.erase(std::find(peakPos.begin(), peakPos.end(), + pos)); // remove this peak position from the list + // final estimation of the peak range (idem fitting window) with this peak + // position pos start = pos * 0.8; end = pos * 1.2; - const double relDist = peakPos.size()>0 ? (pos-peakPos.front())/pos : 999; - if (relDist < 0.2) { //if the next peak is too close reduce the window width - start = pos * (1-relDist/2); - end = pos * (1+relDist/2); + const double relDist = peakPos.size() > 0 ? (pos - peakPos.front()) / pos : 999; + if (relDist < 0.2) { // if the next peak is too close reduce the window width + start = pos * (1 - relDist / 2); + end = pos * (1 + relDist / 2); } } } - + std::string name = "g" + std::to_string(c); TF1* g = new TF1(name.c_str(), "gaus", start, end); - RESTExtreme << "\t\tat " << DoubleToString(pos, "%.3g") - << ". Range(" << DoubleToString(start, "%.3g") << ", " - << DoubleToString(end, "%.3g") << ")"<< p->RESTendl; + RESTExtreme << "\t\tat " << DoubleToString(pos, "%.3g") << ". Range(" + << DoubleToString(start, "%.3g") << ", " << DoubleToString(end, "%.3g") << ")" + << p->RESTendl; - if ( h[i][j]->GetFunction(name.c_str()) ) //remove previous fit + if (h[i][j]->GetFunction(name.c_str())) // remove previous fit h[i][j]->GetListOfFunctions()->Remove(h[i][j]->GetFunction(name.c_str())); - h[i][j]->Fit(g,"R+Q0"); //use 0 to not draw the fit but save it + h[i][j]->Fit(g, "R+Q0"); // use 0 to not draw the fit but save it mu = g->GetParameter(1); RESTExtreme << "\t\tgaus mean " << DoubleToString(mu, "%g") << p->RESTendl; - } while (fAutoRangePeaks && peakPos.size()>0 && - !(start 0 && + !(start < mu && mu < end)); // avoid small peaks on main peak tail gr->SetPoint(c++, mu, energy); } - s.reset(); //delete s; + s.reset(); // delete s; - if (fZeroPoint) - gr->SetPoint(c++, 0, 0); - while (gr->GetN()<2){ //minimun 2 points needed for linear fit + if (fZeroPoint) gr->SetPoint(c++, 0, 0); + while (gr->GetN() < 2) { // minimun 2 points needed for linear fit gr->SetPoint(c++, 0, 0); SetZeroPoint(true); - RESTDebug << "Not enough points for linear fit. Adding and setting zero point to true" << p->RESTendl; + RESTDebug << "Not enough points for linear fit. Adding and setting zero point to true" + << p->RESTendl; } - //Linear fit + // Linear fit std::unique_ptr linearFit; linearFit = std::unique_ptr(new TF1("linearFit", "pol1")); - gr->Fit("linearFit", "SQ"); //Q for quiet mode + gr->Fit("linearFit", "SQ"); // Q for quiet mode + + fSegLinearFit.at(i).at(j) = (TGraph*)gr->Clone(); - fSegLinearFit.at(i).at(j) = (TGraph*) gr->Clone(); - const double slope = linearFit->GetParameter(1); const double intercept = linearFit->GetParameter(0); calParSlope.at(i).at(j) = slope; calParIntercept.at(i).at(j) = intercept; - } + } } fSegSpectra = h; fSlope = calParSlope; @@ -728,13 +733,14 @@ void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { /// \param energyPeak The energy of the peak to be fitted (in physical units). /// \param range The range for the fitting of the peak (in the observables corresponding units). /// -void TRestCalibrationCorrection::Module::Refit(const double x, const double y, - const double energyPeak, const TVector2& range) { - auto [index_x, index_y] = GetIndexMatrix(x,y); +void TRestCalibrationCorrection::Module::Refit(const double x, const double y, const double energyPeak, + const TVector2& range) { + auto [index_x, index_y] = GetIndexMatrix(x, y); int peakNumber = -1; - for (int i=0; iRESTendl; @@ -751,28 +757,28 @@ void TRestCalibrationCorrection::Module::Refit(const double x, const double y, /// \param peakNumber The index of the peak to be fitted. /// \param range The range for the fitting of the peak (in the observables corresponding units). /// -void TRestCalibrationCorrection::Module::Refit(const int x, const int y, - const int peakNumber,const TVector2& range){ - //Refit the desired peak +void TRestCalibrationCorrection::Module::Refit(const int x, const int y, const int peakNumber, + const TVector2& range) { + // Refit the desired peak std::string name = "g" + std::to_string(peakNumber); TF1* g = new TF1(name.c_str(), "gaus", range.X(), range.Y()); TH1F* h = fSegSpectra.at(x).at(y); - if ( h->GetFunction(name.c_str()) ) //remove previous fit + if (h->GetFunction(name.c_str())) // remove previous fit h->GetListOfFunctions()->Remove(h->GetFunction(name.c_str())); - h->Fit(g,"R+Q0"); //use 0 to not draw the fit but save it + h->Fit(g, "R+Q0"); // use 0 to not draw the fit but save it const double mu = g->GetParameter(1); - //Change the point of the graph + // Change the point of the graph TGraph* gr = fSegLinearFit.at(x).at(y); gr->SetPoint(peakNumber, mu, fEnergyPeaks.at(peakNumber)); - //Refit the calibration curve + // Refit the calibration curve TF1* lf = nullptr; - if ( gr->GetFunction("linearFit") ) + if (gr->GetFunction("linearFit")) lf = gr->GetFunction("linearFit"); else lf = new TF1("linearFit", "pol1"); - gr->Fit(lf, "SQ"); //Q for quiet mode + gr->Fit(lf, "SQ"); // Q for quiet mode fSlope.at(x).at(y) = lf->GetParameter(1); fIntercept.at(x).at(y) = lf->GetParameter(0); } @@ -781,7 +787,7 @@ void TRestCalibrationCorrection::Module::Refit(const int x, const int y, /// \brief Function to read the parameters from the RML element (TiXmlElement) /// and set those class members. /// -/// \param module A const TiXmlElement pointer that contains the information. +/// \param module A const TiXmlElement pointer that contains the information. /// Example of this RML element: /// /// /// -/// -void TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement(const TiXmlElement* module){ - +/// +void TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement(const TiXmlElement* module) { if (module == nullptr) { - RESTError << "TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement: module is nullptr" << p->RESTendl; + RESTError << "TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement: module is nullptr" + << p->RESTendl; return; } - std::string el = !module->Attribute("planeId") ? "Not defined" : module->Attribute("planeId") ; - if ( !(el.empty() || el == "Not defined")) - this->SetPlaneId(StringToInteger(el)); - el = !module->Attribute("moduleId") ? "Not defined" : module->Attribute("moduleId") ; - if ( !(el.empty() || el == "Not defined")) - this->SetModuleId(StringToInteger(el)); - - el = !module->Attribute("moduleDefinitionCut") ? "Not defined" : module->Attribute("moduleDefinitionCut") ; - if ( !(el.empty() || el == "Not defined")) - this->SetModuleDefinitionCut(el); - - el = !module->Attribute("numberOfSegmentsX") ? "Not defined" : module->Attribute("numberOfSegmentsX") ; - if ( !(el.empty() || el == "Not defined")) - this->SetNumberOfSegmentsX(StringToInteger(el)); - el = !module->Attribute("numberOfSegmentsY") ? "Not defined" : module->Attribute("numberOfSegmentsY") ; - if ( !(el.empty() || el == "Not defined")) - this->SetNumberOfSegmentsY(StringToInteger(el)); - el = !module->Attribute("readoutRange") ? "Not defined" : module->Attribute("readoutRange") ; - if ( !(el.empty() || el == "Not defined")) - this->SetReadoutRange(StringTo2DVector(el)); - - el = !module->Attribute("calibRange") ? "Not defined" : module->Attribute("calibRange") ; - if ( !(el.empty() || el == "Not defined")) - this->SetCalibrationRange(StringTo2DVector(el)); - el = !module->Attribute("nBins") ? "Not defined" : module->Attribute("nBins") ; - if ( !(el.empty() || el == "Not defined")) - this->SetNBins(StringToInteger(el)); - - el = !module->Attribute("dataSetFileName") ? "Not defined" : module->Attribute("dataSetFileName") ; - if ( !(el.empty() || el == "Not defined")) - this->SetDataSetFileName(el); - - el = !module->Attribute("zeroPoint") ? "Not defined" : module->Attribute("zeroPoint") ; - if ( !(el.empty() || el == "Not defined")) - this->SetZeroPoint(ToLower(el)=="true"); - el = !module->Attribute("autoRangePeaks") ? "Not defined" : module->Attribute("autoRangePeaks") ; - if ( !(el.empty() || el == "Not defined")) - this->SetAutoRangePeaks(ToLower(el)=="true"); - - //Get peaks energy and range - TiXmlElement* peakDefinition = (TiXmlElement*) module->FirstChildElement("peak"); + std::string el = !module->Attribute("planeId") ? "Not defined" : module->Attribute("planeId"); + if (!(el.empty() || el == "Not defined")) this->SetPlaneId(StringToInteger(el)); + el = !module->Attribute("moduleId") ? "Not defined" : module->Attribute("moduleId"); + if (!(el.empty() || el == "Not defined")) this->SetModuleId(StringToInteger(el)); + + el = !module->Attribute("moduleDefinitionCut") ? "Not defined" : module->Attribute("moduleDefinitionCut"); + if (!(el.empty() || el == "Not defined")) this->SetModuleDefinitionCut(el); + + el = !module->Attribute("numberOfSegmentsX") ? "Not defined" : module->Attribute("numberOfSegmentsX"); + if (!(el.empty() || el == "Not defined")) this->SetNumberOfSegmentsX(StringToInteger(el)); + el = !module->Attribute("numberOfSegmentsY") ? "Not defined" : module->Attribute("numberOfSegmentsY"); + if (!(el.empty() || el == "Not defined")) this->SetNumberOfSegmentsY(StringToInteger(el)); + el = !module->Attribute("readoutRange") ? "Not defined" : module->Attribute("readoutRange"); + if (!(el.empty() || el == "Not defined")) this->SetReadoutRange(StringTo2DVector(el)); + + el = !module->Attribute("calibRange") ? "Not defined" : module->Attribute("calibRange"); + if (!(el.empty() || el == "Not defined")) this->SetCalibrationRange(StringTo2DVector(el)); + el = !module->Attribute("nBins") ? "Not defined" : module->Attribute("nBins"); + if (!(el.empty() || el == "Not defined")) this->SetNBins(StringToInteger(el)); + + el = !module->Attribute("dataSetFileName") ? "Not defined" : module->Attribute("dataSetFileName"); + if (!(el.empty() || el == "Not defined")) this->SetDataSetFileName(el); + + el = !module->Attribute("zeroPoint") ? "Not defined" : module->Attribute("zeroPoint"); + if (!(el.empty() || el == "Not defined")) this->SetZeroPoint(ToLower(el) == "true"); + el = !module->Attribute("autoRangePeaks") ? "Not defined" : module->Attribute("autoRangePeaks"); + if (!(el.empty() || el == "Not defined")) this->SetAutoRangePeaks(ToLower(el) == "true"); + + // Get peaks energy and range + TiXmlElement* peakDefinition = (TiXmlElement*)module->FirstChildElement("peak"); while (peakDefinition != nullptr) { double energy = 0; - TVector2 range = TVector2(0,0); + TVector2 range = TVector2(0, 0); - std::string ell = !peakDefinition->Attribute("energy") ? "Not defined" : peakDefinition->Attribute("energy") ; + std::string ell = + !peakDefinition->Attribute("energy") ? "Not defined" : peakDefinition->Attribute("energy"); if (ell.empty() || ell == "Not defined") { RESTError << "< peak variable key does not contain energy!" << p->RESTendl; exit(1); } energy = StringToDouble(ell); - ell = !peakDefinition->Attribute("range") ? "Not defined" : peakDefinition->Attribute("range") ; - if ( !(ell.empty() || ell == "Not defined")) - range = StringTo2DVector(ell); - - this->AddPeak(energy,range); - peakDefinition = (TiXmlElement*) peakDefinition->NextSiblingElement(); + ell = !peakDefinition->Attribute("range") ? "Not defined" : peakDefinition->Attribute("range"); + if (!(ell.empty() || ell == "Not defined")) range = StringTo2DVector(ell); + + this->AddPeak(energy, range); + peakDefinition = (TiXmlElement*)peakDefinition->NextSiblingElement(); } } - -void TRestCalibrationCorrection::Module::DrawSpectrum(const double x, const double y, TCanvas *c){ - auto [index_x, index_y] = GetIndexMatrix(x,y); +void TRestCalibrationCorrection::Module::DrawSpectrum(const double x, const double y, TCanvas* c) { + auto [index_x, index_y] = GetIndexMatrix(x, y); DrawSpectrum(index_x, index_y, c); } -void TRestCalibrationCorrection::Module::DrawSpectrum(const int index_x, const int index_y, TCanvas *c){ +void TRestCalibrationCorrection::Module::DrawSpectrum(const int index_x, const int index_y, TCanvas* c) { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; } - if (index_x >= fSegSpectra.size() || index_y >= fSegSpectra.at(index_x).size()){ + if (index_x >= fSegSpectra.size() || index_y >= fSegSpectra.at(index_x).size()) { RESTError << "Index out of range." << p->RESTendl; return; } - if ( !c ){ - std::string t = "spectrum_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId) - +"_"+std::to_string(index_x)+"_"+std::to_string(index_y); - c = new TCanvas(t.c_str(),t.c_str()); + if (!c) { + std::string t = "spectrum_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId) + "_" + + std::to_string(index_x) + "_" + std::to_string(index_y); + c = new TCanvas(t.c_str(), t.c_str()); } auto xLower = *std::next(fSplitX.begin(), index_x); - auto xUpper = *std::next(fSplitX.begin(), index_x+1); + auto xUpper = *std::next(fSplitX.begin(), index_x + 1); auto yLower = *std::next(fSplitY.begin(), index_y); - auto yUpper = *std::next(fSplitY.begin(), index_y+1); - std::string tH = "Spectrum x=["+DoubleToString(xLower,"%g")+", "+DoubleToString(xUpper,"%g") - +") y=["+DoubleToString(yLower,"%g")+", "+DoubleToString(yUpper,"%g")+");" - +GetObservable()+";counts"; + auto yUpper = *std::next(fSplitY.begin(), index_y + 1); + std::string tH = "Spectrum x=[" + DoubleToString(xLower, "%g") + ", " + DoubleToString(xUpper, "%g") + + ") y=[" + DoubleToString(yLower, "%g") + ", " + DoubleToString(yUpper, "%g") + ");" + + GetObservable() + ";counts"; fSegSpectra[index_x][index_y]->SetTitle(tH.c_str()); fSegSpectra[index_x][index_y]->Draw(); - for (int c=0; cGetFunction(("g"+std::to_string(c)).c_str()); - if (!fit) RESTError << "Fit for energy peak" << fEnergyPeaks[c] <<" not found." << p->RESTendl; + for (int c = 0; c < fEnergyPeaks.size(); c++) { + auto fit = fSegSpectra[index_x][index_y]->GetFunction(("g" + std::to_string(c)).c_str()); + if (!fit) RESTError << "Fit for energy peak" << fEnergyPeaks[c] << " not found." << p->RESTendl; if (!fit) break; - fit->SetLineColor(c+2); //skipe 0 which is white and 1 which is blue as histogram + fit->SetLineColor(c + 2); // skipe 0 which is white and 1 which is blue as histogram fit->Draw("same"); } - } -void TRestCalibrationCorrection::Module::DrawSpectrum(){ +void TRestCalibrationCorrection::Module::DrawSpectrum() { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; } - std::string t = "spectra_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); - TCanvas *myCanvas = new TCanvas(t.c_str(),t.c_str()); - myCanvas->Divide(fSegSpectra.size(),fSegSpectra.at(0).size()); - for (int i=0; icd(i+1+fSegSpectra[i].size()*j); - DrawSpectrum(i, fSegSpectra[i].size()-1-j, myCanvas); + std::string t = "spectra_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); + TCanvas* myCanvas = new TCanvas(t.c_str(), t.c_str()); + myCanvas->Divide(fSegSpectra.size(), fSegSpectra.at(0).size()); + for (int i = 0; i < fSegSpectra.size(); i++) { + for (int j = 0; j < fSegSpectra[i].size(); j++) { + myCanvas->cd(i + 1 + fSegSpectra[i].size() * j); + DrawSpectrum(i, fSegSpectra[i].size() - 1 - j, myCanvas); } } } -void TRestCalibrationCorrection::Module::DrawFullSpectrum(){ +void TRestCalibrationCorrection::Module::DrawFullSpectrum() { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; } - TH1F* sumHist = new TH1F("fullSpc", "", fSegSpectra[0][0]->GetNbinsX(), - fSegSpectra[0][0]->GetXaxis()->GetXmin(), - fSegSpectra[0][0]->GetXaxis()->GetXmax() ); - - sumHist->SetTitle(("Full spectrum;"+GetObservable()+";counts").c_str()); - for (int i=0; iGetNbinsX(), fSegSpectra[0][0]->GetXaxis()->GetXmin(), + fSegSpectra[0][0]->GetXaxis()->GetXmax()); + + sumHist->SetTitle(("Full spectrum;" + GetObservable() + ";counts").c_str()); + for (int i = 0; i < fSegSpectra.size(); i++) { + for (int j = 0; j < fSegSpectra.at(0).size(); j++) { sumHist->Add(fSegSpectra[i][j]); } } - std::string t = "fullSpc_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); - TCanvas *c = new TCanvas(t.c_str(),t.c_str()); + std::string t = "fullSpc_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); + TCanvas* c = new TCanvas(t.c_str(), t.c_str()); sumHist->Draw(); } - -void TRestCalibrationCorrection::Module::DrawLinearFit(const double x, const double y, TCanvas *c){ - auto [index_x, index_y] = GetIndexMatrix(x,y); +void TRestCalibrationCorrection::Module::DrawLinearFit(const double x, const double y, TCanvas* c) { + auto [index_x, index_y] = GetIndexMatrix(x, y); DrawLinearFit(index_x, index_y, c); } -void TRestCalibrationCorrection::Module::DrawLinearFit(const int index_x, const int index_y, TCanvas *c){ +void TRestCalibrationCorrection::Module::DrawLinearFit(const int index_x, const int index_y, TCanvas* c) { if (fSegLinearFit.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; } - if (index_x >= fSegLinearFit.size() || index_y >= fSegLinearFit.at(index_x).size()){ + if (index_x >= fSegLinearFit.size() || index_y >= fSegLinearFit.at(index_x).size()) { RESTError << "Index out of range." << p->RESTendl; return; } - if ( !c ){ - std::string t = "linearFit_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId) - +"_"+std::to_string(index_x)+"_"+std::to_string(index_y); - c = new TCanvas(t.c_str(),t.c_str()); + if (!c) { + std::string t = "linearFit_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId) + "_" + + std::to_string(index_x) + "_" + std::to_string(index_y); + c = new TCanvas(t.c_str(), t.c_str()); } auto xLower = *std::next(fSplitX.begin(), index_x); - auto xUpper = *std::next(fSplitX.begin(), index_x+1); + auto xUpper = *std::next(fSplitX.begin(), index_x + 1); auto yLower = *std::next(fSplitY.begin(), index_y); - auto yUpper = *std::next(fSplitY.begin(), index_y+1); - std::string tH = "Linear Fit x=["+DoubleToString(xLower,"%g")+", "+DoubleToString(xUpper,"%g") - +") y=["+DoubleToString(yLower,"%g")+", "+DoubleToString(yUpper,"%g")+");" - +GetObservable()+";energy"; + auto yUpper = *std::next(fSplitY.begin(), index_y + 1); + std::string tH = "Linear Fit x=[" + DoubleToString(xLower, "%g") + ", " + DoubleToString(xUpper, "%g") + + ") y=[" + DoubleToString(yLower, "%g") + ", " + DoubleToString(yUpper, "%g") + ");" + + GetObservable() + ";energy"; fSegLinearFit[index_x][index_y]->SetTitle(tH.c_str()); fSegLinearFit[index_x][index_y]->Draw("AL*"); } -void TRestCalibrationCorrection::Module::DrawLinearFit(){ - std::string t = "linearFits_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); - TCanvas *myCanvas = new TCanvas(t.c_str(),t.c_str()); - myCanvas->Divide(fSegLinearFit.size(),fSegLinearFit.at(0).size()); - for (int i=0; icd(i+1+fSegLinearFit[i].size()*j); - //fSegLinearFit[i][j]->Draw("AL*"); - DrawLinearFit(i, fSegSpectra[i].size()-1-j, myCanvas); +void TRestCalibrationCorrection::Module::DrawLinearFit() { + std::string t = "linearFits_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); + TCanvas* myCanvas = new TCanvas(t.c_str(), t.c_str()); + myCanvas->Divide(fSegLinearFit.size(), fSegLinearFit.at(0).size()); + for (int i = 0; i < fSegLinearFit.size(); i++) { + for (int j = 0; j < fSegLinearFit[i].size(); j++) { + myCanvas->cd(i + 1 + fSegLinearFit[i].size() * j); + // fSegLinearFit[i][j]->Draw("AL*"); + DrawLinearFit(i, fSegSpectra[i].size() - 1 - j, myCanvas); } } } -void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber){ - if (peakNumber < 0 || peakNumber >= fEnergyPeaks.size()){ - RESTError << "Peak number out of range (peakNumber should be between 0 and " - << fEnergyPeaks.size()-1 << " )" << p->RESTendl; +void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber) { + if (peakNumber < 0 || peakNumber >= fEnergyPeaks.size()) { + RESTError << "Peak number out of range (peakNumber should be between 0 and " + << fEnergyPeaks.size() - 1 << " )" << p->RESTendl; return; } double peakEnergy = fEnergyPeaks[peakNumber]; - std::string title = "Gain map for energy " + DoubleToString(peakEnergy,"%g") - +";"+GetSpatialObservableX()+";"+GetSpatialObservableY();// + " keV"; - std::string t = "gainMap"+std::to_string(peakNumber) - +"_"+std::to_string(fPlaneId)+"_"+std::to_string(fModuleId); - TCanvas *gainMap = new TCanvas(t.c_str(),t.c_str()); - TH2F *hGainMap= new TH2F(("h"+t).c_str(), title.c_str(), - fNumberOfSegmentsY, fReadoutRange.X(), fReadoutRange.Y(), - fNumberOfSegmentsX, fReadoutRange.X(), fReadoutRange.Y()); - - const double peakPosRef = - fSegLinearFit[(fNumberOfSegmentsX-1)/2][(fNumberOfSegmentsY-1)/2]->GetPointX(peakNumber); - + std::string title = "Gain map for energy " + DoubleToString(peakEnergy, "%g") + ";" + + GetSpatialObservableX() + ";" + GetSpatialObservableY(); // + " keV"; + std::string t = "gainMap" + std::to_string(peakNumber) + "_" + std::to_string(fPlaneId) + "_" + + std::to_string(fModuleId); + TCanvas* gainMap = new TCanvas(t.c_str(), t.c_str()); + TH2F* hGainMap = new TH2F(("h" + t).c_str(), title.c_str(), fNumberOfSegmentsY, fReadoutRange.X(), + fReadoutRange.Y(), fNumberOfSegmentsX, fReadoutRange.X(), fReadoutRange.Y()); + + const double peakPosRef = + fSegLinearFit[(fNumberOfSegmentsX - 1) / 2][(fNumberOfSegmentsY - 1) / 2]->GetPointX(peakNumber); + auto itX = fSplitX.begin(); - for ( int i=0; iFill((xUpper+xLower)/2., (yUpper+yLower)/2., - fSegLinearFit[i][j]->GetPointX(peakNumber)/peakPosRef); + hGainMap->Fill((xUpper + xLower) / 2., (yUpper + yLower) / 2., + fSegLinearFit[i][j]->GetPointX(peakNumber) / peakPosRef); itY++; } itX++; @@ -1034,28 +1025,36 @@ void TRestCalibrationCorrection::Module::Print() { RESTMetadata << " Energy peaks: "; for (const auto& peak : fEnergyPeaks) RESTMetadata << peak << ", "; - RESTMetadata << p->RESTendl; + RESTMetadata << p->RESTendl; bool anyRange = false; - for (const auto& r : fRangePeaks) if (r.X() < r.Y()){RESTMetadata<<" Range peaks: ";anyRange=true;break;} - if (anyRange) for (const auto& r : fRangePeaks) RESTMetadata << "(" << r.X() << ", " << r.Y() << ") "; + for (const auto& r : fRangePeaks) + if (r.X() < r.Y()) { + RESTMetadata << " Range peaks: "; + anyRange = true; + break; + } + if (anyRange) + for (const auto& r : fRangePeaks) RESTMetadata << "(" << r.X() << ", " << r.Y() << ") "; if (anyRange) RESTMetadata << p->RESTendl; RESTMetadata << " Auto range peaks: " << (fAutoRangePeaks ? "true" : "false") << p->RESTendl; RESTMetadata << " Zero point: " << (fZeroPoint ? "true" : "false") << p->RESTendl; - RESTMetadata << " Calibration range: (" << fCalibRange.X() << ", " << fCalibRange.Y() << " )" << p->RESTendl; + RESTMetadata << " Calibration range: (" << fCalibRange.X() << ", " << fCalibRange.Y() << " )" + << p->RESTendl; RESTMetadata << " Number of bins: " << fNBins << p->RESTendl; RESTMetadata << p->RESTendl; RESTMetadata << " Number of segments X: " << fNumberOfSegmentsX << p->RESTendl; RESTMetadata << " Number of segments Y: " << fNumberOfSegmentsY << p->RESTendl; - //RESTMetadata << " Draw: " << (fDrawVar ? "true" : "false") << p->RESTendl; - RESTMetadata << " Readout range (" << fReadoutRange.X() << ", " << fReadoutRange.Y() << " )" << p->RESTendl; + // RESTMetadata << " Draw: " << (fDrawVar ? "true" : "false") << p->RESTendl; + RESTMetadata << " Readout range (" << fReadoutRange.X() << ", " << fReadoutRange.Y() << " )" + << p->RESTendl; RESTMetadata << "SplitX: "; - for (auto &i : fSplitX){ + for (auto& i : fSplitX) { RESTMetadata << " " << i; } RESTMetadata << p->RESTendl; RESTMetadata << "SplitY: "; - for (auto &i : fSplitY){ + for (auto& i : fSplitY) { RESTMetadata << " " << i; } RESTMetadata << p->RESTendl; @@ -1063,28 +1062,29 @@ void TRestCalibrationCorrection::Module::Print() { RESTMetadata << " Slope: " << p->RESTendl; int maxSize = 0; - for (auto& x : fSlope) if (maxSize < x.size()) maxSize = x.size(); + for (auto& x : fSlope) + if (maxSize < x.size()) maxSize = x.size(); for (int j = 0; j < maxSize; j++) { - for (int k = 0; k < fSlope.size(); k++){ + for (int k = 0; k < fSlope.size(); k++) { if (j < fSlope[k].size()) - RESTMetadata << DoubleToString(fSlope[k][fSlope[k].size()-1-j],"%.3e") - << " "; - else RESTMetadata << " "; + RESTMetadata << DoubleToString(fSlope[k][fSlope[k].size() - 1 - j], "%.3e") << " "; + else + RESTMetadata << " "; } RESTMetadata << p->RESTendl; } RESTMetadata << " Intercept: " << p->RESTendl; maxSize = 0; - for (auto& x : fIntercept) if (maxSize < x.size()) maxSize = x.size(); + for (auto& x : fIntercept) + if (maxSize < x.size()) maxSize = x.size(); for (int j = 0; j < maxSize; j++) { - for (int k = 0; k < fIntercept.size(); k++){ + for (int k = 0; k < fIntercept.size(); k++) { if (j < fIntercept[k].size()) - RESTMetadata << DoubleToString(fIntercept[k][fIntercept[k].size()-1-j],"%+.3e") - << " "; - else RESTMetadata << " "; + RESTMetadata << DoubleToString(fIntercept[k][fIntercept[k].size() - 1 - j], "%+.3e") << " "; + else + RESTMetadata << " "; } RESTMetadata << p->RESTendl; } RESTMetadata << "-----------------------------------------------" << p->RESTendl; } - From 3d40d87d4af3f3820f964ea16e823c30e7a6e9c8 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Tue, 26 Sep 2023 12:18:54 +0200 Subject: [PATCH 03/19] Fixing compilation issues with release mode --- .../analysis/inc/TRestCalibrationCorrection.h | 4 +- .../src/TRestCalibrationCorrection.cxx | 82 ++++++++++--------- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/source/framework/analysis/inc/TRestCalibrationCorrection.h b/source/framework/analysis/inc/TRestCalibrationCorrection.h index df78cb230..f829c3850 100644 --- a/source/framework/analysis/inc/TRestCalibrationCorrection.h +++ b/source/framework/analysis/inc/TRestCalibrationCorrection.h @@ -171,12 +171,12 @@ class TRestCalibrationCorrection : public TRestMetadata { void DrawSpectrum(); void DrawSpectrum(const double x, const double y, TCanvas* c = nullptr); - void DrawSpectrum(const int index_x, const int index_y, TCanvas* c = nullptr); + void DrawSpectrum(const size_t index_x, const size_t index_y, TCanvas* c = nullptr); void DrawFullSpectrum(); void DrawLinearFit(); void DrawLinearFit(const double x, const double y, TCanvas* c = nullptr); - void DrawLinearFit(const int index_x, const int index_y, TCanvas* c = nullptr); + void DrawLinearFit(const size_t index_x, const size_t index_y, TCanvas* c = nullptr); void DrawGainMap(const int peakNumber = 0); diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index 5dc39c94a..9a3bf3b5e 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -157,12 +157,12 @@ void TRestCalibrationCorrection::InitFromConfigFile() { /// \brief Function to calculate the calibration parameters of all modules /// void TRestCalibrationCorrection::Calibrate() { - for (auto& i : fModulesCal) { - RESTInfo << "Calibrating plane " << i.GetPlaneId() << " module " << i.GetModuleId() << RESTendl; - i.CalculateCalibrationParameters(); + for (auto& mod : fModulesCal) { + RESTInfo << "Calibrating plane " << mod.GetPlaneId() << " module " << mod.GetModuleId() << RESTendl; + mod.CalculateCalibrationParameters(); if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info) { - i.DrawSpectrum(); - i.DrawGainMap(); + mod.DrawSpectrum(); + mod.DrawGainMap(); } } } @@ -172,7 +172,7 @@ void TRestCalibrationCorrection::Calibrate() { /// void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFileName, std::string outputFileName) { - if (fModulesCal.size() == 0) { + if (fModulesCal.empty()) { RESTError << "TRestCalibrationCorrection::CalibrateDataSet: No modules defined." << RESTendl; return; } @@ -186,7 +186,7 @@ void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFile std::string pmIDname = (std::string)GetName() + "_pmID"; int pmID = fModulesCal[0].GetPlaneId() * 10 + fModulesCal[0].GetModuleId(); dataFrame = dataFrame.Define(pmIDname, modCut + " ? " + std::to_string(pmID) + " : -1"); - for (int n = 1; n < fModulesCal.size(); n++) { + for (size_t n = 1; n < fModulesCal.size(); n++) { modCut = fModulesCal[n].GetModuleDefinitionCut(); pmID = fModulesCal[n].GetPlaneId() * 10 + fModulesCal[n].GetModuleId(); dataFrame = dataFrame.Redefine(pmIDname, (modCut + " ? " + std::to_string(pmID) + " : " + pmIDname)); @@ -442,12 +442,12 @@ std::pair TRestCalibrationCorrection::Module::GetIndexMatrix(const dou /// double TRestCalibrationCorrection::Module::GetSlope(const double x, const double y) const { auto [index_x, index_y] = GetIndexMatrix(x, y); - if (fSlope.size() == 0) { + if (fSlope.empty()) { RESTError << "Calibration slope matrix is empty. Returning 0" << p->RESTendl; return 0; } - if (index_x > fSlope.size() || index_y > fSlope.at(0).size()) { + if (index_x > (int)fSlope.size() || index_y > (int)fSlope.at(0).size()) { RESTError << "Index out of range. Returning 0" << p->RESTendl; return 0; } @@ -464,12 +464,12 @@ double TRestCalibrationCorrection::Module::GetSlope(const double x, const double /// double TRestCalibrationCorrection::Module::GetIntercept(const double x, const double y) const { auto [index_x, index_y] = GetIndexMatrix(x, y); - if (fIntercept.size() == 0) { + if (fIntercept.empty()) { RESTError << "Calibration constant matrix is empty. Returning 0" << p->RESTendl; return 0; } - if (index_x > fIntercept.size() || index_y > fIntercept.at(0).size()) { + if (index_x > (int)fIntercept.size() || index_y > (int)fIntercept.at(0).size()) { RESTError << "Index out of range. Returning 0" << p->RESTendl; return 0; } @@ -550,7 +550,7 @@ void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { if (cut.empty()) cut = "1"; auto histo = dataSet.GetDataFrame().Filter(cut).Histo1D({"temp", "", fNBins, 0, 0}, GetObservable()); std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); - double xMin = hpunt->GetXaxis()->GetXmin(); + //double xMin = hpunt->GetXaxis()->GetXmin(); double xMax = hpunt->GetXaxis()->GetXmax(); // Reduce the range to avoid the possible empty (nCounts<1%) end part of the spectrum @@ -572,8 +572,8 @@ void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { //--- Definition of histogram matrix --- std::vector> h(fNumberOfSegmentsX, std::vector(fNumberOfSegmentsY, nullptr)); - for (int i = 0; i < h.size(); i++) { - for (int j = 0; j < h.at(0).size(); j++) { + for (size_t i = 0; i < h.size(); i++) { + for (size_t j = 0; j < h.at(0).size(); j++) { h[i][j] = new TH1F("", "", fNBins, fCalibRange.X(), fCalibRange.Y()); // h[column][row] equivalent to h[x][y] } @@ -585,9 +585,9 @@ void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { // build the spectrum for each segment auto itX = fSplitX.begin(); - for (int i = 0; i < h.size(); i++) { + for (size_t i = 0; i < h.size(); i++) { auto itY = fSplitY.begin(); - for (int j = 0; j < h.at(0).size(); j++) { + for (size_t j = 0; j < h.at(0).size(); j++) { // Get the segment limits from the splits auto xLower = *itX; auto xUpper = *std::next(itX); @@ -619,8 +619,8 @@ void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { //--- Fit every peak energy for every segment --- fSegLinearFit = std::vector(h.size(), std::vector(h.at(0).size(), nullptr)); - for (int i = 0; i < h.size(); i++) { - for (int j = 0; j < h.at(0).size(); j++) { + for (size_t i = 0; i < h.size(); i++) { + for (size_t j = 0; j < h.at(0).size(); j++) { RESTExtreme << "Segment[" << i << "][" << j << "]" << p->RESTendl; // Search for peaks --> peakPos std::unique_ptr s(new TSpectrum(2 * fEnergyPeaks.size() + 1)); @@ -737,7 +737,7 @@ void TRestCalibrationCorrection::Module::Refit(const double x, const double y, c const TVector2& range) { auto [index_x, index_y] = GetIndexMatrix(x, y); int peakNumber = -1; - for (int i = 0; i < fEnergyPeaks.size(); i++) + for (size_t i = 0; i < fEnergyPeaks.size(); i++) if (fEnergyPeaks.at(i) == energyPeak) { peakNumber = i; break; @@ -855,11 +855,11 @@ void TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement(const TiXmlE } void TRestCalibrationCorrection::Module::DrawSpectrum(const double x, const double y, TCanvas* c) { - auto [index_x, index_y] = GetIndexMatrix(x, y); - DrawSpectrum(index_x, index_y, c); + std::pair index = GetIndexMatrix(x, y); + DrawSpectrum(index.first, index.second, c); } -void TRestCalibrationCorrection::Module::DrawSpectrum(const int index_x, const int index_y, TCanvas* c) { +void TRestCalibrationCorrection::Module::DrawSpectrum(const size_t index_x, const size_t index_y, TCanvas* c) { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -884,7 +884,7 @@ void TRestCalibrationCorrection::Module::DrawSpectrum(const int index_x, const i GetObservable() + ";counts"; fSegSpectra[index_x][index_y]->SetTitle(tH.c_str()); fSegSpectra[index_x][index_y]->Draw(); - for (int c = 0; c < fEnergyPeaks.size(); c++) { + for (size_t c = 0; c < fEnergyPeaks.size(); c++) { auto fit = fSegSpectra[index_x][index_y]->GetFunction(("g" + std::to_string(c)).c_str()); if (!fit) RESTError << "Fit for energy peak" << fEnergyPeaks[c] << " not found." << p->RESTendl; if (!fit) break; @@ -901,8 +901,8 @@ void TRestCalibrationCorrection::Module::DrawSpectrum() { std::string t = "spectra_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); TCanvas* myCanvas = new TCanvas(t.c_str(), t.c_str()); myCanvas->Divide(fSegSpectra.size(), fSegSpectra.at(0).size()); - for (int i = 0; i < fSegSpectra.size(); i++) { - for (int j = 0; j < fSegSpectra[i].size(); j++) { + for (size_t i = 0; i < fSegSpectra.size(); i++) { + for (size_t j = 0; j < fSegSpectra[i].size(); j++) { myCanvas->cd(i + 1 + fSegSpectra[i].size() * j); DrawSpectrum(i, fSegSpectra[i].size() - 1 - j, myCanvas); } @@ -918,22 +918,23 @@ void TRestCalibrationCorrection::Module::DrawFullSpectrum() { fSegSpectra[0][0]->GetXaxis()->GetXmax()); sumHist->SetTitle(("Full spectrum;" + GetObservable() + ";counts").c_str()); - for (int i = 0; i < fSegSpectra.size(); i++) { - for (int j = 0; j < fSegSpectra.at(0).size(); j++) { + for (size_t i = 0; i < fSegSpectra.size(); i++) { + for (size_t j = 0; j < fSegSpectra.at(0).size(); j++) { sumHist->Add(fSegSpectra[i][j]); } } std::string t = "fullSpc_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); TCanvas* c = new TCanvas(t.c_str(), t.c_str()); + c->cd(); sumHist->Draw(); } void TRestCalibrationCorrection::Module::DrawLinearFit(const double x, const double y, TCanvas* c) { - auto [index_x, index_y] = GetIndexMatrix(x, y); - DrawLinearFit(index_x, index_y, c); + std::pair index = GetIndexMatrix(x, y); + DrawLinearFit(index.first, index.second, c); } -void TRestCalibrationCorrection::Module::DrawLinearFit(const int index_x, const int index_y, TCanvas* c) { +void TRestCalibrationCorrection::Module::DrawLinearFit(const size_t index_x, const size_t index_y, TCanvas* c) { if (fSegLinearFit.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -962,8 +963,8 @@ void TRestCalibrationCorrection::Module::DrawLinearFit() { std::string t = "linearFits_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); TCanvas* myCanvas = new TCanvas(t.c_str(), t.c_str()); myCanvas->Divide(fSegLinearFit.size(), fSegLinearFit.at(0).size()); - for (int i = 0; i < fSegLinearFit.size(); i++) { - for (int j = 0; j < fSegLinearFit[i].size(); j++) { + for (size_t i = 0; i < fSegLinearFit.size(); i++) { + for (size_t j = 0; j < fSegLinearFit[i].size(); j++) { myCanvas->cd(i + 1 + fSegLinearFit[i].size() * j); // fSegLinearFit[i][j]->Draw("AL*"); DrawLinearFit(i, fSegSpectra[i].size() - 1 - j, myCanvas); @@ -972,7 +973,7 @@ void TRestCalibrationCorrection::Module::DrawLinearFit() { } void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber) { - if (peakNumber < 0 || peakNumber >= fEnergyPeaks.size()) { + if (peakNumber < 0 || peakNumber >= (int)fEnergyPeaks.size()) { RESTError << "Peak number out of range (peakNumber should be between 0 and " << fEnergyPeaks.size() - 1 << " )" << p->RESTendl; return; @@ -983,6 +984,7 @@ void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber) { std::string t = "gainMap" + std::to_string(peakNumber) + "_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); TCanvas* gainMap = new TCanvas(t.c_str(), t.c_str()); + gainMap->cd(); TH2F* hGainMap = new TH2F(("h" + t).c_str(), title.c_str(), fNumberOfSegmentsY, fReadoutRange.X(), fReadoutRange.Y(), fNumberOfSegmentsX, fReadoutRange.X(), fReadoutRange.Y()); @@ -990,9 +992,9 @@ void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber) { fSegLinearFit[(fNumberOfSegmentsX - 1) / 2][(fNumberOfSegmentsY - 1) / 2]->GetPointX(peakNumber); auto itX = fSplitX.begin(); - for (int i = 0; i < fSegLinearFit.size(); i++) { + for (size_t i = 0; i < fSegLinearFit.size(); i++) { auto itY = fSplitY.begin(); - for (int j = 0; j < fSegLinearFit.at(0).size(); j++) { + for (size_t j = 0; j < fSegLinearFit.at(0).size(); j++) { auto xLower = *itX; auto xUpper = *std::next(itX); auto yLower = *itY; @@ -1061,11 +1063,11 @@ void TRestCalibrationCorrection::Module::Print() { RESTMetadata << p->RESTendl; RESTMetadata << " Slope: " << p->RESTendl; - int maxSize = 0; + size_t maxSize = 0; for (auto& x : fSlope) if (maxSize < x.size()) maxSize = x.size(); - for (int j = 0; j < maxSize; j++) { - for (int k = 0; k < fSlope.size(); k++) { + for (size_t j = 0; j < maxSize; j++) { + for (size_t k = 0; k < fSlope.size(); k++) { if (j < fSlope[k].size()) RESTMetadata << DoubleToString(fSlope[k][fSlope[k].size() - 1 - j], "%.3e") << " "; else @@ -1077,8 +1079,8 @@ void TRestCalibrationCorrection::Module::Print() { maxSize = 0; for (auto& x : fIntercept) if (maxSize < x.size()) maxSize = x.size(); - for (int j = 0; j < maxSize; j++) { - for (int k = 0; k < fIntercept.size(); k++) { + for (size_t j = 0; j < maxSize; j++) { + for (size_t k = 0; k < fIntercept.size(); k++) { if (j < fIntercept[k].size()) RESTMetadata << DoubleToString(fIntercept[k][fIntercept[k].size() - 1 - j], "%+.3e") << " "; else From d402ad9c7f9c37d535aacc7065124248bf657b34 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 10:20:04 +0000 Subject: [PATCH 04/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../framework/analysis/src/TRestCalibrationCorrection.cxx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index 9a3bf3b5e..2afc10a1e 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -550,7 +550,7 @@ void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { if (cut.empty()) cut = "1"; auto histo = dataSet.GetDataFrame().Filter(cut).Histo1D({"temp", "", fNBins, 0, 0}, GetObservable()); std::unique_ptr hpunt = std::unique_ptr(static_cast(histo->Clone())); - //double xMin = hpunt->GetXaxis()->GetXmin(); + // double xMin = hpunt->GetXaxis()->GetXmin(); double xMax = hpunt->GetXaxis()->GetXmax(); // Reduce the range to avoid the possible empty (nCounts<1%) end part of the spectrum @@ -859,7 +859,8 @@ void TRestCalibrationCorrection::Module::DrawSpectrum(const double x, const doub DrawSpectrum(index.first, index.second, c); } -void TRestCalibrationCorrection::Module::DrawSpectrum(const size_t index_x, const size_t index_y, TCanvas* c) { +void TRestCalibrationCorrection::Module::DrawSpectrum(const size_t index_x, const size_t index_y, + TCanvas* c) { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -934,7 +935,8 @@ void TRestCalibrationCorrection::Module::DrawLinearFit(const double x, const dou DrawLinearFit(index.first, index.second, c); } -void TRestCalibrationCorrection::Module::DrawLinearFit(const size_t index_x, const size_t index_y, TCanvas* c) { +void TRestCalibrationCorrection::Module::DrawLinearFit(const size_t index_x, const size_t index_y, + TCanvas* c) { if (fSegLinearFit.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; From 65a1f95a16e25b9a074ab5eb090e912a988a0a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro?= <110617994+AlvaroEzq@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:26:28 +0200 Subject: [PATCH 05/19] Update source/framework/analysis/inc/TRestCalibrationCorrection.h Co-authored-by: Luis Antonio Obis Aparicio <35803280+lobis@users.noreply.github.com> --- source/framework/analysis/inc/TRestCalibrationCorrection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/framework/analysis/inc/TRestCalibrationCorrection.h b/source/framework/analysis/inc/TRestCalibrationCorrection.h index f829c3850..57463e16d 100644 --- a/source/framework/analysis/inc/TRestCalibrationCorrection.h +++ b/source/framework/analysis/inc/TRestCalibrationCorrection.h @@ -206,7 +206,7 @@ class TRestCalibrationCorrection : public TRestMetadata { void SetZeroPoint(const bool& ZeroPoint) { fZeroPoint = ZeroPoint; } void SetAutoRangePeaks(const bool& autoRangePeaks) { fAutoRangePeaks = autoRangePeaks; } - void Print(); + void Print() const; void CalculateCalibrationParameters(); void Initialize(); From 737eaf8b8e8aff98efddceaf89caeef2ea88d2d4 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Tue, 26 Sep 2023 12:55:37 +0200 Subject: [PATCH 06/19] Clang format --- source/framework/analysis/src/TRestCalibrationCorrection.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index 2afc10a1e..bb69b6967 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -1017,7 +1017,7 @@ void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber) { /// \brief Prints on screen the information about the /// members of Module /// -void TRestCalibrationCorrection::Module::Print() { +void TRestCalibrationCorrection::Module::Print() const { RESTMetadata << "-----------------------------------------------" << p->RESTendl; RESTMetadata << " Plane ID: " << fPlaneId << p->RESTendl; RESTMetadata << " Module ID: " << fModuleId << p->RESTendl; From 14acd61c488857e9255ca632375a553a6fc801f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro?= <110617994+AlvaroEzq@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:30:34 +0200 Subject: [PATCH 07/19] Update source/framework/analysis/src/TRestCalibrationCorrection.cxx Co-authored-by: Javier Galan --- source/framework/analysis/src/TRestCalibrationCorrection.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index bb69b6967..7ded9cbb2 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -23,7 +23,7 @@ ///////////////////////////////////////////////////////////////////////// /// TRestCalibrationCorrection calculates and stores the calibration /// parameters for a given detector with multiple (or just one) modules. -/// This modules are defined in the Module class. It performs a gain correction +/// The modules are defined using the Module class (defined internally). It performs a gain correction /// based on a spatial segmentation of the detector module. This is useful for /// big modules such as the ones used in TREX-DM experiment. /// From 4eb349736fc2bb3c6aa8612c727ec644805534e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro?= <110617994+AlvaroEzq@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:30:54 +0200 Subject: [PATCH 08/19] Update source/framework/analysis/src/TRestCalibrationCorrection.cxx Co-authored-by: Javier Galan --- source/framework/analysis/src/TRestCalibrationCorrection.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index 7ded9cbb2..d08dee31e 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -29,7 +29,7 @@ /// /// ### Parameters /// * **calibFileName**: name of the file to use for the calibration. It should be a DataSet -/// * **outputFileName**: name of the file to save the this calibration metadata +/// * **outputFileName**: name of the file to save this calibration metadata /// * **observable**: name of the observable to be calibrated. It must be a branch of the calibration DataSet /// * **spatialObservableX**: name of the observable to be used for the spatial segmentation along the X axis. /// It must be a branch of the calibration DataSet From ac1786e571abc9306fe589e9339667391b270164 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Wed, 27 Sep 2023 15:34:28 +0200 Subject: [PATCH 09/19] Improving documentation --- doc/doxygen/images/drawGainMap.png | Bin 0 -> 27557 bytes doc/doxygen/images/drawSpectrum.png | Bin 0 -> 72848 bytes .../images/gainCorrectionComparison.png | Bin 0 -> 24864 bytes examples/calibrationCorrection.rml | 24 +++++ .../src/TRestCalibrationCorrection.cxx | 85 ++++++++++-------- 5 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 doc/doxygen/images/drawGainMap.png create mode 100644 doc/doxygen/images/drawSpectrum.png create mode 100644 doc/doxygen/images/gainCorrectionComparison.png create mode 100644 examples/calibrationCorrection.rml diff --git a/doc/doxygen/images/drawGainMap.png b/doc/doxygen/images/drawGainMap.png new file mode 100644 index 0000000000000000000000000000000000000000..f6d3df52a048b7c391f205a6e769d4a65cb77ad4 GIT binary patch literal 27557 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 UBBcQQ{nidWEhA0b*~<_93#Tq*W&i*H literal 0 HcmV?d00001 diff --git a/doc/doxygen/images/drawSpectrum.png b/doc/doxygen/images/drawSpectrum.png new file mode 100644 index 0000000000000000000000000000000000000000..3d5e148e7ce313ebd615149a4f3ecfca36cd39c3 GIT binary patch literal 72848 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 z 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 z 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 RGY16H(>A(aanI@R{{{3Mx0V0^ literal 0 HcmV?d00001 diff --git a/doc/doxygen/images/gainCorrectionComparison.png b/doc/doxygen/images/gainCorrectionComparison.png new file mode 100644 index 0000000000000000000000000000000000000000..d1dea811c8b245cbc87988dba88a26de623dcd4b GIT binary patch literal 24864 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 + + + + + + + + + + + + + + + diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index d08dee31e..4a9c70b42 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -23,10 +23,36 @@ ///////////////////////////////////////////////////////////////////////// /// TRestCalibrationCorrection calculates and stores the calibration /// parameters for a given detector with multiple (or just one) modules. -/// The modules are defined using the Module class (defined internally). It performs a gain correction -/// based on a spatial segmentation of the detector module. This is useful for -/// big modules such as the ones used in TREX-DM experiment. +/// The modules are defined using the Module class (defined internally). +/// It performs a gain correction based on a spatial segmentation of the +/// detector module. This is useful forbig modules such as the ones used +/// in TREX-DM experiment. The input data files for this methods are +/// TRestDataSet for both calculating the calibration parameters and +/// performing the calibration of the desired events. /// +/// To correct the gain inhomogeneities, the module is divided in +/// fNumberOfSegmentsX*fNumberOfSegmentsY segments. The energy peaks provided +/// are fitted in each segment. The events are associated to each segment based on +/// the observables fSpatialObservableX and fSpatialObservablesY. This results in +/// the following plot that can be obtain with the function +/// TRestCalibrationCorrection::Module::DrawSpectrum() +/// +/// \htmlonly \endhtmlonly +/// ![Peak fitting of each segment. Plot obtain with +/// TRestCalibrationCorrection::Module::DrawSpectrum()](drawSpectrum.png) +/// +/// Also, the peak position provides a gain map that can be plotted with the function +/// TRestCalibrationCorrection::Module::DrawGainMap(peakNumber) +/// +/// \htmlonly \endhtmlonly +/// ![Gain map. Plot obtain with TRestCalibrationCorrection::Module::DrawGainMap()](drawGainMap.png) +/// +/// The result is a better energy resolution with the gain corrected +/// calibration (red) than the plain calibration (blue). +/// +/// \htmlonly \endhtmlonly +/// ![Gain correction comparison.](gainCorrectionComparison.png) + /// ### Parameters /// * **calibFileName**: name of the file to use for the calibration. It should be a DataSet /// * **outputFileName**: name of the file to save this calibration metadata @@ -38,51 +64,38 @@ /// * **modulesCal**: vector of Module objects /// /// ### Examples -/// Give examples of usage and RML descriptions that can be tested. +/// A example of RML definition of this class can be found inside the file +/// `REST_PATH/examples/calibrationCorrection.rml`. This have the following structure: /// \code -/// -/// -/// -/// -/// -/// -/// -/// -/// +/// +/// ... +/// +/// ... /// -/// -/// -/// +/// +/// ... /// /// /// \endcode /// /// Example to calculate the calibration parameters over a calibration dataSet using restRoot: /// \code -/// [0] TRestCalibrationCorrection cal ("makeCalibration.rml"); -/// [1] cal.SetCalibrationFileName("myDataSet.root"); //if not already defined in rml file -/// [2] cal.SetOutputFileName("myCalibration.root"); //if not already defined in rml file -/// [3] cal.Calibrate(); -/// [4] cal.Export(); +/// TRestCalibrationCorrection cal ("calibrationCorrection.rml"); +/// cal.SetCalibrationFileName("myDataSet.root"); //if not already defined in rml file +/// cal.SetOutputFileName("myCalibration.root"); //if not already defined in rml file +/// cal.Calibrate(); +/// cal.Export(); /// \endcode /// /// Example to calibrate a dataSet with the previously calculated calibration parameters /// and draw the result (using restRoot): /// \code -/// [0] TRestCalibrationCorrection cal; -/// [1] cal.Import("myCalibration.root"); -/// [2] cal.CalibrateDataSet("dataSetToCalibrate.root", "calibratedDataSet.root"); -/// [3] TRestDataSet ds("calibratedDataSet.root"); -/// [4] auto h = ds->GetDataFrame().Histo1D({"hname", "",100,-1,40.}, "calib_ThresholdIntegral"); -/// [5] h->Draw(); +/// TRestCalibrationCorrection cal; +/// cal.Import("myCalibration.root"); +/// cal.CalibrateDataSet("dataSetToCalibrate.root", "calibratedDataSet.root"); +/// TRestDataSet ds("calibratedDataSet.root"); +/// auto h = ds->GetDataFrame().Histo1D({"hname", "",100,-1,40.}, "calib_ThresholdIntegral"); +/// h->Draw(); /// \endcode ///---------------------------------------------------------------------- /// @@ -90,7 +103,7 @@ /// /// History of developments: /// -/// 2023-May: First implementation of TRestCalibrationCorrection +/// 2023-September: First implementation of TRestCalibrationCorrection /// Álvaro Ezquerro /// /// \class TRestCalibrationCorrection From a01aefeb2c19a508b6e9aa96edd22b4260946bc8 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Thu, 28 Sep 2023 12:45:47 +0200 Subject: [PATCH 10/19] Fix GetNumberOfModules() --- source/framework/analysis/inc/TRestCalibrationCorrection.h | 3 +-- source/framework/analysis/src/TRestCalibrationCorrection.cxx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/framework/analysis/inc/TRestCalibrationCorrection.h b/source/framework/analysis/inc/TRestCalibrationCorrection.h index 57463e16d..1c2d7e84a 100644 --- a/source/framework/analysis/inc/TRestCalibrationCorrection.h +++ b/source/framework/analysis/inc/TRestCalibrationCorrection.h @@ -60,8 +60,7 @@ class TRestCalibrationCorrection : public TRestMetadata { Int_t GetNumberOfPlanes() const { return GetPlaneIDs().size(); } Int_t GetNumberOfModules() const { int sum = 0; - for (auto pID : GetPlaneIDs()) - for (auto n : GetModuleIDs(pID)) sum += n; + for (auto pID : GetPlaneIDs()) sum += GetModuleIDs(pID).size(); return sum; } // Int_t GetNumberOfModulesOfPlane(const int planeID) const { return fNModules.at(planeID);} diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestCalibrationCorrection.cxx index 4a9c70b42..7b60fd85c 100644 --- a/source/framework/analysis/src/TRestCalibrationCorrection.cxx +++ b/source/framework/analysis/src/TRestCalibrationCorrection.cxx @@ -94,7 +94,7 @@ /// cal.Import("myCalibration.root"); /// cal.CalibrateDataSet("dataSetToCalibrate.root", "calibratedDataSet.root"); /// TRestDataSet ds("calibratedDataSet.root"); -/// auto h = ds->GetDataFrame().Histo1D({"hname", "",100,-1,40.}, "calib_ThresholdIntegral"); +/// auto h = ds.GetDataFrame().Histo1D({"hname", "",100,-1,40.}, "calib_ThresholdIntegral"); /// h->Draw(); /// \endcode ///---------------------------------------------------------------------- From b1a482aa41a4e042abfe12e98eb43916236f1cc7 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Thu, 28 Sep 2023 12:50:26 +0200 Subject: [PATCH 11/19] Renaming TRestCalibrationCorrection to TRestDataSetGainMap --- .../inc/{TRestCalibrationCorrection.h => TRestDataSetGainMap.h} | 0 .../{TRestCalibrationCorrection.cxx => TRestDataSetGainMap.cxx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename source/framework/analysis/inc/{TRestCalibrationCorrection.h => TRestDataSetGainMap.h} (100%) rename source/framework/analysis/src/{TRestCalibrationCorrection.cxx => TRestDataSetGainMap.cxx} (100%) diff --git a/source/framework/analysis/inc/TRestCalibrationCorrection.h b/source/framework/analysis/inc/TRestDataSetGainMap.h similarity index 100% rename from source/framework/analysis/inc/TRestCalibrationCorrection.h rename to source/framework/analysis/inc/TRestDataSetGainMap.h diff --git a/source/framework/analysis/src/TRestCalibrationCorrection.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx similarity index 100% rename from source/framework/analysis/src/TRestCalibrationCorrection.cxx rename to source/framework/analysis/src/TRestDataSetGainMap.cxx From 243241d6a0b29eedd5c6c7734a0757a7ed38ea31 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Thu, 28 Sep 2023 12:55:57 +0200 Subject: [PATCH 12/19] Changing class name inside the class after renaming --- .../analysis/inc/TRestDataSetGainMap.h | 29 ++-- .../analysis/src/TRestDataSetGainMap.cxx | 134 +++++++++--------- 2 files changed, 79 insertions(+), 84 deletions(-) diff --git a/source/framework/analysis/inc/TRestDataSetGainMap.h b/source/framework/analysis/inc/TRestDataSetGainMap.h index 1c2d7e84a..15c29b178 100644 --- a/source/framework/analysis/inc/TRestDataSetGainMap.h +++ b/source/framework/analysis/inc/TRestDataSetGainMap.h @@ -20,8 +20,8 @@ * For the list of contributors see $REST_PATH/CREDITS. * *************************************************************************/ -#ifndef REST_TRestCalibrationCorrection -#define REST_TRestCalibrationCorrection +#ifndef REST_TRestDataSetGainMap +#define REST_TRestDataSetGainMap #include #include @@ -37,7 +37,7 @@ #include "TRestMetadata.h" /// Metadata class to calculate,store and apply the gain corrected calibration of a group of detectors. -class TRestCalibrationCorrection : public TRestMetadata { +class TRestDataSetGainMap : public TRestMetadata { public: class Module; @@ -88,7 +88,7 @@ class TRestCalibrationCorrection : public TRestMetadata { void Import(const std::string& fileName); void Export(const std::string& fileName = ""); - TRestCalibrationCorrection& operator=(TRestCalibrationCorrection& src); + TRestDataSetGainMap& operator=(TRestDataSetGainMap& src); public: void PrintMetadata() override; @@ -96,20 +96,20 @@ class TRestCalibrationCorrection : public TRestMetadata { void Calibrate(); void CalibrateDataSet(const std::string& dataSetFileName, std::string outputFileName = ""); - TRestCalibrationCorrection(); - TRestCalibrationCorrection(const char* configFilename, std::string name = ""); - ~TRestCalibrationCorrection(); + TRestDataSetGainMap(); + TRestDataSetGainMap(const char* configFilename, std::string name = ""); + ~TRestDataSetGainMap(); // REMOVE COMMENT. ROOT class definition helper. Increase the number in it every time // you add/rename/remove the metadata members - ClassDefOverride(TRestCalibrationCorrection, 1); + ClassDefOverride(TRestDataSetGainMap, 1); class Module { private: - const TRestCalibrationCorrection* p = nullptr; // fEnergyPeaks = {}; //{22.5, 8.0}; std::vector fRangePeaks = @@ -211,9 +211,8 @@ class TRestCalibrationCorrection : public TRestMetadata { void Initialize(); Module() {} - Module(const TRestCalibrationCorrection& parent) : p(&parent){}; - Module(const TRestCalibrationCorrection& parent, const Int_t planeId, const Int_t moduleId) - : p(&parent) { + Module(const TRestDataSetGainMap& parent) : p(&parent){}; + Module(const TRestDataSetGainMap& parent, const Int_t planeId, const Int_t moduleId) : p(&parent) { SetPlaneId(planeId); SetModuleId(moduleId); }; diff --git a/source/framework/analysis/src/TRestDataSetGainMap.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx index 7b60fd85c..4a6022340 100644 --- a/source/framework/analysis/src/TRestDataSetGainMap.cxx +++ b/source/framework/analysis/src/TRestDataSetGainMap.cxx @@ -21,7 +21,7 @@ *************************************************************************/ ///////////////////////////////////////////////////////////////////////// -/// TRestCalibrationCorrection calculates and stores the calibration +/// TRestDataSetGainMap calculates and stores the calibration /// parameters for a given detector with multiple (or just one) modules. /// The modules are defined using the Module class (defined internally). /// It performs a gain correction based on a spatial segmentation of the @@ -35,17 +35,17 @@ /// are fitted in each segment. The events are associated to each segment based on /// the observables fSpatialObservableX and fSpatialObservablesY. This results in /// the following plot that can be obtain with the function -/// TRestCalibrationCorrection::Module::DrawSpectrum() +/// TRestDataSetGainMap::Module::DrawSpectrum() /// /// \htmlonly \endhtmlonly /// ![Peak fitting of each segment. Plot obtain with -/// TRestCalibrationCorrection::Module::DrawSpectrum()](drawSpectrum.png) +/// TRestDataSetGainMap::Module::DrawSpectrum()](drawSpectrum.png) /// /// Also, the peak position provides a gain map that can be plotted with the function -/// TRestCalibrationCorrection::Module::DrawGainMap(peakNumber) +/// TRestDataSetGainMap::Module::DrawGainMap(peakNumber) /// /// \htmlonly \endhtmlonly -/// ![Gain map. Plot obtain with TRestCalibrationCorrection::Module::DrawGainMap()](drawGainMap.png) +/// ![Gain map. Plot obtain with TRestDataSetGainMap::Module::DrawGainMap()](drawGainMap.png) /// /// The result is a better energy resolution with the gain corrected /// calibration (red) than the plain calibration (blue). @@ -67,7 +67,7 @@ /// A example of RML definition of this class can be found inside the file /// `REST_PATH/examples/calibrationCorrection.rml`. This have the following structure: /// \code -/// +/// /// ... /// /// ... @@ -75,12 +75,12 @@ /// /// ... /// -/// +/// /// \endcode /// /// Example to calculate the calibration parameters over a calibration dataSet using restRoot: /// \code -/// TRestCalibrationCorrection cal ("calibrationCorrection.rml"); +/// TRestDataSetGainMap cal ("calibrationCorrection.rml"); /// cal.SetCalibrationFileName("myDataSet.root"); //if not already defined in rml file /// cal.SetOutputFileName("myCalibration.root"); //if not already defined in rml file /// cal.Calibrate(); @@ -90,7 +90,7 @@ /// Example to calibrate a dataSet with the previously calculated calibration parameters /// and draw the result (using restRoot): /// \code -/// TRestCalibrationCorrection cal; +/// TRestDataSetGainMap cal; /// cal.Import("myCalibration.root"); /// cal.CalibrateDataSet("dataSetToCalibrate.root", "calibratedDataSet.root"); /// TRestDataSet ds("calibratedDataSet.root"); @@ -103,22 +103,22 @@ /// /// History of developments: /// -/// 2023-September: First implementation of TRestCalibrationCorrection +/// 2023-September: First implementation of TRestDataSetGainMap /// Álvaro Ezquerro /// -/// \class TRestCalibrationCorrection +/// \class TRestDataSetGainMap /// \author: Álvaro Ezquerro aezquerro@unizar.es /// ///
    /// -#include "TRestCalibrationCorrection.h" +#include "TRestDataSetGainMap.h" -ClassImp(TRestCalibrationCorrection); +ClassImp(TRestDataSetGainMap); /////////////////////////////////////////////// /// \brief Default constructor /// -TRestCalibrationCorrection::TRestCalibrationCorrection() { Initialize(); } +TRestDataSetGainMap::TRestDataSetGainMap() { Initialize(); } ///////////////////////////////////////////// /// \brief Constructor loading data from a config file @@ -132,9 +132,9 @@ TRestCalibrationCorrection::TRestCalibrationCorrection() { Initialize(); } /// /// \param configFilename A const char* that defines the RML filename. /// \param name The name of the metadata section. It will be used to find the -/// corresponding TRestCalibrationCorrection section inside the RML. +/// corresponding TRestDataSetGainMap section inside the RML. /// -TRestCalibrationCorrection::TRestCalibrationCorrection(const char* configFilename, std::string name) +TRestDataSetGainMap::TRestDataSetGainMap(const char* configFilename, std::string name) : TRestMetadata(configFilename) { LoadConfigFromFile(fConfigFileName, name); if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info) PrintMetadata(); @@ -143,14 +143,14 @@ TRestCalibrationCorrection::TRestCalibrationCorrection(const char* configFilenam /////////////////////////////////////////////// /// \brief Default destructor /// -TRestCalibrationCorrection::~TRestCalibrationCorrection() {} +TRestDataSetGainMap::~TRestDataSetGainMap() {} -void TRestCalibrationCorrection::Initialize() { SetSectionName(this->ClassName()); } +void TRestDataSetGainMap::Initialize() { SetSectionName(this->ClassName()); } /////////////////////////////////////////////// -/// \brief Initialization of TRestCalibrationCorrection +/// \brief Initialization of TRestDataSetGainMap /// members through a RML file /// -void TRestCalibrationCorrection::InitFromConfigFile() { +void TRestDataSetGainMap::InitFromConfigFile() { this->Initialize(); TRestMetadata::InitFromConfigFile(); @@ -169,7 +169,7 @@ void TRestCalibrationCorrection::InitFromConfigFile() { ///////////////////////////////////////////// /// \brief Function to calculate the calibration parameters of all modules /// -void TRestCalibrationCorrection::Calibrate() { +void TRestDataSetGainMap::Calibrate() { for (auto& mod : fModulesCal) { RESTInfo << "Calibrating plane " << mod.GetPlaneId() << " module " << mod.GetModuleId() << RESTendl; mod.CalculateCalibrationParameters(); @@ -183,10 +183,9 @@ void TRestCalibrationCorrection::Calibrate() { ///////////////////////////////////////////// /// \brief Function to calibrate a dataSet /// -void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFileName, - std::string outputFileName) { +void TRestDataSetGainMap::CalibrateDataSet(const std::string& dataSetFileName, std::string outputFileName) { if (fModulesCal.empty()) { - RESTError << "TRestCalibrationCorrection::CalibrateDataSet: No modules defined." << RESTendl; + RESTError << "TRestDataSetGainMap::CalibrateDataSet: No modules defined." << RESTendl; return; } @@ -211,7 +210,7 @@ void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFile if (pmID == m.GetPlaneId() * 10 + m.GetModuleId()) return m.GetSlope(x, y) * val + m.GetIntercept(x, y); } - // RESTError << "TRestCalibrationCorrection::CalibrateDataSet: Module not found for pmID " << pmID << + // RESTError << "TRestDataSetGainMap::CalibrateDataSet: Module not found for pmID " << pmID << // RESTendl; return std::numeric_limits::quiet_NaN(); }; @@ -225,7 +224,7 @@ void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFile // Format the output file name and export the dataSet if (outputFileName.empty()) { outputFileName = dataSetFileName; - RESTWarning << "TRestCalibrationCorrection::CalibrateDataSet: No output file name defined. " + RESTWarning << "TRestDataSetGainMap::CalibrateDataSet: No output file name defined. " << "Using input file name " << outputFileName << RESTendl; } else if (TRestTools::GetFileNameExtension(outputFileName) != "root") outputFileName = outputFileName + ".root"; @@ -233,7 +232,7 @@ void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFile RESTInfo << "Exporting calibrated dataSet to " << outputFileName << RESTendl; dataSet.Export(outputFileName); - // Add this TRestCalibrationCorrection metadata to the output file + // Add this TRestDataSetGainMap metadata to the output file TFile* f = TFile::Open(outputFileName.c_str(), "UPDATE"); this->Write(); f->Close(); @@ -244,8 +243,8 @@ void TRestCalibrationCorrection::CalibrateDataSet(const std::string& dataSetFile /// \brief Function to retrieve the module calibration with planeID and moduleID /// /// -TRestCalibrationCorrection::Module* TRestCalibrationCorrection::GetModuleCalibration(const int planeID, - const int moduleID) { +TRestDataSetGainMap::Module* TRestDataSetGainMap::GetModuleCalibration(const int planeID, + const int moduleID) { for (auto& i : fModulesCal) { if (i.GetPlaneId() == planeID && i.GetModuleId() == moduleID) return &i; } @@ -258,8 +257,8 @@ TRestCalibrationCorrection::Module* TRestCalibrationCorrection::GetModuleCalibra /// planeID and moduleID at physical position (x,y) /// /// -double TRestCalibrationCorrection::GetSlopeParameter(const int planeID, const int moduleID, const double x, - const double y) { +double TRestDataSetGainMap::GetSlopeParameter(const int planeID, const int moduleID, const double x, + const double y) { Module* moduleCal = GetModuleCalibration(planeID, moduleID); if (moduleCal == nullptr) return 0; // return numeric_limits::quiet_NaN() return moduleCal->GetSlope(x, y); @@ -270,8 +269,8 @@ double TRestCalibrationCorrection::GetSlopeParameter(const int planeID, const in /// planeID and moduleID at physical position (x,y) /// /// -double TRestCalibrationCorrection::GetInterceptParameter(const int planeID, const int moduleID, - const double x, const double y) { +double TRestDataSetGainMap::GetInterceptParameter(const int planeID, const int moduleID, const double x, + const double y) { Module* moduleCal = GetModuleCalibration(planeID, moduleID); if (moduleCal == nullptr) return 0; // return numeric_limits::quiet_NaN() return moduleCal->GetIntercept(x, y); @@ -281,7 +280,7 @@ double TRestCalibrationCorrection::GetInterceptParameter(const int planeID, cons /// \brief Function to get a list (set) of the plane IDs /// /// -std::set TRestCalibrationCorrection::GetPlaneIDs() const { +std::set TRestDataSetGainMap::GetPlaneIDs() const { std::set planeIDs; for (const auto& mc : fModulesCal) planeIDs.insert(mc.GetPlaneId()); return planeIDs; @@ -291,7 +290,7 @@ std::set TRestCalibrationCorrection::GetPlaneIDs() const { /// \brief Function to get a list (set) of the module IDs /// of the plane with planeID /// -std::set TRestCalibrationCorrection::GetModuleIDs(const int planeId) const { +std::set TRestDataSetGainMap::GetModuleIDs(const int planeId) const { std::set moduleIDs; for (const auto& mc : fModulesCal) if (mc.GetPlaneId() == planeId) moduleIDs.insert(mc.GetModuleId()); @@ -301,14 +300,14 @@ std::set TRestCalibrationCorrection::GetModuleIDs(const int planeId) const ///////////////////////////////////////////// /// \brief Function to get the map of the module IDs for each plane ID /// -std::map> TRestCalibrationCorrection::GetModuleIDs() const { +std::map> TRestDataSetGainMap::GetModuleIDs() const { std::map> moduleIds; for (const int planeId : GetPlaneIDs()) moduleIds.insert(std::pair>(planeId, GetModuleIDs(planeId))); return moduleIds; } -TRestCalibrationCorrection& TRestCalibrationCorrection::operator=(TRestCalibrationCorrection& src) { +TRestDataSetGainMap& TRestDataSetGainMap::operator=(TRestDataSetGainMap& src) { SetName(src.GetName()); fOutputFileName = src.GetOutputFileName(); fObservable = src.GetObservable(); @@ -324,7 +323,7 @@ TRestCalibrationCorrection& TRestCalibrationCorrection::operator=(TRestCalibrati /// \brief Function to set a module calibration. If the module calibration /// already exists (same planeId and moduleId), it will be replaced. /// -void TRestCalibrationCorrection::SetModuleCalibration(const Module& moduleCal) { +void TRestDataSetGainMap::SetModuleCalibration(const Module& moduleCal) { for (auto& i : fModulesCal) { if (i.GetPlaneId() == moduleCal.GetPlaneId() && i.GetModuleId() == moduleCal.GetModuleId()) { i = moduleCal; @@ -338,7 +337,7 @@ void TRestCalibrationCorrection::SetModuleCalibration(const Module& moduleCal) { /// \brief Function to import the calibration parameters /// from the root file fileName. /// -void TRestCalibrationCorrection::Import(const std::string& fileName) { +void TRestDataSetGainMap::Import(const std::string& fileName) { if (fileName.empty()) { RESTError << "No input calibration file defined" << RESTendl; return; @@ -352,16 +351,15 @@ void TRestCalibrationCorrection::Import(const std::string& fileName) { return; } - TRestCalibrationCorrection* cal = nullptr; + TRestDataSetGainMap* cal = nullptr; if (f != nullptr) { TIter nextkey(f->GetListOfKeys()); TKey* key; while ((key = (TKey*)nextkey())) { std::string kName = key->GetClassName(); if (REST_Reflection::GetClassQuick(kName.c_str()) != nullptr && - REST_Reflection::GetClassQuick(kName.c_str()) - ->InheritsFrom("TRestCalibrationCorrection")) { - cal = f->Get(key->GetName()); + REST_Reflection::GetClassQuick(kName.c_str())->InheritsFrom("TRestDataSetGainMap")) { + cal = f->Get(key->GetName()); *this = *cal; } } @@ -375,7 +373,7 @@ void TRestCalibrationCorrection::Import(const std::string& fileName) { /// \brief Function to export the calibration /// to the file fileName. /// -void TRestCalibrationCorrection::Export(const std::string& fileName) { +void TRestDataSetGainMap::Export(const std::string& fileName) { if (!fileName.empty()) fOutputFileName = fileName; if (fOutputFileName.empty()) { RESTError << "No output file defined" << RESTendl; @@ -395,7 +393,7 @@ void TRestCalibrationCorrection::Export(const std::string& fileName) { ///////////////////////////////////////////// /// \brief Prints on screen the information about the metadata members /// -void TRestCalibrationCorrection::PrintMetadata() { +void TRestDataSetGainMap::PrintMetadata() { TRestMetadata::PrintMetadata(); RESTMetadata << " Calibration file: " << fCalibFileName << RESTendl; RESTMetadata << " Output file: " << fOutputFileName << RESTendl; @@ -417,7 +415,7 @@ void TRestCalibrationCorrection::PrintMetadata() { /// \param x A const double that defines the x position on the detector plane. /// \param y A const double that defines the y position on the detector plane. /// -std::pair TRestCalibrationCorrection::Module::GetIndexMatrix(const double x, const double y) const { +std::pair TRestDataSetGainMap::Module::GetIndexMatrix(const double x, const double y) const { int index_x = -1, index_y = -1; if (fSplitX.upper_bound(x) != fSplitX.end()) { @@ -453,7 +451,7 @@ std::pair TRestCalibrationCorrection::Module::GetIndexMatrix(const dou /// \param x A const double that defines the x position on the detector plane. /// \param y A const double that defines the y position on the detector plane. /// -double TRestCalibrationCorrection::Module::GetSlope(const double x, const double y) const { +double TRestDataSetGainMap::Module::GetSlope(const double x, const double y) const { auto [index_x, index_y] = GetIndexMatrix(x, y); if (fSlope.empty()) { RESTError << "Calibration slope matrix is empty. Returning 0" << p->RESTendl; @@ -475,7 +473,7 @@ double TRestCalibrationCorrection::Module::GetSlope(const double x, const double /// \param x A const double that defines the x position on the detector plane. /// \param y A const double that defines the y position on the detector plane. /// -double TRestCalibrationCorrection::Module::GetIntercept(const double x, const double y) const { +double TRestDataSetGainMap::Module::GetIntercept(const double x, const double y) const { auto [index_x, index_y] = GetIndexMatrix(x, y); if (fIntercept.empty()) { RESTError << "Calibration constant matrix is empty. Returning 0" << p->RESTendl; @@ -493,7 +491,7 @@ double TRestCalibrationCorrection::Module::GetIntercept(const double x, const do ///////////////////////////////////////////// /// \brief Function to set the class members for segmentation of /// the detector plane along the X and Y axis. -void TRestCalibrationCorrection::Module::SetSplits() { +void TRestDataSetGainMap::Module::SetSplits() { SetSplitX(); SetSplitY(); } @@ -503,7 +501,7 @@ void TRestCalibrationCorrection::Module::SetSplits() { /// /// It uses the number of segments and the readout range to define the /// edges of the segments. -void TRestCalibrationCorrection::Module::SetSplitX() { +void TRestDataSetGainMap::Module::SetSplitX() { fSplitX.clear(); for (int i = 0; i <= fNumberOfSegmentsX; i++) { // <= so that the last segment is included double x = @@ -517,7 +515,7 @@ void TRestCalibrationCorrection::Module::SetSplitX() { /// /// It uses the number of segments and the readout range to define the /// edges of the segments. -void TRestCalibrationCorrection::Module::SetSplitY() { +void TRestDataSetGainMap::Module::SetSplitY() { fSplitY.clear(); for (int i = 0; i <= fNumberOfSegmentsY; i++) { // <= so that the last segment is included double y = @@ -542,7 +540,7 @@ void TRestCalibrationCorrection::Module::SetSplitY() { /// and the peak position of the next peak to define the range. /// 4. If fRangePeaks is not defined and fAutoRangePeaks is true: same as 3. /// -void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { +void TRestDataSetGainMap::Module::CalculateCalibrationParameters() { //--- Initial checks and settings --- if (fDataSetFileName.empty()) fDataSetFileName = p->GetCalibrationFileName(); if (fDataSetFileName.empty()) { @@ -746,8 +744,8 @@ void TRestCalibrationCorrection::Module::CalculateCalibrationParameters() { /// \param energyPeak The energy of the peak to be fitted (in physical units). /// \param range The range for the fitting of the peak (in the observables corresponding units). /// -void TRestCalibrationCorrection::Module::Refit(const double x, const double y, const double energyPeak, - const TVector2& range) { +void TRestDataSetGainMap::Module::Refit(const double x, const double y, const double energyPeak, + const TVector2& range) { auto [index_x, index_y] = GetIndexMatrix(x, y); int peakNumber = -1; for (size_t i = 0; i < fEnergyPeaks.size(); i++) @@ -770,8 +768,8 @@ void TRestCalibrationCorrection::Module::Refit(const double x, const double y, c /// \param peakNumber The index of the peak to be fitted. /// \param range The range for the fitting of the peak (in the observables corresponding units). /// -void TRestCalibrationCorrection::Module::Refit(const int x, const int y, const int peakNumber, - const TVector2& range) { +void TRestDataSetGainMap::Module::Refit(const int x, const int y, const int peakNumber, + const TVector2& range) { // Refit the desired peak std::string name = "g" + std::to_string(peakNumber); TF1* g = new TF1(name.c_str(), "gaus", range.X(), range.Y()); @@ -810,9 +808,9 @@ void TRestCalibrationCorrection::Module::Refit(const int x, const int y, const i /// ///
    /// -void TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement(const TiXmlElement* module) { +void TRestDataSetGainMap::Module::LoadConfigFromTiXmlElement(const TiXmlElement* module) { if (module == nullptr) { - RESTError << "TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement: module is nullptr" + RESTError << "TRestDataSetGainMap::Module::LoadConfigFromTiXmlElement: module is nullptr" << p->RESTendl; return; } @@ -867,13 +865,12 @@ void TRestCalibrationCorrection::Module::LoadConfigFromTiXmlElement(const TiXmlE } } -void TRestCalibrationCorrection::Module::DrawSpectrum(const double x, const double y, TCanvas* c) { +void TRestDataSetGainMap::Module::DrawSpectrum(const double x, const double y, TCanvas* c) { std::pair index = GetIndexMatrix(x, y); DrawSpectrum(index.first, index.second, c); } -void TRestCalibrationCorrection::Module::DrawSpectrum(const size_t index_x, const size_t index_y, - TCanvas* c) { +void TRestDataSetGainMap::Module::DrawSpectrum(const size_t index_x, const size_t index_y, TCanvas* c) { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -907,7 +904,7 @@ void TRestCalibrationCorrection::Module::DrawSpectrum(const size_t index_x, cons } } -void TRestCalibrationCorrection::Module::DrawSpectrum() { +void TRestDataSetGainMap::Module::DrawSpectrum() { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -922,7 +919,7 @@ void TRestCalibrationCorrection::Module::DrawSpectrum() { } } } -void TRestCalibrationCorrection::Module::DrawFullSpectrum() { +void TRestDataSetGainMap::Module::DrawFullSpectrum() { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -943,13 +940,12 @@ void TRestCalibrationCorrection::Module::DrawFullSpectrum() { sumHist->Draw(); } -void TRestCalibrationCorrection::Module::DrawLinearFit(const double x, const double y, TCanvas* c) { +void TRestDataSetGainMap::Module::DrawLinearFit(const double x, const double y, TCanvas* c) { std::pair index = GetIndexMatrix(x, y); DrawLinearFit(index.first, index.second, c); } -void TRestCalibrationCorrection::Module::DrawLinearFit(const size_t index_x, const size_t index_y, - TCanvas* c) { +void TRestDataSetGainMap::Module::DrawLinearFit(const size_t index_x, const size_t index_y, TCanvas* c) { if (fSegLinearFit.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -974,7 +970,7 @@ void TRestCalibrationCorrection::Module::DrawLinearFit(const size_t index_x, con fSegLinearFit[index_x][index_y]->Draw("AL*"); } -void TRestCalibrationCorrection::Module::DrawLinearFit() { +void TRestDataSetGainMap::Module::DrawLinearFit() { std::string t = "linearFits_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); TCanvas* myCanvas = new TCanvas(t.c_str(), t.c_str()); myCanvas->Divide(fSegLinearFit.size(), fSegLinearFit.at(0).size()); @@ -987,7 +983,7 @@ void TRestCalibrationCorrection::Module::DrawLinearFit() { } } -void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber) { +void TRestDataSetGainMap::Module::DrawGainMap(const int peakNumber) { if (peakNumber < 0 || peakNumber >= (int)fEnergyPeaks.size()) { RESTError << "Peak number out of range (peakNumber should be between 0 and " << fEnergyPeaks.size() - 1 << " )" << p->RESTendl; @@ -1030,7 +1026,7 @@ void TRestCalibrationCorrection::Module::DrawGainMap(const int peakNumber) { /// \brief Prints on screen the information about the /// members of Module /// -void TRestCalibrationCorrection::Module::Print() const { +void TRestDataSetGainMap::Module::Print() const { RESTMetadata << "-----------------------------------------------" << p->RESTendl; RESTMetadata << " Plane ID: " << fPlaneId << p->RESTendl; RESTMetadata << " Module ID: " << fModuleId << p->RESTendl; From a50485d609389b52e940a7d90f61a242be4239aa Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Fri, 6 Oct 2023 17:31:54 +0200 Subject: [PATCH 13/19] Use Redefine for pmID if a column with same name already exists --- source/framework/analysis/src/TRestDataSetGainMap.cxx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source/framework/analysis/src/TRestDataSetGainMap.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx index 4a6022340..df85473da 100644 --- a/source/framework/analysis/src/TRestDataSetGainMap.cxx +++ b/source/framework/analysis/src/TRestDataSetGainMap.cxx @@ -194,10 +194,16 @@ void TRestDataSetGainMap::CalibrateDataSet(const std::string& dataSetFileName, s auto dataFrame = dataSet.GetDataFrame(); // Define a new column with the identifier (pmID) of the module for each row (event) - std::string modCut = fModulesCal[0].GetModuleDefinitionCut(); std::string pmIDname = (std::string)GetName() + "_pmID"; + std::string modCut = fModulesCal[0].GetModuleDefinitionCut(); int pmID = fModulesCal[0].GetPlaneId() * 10 + fModulesCal[0].GetModuleId(); - dataFrame = dataFrame.Define(pmIDname, modCut + " ? " + std::to_string(pmID) + " : -1"); + + auto columnList = dataFrame.GetColumnNames(); + if (std::find(columnList.begin(), columnList.end(), pmIDname) == columnList.end()) + dataFrame = dataFrame.Define(pmIDname, modCut + " ? " + std::to_string(pmID) + " : -1"); + else + dataFrame = dataFrame.Redefine(pmIDname, modCut + " ? " + std::to_string(pmID) + " : -1"); + for (size_t n = 1; n < fModulesCal.size(); n++) { modCut = fModulesCal[n].GetModuleDefinitionCut(); pmID = fModulesCal[n].GetPlaneId() * 10 + fModulesCal[n].GetModuleId(); From 9b7d6afb7a15d66978f15f3acdf588545fe1a159 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Thu, 12 Oct 2023 18:24:02 +0200 Subject: [PATCH 14/19] Changed automatic naming on CalibrateDataSet() to avoid overwritting input dataset file --- .../framework/analysis/src/TRestDataSetGainMap.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/framework/analysis/src/TRestDataSetGainMap.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx index df85473da..2494636a4 100644 --- a/source/framework/analysis/src/TRestDataSetGainMap.cxx +++ b/source/framework/analysis/src/TRestDataSetGainMap.cxx @@ -228,12 +228,12 @@ void TRestDataSetGainMap::CalibrateDataSet(const std::string& dataSetFileName, s dataSet.SetDataFrame(dataFrame); // Format the output file name and export the dataSet - if (outputFileName.empty()) { - outputFileName = dataSetFileName; - RESTWarning << "TRestDataSetGainMap::CalibrateDataSet: No output file name defined. " - << "Using input file name " << outputFileName << RESTendl; - } else if (TRestTools::GetFileNameExtension(outputFileName) != "root") - outputFileName = outputFileName + ".root"; + if (outputFileName.empty()) outputFileName = dataSetFileName; + + if (outputFileName == dataSetFileName) { // TRestDataSet cannot be overwritten + outputFileName = outputFileName.substr(0, outputFileName.find_last_of(".")) + "_cc."; + outputFileName += TRestTools::GetFileNameExtension(dataSetFileName); + } RESTInfo << "Exporting calibrated dataSet to " << outputFileName << RESTendl; dataSet.Export(outputFileName); From b006c3937b7fd26994ec37a95cabee8048a238b9 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Thu, 12 Oct 2023 18:57:48 +0200 Subject: [PATCH 15/19] Renaming GetModuleCalibration -> GetModule and cleaning some comments --- .../analysis/inc/TRestDataSetGainMap.h | 42 ++++++++----------- .../analysis/src/TRestDataSetGainMap.cxx | 9 ++-- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/source/framework/analysis/inc/TRestDataSetGainMap.h b/source/framework/analysis/inc/TRestDataSetGainMap.h index 15c29b178..03b44f6cc 100644 --- a/source/framework/analysis/inc/TRestDataSetGainMap.h +++ b/source/framework/analysis/inc/TRestDataSetGainMap.h @@ -63,14 +63,14 @@ class TRestDataSetGainMap : public TRestMetadata { for (auto pID : GetPlaneIDs()) sum += GetModuleIDs(pID).size(); return sum; } - // Int_t GetNumberOfModulesOfPlane(const int planeID) const { return fNModules.at(planeID);} + std::string GetCalibrationFileName() const { return fCalibFileName; } std::string GetOutputFileName() const { return fOutputFileName; } std::string GetObservable() const { return fObservable; } std::string GetSpatialObservableX() const { return fSpatialObservableX; } std::string GetSpatialObservableY() const { return fSpatialObservableY; } - Module* GetModuleCalibration(const int planeID, const int moduleID); + Module* GetModule(const int planeID, const int moduleID); double GetSlopeParameter(const int planeID, const int moduleID, const double x, const double y); double GetInterceptParameter(const int planeID, const int moduleID, const double x, const double y); @@ -100,8 +100,6 @@ class TRestDataSetGainMap : public TRestMetadata { TRestDataSetGainMap(const char* configFilename, std::string name = ""); ~TRestDataSetGainMap(); - // REMOVE COMMENT. ROOT class definition helper. Increase the number in it every time - // you add/rename/remove the metadata members ClassDefOverride(TRestDataSetGainMap, 1); class Module { @@ -111,32 +109,27 @@ class TRestDataSetGainMap : public TRestMetadata { Int_t fPlaneId = -1; //< // Plane ID Int_t fModuleId = -1; //< // Module ID - std::vector fEnergyPeaks = {}; //{22.5, 8.0}; - std::vector fRangePeaks = - {}; //{TVector2(230000, 650000), TVector2(40000, 230000)}; //in development... - TVector2 fCalibRange = TVector2(0, 0); //< // Calibration range - Int_t fNBins = 100; //< // Number of bins for the spectrum histograms - - /*std::string fObservable = ""; //"rawAna_ThresholdIntegral"; //< - std::string fSpatialObservableX = ""; //"hitsAna_xMean"; //< - std::string fSpatialObservableY = ""; //"hitsAna_yMean"; //<*/ - std::string fDefinitionCut = ""; //"TREXsides_tagId == 2"; //< + std::vector fEnergyPeaks = {}; + std::vector fRangePeaks = {}; //{TVector2(230000, 650000), TVector2(40000, 230000)}; + TVector2 fCalibRange = TVector2(0, 0); //< // Calibration range + Int_t fNBins = 100; //< // Number of bins for the spectrum histograms + std::string fDefinitionCut = ""; //"TREXsides_tagId == 2"; //< - Int_t fNumberOfSegmentsX = 1; //< - Int_t fNumberOfSegmentsY = 1; //< - TVector2 fReadoutRange = TVector2(0, 246); //< // Readout dimensions - std::set fSplitX = {}; //< - std::set fSplitY = {}; //< + Int_t fNumberOfSegmentsX = 1; //< + Int_t fNumberOfSegmentsY = 1; //< + TVector2 fReadoutRange = TVector2(-1, 246.24); //< // Readout dimensions + std::set fSplitX = {}; //< + std::set fSplitY = {}; //< - std::string fDataSetFileName = ""; //< // File name for the dataset + std::string fDataSetFileName = ""; //< // File name for the calibration dataset std::vector> fSlope = {}; //< std::vector> fIntercept = {}; //< bool fZeroPoint = false; //< Zero point will be automatically added if there are less than 2 peaks - bool fAutoRangePeaks = true; //< Automatic range peaks - std::vector> fSegSpectra = {}; // fSegmentedSpectra - std::vector> fSegLinearFit = {}; // fSegmentedLinearFit + bool fAutoRangePeaks = true; //< Automatic range peaks + std::vector> fSegSpectra = {}; + std::vector> fSegLinearFit = {}; public: void SetSplitX(); @@ -198,8 +191,7 @@ class TRestDataSetGainMap : public TRestMetadata { fNumberOfSegmentsY = numberOfSegmentsY; SetSplitY(); } - // void SetInputFile( const std::string &inputFile) { fInputFile = inputFile;} - // void SetOutputFile( const std::string &outputFile) { fOutputFile = outputFile;} + void SetDataSetFileName(const std::string& dataSetFileName) { fDataSetFileName = dataSetFileName; } void SetReadoutRange(const TVector2& readoutRange) { fReadoutRange = readoutRange; } void SetZeroPoint(const bool& ZeroPoint) { fZeroPoint = ZeroPoint; } diff --git a/source/framework/analysis/src/TRestDataSetGainMap.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx index 2494636a4..599871611 100644 --- a/source/framework/analysis/src/TRestDataSetGainMap.cxx +++ b/source/framework/analysis/src/TRestDataSetGainMap.cxx @@ -249,8 +249,7 @@ void TRestDataSetGainMap::CalibrateDataSet(const std::string& dataSetFileName, s /// \brief Function to retrieve the module calibration with planeID and moduleID /// /// -TRestDataSetGainMap::Module* TRestDataSetGainMap::GetModuleCalibration(const int planeID, - const int moduleID) { +TRestDataSetGainMap::Module* TRestDataSetGainMap::GetModule(const int planeID, const int moduleID) { for (auto& i : fModulesCal) { if (i.GetPlaneId() == planeID && i.GetModuleId() == moduleID) return &i; } @@ -265,7 +264,7 @@ TRestDataSetGainMap::Module* TRestDataSetGainMap::GetModuleCalibration(const int /// double TRestDataSetGainMap::GetSlopeParameter(const int planeID, const int moduleID, const double x, const double y) { - Module* moduleCal = GetModuleCalibration(planeID, moduleID); + Module* moduleCal = GetModule(planeID, moduleID); if (moduleCal == nullptr) return 0; // return numeric_limits::quiet_NaN() return moduleCal->GetSlope(x, y); } @@ -277,7 +276,7 @@ double TRestDataSetGainMap::GetSlopeParameter(const int planeID, const int modul /// double TRestDataSetGainMap::GetInterceptParameter(const int planeID, const int moduleID, const double x, const double y) { - Module* moduleCal = GetModuleCalibration(planeID, moduleID); + Module* moduleCal = GetModule(planeID, moduleID); if (moduleCal == nullptr) return 0; // return numeric_limits::quiet_NaN() return moduleCal->GetIntercept(x, y); } @@ -321,7 +320,7 @@ TRestDataSetGainMap& TRestDataSetGainMap::operator=(TRestDataSetGainMap& src) { fSpatialObservableY = src.GetSpatialObservableY(); fModulesCal.clear(); for (auto pID : src.GetPlaneIDs()) - for (auto mID : src.GetModuleIDs(pID)) fModulesCal.push_back(*src.GetModuleCalibration(pID, mID)); + for (auto mID : src.GetModuleIDs(pID)) fModulesCal.push_back(*src.GetModule(pID, mID)); return *this; } From 6c44a6fe929024149a50f61a48bb2da08dbc8158 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Thu, 12 Oct 2023 20:45:56 +0200 Subject: [PATCH 16/19] Improving DrawSpectrum() method and documentation --- .../analysis/inc/TRestDataSetGainMap.h | 8 +- .../analysis/src/TRestDataSetGainMap.cxx | 97 +++++++++++++++---- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/source/framework/analysis/inc/TRestDataSetGainMap.h b/source/framework/analysis/inc/TRestDataSetGainMap.h index 03b44f6cc..340e0fbd3 100644 --- a/source/framework/analysis/inc/TRestDataSetGainMap.h +++ b/source/framework/analysis/inc/TRestDataSetGainMap.h @@ -161,9 +161,11 @@ class TRestDataSetGainMap : public TRestMetadata { std::string GetDataSetFileName() const { return fDataSetFileName; } TVector2 GetReadoutRangeVar() const { return fReadoutRange; } - void DrawSpectrum(); - void DrawSpectrum(const double x, const double y, TCanvas* c = nullptr); - void DrawSpectrum(const size_t index_x, const size_t index_y, TCanvas* c = nullptr); + void DrawSpectrum(bool drawFits = true, int color = -1, TCanvas* c = nullptr); + void DrawSpectrum(const double x, const double y, bool drawFits = true, int color = -1, + TCanvas* c = nullptr); + void DrawSpectrum(const size_t index_x, const size_t index_y, bool drawFits = true, int color = -1, + TCanvas* c = nullptr); void DrawFullSpectrum(); void DrawLinearFit(); diff --git a/source/framework/analysis/src/TRestDataSetGainMap.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx index 599871611..baa9d46b4 100644 --- a/source/framework/analysis/src/TRestDataSetGainMap.cxx +++ b/source/framework/analysis/src/TRestDataSetGainMap.cxx @@ -84,7 +84,7 @@ /// cal.SetCalibrationFileName("myDataSet.root"); //if not already defined in rml file /// cal.SetOutputFileName("myCalibration.root"); //if not already defined in rml file /// cal.Calibrate(); -/// cal.Export(); +/// cal.Export(); // cal.Export("anyOtherFileName.root") /// \endcode /// /// Example to calibrate a dataSet with the previously calculated calibration parameters @@ -97,6 +97,26 @@ /// auto h = ds.GetDataFrame().Histo1D({"hname", "",100,-1,40.}, "calib_ThresholdIntegral"); /// h->Draw(); /// \endcode +/// +/// Example to refit manually the peaks of the gain map if any of them is not well fitted +/// (using restRoot): +/// \code +/// TRestDataSetGainMap cal; +/// cal.Import("myCalibration.root"); +/// // Draw all segmented spectra and check if any need a refit +/// for (auto pID : cal.GetPlaneIDs()) +/// for (auto mID : cal.GetModuleIDs(pID)) +/// cal.GetModule(pID,mID)->DrawSpectrum(); +/// // Draw only the desired spectrum (the one at position (x,y)=(0,0) in this case) +/// cal.GetModule(0,0)->DrawSpectrum(0.0, 0.0); +/// // Refit the desired peak (peak with energy 22.5 in this case) with a new range +/// TVector2 range(100000, 200000); // Define here the new range for the fit as you wish +/// cal.GetModule(0,0)->Refit(0.0, 0.0, 22.5, range) +/// // Check the result +/// cal.GetModule(0,0)->DrawSpectrum(0.0, 0.0); +/// Export the new calibration +/// cal.Export(); // cal.Export("anyOtherFileName.root") +/// \endcode ///---------------------------------------------------------------------- /// /// REST-for-Physics - Software for Rare Event Searches Toolkit @@ -870,12 +890,14 @@ void TRestDataSetGainMap::Module::LoadConfigFromTiXmlElement(const TiXmlElement* } } -void TRestDataSetGainMap::Module::DrawSpectrum(const double x, const double y, TCanvas* c) { +void TRestDataSetGainMap::Module::DrawSpectrum(const double x, const double y, bool drawFits, int color, + TCanvas* c) { std::pair index = GetIndexMatrix(x, y); - DrawSpectrum(index.first, index.second, c); + DrawSpectrum(index.first, index.second, drawFits, color, c); } -void TRestDataSetGainMap::Module::DrawSpectrum(const size_t index_x, const size_t index_y, TCanvas* c) { +void TRestDataSetGainMap::Module::DrawSpectrum(const size_t index_x, const size_t index_y, bool drawFits, + int color, TCanvas* c) { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; @@ -899,28 +921,67 @@ void TRestDataSetGainMap::Module::DrawSpectrum(const size_t index_x, const size_ ") y=[" + DoubleToString(yLower, "%g") + ", " + DoubleToString(yUpper, "%g") + ");" + GetObservable() + ";counts"; fSegSpectra[index_x][index_y]->SetTitle(tH.c_str()); - fSegSpectra[index_x][index_y]->Draw(); - for (size_t c = 0; c < fEnergyPeaks.size(); c++) { - auto fit = fSegSpectra[index_x][index_y]->GetFunction(("g" + std::to_string(c)).c_str()); - if (!fit) RESTError << "Fit for energy peak" << fEnergyPeaks[c] << " not found." << p->RESTendl; - if (!fit) break; - fit->SetLineColor(c + 2); // skipe 0 which is white and 1 which is blue as histogram - fit->Draw("same"); - } + + if (color > 0) fSegSpectra[index_x][index_y]->SetLineColor(color); + size_t colorT = fSegSpectra[index_x][index_y]->GetLineColor(); + fSegSpectra[index_x][index_y]->Draw("same"); + + if (drawFits) + for (size_t c = 0; c < fEnergyPeaks.size(); c++) { + auto fit = fSegSpectra[index_x][index_y]->GetFunction(("g" + std::to_string(c)).c_str()); + if (!fit) RESTError << "Fit for energy peak" << fEnergyPeaks[c] << " not found." << p->RESTendl; + if (!fit) break; + fit->SetLineColor(c + 2 != colorT++ ? c + 2 : c + 3); /* does not work with kRed, kBlue, etc. + as they are not defined with the same number as the first 10 basic colors. See + https://root.cern.ch/doc/master/classTColor.html#C01 and + https://root.cern.ch/doc/master/classTColor.html#C02 */ + fit->Draw("same"); + } } -void TRestDataSetGainMap::Module::DrawSpectrum() { +///////////////////////////////////////////// +/// \brief Function to draw the spectrum for each segment of the module +/// on the same canvas. The canvas is divided in fNumberOfSegmentsX x fNumberOfSegmentsY +/// pads. The segments are drawn with the bottom left corner corresponding to the +/// minimun x and y values of the readout range and top right corner corresponding +/// to the maximum x and y values of the readout range. +/// Tip: define a canvas and use this same canvas along different calls to this function +/// to draw the spectra of different modules on the same canvas. +/// Example: +/// TCanvas *myCanvas = new TCanvas(); +/// module1->DrawSpectrum(false, kBlue, myCanvas); +/// module2->DrawSpectrum(false, kRed, myCanvas); +/// +/// \param drawFits A bool to also draw the fits or not. +/// \param color An int to set the color of the spectra. If negative, +/// the color of the spectra is not changed. +/// \param c A TCanvas pointer to draw the spectra. If none (nullptr) is given, +/// a new one is created. +/// +void TRestDataSetGainMap::Module::DrawSpectrum(bool drawFits, int color, TCanvas* c) { if (fSegSpectra.size() == 0) { RESTError << "Spectra matrix is empty." << p->RESTendl; return; } - std::string t = "spectra_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); - TCanvas* myCanvas = new TCanvas(t.c_str(), t.c_str()); - myCanvas->Divide(fSegSpectra.size(), fSegSpectra.at(0).size()); + if (!c) { + std::string t = "spectrum_" + std::to_string(fPlaneId) + "_" + std::to_string(fModuleId); + c = new TCanvas(t.c_str(), t.c_str()); + } + + size_t nPads = 0; + for (const auto& object : *c->GetListOfPrimitives()) + if (object->InheritsFrom(TVirtualPad::Class())) ++nPads; + if (nPads != 0 && nPads != fSegSpectra.size() * fSegSpectra.at(0).size()) { + RESTError << "Canvas " << c->GetName() << " has " << nPads << " pads, but " + << fSegSpectra.size() * fSegSpectra.at(0).size() << " are needed." << p->RESTendl; + return; + } else if (nPads == 0) + c->Divide(fSegSpectra.size(), fSegSpectra.at(0).size()); + for (size_t i = 0; i < fSegSpectra.size(); i++) { for (size_t j = 0; j < fSegSpectra[i].size(); j++) { - myCanvas->cd(i + 1 + fSegSpectra[i].size() * j); - DrawSpectrum(i, fSegSpectra[i].size() - 1 - j, myCanvas); + c->cd(i + 1 + fSegSpectra[i].size() * j); + DrawSpectrum(i, fSegSpectra[i].size() - 1 - j, drawFits, color, c); } } } From bcc8a38e3acc75e1cbd4092fc00a760e5bbfb593 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Sat, 21 Oct 2023 15:58:17 +0200 Subject: [PATCH 17/19] Fixing class name in rml example --- examples/calibrationCorrection.rml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/calibrationCorrection.rml b/examples/calibrationCorrection.rml index 734b001fb..c233b5bd9 100644 --- a/examples/calibrationCorrection.rml +++ b/examples/calibrationCorrection.rml @@ -1,5 +1,5 @@ - + @@ -21,4 +21,4 @@ - + From e3063462c5b886af1ca6348fc5404d4e6828e409 Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Sat, 21 Oct 2023 16:18:23 +0200 Subject: [PATCH 18/19] Changing method name Calibrate and CalculateCalibrationParameters to GenerateGainMap. Adding file existance check. --- .../analysis/inc/TRestDataSetGainMap.h | 4 +-- .../analysis/src/TRestDataSetGainMap.cxx | 36 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/source/framework/analysis/inc/TRestDataSetGainMap.h b/source/framework/analysis/inc/TRestDataSetGainMap.h index 340e0fbd3..e2ac6ef2e 100644 --- a/source/framework/analysis/inc/TRestDataSetGainMap.h +++ b/source/framework/analysis/inc/TRestDataSetGainMap.h @@ -93,7 +93,7 @@ class TRestDataSetGainMap : public TRestMetadata { public: void PrintMetadata() override; - void Calibrate(); + void GenerateGainMap(); void CalibrateDataSet(const std::string& dataSetFileName, std::string outputFileName = ""); TRestDataSetGainMap(); @@ -201,7 +201,7 @@ class TRestDataSetGainMap : public TRestMetadata { void Print() const; - void CalculateCalibrationParameters(); + void GenerateGainMap(); void Initialize(); Module() {} diff --git a/source/framework/analysis/src/TRestDataSetGainMap.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx index baa9d46b4..6872c0f13 100644 --- a/source/framework/analysis/src/TRestDataSetGainMap.cxx +++ b/source/framework/analysis/src/TRestDataSetGainMap.cxx @@ -83,7 +83,7 @@ /// TRestDataSetGainMap cal ("calibrationCorrection.rml"); /// cal.SetCalibrationFileName("myDataSet.root"); //if not already defined in rml file /// cal.SetOutputFileName("myCalibration.root"); //if not already defined in rml file -/// cal.Calibrate(); +/// cal.GenerateGainMap(); /// cal.Export(); // cal.Export("anyOtherFileName.root") /// \endcode /// @@ -189,10 +189,10 @@ void TRestDataSetGainMap::InitFromConfigFile() { ///////////////////////////////////////////// /// \brief Function to calculate the calibration parameters of all modules /// -void TRestDataSetGainMap::Calibrate() { +void TRestDataSetGainMap::GenerateGainMap() { for (auto& mod : fModulesCal) { RESTInfo << "Calibrating plane " << mod.GetPlaneId() << " module " << mod.GetModuleId() << RESTendl; - mod.CalculateCalibrationParameters(); + mod.GenerateGainMap(); if (GetVerboseLevel() >= TRestStringOutput::REST_Verbose_Level::REST_Info) { mod.DrawSpectrum(); mod.DrawGainMap(); @@ -420,7 +420,7 @@ void TRestDataSetGainMap::Export(const std::string& fileName) { /// void TRestDataSetGainMap::PrintMetadata() { TRestMetadata::PrintMetadata(); - RESTMetadata << " Calibration file: " << fCalibFileName << RESTendl; + RESTMetadata << " Calibration dataset: " << fCalibFileName << RESTendl; RESTMetadata << " Output file: " << fOutputFileName << RESTendl; RESTMetadata << " Number of planes: " << GetNumberOfPlanes() << RESTendl; RESTMetadata << " Number of modules: " << GetNumberOfModules() << RESTendl; @@ -550,14 +550,14 @@ void TRestDataSetGainMap::Module::SetSplitY() { } ///////////////////////////////////////////// -/// \brief Function calculate the calibration parameters for each segment -/// defined at fSplitX and fSplitY. +/// \brief Function that calculates the calibration parameters for each segment +/// defined at fSplitX and fSplitY ang generate their spectra and gain map. /// /// It uses the data of the observable fObservable from the TRestDataSet -/// at fDataSetFileName (or fCalibrationFileName if first is empty). +/// at fDataSetFileName (or fCalibFileName if first is empty). /// The segmentation is given by the splits. /// -/// Fitting ranges logic is as follows: +/// Ranges for peak fitting follows this logic: /// 1. If fRangePeaks is defined and fAutoRangePeaks is false: fRangePeaks is used. /// 2. If fRangePeaks is defined and fAutoRangePeaks is true: the fitting range /// is calculated by the peak position found by TSpectrum inside fRangePeaks. @@ -565,18 +565,24 @@ void TRestDataSetGainMap::Module::SetSplitY() { /// and the peak position of the next peak to define the range. /// 4. If fRangePeaks is not defined and fAutoRangePeaks is true: same as 3. /// -void TRestDataSetGainMap::Module::CalculateCalibrationParameters() { +void TRestDataSetGainMap::Module::GenerateGainMap() { //--- Initial checks and settings --- - if (fDataSetFileName.empty()) fDataSetFileName = p->GetCalibrationFileName(); - if (fDataSetFileName.empty()) { + std::string dsFileName = fDataSetFileName; + if (dsFileName.empty()) dsFileName = p->GetCalibrationFileName(); + if (dsFileName.empty()) { RESTError << "No calibration file defined" << p->RESTendl; return; } - if (!TRestTools::isDataSet(fDataSetFileName)) - RESTWarning << fDataSetFileName << " is not a dataset." << p->RESTendl; + if (!TRestTools::fileExists(dsFileName)) { + RESTError << "Calibration file " << dsFileName << " does not exist." << p->RESTendl; + return; + } + if (!TRestTools::isDataSet(dsFileName)) RESTWarning << dsFileName << " is not a dataset." << p->RESTendl; TRestDataSet dataSet; - dataSet.Import(fDataSetFileName); + dataSet.Import(dsFileName); + fDataSetFileName = dsFileName; + SetSplits(); //--- Get the calibration range if not provided (default is 0,0) --- @@ -1099,7 +1105,7 @@ void TRestDataSetGainMap::Module::Print() const { RESTMetadata << " Definition cut: " << fDefinitionCut << p->RESTendl; RESTMetadata << p->RESTendl; - RESTMetadata << " DataSet: " << fDataSetFileName << p->RESTendl; + RESTMetadata << " Calibration dataset: " << fDataSetFileName << p->RESTendl; RESTMetadata << p->RESTendl; RESTMetadata << " Energy peaks: "; From 2346740b749cdef0968879327c33c378d70ff50b Mon Sep 17 00:00:00 2001 From: Alvaro Ezquerro Date: Sat, 21 Oct 2023 16:22:53 +0200 Subject: [PATCH 19/19] Adding methods to set any split distribution 'by hand'. --- .../analysis/inc/TRestDataSetGainMap.h | 23 +++++---- .../analysis/src/TRestDataSetGainMap.cxx | 49 +++++++++++++++++-- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/source/framework/analysis/inc/TRestDataSetGainMap.h b/source/framework/analysis/inc/TRestDataSetGainMap.h index e2ac6ef2e..3f7dbe652 100644 --- a/source/framework/analysis/inc/TRestDataSetGainMap.h +++ b/source/framework/analysis/inc/TRestDataSetGainMap.h @@ -132,10 +132,6 @@ class TRestDataSetGainMap : public TRestMetadata { std::vector> fSegLinearFit = {}; public: - void SetSplitX(); - void SetSplitY(); - void SetSplits(); - void AddPeak(const double& energyPeak, const TVector2& rangePeak = TVector2(0, 0)) { fEnergyPeaks.push_back(energyPeak); fRangePeaks.push_back(rangePeak); @@ -184,16 +180,19 @@ class TRestDataSetGainMap : public TRestMetadata { } void SetCalibrationRange(const TVector2& calibrationRange) { fCalibRange = calibrationRange; } void SetNBins(const Int_t& nBins) { fNBins = nBins; } - - void SetNumberOfSegmentsX(const Int_t& numberOfSegmentsX) { - fNumberOfSegmentsX = numberOfSegmentsX; - SetSplitX(); - } - void SetNumberOfSegmentsY(const Int_t& numberOfSegmentsY) { - fNumberOfSegmentsY = numberOfSegmentsY; - SetSplitY(); + void SetSplitX(); + void SetSplitY(); + void SetSplitX(const std::set& splitX); + void SetSplitY(const std::set& splitY); + void SetSplits(); + void SetSplits(const std::set& splitXandY) { + SetSplitX(splitXandY); + SetSplitY(splitXandY); } + void SetNumberOfSegmentsX(const Int_t& numberOfSegmentsX) { fNumberOfSegmentsX = numberOfSegmentsX; } + void SetNumberOfSegmentsY(const Int_t& numberOfSegmentsY) { fNumberOfSegmentsY = numberOfSegmentsY; } + void SetDataSetFileName(const std::string& dataSetFileName) { fDataSetFileName = dataSetFileName; } void SetReadoutRange(const TVector2& readoutRange) { fReadoutRange = readoutRange; } void SetZeroPoint(const bool& ZeroPoint) { fZeroPoint = ZeroPoint; } diff --git a/source/framework/analysis/src/TRestDataSetGainMap.cxx b/source/framework/analysis/src/TRestDataSetGainMap.cxx index 6872c0f13..0212e179b 100644 --- a/source/framework/analysis/src/TRestDataSetGainMap.cxx +++ b/source/framework/analysis/src/TRestDataSetGainMap.cxx @@ -527,12 +527,31 @@ void TRestDataSetGainMap::Module::SetSplits() { /// It uses the number of segments and the readout range to define the /// edges of the segments. void TRestDataSetGainMap::Module::SetSplitX() { - fSplitX.clear(); + if (fNumberOfSegmentsX < 1) { + RESTError << "SetSplitX: fNumberOfSegmentsX must be >=1." << p->RESTendl; + return; + } + std::set split; for (int i = 0; i <= fNumberOfSegmentsX; i++) { // <= so that the last segment is included double x = fReadoutRange.X() + ((fReadoutRange.Y() - fReadoutRange.X()) / (float)fNumberOfSegmentsX) * i; - fSplitX.insert(x); + split.insert(x); + } + SetSplitX(split); +} + +void TRestDataSetGainMap::Module::SetSplitX(const std::set& splitX) { + if (splitX.size() < 2) { + RESTError << "SetSplitX: split size must be >=2 (start and end of range must be included)." + << p->RESTendl; + return; } + if (!fSlope.empty()) + RESTWarning << "SetSplitX: changing split but current gain map and calibration paremeters correspond " + "to previous splitting. Use GenerateGainMap() to update them." + << p->RESTendl; + fSplitX = splitX; + fNumberOfSegmentsX = fSplitX.size() - 1; } ///////////////////////////////////////////// /// \brief Function to set the class members for segmentation of @@ -541,12 +560,31 @@ void TRestDataSetGainMap::Module::SetSplitX() { /// It uses the number of segments and the readout range to define the /// edges of the segments. void TRestDataSetGainMap::Module::SetSplitY() { - fSplitY.clear(); + if (fNumberOfSegmentsY < 1) { + RESTError << "SetSplitY: fNumberOfSegmentsY must be >=1." << p->RESTendl; + return; + } + std::set split; for (int i = 0; i <= fNumberOfSegmentsY; i++) { // <= so that the last segment is included double y = fReadoutRange.X() + ((fReadoutRange.Y() - fReadoutRange.X()) / (float)fNumberOfSegmentsY) * i; - fSplitY.insert(y); + split.insert(y); } + SetSplitY(split); +} + +void TRestDataSetGainMap::Module::SetSplitY(const std::set& splitY) { + if (splitY.size() < 2) { + RESTError << "SetSplitY: split size must be >=2 (start and end of range must be included)." + << p->RESTendl; + return; + } + if (!fSlope.empty()) + RESTWarning << "SetSplitY: changing split but current gain map and calibration paremeters correspond " + "to previous splitting. Use GenerateGainMap() to update them." + << p->RESTendl; + fSplitY = splitY; + fNumberOfSegmentsY = fSplitY.size() - 1; } ///////////////////////////////////////////// @@ -585,6 +623,9 @@ void TRestDataSetGainMap::Module::GenerateGainMap() { SetSplits(); + if (fSplitX.empty()) SetSplitX(); + if (fSplitY.empty()) SetSplitY(); + //--- Get the calibration range if not provided (default is 0,0) --- if (fCalibRange.X() >= fCalibRange.Y()) { // Get spectrum for this file