Skip to content
Snippets Groups Projects
Forked from Corryvreckan / Corryvreckan
681 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
AnalysisEfficiency.cpp 40.42 KiB
/**
 * @file
 * @brief Implementation of [AnalysisEfficiency] module
 *
 * @copyright Copyright (c) 2017-2022 CERN and the Corryvreckan authors.
 * This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
 * In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
 * Intergovernmental Organization or submit itself to any jurisdiction.
 * SPDX-License-Identifier: MIT
 */

#include "AnalysisEfficiency.h"

#include "objects/Cluster.hpp"
#include "objects/Pixel.hpp"
#include "objects/Track.hpp"

using namespace corryvreckan;

AnalysisEfficiency::AnalysisEfficiency(Configuration& config, std::shared_ptr<Detector> detector)
    : Module(config, detector) {
    m_detector = detector;

    config_.setDefault<double>("time_cut_frameedge", Units::get<double>(20, "ns"));
    config_.setDefault<double>("chi2ndof_cut", 3.);
    config_.setDefault<double>("inpixel_bin_size", Units::get<double>(1.0, "um"));
    config_.setDefault<XYVector>("inpixel_cut_edge", {Units::get(5.0, "um"), Units::get(5.0, "um")});
    config_.setDefault<double>("masked_pixel_distance_cut", 1.);
    config_.setDefault<double>("spatial_cut_sensoredge", 1.);
    config_.setDefault<double>("fake_rate_radius", -1.);
    config_.setDefault<double>("fake_rate_sensoredge", -1);
    config_.setDefault<int>("n_charge_bins", 1000);
    config_.setDefault<double>("charge_histo_range", 1000.0);

    m_timeCutFrameEdge = config_.get<double>("time_cut_frameedge");
    m_chi2ndofCut = config_.get<double>("chi2ndof_cut");
    m_inpixelBinSize = config_.get<double>("inpixel_bin_size");
    require_associated_cluster_on_ = config_.getArray<std::string>("require_associated_cluster_on", {});
    m_inpixelEdgeCut = config_.get<XYVector>("inpixel_cut_edge");
    m_maskedPixelDistanceCut = config_.get<int>("masked_pixel_distance_cut");
    spatial_cut_sensoredge = config_.get<double>("spatial_cut_sensoredge");
    m_fake_rate_radius = config_.get<double>("fake_rate_radius");
    m_fake_rate_sensoredge = config_.get<double>("fake_rate_sensoredge");
    m_n_charge_bins = config_.get<int>("n_charge_bins");
    m_charge_histo_range = config_.get<double>("charge_histo_range");
}
void AnalysisEfficiency::initialize() {

    hPixelEfficiency = new TH1D(
        "hPixelEfficiency", "hPixelEfficiency; single pixel efficiency; # entries", 201, 0, 1.005); // get 0.5%-wide bins

    hPixelEfficiencyMatrix = new TH1D("hPixelEfficiencyMatrix",
                                      "hPixelEfficiencyMatrix; single pixel efficiency; # entries",
                                      201,
                                      0,
                                      1.005); // get 0.5%-wide bins

    auto pitch_x = static_cast<double>(Units::convert(m_detector->getPitch().X(), "um"));
    auto pitch_y = static_cast<double>(Units::convert(m_detector->getPitch().Y(), "um"));

    auto nbins_x = static_cast<int>(std::ceil(m_detector->getPitch().X() / m_inpixelBinSize));
    auto nbins_y = static_cast<int>(std::ceil(m_detector->getPitch().Y() / m_inpixelBinSize));
    if(nbins_x > 1e4 || nbins_y > 1e4) {
        throw InvalidValueError(config_, "inpixel_bin_size", "Too many bins for in-pixel histograms.");
    }
    std::string title =
        m_detector->getName() + " Pixel efficiency map;in-pixel x_{track} [#mum];in-pixel y_{track} #mum;#epsilon";
    hPixelEfficiencyMap_trackPos_TProfile = new TProfile2D("pixelEfficiencyMap_trackPos_TProfile",
                                                           title.c_str(),
                                                           nbins_x,
                                                           -pitch_x / 2.,
                                                           pitch_x / 2.,
                                                           nbins_y,
                                                           -pitch_y / 2.,
                                                           pitch_y / 2.,
                                                           0,
                                                           1);
    hPixelEfficiencyMap_trackPos = new TEfficiency("pixelEfficiencyMap_trackPos",
                                                   title.c_str(),
                                                   nbins_x,
                                                   -pitch_x / 2.,
                                                   pitch_x / 2.,
                                                   nbins_y,
                                                   -pitch_y / 2.,
                                                   pitch_y / 2.);
    hPixelEfficiencyMap_trackPos->SetDirectory(this->getROOTDirectory());

    title = m_detector->getName() +
            " Pixel efficiency map (in-pixel ROI);in-pixel x_{track} [#mum];in-pixel y_{track} #mum;#epsilon";
    hPixelEfficiencyMap_inPixelROI_trackPos_TProfile = new TProfile2D("pixelEfficiencyMap_inPixelROI_trackPos_TProfile",
                                                                      title.c_str(),
                                                                      nbins_x,
                                                                      -pitch_x / 2.,
                                                                      pitch_x / 2.,
                                                                      nbins_y,
                                                                      -pitch_y / 2.,
                                                                      pitch_y / 2.,
                                                                      0,
                                                                      1);

    title = m_detector->getName() + " Chip efficiency map;x [px];y [px];#epsilon";
    hChipEfficiencyMap_trackPos_TProfile = new TProfile2D("chipEfficiencyMap_trackPos_TProfile",
                                                          title.c_str(),
                                                          m_detector->nPixels().X(),
                                                          -0.5,
                                                          m_detector->nPixels().X() - 0.5,
                                                          m_detector->nPixels().Y(),
                                                          -0.5,
                                                          m_detector->nPixels().Y() - 0.5,
                                                          0,
                                                          1);
    hChipEfficiencyMap_trackPos = new TEfficiency("chipEfficiencyMap_trackPos",
                                                  title.c_str(),
                                                  m_detector->nPixels().X(),
                                                  -0.5,
                                                  m_detector->nPixels().X() - 0.5,
                                                  m_detector->nPixels().Y(),
                                                  -0.5,
                                                  m_detector->nPixels().Y() - 0.5);
    hChipEfficiencyMap_trackPos->SetDirectory(this->getROOTDirectory());
    title = m_detector->getName() + " Pixel efficiency matrix;x [px];y [px];#epsilon";
    hPixelEfficiencyMatrix_TProfile = new TProfile2D("hPixelEfficiencyMatrixTProfile",
                                                     title.c_str(),
                                                     m_detector->nPixels().X(),
                                                     -0.5,
                                                     m_detector->nPixels().X() - 0.5,
                                                     m_detector->nPixels().Y(),
                                                     -0.5,
                                                     m_detector->nPixels().Y() - 0.5,
                                                     0,
                                                     1);

    title = m_detector->getName() + " Global efficiency map;x [mm];y [mm];#epsilon";
    hGlobalEfficiencyMap_trackPos_TProfile = new TProfile2D("globalEfficiencyMap_trackPos_TProfile",
                                                            title.c_str(),
                                                            300,
                                                            m_detector->displacement().X() - 1.5 * m_detector->getSize().X(),
                                                            m_detector->displacement().X() + 1.5 * m_detector->getSize().X(),
                                                            300,
                                                            m_detector->displacement().Y() - 1.5 * m_detector->getSize().Y(),
                                                            m_detector->displacement().Y() + 1.5 * m_detector->getSize().Y(),
                                                            0,
                                                            1);
    hGlobalEfficiencyMap_trackPos = new TEfficiency("globalEfficiencyMap_trackPos",
                                                    title.c_str(),
                                                    300,
                                                    m_detector->displacement().X() - 1.5 * m_detector->getSize().X(),
                                                    m_detector->displacement().X() + 1.5 * m_detector->getSize().X(),
                                                    300,
                                                    m_detector->displacement().Y() - 1.5 * m_detector->getSize().Y(),
                                                    m_detector->displacement().Y() + 1.5 * m_detector->getSize().Y());
    hGlobalEfficiencyMap_trackPos->SetDirectory(this->getROOTDirectory());

    title = m_detector->getName() + " Chip efficiency map;x [px];y [px];#epsilon";
    hChipEfficiencyMap_clustPos_TProfile = new TProfile2D("chipEfficiencyMap_clustPos_TProfile",
                                                          title.c_str(),
                                                          m_detector->nPixels().X(),
                                                          -0.5,
                                                          m_detector->nPixels().X() - 0.5,
                                                          m_detector->nPixels().Y(),
                                                          -0.5,
                                                          m_detector->nPixels().Y() - 0.5,
                                                          0,
                                                          1);
    hChipEfficiencyMap_clustPos = new TEfficiency("chipEfficiencyMap_clustPos",
                                                  title.c_str(),
                                                  m_detector->nPixels().X(),
                                                  -0.5,
                                                  m_detector->nPixels().X() - 0.5,
                                                  m_detector->nPixels().Y(),
                                                  -0.5,
                                                  m_detector->nPixels().Y() - 0.5);
    hChipEfficiencyMap_clustPos->SetDirectory(this->getROOTDirectory());
    title = m_detector->getName() + " Global efficiency map;x [mm];y [mm];#epsilon";
    hGlobalEfficiencyMap_clustPos_TProfile = new TProfile2D("globalEfficiencyMap_clustPos_TProfile",
                                                            title.c_str(),
                                                            300,
                                                            m_detector->displacement().X() - 1.5 * m_detector->getSize().X(),
                                                            m_detector->displacement().X() + 1.5 * m_detector->getSize().X(),
                                                            300,
                                                            m_detector->displacement().Y() - 1.5 * m_detector->getSize().Y(),
                                                            m_detector->displacement().Y() + 1.5 * m_detector->getSize().Y(),
                                                            0,
                                                            1);
    hGlobalEfficiencyMap_clustPos = new TEfficiency("globalEfficiencyMap_clustPos",
                                                    title.c_str(),
                                                    300,
                                                    m_detector->displacement().X() - 1.5 * m_detector->getSize().X(),
                                                    m_detector->displacement().X() + 1.5 * m_detector->getSize().X(),
                                                    300,
                                                    m_detector->displacement().Y() - 1.5 * m_detector->getSize().Y(),
                                                    m_detector->displacement().Y() + 1.5 * m_detector->getSize().Y());
    hGlobalEfficiencyMap_clustPos->SetDirectory(this->getROOTDirectory());
    hDistanceCluster = new TH1D("distanceTrackHit",
                                "distance between track and hit; | #vec{track} - #vec{dut} | [mm]",
                                static_cast<int>(std::sqrt(m_detector->getPitch().x() * m_detector->getPitch().y())),
                                0,
                                std::sqrt(m_detector->getPitch().x() * m_detector->getPitch().y()));
    hDistanceCluster_track = new TH2D("distanceTrackHit2D",
                                      "distance between track and hit; track_x - dut_x [mm]; track_y - dut_y [mm] ",
                                      150,
                                      -1.5 * m_detector->getPitch().x(),
                                      1.5 * m_detector->getPitch().x(),
                                      150,
                                      -1.5 * m_detector->getPitch().y(),
                                      1.5 * m_detector->getPitch().y());
    eTotalEfficiency = new TEfficiency("eTotalEfficiency", "totalEfficiency;;#epsilon", 1, 0, 1);
    eTotalEfficiency->SetDirectory(this->getROOTDirectory());
    eTotalEfficiency_inPixelROI = new TEfficiency(
        "eTotalEfficiency_inPixelROI", "eTotalEfficiency_inPixelROI;;#epsilon (within in-pixel ROI)", 1, 0, 1);
    eTotalEfficiency_inPixelROI->SetDirectory(this->getROOTDirectory());
    efficiencyColumns = new TEfficiency("efficiencyColumns",
                                        "Efficiency vs. column number; column; #epsilon",
                                        m_detector->nPixels().X(),
                                        -0.5,
                                        m_detector->nPixels().X() - 0.5);
    efficiencyColumns->SetDirectory(this->getROOTDirectory());
    efficiencyRows = new TEfficiency("efficiencyRows",
                                     "Efficiency vs. row number; row; #epsilon",
                                     m_detector->nPixels().Y(),
                                     -0.5,
                                     m_detector->nPixels().Y() - 0.5);
    efficiencyRows->SetDirectory(this->getROOTDirectory());
    efficiencyVsTime = new TEfficiency("efficiencyVsTime", "Efficiency vs. time; time [s]; #epsilon", 3000, 0, 3000);
    efficiencyVsTime->SetDirectory(this->getROOTDirectory());
    efficiencyVsTimeLong =
        new TEfficiency("efficiencyVsTimeLong", "Efficiency vs. time; time [s]; #epsilon", 3000, 0, 30000);
    efficiencyVsTimeLong->SetDirectory(this->getROOTDirectory());
    hTrackTimeToPrevHit_matched =
        new TH1D("trackTimeToPrevHit_matched", "trackTimeToPrevHit_matched;time to prev hit [us];# events", 1e6, 0, 1e6);
    hTrackTimeToPrevHit_notmatched = new TH1D(
        "trackTimeToPrevHit_notmatched", "trackTimeToPrevHit_notmatched;time to prev hit [us];# events", 1e6, 0, 1e6);

    title = m_detector->getName() + "time difference to previous track (if this has assoc cluster)";
    hTimeDiffPrevTrack_assocCluster = new TH1D("timeDiffPrevTrack_assocCluster", title.c_str(), 11000, -1000, 10000);
    hTimeDiffPrevTrack_assocCluster->GetXaxis()->SetTitle("time diff [#mus]");
    hTimeDiffPrevTrack_assocCluster->GetYaxis()->SetTitle("events");
    title = m_detector->getName() + "time difference to previous track (if this has no assoc cluster)";
    hTimeDiffPrevTrack_noAssocCluster = new TH1D("timeDiffPrevTrack_noAssocCluster", title.c_str(), 11000, -1000, 10000);
    hTimeDiffPrevTrack_noAssocCluster->GetXaxis()->SetTitle("time diff [#mus]");
    hTimeDiffPrevTrack_noAssocCluster->GetYaxis()->SetTitle("events");

    hRowDiffPrevTrack_assocCluster =
        new TH1D("rowDiffPrevTrack_assocCluster",
                 "rowDiffPrevTrack_assocCluster; row difference (matched track to prev track) [px];# events",
                 2 * m_detector->nPixels().Y(),
                 -m_detector->nPixels().Y() - 0.5,
                 m_detector->nPixels().Y() - 0.5);

    hColDiffPrevTrack_assocCluster =
        new TH1D("colDiffPrevTrack_assocCluster",
                 "colDiffPrevTrack_assocCluster;column difference (matched track to prev track) [px];# events",
                 2 * m_detector->nPixels().X(),
                 -m_detector->nPixels().X() - 0.5,
                 m_detector->nPixels().X() - 0.5);

    hRowDiffPrevTrack_noAssocCluster =
        new TH1D("rowDiffPrevTrack_noAssocCluster",
                 "rowDiffPrevTrack_noAssocCluster;row difference (non-matched track - prev track) [px];# events",
                 2 * m_detector->nPixels().Y(),
                 -m_detector->nPixels().Y() - 0.5,
                 m_detector->nPixels().Y() - 0.5);

    hColDiffPrevTrack_noAssocCluster =
        new TH1D("colDiffPrevTrack_noAassocCluster",
                 "colDiffPrevTrack_noAssocCluster;column difference (non-matched track - prev track) [px];# events",
                 2 * m_detector->nPixels().X(),
                 -m_detector->nPixels().X() - 0.5,
                 m_detector->nPixels().X() - 0.5);

    hPosDiffPrevTrack_assocCluster = new TH2D("posDiffPrevTrack_assocCluster",
                                              "posDiffPrevTrack_assocCluster;column difference (matched track - prev track) "
                                              "[px];row difference (matched track - prev track) [px];# events",
                                              2 * m_detector->nPixels().X(),
                                              -m_detector->nPixels().X() - 0.5,
                                              m_detector->nPixels().X() - 0.5,
                                              2 * m_detector->nPixels().Y(),
                                              -m_detector->nPixels().Y() - 0.5,
                                              m_detector->nPixels().Y() - 0.5);
    hPosDiffPrevTrack_noAssocCluster =
        new TH2D("posDiffPrevTrack_noAssocCluster",
                 "posDiffPrevTrack_noAssocCluster;column difference (non-matched track - prev track) [px];row difference "
                 "(non-matched track - prev track) [px];# events",
                 2 * m_detector->nPixels().X(),
                 -m_detector->nPixels().X() - 0.5,
                 m_detector->nPixels().X() - 0.5,
                 2 * m_detector->nPixels().Y(),
                 -m_detector->nPixels().Y() - 0.5,
                 m_detector->nPixels().Y() - 0.5);
    htimeRes_cluster_size =
        new TH2D("timeDiff_hit-track_vs_clustersize",
                 "Time difference between track and clusters vs cluster size; time difference [ns];cluster size [px]",
                 1000,
                 -499.5,
                 500.5,
                 6,
                 -.5,
                 5.5);
    // initialize matrix with hit timestamps to all 0:
    auto nRows = static_cast<size_t>(m_detector->nPixels().Y());
    auto nCols = static_cast<size_t>(m_detector->nPixels().X());
    std::vector<double> v_row(nRows, 0.); // create vector will zeros of length <nRows>
    prev_hit_ts.assign(nCols, v_row);     // use vector v_row to construct matrix

    // check if the user wants to analyze fake rates
    if(m_fake_rate_radius > 0 || m_fake_rate_sensoredge >= 0) {
        LOG(STATUS) << "Configured to perform fake rate analysis.";
        createFakeRatePlots();
    }
}

void AnalysisEfficiency::createFakeRatePlots() {
    TDirectory* directory = getROOTDirectory();
    TDirectory* fake_rate_directory = directory->mkdir("fake_rate");
    if(fake_rate_directory == nullptr) {
        throw RuntimeError("Cannot create or access fake rate ROOT directory for module " + this->getUniqueName());
    }
    fake_rate_directory->cd();

    std::string title = m_detector->getName() + " number of fake hits per event; hits; events";
    hFakePixelPerEvent = new TH1D("hFakePixelPerEvent", title.c_str(), 25, 0 - 0.5, 25 - 0.5);

    title = m_detector->getName() + " pixel fake hits per event;x [px];y [px]; hits";
    fakePixelPerEventMap = new TH2D("fakePixelPerEventMap",
                                    title.c_str(),
                                    m_detector->nPixels().X(),
                                    -0.5,
                                    m_detector->nPixels().X() - 0.5,
                                    m_detector->nPixels().Y(),
                                    -0.5,
                                    m_detector->nPixels().Y() - 0.5);

    title = m_detector->getName() + "pixel fake hits per event vs. time; time [s]; hits";
    fakePixelPerEventVsTime = new TProfile("fakePixelPerEventVsTime", title.c_str(), 3000, 0, 3000);

    title = m_detector->getName() + "pixel fake hits per event vs. time; time [s]; hits";
    fakePixelPerEventVsTimeLong = new TProfile("efficiencyVsTimeLong", title.c_str(), 3000, 0, 30000);

    title = m_detector->getName() + "charge distribution for fake pixels; charge [a.u.]; entries";
    hFakePixelCharge = new TH1D("hFakePixelCharge", title.c_str(), m_n_charge_bins, 0.0, m_charge_histo_range);

    title = m_detector->getName() + "charge distribution for fake clusters; charge [a.u.]; entries";
    hFakeClusterCharge = new TH1D("hFakeClusterCharge", title.c_str(), m_n_charge_bins, 0.0, m_charge_histo_range);

    title = m_detector->getName() + " number of fake clusters per event; clusters; events";
    hFakeClusterPerEvent = new TH1D("hFakeClusterPerEvent", title.c_str(), 25, 0 - 0.5, 25 - 0.5);

    title = m_detector->getName() + " cluster size of fake clusters; cluster size; events";
    hFakeClusterSize = new TH1D("hFakeClusterSize", title.c_str(), 25, 0 - 0.5, 25 - 0.5);
}

StatusCode AnalysisEfficiency::run(const std::shared_ptr<Clipboard>& clipboard) {

    // Get the telescope tracks from the clipboard
    auto tracks = clipboard->getData<Track>();

    auto pitch_x = m_detector->getPitch().X();
    auto pitch_y = m_detector->getPitch().Y();
    // Get the event:
    auto event = clipboard->getEvent();

    // Loop over all tracks
    for(auto& track : tracks) {
        n_track++;
        bool has_associated_cluster = false;
        bool is_within_roi = true;
        LOG(DEBUG) << "Looking at next track";

        // Cut on the chi2/ndof
        if(track->getChi2ndof() > m_chi2ndofCut) {
            LOG(DEBUG) << " - track discarded due to Chi2/ndof";
            n_chi2++;
            continue;
        }

        // Check if it intercepts the DUT
        auto globalIntercept = m_detector->getIntercept(track.get());
        auto localIntercept = m_detector->globalToLocal(globalIntercept);

        LOG(TRACE) << " Checking if track is outside DUT area";
        if(!m_detector->hasIntercept(track.get(), spatial_cut_sensoredge)) {
            LOG(DEBUG) << " - track outside DUT area: " << localIntercept;
            n_dut++;
            continue;
        }

        // Check that track is within region of interest using winding number algorithm
        LOG(TRACE) << " Checking if track is outside ROI";
        if(!m_detector->isWithinROI(track.get())) {
            LOG(DEBUG) << " - track outside ROI";
            n_roi++;
            is_within_roi = false;
            // here we don't continue because only some particular histograms shall be effected
        }

        // Check that it doesn't go through/near a masked pixel
        LOG(TRACE) << " Checking if track is close to masked pixel";
        if(m_detector->hitMasked(track.get(), m_maskedPixelDistanceCut)) {
            n_masked++;
            LOG(DEBUG) << " - track close to masked pixel";
            continue;
        }

        // Discard tracks which are very close to the frame edges
        if(fabs(track->timestamp() - event->end()) < m_timeCutFrameEdge) {
            // Late edge - eventEnd points to the end of the frame`
            LOG(DEBUG) << " - track close to end of readout frame: "
                       << Units::display(fabs(track->timestamp() - event->end()), {"us", "ns"}) << " at "
                       << Units::display(track->timestamp(), {"us"});
            n_frameedge++;
            continue;
        } else if(fabs(track->timestamp() - event->start()) < m_timeCutFrameEdge) {
            // Early edge - eventStart points to the beginning of the frame
            LOG(DEBUG) << " - track close to start of readout frame: "
                       << Units::display(fabs(track->timestamp() - event->start()), {"us", "ns"}) << " at "
                       << Units::display(track->timestamp(), {"us"});
            n_frameedge++;
            continue;
        }

        // check if track has an associated cluster on required detector(s):
        auto foundRequiredAssocCluster = [this](Track* t) {
            for(auto& requireAssocCluster : require_associated_cluster_on_) {
                if(!requireAssocCluster.empty() && t->getAssociatedClusters(requireAssocCluster).size() == 0) {
                    LOG(DEBUG) << "No associated cluster from required detector " << requireAssocCluster << " on the track.";
                    return false;
                }
            }
            return true;
        };
        if(!foundRequiredAssocCluster(track.get())) {
            n_requirecluster++;
            continue;
        }

        // Count this as reference track:
        total_tracks++;

        // Calculate in-pixel position of track in microns
        auto inpixel = m_detector->inPixel(localIntercept);
        auto xmod = inpixel.X();
        auto ymod = inpixel.Y();
        auto xmod_um = xmod * 1000.; // mm->um (for plotting)
        auto ymod_um = ymod * 1000.; // mm->um (for plotting)

        bool isWithinInPixelROI =
            (pitch_x - fabs(xmod * 2.) > m_inpixelEdgeCut.x()) && (pitch_y - fabs(ymod * 2.) > m_inpixelEdgeCut.y());

        // Get the DUT clusters from the clipboard, that are assigned to the track
        auto associated_clusters = track->getAssociatedClusters(m_detector->getName());
        if(associated_clusters.size() > 0) {
            auto cluster = track->getClosestCluster(m_detector->getName());
            has_associated_cluster = true;
            matched_tracks++;
            auto pixels = cluster->pixels();
            for(auto& pixel : pixels) {
                if((pixel->column() == static_cast<int>(m_detector->getColumn(localIntercept)) &&
                    pixel->row() == static_cast<int>(m_detector->getRow(localIntercept))) &&
                   isWithinInPixelROI) {
                    hPixelEfficiencyMatrix_TProfile->Fill(pixel->column(), pixel->row(), 1);
                    break; // There cannot be a second pixel within the cluster through which the track goes.
                }
            }

            auto clusterLocal = m_detector->globalToLocal(cluster->global());

            auto distance =
                ROOT::Math::XYZVector(localIntercept.x() - clusterLocal.x(), localIntercept.y() - clusterLocal.y(), 0);
            hDistanceCluster_track->Fill(distance.X(), distance.Y());
            hDistanceCluster->Fill(std::sqrt(distance.Mag2()));

            hGlobalEfficiencyMap_clustPos_TProfile->Fill(
                cluster->global().x(), cluster->global().y(), has_associated_cluster);
            hGlobalEfficiencyMap_clustPos->Fill(has_associated_cluster, cluster->global().x(), cluster->global().y());

            hChipEfficiencyMap_clustPos_TProfile->Fill(
                m_detector->getColumn(clusterLocal), m_detector->getRow(clusterLocal), has_associated_cluster);
            hChipEfficiencyMap_clustPos->Fill(
                has_associated_cluster, m_detector->getColumn(clusterLocal), m_detector->getRow(clusterLocal));
        }

        if(!has_associated_cluster && isWithinInPixelROI) {
            hPixelEfficiencyMatrix_TProfile->Fill(
                m_detector->getColumn(localIntercept), m_detector->getRow(localIntercept), 0);
        }

        hGlobalEfficiencyMap_trackPos_TProfile->Fill(globalIntercept.X(), globalIntercept.Y(), has_associated_cluster);
        hGlobalEfficiencyMap_trackPos->Fill(has_associated_cluster, globalIntercept.X(), globalIntercept.Y());
        hChipEfficiencyMap_trackPos_TProfile->Fill(
            m_detector->getColumn(localIntercept), m_detector->getRow(localIntercept), has_associated_cluster);
        hChipEfficiencyMap_trackPos->Fill(
            has_associated_cluster, m_detector->getColumn(localIntercept), m_detector->getRow(localIntercept));

        // For pixels, only look at the ROI:
        if(is_within_roi) {
            hPixelEfficiencyMap_trackPos_TProfile->Fill(xmod_um, ymod_um, has_associated_cluster);
            hPixelEfficiencyMap_trackPos->Fill(has_associated_cluster, xmod_um, ymod_um);
            eTotalEfficiency->Fill(has_associated_cluster, 0); // use 0th bin for total efficiency
            efficiencyColumns->Fill(has_associated_cluster, m_detector->getColumn(localIntercept));
            efficiencyRows->Fill(has_associated_cluster, m_detector->getRow(localIntercept));
            efficiencyVsTime->Fill(has_associated_cluster, track->timestamp() / 1e9);     // convert nanoseconds to seconds
            efficiencyVsTimeLong->Fill(has_associated_cluster, track->timestamp() / 1e9); // convert nanoseconds to seconds
            if(isWithinInPixelROI) {
                hPixelEfficiencyMap_inPixelROI_trackPos_TProfile->Fill(xmod_um, ymod_um, has_associated_cluster);
                eTotalEfficiency_inPixelROI->Fill(has_associated_cluster, 0); // use 0th bin for total efficiency
            }
        }

        auto intercept_col = static_cast<size_t>(m_detector->getColumn(localIntercept));
        auto intercept_row = static_cast<size_t>(m_detector->getRow(localIntercept));

        if(has_associated_cluster) {
            for(auto c : associated_clusters) {
                htimeRes_cluster_size->Fill(track->timestamp() - c->timestamp(),
                                            ((c->pixels().size() > 4) ? 5.0 : static_cast<double>(c->pixels().size())));
            }
            hTimeDiffPrevTrack_assocCluster->Fill(
                static_cast<double>(Units::convert(track->timestamp() - last_track_timestamp, "us")));
            hRowDiffPrevTrack_assocCluster->Fill(m_detector->getRow(localIntercept) - last_track_row);
            hColDiffPrevTrack_assocCluster->Fill(m_detector->getColumn(localIntercept) - last_track_col);
            hPosDiffPrevTrack_assocCluster->Fill(m_detector->getColumn(localIntercept) - last_track_col,
                                                 m_detector->getRow(localIntercept) - last_track_row);
            if((prev_hit_ts.at(intercept_col)).at(intercept_row) != 0) {
                hTrackTimeToPrevHit_matched->Fill(static_cast<double>(
                    Units::convert(track->timestamp() - prev_hit_ts.at(intercept_col).at(intercept_row), "us")));
            }
        } else {
            hGlobalEfficiencyMap_clustPos_TProfile->Fill(globalIntercept.X(), globalIntercept.Y(), has_associated_cluster);
            hGlobalEfficiencyMap_clustPos->Fill(has_associated_cluster, globalIntercept.X(), globalIntercept.Y());

            hChipEfficiencyMap_clustPos_TProfile->Fill(
                m_detector->getColumn(localIntercept), m_detector->getRow(localIntercept), has_associated_cluster);
            hChipEfficiencyMap_clustPos->Fill(
                has_associated_cluster, m_detector->getColumn(localIntercept), m_detector->getRow(localIntercept));

            hTimeDiffPrevTrack_noAssocCluster->Fill(
                static_cast<double>(Units::convert(track->timestamp() - last_track_timestamp, "us")));
            hRowDiffPrevTrack_noAssocCluster->Fill(m_detector->getRow(localIntercept) - last_track_row);
            hColDiffPrevTrack_noAssocCluster->Fill(m_detector->getColumn(localIntercept) - last_track_col);
            hPosDiffPrevTrack_noAssocCluster->Fill(m_detector->getColumn(localIntercept) - last_track_col,
                                                   m_detector->getRow(localIntercept) - last_track_row);
            if((prev_hit_ts.at(intercept_col)).at(intercept_row) != 0) {
                LOG(DEBUG) << "Found a time difference of "
                           << Units::display(track->timestamp() - prev_hit_ts.at(intercept_col).at(intercept_row), "us");
                hTrackTimeToPrevHit_notmatched->Fill(static_cast<double>(
                    Units::convert(track->timestamp() - prev_hit_ts.at(intercept_col).at(intercept_row), "us")));
            }
        }
        last_track_timestamp = track->timestamp();
        last_track_col = m_detector->getColumn(localIntercept);
        last_track_row = m_detector->getRow(localIntercept);
    } // end loop over tracks

    // Before going to the next event, loop over all pixels (all hits incl. noise)
    // and fill matrix with timestamps of previous pixels.
    auto pixels = clipboard->getData<Pixel>(m_detector->getName());
    if(pixels.empty()) {
        LOG(DEBUG) << "Detector " << m_detector->getName() << " does not have any pixels on the clipboard";
    }

    for(auto& pixel : pixels) {
        if(pixel->column() > m_detector->nPixels().X() || pixel->row() > m_detector->nPixels().Y()) {
            continue;
        }
        try {
            prev_hit_ts.at(static_cast<size_t>(pixel->column())).at(static_cast<size_t>(pixel->row())) = pixel->timestamp();
        } catch(std::out_of_range&) {
        }
    }

    // fake rate analysis
    if(m_fake_rate_radius > 0) {
        LOG_ONCE(STATUS) << "Estimating fake rate based on radial cut around pixels.";

        int fake_hits = 0;
        int fake_clusters = 0;

        // get and iterate dut clusters from clipboard
        auto clusters = clipboard->getData<Cluster>(m_detector->getName());
        for(auto& cluster : clusters) {

            bool track_too_close = false;
            // iterate the tracks from the clipboard
            for(auto& track : tracks) {

                // discard tracks without intercept, using a tolerance defined
                // by the radial cut which we will use later.
                if(m_detector->hasIntercept(track.get(), -m_fake_rate_radius)) {
                    continue;
                }

                // now check if the track is too close to the considered cluster,
                // using the distance in units of the pitch.
                auto interceptLocal = m_detector->getLocalIntercept(track.get());
                double norm_xdistance = (interceptLocal.X() - cluster->local().x()) / m_detector->getPitch().X();
                double norm_ydistance = (interceptLocal.Y() - cluster->local().y()) / m_detector->getPitch().Y();
                double norm_rdistance = sqrt(norm_xdistance * norm_xdistance + norm_ydistance * norm_ydistance);
                if(norm_rdistance < m_fake_rate_radius) {
                    track_too_close = true;
                    break;
                }
            }

            // now study cluster and pixel properties, which seems to be a fake
            // cluster, as there is no track nearby.
            if(!track_too_close) {

                fake_clusters++;
                hFakeClusterCharge->Fill(cluster->charge());
                hFakeClusterSize->Fill(static_cast<double>(cluster->size()));

                for(auto& pixel : cluster->pixels()) {
                    fake_hits++;
                    hFakePixelCharge->Fill(pixel->charge());
                    fakePixelPerEventMap->Fill(pixel->column(), pixel->row(), 1);
                }
            }
        }

        hFakePixelPerEvent->Fill(fake_hits);
        fakePixelPerEventVsTime->Fill(static_cast<double>(Units::convert(event->start(), "s")), fake_hits);
        fakePixelPerEventVsTimeLong->Fill(static_cast<double>(Units::convert(event->start(), "s")), fake_hits);
        hFakeClusterPerEvent->Fill(fake_clusters);

    } else if(m_fake_rate_sensoredge > 0) {
        LOG_ONCE(STATUS) << "Estimating fake rate based on events without DUT intercepting tracks.";

        bool track_in_active = false;
        int fake_hits = 0;
        int fake_clusters = 0;

        // iterate the tracks from the clipboard
        for(auto& track : tracks) {
            // check if one of the tracks intercepts the dut
            // A positive value of m_fake_rate_radius means that we want to
            // check an area that is larger than the sensor matrix.
            if(m_detector->hasIntercept(track.get(), -m_fake_rate_sensoredge)) {
                track_in_active = true;
                break;
            }
        }

        // Study the dut response if there is no track pointing to the active
        // area of the dut. There might still be particles, that we failed to
        // track!
        if(!track_in_active) {

            // iterate the dut pixels from clipboard
            for(auto& pixel : pixels) {
                fake_hits++;
                hFakePixelCharge->Fill(pixel->charge());
                fakePixelPerEventMap->Fill(pixel->column(), pixel->row(), 1);
            }
            hFakePixelPerEvent->Fill(fake_hits);
            fakePixelPerEventVsTime->Fill(static_cast<double>(Units::convert(event->start(), "s")), fake_hits);
            fakePixelPerEventVsTimeLong->Fill(static_cast<double>(Units::convert(event->start(), "s")), fake_hits);

            // get and iterate dut clusters from clipboard
            auto clusters = clipboard->getData<Cluster>(m_detector->getName());
            for(auto& cluster : clusters) {
                fake_clusters++;
                hFakeClusterCharge->Fill(cluster->charge());
                hFakeClusterSize->Fill(static_cast<double>(cluster->size()));
            }
            hFakeClusterPerEvent->Fill(fake_clusters);
        }
    }

    return StatusCode::Success;
}

void AnalysisEfficiency::finalize(const std::shared_ptr<ReadonlyClipboard>&) {
    // Track selection flow:
    LOG(STATUS) << "Track selection flow:       " << n_track << std::endl
                << "* rejected by chi2          -" << n_chi2 << std::endl
                << "* track outside ROI         -" << n_roi << std::endl
                << "* track outside DUT         -" << n_dut << std::endl
                << "* track close to masked px  -" << n_masked << std::endl
                << "* track close to frame edge -" << n_frameedge << std::endl
                << "* track without an associated cluster on required detector - " << n_requirecluster << std::endl
                << "Accepted tracks:            " << total_tracks;

    double totalEff = 100 * static_cast<double>(matched_tracks) / (total_tracks > 0 ? total_tracks : 1);
    double lowerEffError = totalEff - 100 * (TEfficiency::ClopperPearson(total_tracks, matched_tracks, 0.683, false));
    double upperEffError = 100 * (TEfficiency::ClopperPearson(total_tracks, matched_tracks, 0.683, true)) - totalEff;
    LOG(STATUS) << "Total efficiency of detector " << m_detector->getName() << ": " << totalEff << "(+" << upperEffError
                << " -" << lowerEffError << ")%, measured with " << matched_tracks << "/" << total_tracks
                << " matched/total tracks";

    for(int icol = 1; icol < m_detector->nPixels().X() + 1; icol++) {
        for(int irow = 1; irow < m_detector->nPixels().Y() + 1; irow++) {
            // calculate total efficiency: (just to double check the other calculation)
            const int bin = hChipEfficiencyMap_trackPos->GetGlobalBin(icol, irow);
            double eff = hChipEfficiencyMap_trackPos->GetEfficiency(bin);
            if(eff > 0) {
                LOG(TRACE) << "col/row = " << icol << "/" << irow << ", binContent = " << eff;
                hPixelEfficiency->Fill(eff);
            }
            eff = hPixelEfficiencyMatrix_TProfile->GetBinContent(bin);
            if(eff > 0) {
                LOG(TRACE) << "col/row = " << icol << "/" << irow << ", binContent = " << eff;
                hPixelEfficiencyMatrix->Fill(eff);
            }
        }
    }
    // normalize fake rate map, if it exists
    if((m_fake_rate_radius > 0 || m_fake_rate_sensoredge >= 0) && hFakeClusterPerEvent->GetEntries() > 0) {
        fakePixelPerEventMap->Scale(1. / hFakePixelPerEvent->GetEntries());
    }
}