Skip to content
Snippets Groups Projects
CutAutoDEE_FineTuning.C 25.27 KiB
#include "CutAutoDEE_FineTuning.h"
#include <numeric>
#include <set>
#include <iostream>
#include <unordered_map>

// Important variable
#define STEP  2;        // Step to parse through the histogram in X
#define BINSIZE 10;    // Size of the bin in X on which to projectY the plot
#define THRESHOLDY 75.0;
#define THRESHOLDX 200.0;

//Main
void CutAutoDEE_FineTuning(){

    // Retrieve initial histogram
    TFile *inFile = new TFile("Output/DE_E_merged.root","read");
    TH2F *hDE_E = (TH2F*)inFile->Get("DE_E");
    // Inverse data to use tspectrum
    for (int xBin=1; xBin<= hDE_E->GetNbinsX();xBin++){
        for (int yBin=1; yBin<= hDE_E->GetNbinsY();yBin++){
        hDE_E->SetBinContent(xBin,yBin , -hDE_E->GetBinContent(xBin,yBin) );
        }
    }

    //Store result of peak finding
    //This is not a good way to do this : We should normalize the structure of the code by
    //directly using a PeakData class to store each of the peak.
    vector<int> *vPeaks = new vector<int>() ;
    vector<TSpectrum*> *vPeakFinder = new vector<TSpectrum*>();
    vector<vector<Double_t>> *LinePos = new vector<vector<Double_t>> ;

    // Tspectrum used to find the peak : If you have problem with peak finding
    // adjust the setting in this function 
    // Step and Binsize are used in this function
    Double_t sigma = 3;
    Double_t threshold = 0.0001;
    PeakFinder(vPeakFinder,vPeaks,hDE_E,LinePos,sigma,threshold);


    // Link the peak, this is where the most critical variable are.
    // Specifically the maximum distance between each peak and the minimum number of peak in a line 
    vector<TCutG*> CutLine =  PeakLinker(LinePos);

    // Inverse data for plotting
    for (int xBin=1; xBin<= hDE_E->GetNbinsX();xBin++){
        for (int yBin=1; yBin<= hDE_E->GetNbinsY();yBin++){
        hDE_E->SetBinContent(xBin,yBin , -hDE_E->GetBinContent(xBin,yBin));
        }
    }
    
    //Plot
    gStyle->SetPalette(kRainBow);
    TCanvas* c2 = new TCanvas("c2","c2");
    hDE_E->Draw("colz");

    TCanvas* c3 = new TCanvas("c3","c3");
    hDE_E->Draw("colz");
    for (auto cut : CutLine){
        cut->Draw("SAME");
    }
    
    //Save the cut !
    TFile *ofile = new TFile("Output/CutAutoZtest.root","RECREATE");
    for (auto cut : CutLine){
        cut->Write();
    }
    ofile->Close();
}
/** 
  *  Find the peak of an histogram by subdivinding the histogram on it's x axis.
  *  Store the number of peak and their position in different vector
  *
  *  input and Output:
  *     vector<TSpectrum*> *sPeak : Vector of TSpectrum to output
  *     vector<int> *vPeak        : Vector of number of peak for each bin range
  *     vector<vector<Double_t>> *LinePos : Vector of position of each peak for each bin range 
  */
void PeakFinder(vector<TSpectrum*> *sPeak , vector<int> *vPeak, TH2F *h, vector<vector<Double_t>> *LinePos, Double_t sigma, Double_t threshold){

    // Initialising
    int nbinsX = h->GetNbinsX();
    int iter = 0;
    int stepParsing = STEP;
    int SizeBin = BINSIZE;

    // Looping on the histogram x axis.
    for (int i = 1; i <= nbinsX; i += stepParsing) {

        // Limit in binnumber
        iter +=1;
        int x1 = i;
        int x2 = std::min(i + SizeBin - 1, nbinsX);

        // Projection on Y between the binnumber
        TString projName = Form("projY_%d_to_%d", x1, x2);
        TH1D* projY = h->ProjectionY(projName, x1, x2);
        projY->SetTitle(Form("Projection Y: bins %d to %d", x1, x2));

        // PeakFinder
		TSpectrum *PeakFinder = new TSpectrum(600); 
		int nPeaks = PeakFinder->Search(projY,sigma,"",threshold);
      
        //Store npeaks and tspectrum
        vPeak->push_back(nPeaks); // number of peak for this bin range
        sPeak->push_back(PeakFinder);

        //Save the position of each peak
        Double_t * x = PeakFinder->GetPositionX();
        
        for(int peaks = 0 ; peaks < nPeaks ; peaks++){
            vector<Double_t> Position(2); 
            Position.at(0) = (double(i) + double(SizeBin)/2.0) * h->GetXaxis()->GetBinWidth(4) ; //Retrieve E position as the center
                                                                                              //of the bin
            Position.at(1) = PeakFinder->GetPositionX()[peaks]; // Position in DE
            LinePos->push_back(Position);
        }


        //Test
        if (true && iter ==50){
            // Draw the histogram
            TCanvas* c = new TCanvas(Form("c%d",i), Form("Histogram with E = %f",(h->GetXaxis()->GetBinCenter(i))), 800, 600);
            projY->Draw();
        }

    }
}

/** 
  *  Link the different point to create TCUT from the peak found previously
  *  To do so a max range is given and then all point separated by a distance inferior to it are linked.
  *
  *  Input :
  *     vec<vec<Double>> *LinePos : pointer to a vector of length number of peak holding position in x and y for each peak
  *  Output:
  *    TCUTG : a cut for each charge in vamos
  */
vector<TCutG*> PeakLinker(vector<vector<Double_t>> *LinePos){

    //Retrieve position of peaks
    vector<Double_t> X , Y ;
    for (int ipeak = 0; ipeak<LinePos->size() ; ipeak ++){
        X.push_back(LinePos->at(ipeak).at(0));  
        Y.push_back(LinePos->at(ipeak).at(1));
    }

    //===========================================================================================================
    //                             1. Find the threshold between two point
    //===========================================================================================================
  
    // Find the distance between two point in X.
    //Sort the vec
    vector<Double_t> XSub(X); 
    sort(XSub.begin(),XSub.end());    

	//Take only the unique element 
    set<Double_t> SetX(XSub.begin() , XSub.end());
    vector<Double_t> XThresh ;
    for (Double_t elem : SetX) {
        XThresh.push_back(elem);
    } 

	//Find the average dist in X
    vector<Double_t> DiffX = diff(XThresh);
    Double_t ThresholdXestim ;
    Double_t ThresholdX ;
	for (int i =0 ; i<DiffX.size() ; i++) { ThresholdXestim += DiffX.at(i);}
	ThresholdXestim = ThresholdXestim / DiffX.size();	


    // In Y the threshold is not easily findable, However it's critical that the threshold 
    // is superior to the average distance in X. If not line can't be found by construction
    // The threshold should also be inferior to the distance between each line in Y
    // To do so adjust the bin STEP at the beginning of the code.

    //Very important set
    ThresholdX = THRESHOLDX;
    Double_t ThresholdY = THRESHOLDY;
    cout << "Threshold X : "  << ThresholdX  << endl;
    cout << "Threshold X approx : "  << ThresholdXestim  << endl;
    cout <<   "N of X "       << SetX.size() << endl;
    cout << "ThresholdY " << ThresholdY << endl;

    //===========================================================================================================
    //                                           2.LINK POINT
    //===========================================================================================================
   
    // Now that the threshold is found we will link the point by comparing each point 
    // Notice that if two point have the same X they can't be linked 

    // Create a vector holding the id of each peak
    int ID = 0 ; // Initial Id of the first line
    vector<int> vID(LinePos->size(), -1000);

    // We want to loop on the UNIDENTIFIED peak.
    for (int ipeak = 0 ; ipeak < LinePos->size() ; ipeak ++){

        //Here do ID manipulation
        //Create a set of ID without the unidentified peak
        set<int> SetId(vID.begin() , vID.end());
        if (SetId.size() > 1 ) {SetId.erase(-1000) ;  ID = *prev(SetId.end()) + 1 ;}

        //If the current peak is identified set newID to it's ID
        if (vID.at(ipeak) != -1000) ID = vID.at(ipeak); // If the point is identified propagate it!

        Double_t XPosPeak = LinePos->at(ipeak).at(0);
        Double_t YPosPeak = LinePos->at(ipeak).at(1);

        // Parse through all the next peak
        // This could be done faster by sorting the list by X and Y before and stoping after a threshold
        // For now it'll do just fine
        for (int ipeakCompare = ipeak + 1  ; ipeakCompare < LinePos->size() ; ipeakCompare ++){
           
            if ((vID.at(ipeakCompare) != -1000) || (XPosPeak == LinePos->at(ipeakCompare).at(0)))  continue; // If the point is identified skip it !
            
            //Calculate the distance between the two peak
            Double_t XPosPeakComp = LinePos->at(ipeakCompare).at(0);
            Double_t YPosPeakComp = LinePos->at(ipeakCompare).at(1);
            Double_t dist = sqrt( pow(abs(XPosPeak-XPosPeakComp),2) +  pow(abs(YPosPeak-YPosPeakComp),2) );
            Double_t distX = sqrt( pow(abs(XPosPeak-XPosPeakComp),2) );
            Double_t distY = sqrt(  pow(abs(YPosPeak-YPosPeakComp),2) );

            //If the distance is inferior to the threshold then set both ID to the new one !
            if ((distX < ThresholdX )&& (distY <ThresholdY )) {
                vID.at(ipeak) = ID ;
                vID.at(ipeakCompare) = ID ;                    
            } // end threshold if 
        } // end compare
    }   // end loop peak

    //Check if needed
    /*
       for (int i=0 ; i<vID.size() ; i++){
       if (vID.at(i)!=-1000){
       cout << vID.at(i) << " , ";
       }
       }
       */




    // Here we flush all the id that have less element than a threshold value
    //===========================================================================================================
    //                                 3. Flush line with not enough value
    //===========================================================================================================
   
    //This is a pretty sensitive variable 
    int MinElem = SetX.size()/4;
    cout << "Min Elem " << MinElem << endl ;


    set<int> SetIdTemp(vID.begin() , vID.end());
    for (int Id : SetIdTemp){
        int NId = 0 ;
        for (int ipeak = 0 ; ipeak < vID.size() ; ipeak ++) {
            if (vID.at(ipeak) == Id) NId += 1 ;
        }
        if ( NId < MinElem ) {

            for (int ipeak = 0 ; ipeak < vID.size() ; ipeak ++) {
                if (vID.at(ipeak) == Id) vID.at(ipeak) = -1000 ;
            }

        }
    }

    //===========================================================================================================
    //                                    4. TCutG Creator
    //===========================================================================================================

    // We now have identified each peak to a id that correspond to a line in the DE E
    // Now we need to link the ADJACENT line and then transform those point into tcutg

    set<int> SetId(vID.begin() , vID.end()); // This is all the unique element of vID 
    vector<int> IndexPeak(LinePos->size());
    for (int ipeak = 0 ; ipeak < LinePos->size() ; ipeak ++){
        IndexPeak.at(ipeak) = ipeak ;
    }

    //===========================================================================================================
    //                      4.1 Sort by id
    //===========================================================================================================
    // The next part of the code SHOULD sort by meanY but does not.
    // It therefore only serve as a convoluted way to sort by ID.
    // We need to sort by id either way so no need to rewrite i
    
    vector<Double_t> MeanYId(SetId.size()) ;
    vector<Double_t> NelemYId(SetId.size()) ;

    //Loop on the element of the set
    for (int Id = 0 ; Id < SetId.size() ; Id ++){
        Double_t Sum = 0 , Npeak = 1;
        auto itSet = SetId.begin();
        advance(itSet,Id);

        //Loop on all the peak
        for (int ipeak = 0 ; ipeak < LinePos->size() ; ipeak ++){

            // If the ID of the peak correspond to one of the set then increment his Y and NPeak value
            if (vID.at(ipeak) == *itSet )
            {
                Npeak += 1 ; 
                Sum += LinePos->at(ipeak).at(1);
            } // End if Id 
        } // Loop on Peak

        NelemYId.at(Id) = Npeak ;
        MeanYId.at(Id) = Sum /Npeak ;
        // cout << Id << "  " << MeanYId.at(Id) << endl;
    } // Loop on Id

    //Now we will sort the MeanY vector and return a vector holding the ID sorted 
    vector<size_t> IndexMeanY(MeanYId.size());
    iota(IndexMeanY.begin(),IndexMeanY.end(),0);
    sort(IndexMeanY.begin(),IndexMeanY.end() , [&](size_t i, size_t j) {
            return MeanYId.at(i) < MeanYId.at(j);
            });

    // Now we have to sort the SetId according to this index ordering
    // Since set can't be sorted we need to copy it to a vector
    vector<int> vSetId(SetId.begin(), SetId.end());
    vector<int> SortedSetId(SetId.begin(), SetId.end());
    for (int Index = 0  ; Index < IndexMeanY.size() ; Index ++){ SortedSetId.at(IndexMeanY.at(Index)) = vSetId.at(Index) ;}

    /*
       cout << endl << " SORTED" <<endl ; 
       for (int i = 0 ; i<IndexMeanY.size() ; i++){
       if (IndexMeanY.at(i) == 49 || IndexMeanY.at(i) == 32){
       cout << NelemYId.at(IndexMeanY.at(i)) << endl; 
       }
       cout << IndexMeanY.at(i) << "  " << SortedSetId.at(i) << "   "  << MeanYId.at(IndexMeanY.at(i)) << endl;
       }

       cout << endl << " *********************" <<endl ; 
       */
    
    //===========================================================================================================
    //                         4.2 Recompacting data into a struct
    //===========================================================================================================
    

    // We now have the different lines sorted by Id we just need to link them together by group of two.
    // to do so we will sort each id by x and then link them

    // 1st step is to sort the original IndexPeak according to the MeanY sort
    // To do so we need to sort the vID vector according to meanY and apply this transformation to IndexPeak too

    // vID as the ID of each peak -> correspondance of peak to a line
    // IndexPeak                  -> index of each of these peak of size number of peak
    // SortedSetId                -> Set of sorted ID of peak according to mean y
    
    //Create a map for the sort of ID
    unordered_map<int,int> order_map ;
    for (size_t i = 0 ; i < SortedSetId.size() ; i++  ) {
        order_map[SortedSetId[i]] = i ; 
    }

    // Combine Index and ID into a single vector of pair
    vector<pair<int,int>> PeakId ;       
    for(size_t i = 0 ; i < IndexPeak.size() ; i++){
        PeakId.emplace_back(IndexPeak.at(i), vID.at(i));
    }

    // Sort the PeakId on the custom order of IDs 
    sort(PeakId.begin(), PeakId.end(), [&order_map](const auto& a, const auto& b){
            return order_map[a.second] < order_map[b.second];
            } );

    //2nd step is to take the original Position vector and sort it thanks to PeakId

    vector<vector<Double_t>> LinePosSorted = *LinePos ;

    //Swap LinePosSorted elemn according to Index of PeakID
    for (int i = 0 ; i < PeakId.size() ; i++ ){
        LinePosSorted.at(i) = LinePos->at(PeakId.at(i).first);
    }    

    // 3rd step is to compile x y id and index in one structure

    if (PeakId.size() != LinePos->size() ){
        cerr << "Vector not same size" << endl; 
    }

    vector<PeakData> PeakStruct ;
    for (size_t i=0 ; i<LinePosSorted.size() ; i++){
        PeakData TempPeak = {LinePosSorted.at(i).at(0), LinePosSorted.at(i).at(1) , PeakId.at(i).second , PeakId.at(i).first};
        PeakStruct.push_back(TempPeak);
    }

    //===========================================================================================================
    //                          4.3 Cleanupi and sortY
    //===========================================================================================================
    // We drop all non clustered element and sort the whole struct by the mean value of Y for their ID.

    // 4th step is to drop all element = -1000
    //

    auto groupBegin = find_if(PeakStruct.begin(),PeakStruct.end(),[&](const PeakData& elem){
            return elem.LineId == -1000 ;
            });

    auto groupEnd = find_if(groupBegin,PeakStruct.end(),[&](const PeakData& elem){
            return elem.LineId != -1000 ;
            });

    PeakStruct.erase(groupBegin,groupEnd);

    //4.1 step we will resort by mean y because it didnt work before , probably due to data structure 

    auto ItMeanY = PeakStruct.begin();
    vector<int> SortedId ;
    vector<Indexed2D> MeanY ;
    int index = 0;

    while (ItMeanY != PeakStruct.end()){
        //Get position of end of the current id
        auto IdEnd = find_if(ItMeanY, PeakStruct.end() , [&](const PeakData& elem){
                return elem.LineId != ItMeanY->LineId; 
                });

        // Fill MeanY of the current Id 

        Double_t Sum  = 0;
        Double_t Npts = 0;

        for (auto i = ItMeanY ; i!=IdEnd ; i++){
            Sum += i->Y;        
            Npts += 1;        
        }

        MeanY.push_back({Sum/Npts ,ItMeanY->LineId  , index });

        index +=1;
        ItMeanY = IdEnd;
    }

    // We now have a all the mean Y of each ID and their index
    // Let's now sort the id from this meanY

    sort(MeanY.begin(),MeanY.end(),[&](Indexed2D a , Indexed2D b){
            return a.x < b.x;
            });

    unordered_map<int,size_t> ordered_map ;
    for (size_t i=0 ; i<MeanY.size() ; i++){
        ordered_map[MeanY.at(i).id]=i;
    }


    sort(PeakStruct.begin(),PeakStruct.end(), [&ordered_map](const PeakData& a , const PeakData &b) {
            return ordered_map[a.LineId]<ordered_map[b.LineId];
            });

    //===========================================================================================================
    //              4.4  Sort by X alternating order
    //===========================================================================================================
    // We sort by X in alternating order because a TCutG need the point to be aligned to link them together

    // Iterate and sort groups with the same LineId
    auto it = PeakStruct.begin();// create an iterator that point to the elements of the PeakData
    bool Oddness = true;             // to know in which way sort
    while (it != PeakStruct.end()){

        // Find range where id is the same
        // This return the first iterator where the condition is fulfilled 
        auto groupEnd = find_if(it,PeakStruct.end(),[lineId = it->LineId](const PeakData& elem){
                return elem.LineId != lineId ;
                });

        // Now we sort this whole group by x

        sort(it,groupEnd,[Oddness](const PeakData& a , const PeakData& b){
                if (Oddness) return a.X < b.X ;         
                if (!Oddness) return a.X > b.X ;        
                else return a.X < b.X;
                });

        // Move to next group
        it = groupEnd;
        Oddness = not(Oddness) ;
    }

    // works ! 
    /*
    for (int i : SortedSetId) {cout << i << endl;}

    int tempId= -1000;
    for (PeakData i : PeakStruct) {
        if (tempId != i.LineId  ){
            tempId = i.LineId;
            cout << " Id " << i.LineId << " Y  " << i.Y << endl;
        }
    } 
    */
    //===========================================================================================================
    //              4.5 Create TCut 
    //===========================================================================================================
    // We need to make sublist for each 2 lines

    // We will iterate through our data and create substructure for each adjacent pair of LineID
    // and store them in a vector of TCutG

    vector<TCutG*> Res ;

    it = PeakStruct.begin();
    int iteration = 0;


    while(it != PeakStruct.end()){

        // 1st line
        int LineIdBottom = it->LineId;
        auto BottomLine = find_if(it,PeakStruct.end(), [&] (const PeakData& elem){
                return elem.LineId != LineIdBottom ;
                }) ;

        //2nd line
        int LineIdTop = BottomLine->LineId;
        auto TopLine = find_if(BottomLine,PeakStruct.end(), [&] (const PeakData& elem){
                return elem.LineId != LineIdTop ;
                }) ;

        //cout << LineIdBottom << "  " << LineIdTop << endl;

        // first we need to find the shortest line
        int SizeBottom = distance(it,BottomLine);
        int SizeTop = distance(BottomLine,TopLine);

        //Subvector with the longest line first then the sortest
        vector<PeakData> CutVec;
        vector<PeakData> LongVec;
        vector<PeakData> ShortVec;

        if(SizeBottom >= SizeTop ){
            CutVec.insert(CutVec.end(),it,TopLine);
            LongVec.insert(LongVec.end(),it,BottomLine);
            ShortVec.insert(ShortVec.end(),BottomLine,TopLine);
        }

        else {
            CutVec.insert(CutVec.end(),BottomLine,TopLine);
            CutVec.insert(CutVec.end(),it,BottomLine);
            LongVec.insert(LongVec.end(),BottomLine,TopLine);
            ShortVec.insert(ShortVec.end(),it,BottomLine);
        }
        


        //nbr of point
        int Npoints = CutVec.size(); 
        //Close the loop by finding the closest point to the last one
        int closestIndexLast = 0;
        int closestIndexBegin = LongVec.size()-1;
        int SizeArray = 0;
        if (ShortVec.size() == 0) {
            closestIndexLast = CutVec.size(); 
            SizeArray = CutVec.size();
        }
        else{
            PeakData lastPoint = ShortVec.back();
            double minDistance = distancePeak(lastPoint,LongVec.at(0));
            for (size_t i = 1 ; i < LongVec.size(); i++){
                double dist = distancePeak(lastPoint,LongVec.at(i));
                if(dist<minDistance){
                    minDistance = dist;
                    closestIndexLast = i;
                }
            }
            PeakData firstPoint = ShortVec.at(0);
            double minDistanceFirst = distancePeak(firstPoint,LongVec.at(0));
            for (size_t i = 1 ; i < LongVec.size(); i++){
                double dist = distancePeak(firstPoint,LongVec.at(i));
                if(dist<minDistanceFirst){
                    minDistanceFirst = dist;
                    closestIndexBegin = i;
                }
            }
            SizeArray = ShortVec.size() + (closestIndexBegin - closestIndexLast);
        }
        //Create array to hold position
        double *x = new double[SizeArray + 1 ];
        double *y = new double[SizeArray + 1 ];

        if (SizeTop == 0){

            for(int i = 0 ; i<CutVec.size() ; i++) {
                x[i] = CutVec.at(i).X ;
                y[i] = CutVec.at(i).Y ;
            }
            x[SizeArray ] = CutVec.at(0).X;
            y[SizeArray ] = CutVec.at(0).Y;

        }

        else {
            for(int i = 0 ; i<ShortVec.size() ; i++) {
                x[i] = ShortVec.at(i).X ;
                y[i] = ShortVec.at(i).Y ;
            }

            for(int i = closestIndexLast ; i<=closestIndexBegin ; i++) {
                x[i + ShortVec.size() - closestIndexLast] = LongVec.at(i).X ;
                y[i + ShortVec.size() - closestIndexLast ] = LongVec.at(i).Y ;
            }



            x[SizeArray] = ShortVec.at(0).X;
            y[SizeArray] = ShortVec.at(0).Y;

        }
        //Saving and moving on

        if (Npoints >10 && (TopLine != PeakStruct.end())){ //Dont link last line to itself 
            TCutG* Cut = new TCutG(Form("Cut %d",iteration) , SizeArray+1, x , y);
            Cut->SetLineWidth(2);
            Cut->SetLineColor(kRed);
            Res.push_back(Cut);
        }
        iteration += 1 ;
        it = BottomLine ;
    } // end while

    return Res;
}


vector<Double_t> diff(const vector<Double_t>& input){
    vector<Double_t> result;
    if (input.size() < 2) return result; // Return empty vector if size < 2

    for (size_t i = 1; i < input.size(); ++i) {
        result.push_back(input[i] - input[i - 1]);
    }
    return result;
}

void SortAndRenameTCutG() {
    TIter next(gROOT->GetListOfSpecials());
    std::vector<std::pair<int, TCutG*>> cuts;

    TObject *obj;
    while ((obj = next())) {
        TCutG *cut = dynamic_cast<TCutG*>(obj);
        if (cut) {
            std::string name = cut->GetName();
            if (name.rfind("Cut", 0) == 0) { // Check if name starts with "Cut"
                try {
                    int num = std::stoi(name.substr(3));
                    cuts.emplace_back(num, cut);
                } catch (...) {
                    continue; // Ignore malformed names
                }
            }
        }
    }

    // Sort by extracted number
    std::sort(cuts.begin(), cuts.end(), [](const auto &a, const auto &b) {
            return a.first < b.first;
            });

    // Rename sequentially
    for (size_t i = 0; i < cuts.size(); ++i) {
        cuts[i].second->SetName(Form("Cut %zu", i));
    }

    std::cout << "TCutG objects renamed from original numbering to 0:N.\n";
}
void SaveTCutGToFile() {
    TFile file("Output/CutAutoZ.root", "RECREATE");
    if (!file.IsOpen()) {
        std::cerr << "Error: Could not open Output/CutAutoZ.root for writing.\n";
        return;
    }

    TIter next(gROOT->GetListOfSpecials());
    TObject *obj;
    while ((obj = next())) {
        TCutG *cut = dynamic_cast<TCutG*>(obj);
        if (cut) {
            cut->Write();
        }
    }

    file.Close();
    std::cout << "All TCutG objects saved to Output/CutAutoZ.root.\n";
}

double distancePeak(const PeakData& p1, const PeakData& p2) {
    return abs(p1.X - p2.X);
}