/*************************************************************************** PPrepFourier.cpp Author: Andreas Suter e-mail: andreas.suter@psi.ch ***************************************************************************/ /*************************************************************************** * Copyright (C) 2007-2025 by Andreas Suter * * andreas.suter@psi.ch * * * * This program 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 2 of the License, or * * (at your option) any later version. * * * * This program 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 received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "PPrepFourier.h" //-------------------------------------------------------------------------- // Constructor //-------------------------------------------------------------------------- /** * \brief Default constructor that initializes to default values. * * Sets background range to unused (-1, -1) and packing to 1 (no rebinning). * Background values and data must be set separately using setter methods. */ PPrepFourier::PPrepFourier() { fBkgRange[0] = -1; fBkgRange[1] = -1; fPacking = 1; } //-------------------------------------------------------------------------- // Constructor //-------------------------------------------------------------------------- /** * \brief Full constructor that initializes all configuration parameters. * * Creates a PPrepFourier instance with complete configuration. Background * can be specified either via range (for automatic calculation) or via * explicit values (one per data set to be added later). * * \param packing Rebinning factor (1=no rebinning, N=combine N bins) * \param bkgRange Background range [start, end] in bins (-1 if not used) * \param bkg Vector of explicit background values (one per data set) */ PPrepFourier::PPrepFourier(const Int_t packing, const Int_t *bkgRange, PDoubleVector bkg) : fPacking(packing) { SetBkgRange(bkgRange); SetBkg(bkg); } //-------------------------------------------------------------------------- // Destructor //-------------------------------------------------------------------------- /** * \brief Destructor that cleans up internal data structures. * * Clears both raw data and processed data vectors. Note that TH1F objects * returned by GetData() are owned by the caller and not cleaned up here. */ PPrepFourier::~PPrepFourier() { fRawData.clear(); fData.clear(); } //-------------------------------------------------------------------------- // SetBkgRange //-------------------------------------------------------------------------- /** * \brief Sets the background range for automatic background calculation. * * Specifies the bin range [start, end] for calculating background by averaging. * Values of -1 indicate that background range is not used (explicit background * values should be provided instead). Validates that both values are ≥-1 and * prints warnings if invalid values are encountered. * * \param bkgRange Array [start, end] with background bin range (both must be ≥-1) */ void PPrepFourier::SetBkgRange(const Int_t *bkgRange) { Int_t err=0; if (bkgRange[0] >= -1) { fBkgRange[0] = bkgRange[0]; } else { err = 1; } if (bkgRange[1] >= -1) { fBkgRange[1] = bkgRange[1]; } else { if (err == 0) err = 2; else err = 3; } TString errMsg(""); switch (err) { case 1: errMsg = TString::Format("start bkg range < 0 (given: %d), will ignore it.", bkgRange[0]); break; case 2: errMsg = TString::Format("end bkg range < 0 (given: %d), will ignore it.", bkgRange[1]); break; case 3: errMsg = TString::Format("start/end bkg range < 0 (given: %d/%d), will ignore it.", bkgRange[0], bkgRange[1]); break; default: errMsg = TString("??"); break; } if (err != 0) { std::cerr << std::endl << ">> PPrepFourier::SetBkgRange: **WARNING** " << errMsg << std::endl; } } //-------------------------------------------------------------------------- // SetBkg //-------------------------------------------------------------------------- /** * \brief Sets explicit background values for all data sets. * * Provides pre-calculated background values (one per data set) instead of * calculating them from a background range. This allows different background * values for each histogram. * * \param bkg Vector of background values (should match number of data sets) */ void PPrepFourier::SetBkg(PDoubleVector bkg) { for (UInt_t i=0; i 0) */ void PPrepFourier::SetPacking(const Int_t packing) { if (packing > 0) { fPacking = packing; } else { std::cerr << std::endl << ">> PPrepFourier::SetPacking: **WARNING** found packing=" << packing << " < 0, will ignore it." << std::endl; } } //-------------------------------------------------------------------------- // AddData //-------------------------------------------------------------------------- /** * \brief Adds a time-domain data set to the internal collection. * * Stores the raw data and metadata for later processing. Multiple data sets * can be added and will be processed together by DoBkgCorrection(), DoPacking(), * and DoLifeTimeCorrection(). * * \param data musrFT_data structure containing raw histogram data and metadata */ void PPrepFourier::AddData(musrFT_data &data) { fRawData.push_back(data); } //-------------------------------------------------------------------------- // DoBkgCorrection //-------------------------------------------------------------------------- /** * \brief Applies background correction to all data sets. * * Subtracts background from each data point. The background can be specified * in two ways: * 1. Background range: Calculates average over the specified bin range * 2. Explicit values: Uses the background values set via SetBkg() * * If fData is not yet initialized, calls InitData() first. If neither * background range nor explicit values are provided, no correction is applied. * Validates that background range is within data bounds and that the number * of explicit background values matches the number of data sets. */ void PPrepFourier::DoBkgCorrection() { // make sure fData are already present, and if not create the necessary data sets if (fData.size() != fRawData.size()) { InitData(); } // if no bkg-range is given, nothing needs to be done if ((fBkgRange[0] == -1) && (fBkgRange[1] == -1) && (fBkg.size() == 0)) { return; } if ((fBkgRange[0] != -1) && (fBkgRange[1] != -1)) { // background range is given // make sure that the bkg range is ok for (UInt_t i=0; i= static_cast(fRawData[i].rawData.size())) || (fBkgRange[1] >= static_cast(fRawData[i].rawData.size()))) { std::cerr << std::endl << "PPrepFourier::DoBkgCorrection() **ERROR** bkg-range out of data-range!"; return; } } Double_t bkg=0.0; for (UInt_t i=0; i background " << i << ": " << bkg << std::endl; // correct data for (UInt_t j=0; jfew kGauss) where depolarization * is minimal, but may be less accurate for low fields where significant * relaxation occurs during the muon lifetime. * * If fData is not yet initialized, calls InitData() first. Background correction * should typically be applied before lifetime correction. * * \param fudge Rescaling factor for estimated N0 (typically ~1.0, allows fine-tuning) */ void PPrepFourier::DoLifeTimeCorrection(Double_t fudge) { // make sure fData are already present, and if not create the necessary data sets if (fData.size() != fRawData.size()) { InitData(); } // calc exp(+t/tau)*N(t), where N(t) is already background corrected Double_t scale; for (UInt_t i=0; i PPrepFourier::GetData() { std::vector data; data.resize(fData.size()); // if not data are present, just return an empty vector if (fData.size() == 0) return data; TString name(""); Double_t dt=0.0; Double_t start=0.0; Double_t end=0.0; UInt_t size; UInt_t startIdx; UInt_t endIdx; for (UInt_t i=0; i((end-start)/dt); if (start >= 0.0) { startIdx = static_cast(start/dt)+1; endIdx = static_cast(end/dt)+1; } else { std::cerr << std::endl << ">> PPrepFourier::GetData **WARNING** found start time < 0.0, will set it to 0.0" << std::endl; endIdx = static_cast(end/dt)+1; } } if (start == -1.0) { // no time range given, hence start == 0.0 - dt/2 start = -dt/2.0; } else { // time range given start -= dt/2.0; } if (end == -1.0) { // no time range given, hence end == (fData[idx].size()-1)*dt + dt/2 end = (fData[i].size()-1)*dt+dt/2.0; } else { // time range given end += dt/2.0; } data[i] = new TH1F(name.Data(), fRawData[i].info.Data(), size, start, end); for (UInt_t j=startIdx; jSetBinContent(j-startIdx+1, fData[i][j]); } return data; } //-------------------------------------------------------------------------- // GetData //-------------------------------------------------------------------------- /** * \brief Creates a ROOT histogram for a specific processed data set. * * Converts a single processed data set into a TH1F histogram with proper * time binning. The time range, bin width, and histogram title are taken * from the musrFT_data metadata. The packing factor is applied to the * time resolution. * * \param idx Data set index * \return TH1F pointer (caller owns and must delete), or nullptr if idx out of range * * \note The caller is responsible for deleting the returned histogram. * \note Returns nullptr if no data is present or idx is out of range. */ TH1F *PPrepFourier::GetData(const UInt_t idx) { if (fData.size() == 0) // no data present return nullptr; if (idx > fData.size()) // requested index out of range return nullptr; TString name = TString::Format("histo%2d", idx); Double_t dt = fRawData[idx].timeResolution*fPacking; Double_t start = fRawData[idx].timeRange[0]; Double_t end = fRawData[idx].timeRange[1]; UInt_t size = fData[idx].size(); UInt_t startIdx = 1; UInt_t endIdx = size; // time range given, hence calculate the proper size if (start != -1.0) { size = static_cast((end-start)/dt); if (start >= 0.0) { startIdx = static_cast(start/dt)+1; endIdx = static_cast(end/dt)+1; } else { std::cerr << std::endl << ">> PPrepFourier::GetData **WARNING** found start time < 0.0, will set it to 0.0" << std::endl; endIdx = static_cast(end/dt)+1; } } if (start == -1.0) { // no time range given, hence start == 0.0 - dt/2 start = -dt/2.0; } else { // time range given start -= dt/2.0; } if (end == -1.0) { // no time range given, hence end == (fData[idx].size()-1)*dt + dt/2 end = (fData[idx].size()-1)*dt+dt/2.0; } else { // time range given end += dt/2.0; } TH1F *data = new TH1F(name.Data(), fRawData[idx].info.Data(), size, start, end); for (UInt_t i=startIdx; iSetBinContent(i-startIdx+1, fData[idx][i]); return data; } //-------------------------------------------------------------------------- // InitData (private) //-------------------------------------------------------------------------- /** * \brief Initializes processed data by copying from raw data starting at t0. * * Creates the fData vectors from fRawData, starting from the t0 bin for each * data set. This effectively removes pre-t0 bins and creates a working copy * for subsequent processing (background correction, packing, lifetime correction). * * If t0 is negative or not set, data is copied from bin 0. This method is * called automatically by processing methods if fData is not yet initialized. */ void PPrepFourier::InitData() { fData.resize(fRawData.size()); UInt_t t0; for (UInt_t i=0; i= 0) t0 = fRawData[i].t0; else t0 = 0; for (UInt_t j=t0; j