Skip to content

Commit affac96

Browse files
committed
Hit based CA for ALICE3 tracker
1 parent 2d0b9e3 commit affac96

File tree

16 files changed

+1249
-18
lines changed

16 files changed

+1249
-18
lines changed

Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
add_subdirectory(base)
1313
add_subdirectory(macros)
1414
add_subdirectory(simulation)
15-
add_subdirectory(workflow)
15+
add_subdirectory(reconstruction)
16+
add_subdirectory(workflow)

Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,14 @@ o2_add_test_root_macro(CheckDigits.C
1818
O2::SimulationDataFormat
1919
O2::DetectorsBase
2020
O2::Steer
21+
LABELS trk COMPILE_ONLY)
22+
23+
o2_add_test_root_macro(CheckTracksCA.C
24+
PUBLIC_LINK_LIBRARIES O2::DataFormatsITS
25+
O2::ITStracking
26+
O2::SimulationDataFormat
27+
O2::DetectorsBase
28+
O2::TRKBase
29+
O2::TRKSimulation
30+
O2::Steer
2131
LABELS trk COMPILE_ONLY)
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
/// \file CheckTracksCA.C
13+
/// \brief Quality assurance macro for TRK tracking
14+
15+
#if !defined(__CLING__) || defined(__ROOTCLING__)
16+
#include <array>
17+
#include <cmath>
18+
#include <iostream>
19+
#include <unordered_map>
20+
#include <vector>
21+
22+
#include <TFile.h>
23+
#include <TTree.h>
24+
#include <TH1D.h>
25+
#include <TCanvas.h>
26+
#include <THStack.h>
27+
#include <TLegend.h>
28+
#include <TLatex.h>
29+
#include <TStyle.h>
30+
31+
#include "DataFormatsITS/TrackITS.h"
32+
#include "SimulationDataFormat/MCCompLabel.h"
33+
#include "SimulationDataFormat/MCTrack.h"
34+
#include "Steer/MCKinematicsReader.h"
35+
#include "TRKSimulation/Hit.h"
36+
#include "TRKBase/GeometryTGeo.h"
37+
#include "DetectorsBase/GeometryManager.h"
38+
39+
#endif
40+
41+
using namespace std;
42+
using namespace o2;
43+
44+
/// Structure to track particle hit information
45+
struct ParticleHitInfo {
46+
std::bitset<11> layerHits; ///< Which layers have hits (11 layers for TRK)
47+
int nHits = 0; ///< Total number of hits
48+
float pt = 0.0f; ///< Particle pT
49+
50+
void addHit(int layer)
51+
{
52+
if (!layerHits[layer]) {
53+
layerHits[layer] = true;
54+
nHits++;
55+
}
56+
}
57+
58+
bool hasConsecutiveLayers(int nConsecutive) const
59+
{
60+
for (int startLayer = 0; startLayer <= 11 - nConsecutive; ++startLayer) {
61+
bool allSet = true;
62+
for (int i = 0; i < nConsecutive; ++i) {
63+
if (!layerHits[startLayer + i]) {
64+
allSet = false;
65+
break;
66+
}
67+
}
68+
if (allSet) {
69+
return true;
70+
}
71+
}
72+
return false;
73+
}
74+
};
75+
76+
void CheckTracksCA(std::string tracfile = "o2trac_trk.root",
77+
std::string kinefile = "o2sim_Kine.root",
78+
std::string hitsfile = "o2sim_HitsTRK.root",
79+
std::string outputfile = "trk_qa_output.root")
80+
{
81+
gStyle->SetOptStat(0);
82+
83+
std::cout << "=== Starting TRK Track Quality Assurance ===" << std::endl;
84+
std::cout << "Input files:" << std::endl;
85+
std::cout << " Tracks: " << tracfile << std::endl;
86+
std::cout << " Kinematics: " << kinefile << std::endl;
87+
std::cout << " Hits: " << hitsfile << std::endl;
88+
std::cout << " Output: " << outputfile << std::endl;
89+
std::cout << std::endl;
90+
91+
// MC kinematics reader
92+
o2::steer::MCKinematicsReader kineReader("o2sim", o2::steer::MCKinematicsReader::Mode::kMCKine);
93+
const int nEvents = kineReader.getNEvents(0);
94+
std::cout << "Number of MC events: " << nEvents << std::endl;
95+
96+
// Open hits file to count hits per particle per layer
97+
TFile* hitsFile = TFile::Open(hitsfile.c_str(), "READ");
98+
if (!hitsFile || hitsFile->IsZombie()) {
99+
std::cerr << "ERROR: Cannot open hits file: " << hitsfile << std::endl;
100+
return;
101+
}
102+
TTree* hitsTree = hitsFile->Get<TTree>("o2sim");
103+
if (!hitsTree) {
104+
std::cerr << "ERROR: Cannot find o2sim tree in hits file" << std::endl;
105+
return;
106+
}
107+
108+
// Open reconstructed tracks file
109+
TFile* tracFile = TFile::Open(tracfile.c_str(), "READ");
110+
if (!tracFile || tracFile->IsZombie()) {
111+
std::cerr << "ERROR: Cannot open tracks file: " << tracfile << std::endl;
112+
return;
113+
}
114+
TTree* recTree = tracFile->Get<TTree>("o2sim");
115+
if (!recTree) {
116+
std::cerr << "ERROR: Cannot find o2sim tree in tracks file" << std::endl;
117+
return;
118+
}
119+
120+
// Reconstructed tracks and labels
121+
std::vector<o2::its::TrackITS>* recTracks = nullptr;
122+
std::vector<o2::MCCompLabel>* trkLabels = nullptr;
123+
recTree->SetBranchAddress("TRKTrack", &recTracks);
124+
recTree->SetBranchAddress("TRKTrackMCTruth", &trkLabels);
125+
126+
std::cout << "Reading tracks from tree..." << std::endl;
127+
128+
// Analyze hits tree to count hits per particle per layer
129+
std::cout << "Analyzing hits from tree..." << std::endl;
130+
std::unordered_map<o2::MCCompLabel, ParticleHitInfo> particleHitMap;
131+
132+
// Load geometry for layer determination
133+
o2::base::GeometryManager::loadGeometry();
134+
auto* gman = o2::trk::GeometryTGeo::Instance();
135+
136+
// Array to map detector to starting layer
137+
constexpr std::array<int, 2> startLayer{0, 3};
138+
139+
std::vector<o2::trk::Hit>* trkHit = nullptr;
140+
hitsTree->SetBranchAddress("TRKHit", &trkHit);
141+
142+
Long64_t nHitsEntries = hitsTree->GetEntries();
143+
std::cout << "Processing " << nHitsEntries << " hit entries..." << std::endl;
144+
145+
for (Long64_t iEntry = 0; iEntry < nHitsEntries; ++iEntry) {
146+
hitsTree->GetEntry(iEntry);
147+
148+
for (const auto& hit : *trkHit) {
149+
// Skip disk hits (only barrel)
150+
if (gman->getDisk(hit.GetDetectorID()) != -1) {
151+
continue;
152+
}
153+
154+
// Determine layer
155+
int subDetID = gman->getSubDetID(hit.GetDetectorID());
156+
const int layer = startLayer[subDetID] + gman->getLayer(hit.GetDetectorID());
157+
158+
// Create label for this particle
159+
o2::MCCompLabel label(hit.GetTrackID(), static_cast<int>(iEntry), 0);
160+
161+
// Add hit to particle's hit map
162+
particleHitMap[label].addHit(layer);
163+
}
164+
}
165+
166+
std::cout << "Found " << particleHitMap.size() << " unique particles with hits" << std::endl;
167+
168+
// Store particle info and fill generated histograms
169+
std::unordered_map<o2::MCCompLabel, float> particlePtMap;
170+
171+
// Create histograms
172+
constexpr int nLayers = 11;
173+
constexpr int nb = 100;
174+
double xbins[nb + 1], ptcutl = 0.05, ptcuth = 10.;
175+
double a = std::log(ptcuth / ptcutl) / nb;
176+
for (int i = 0; i <= nb; i++)
177+
xbins[i] = ptcutl * std::exp(i * a);
178+
179+
TH1D genParticlePtHist("genParticlePt", "Generated Particle p_{T} (All Layers); #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins);
180+
TH1D genParticlePt7LayersHist("genParticlePt7Layers", "Generated Particle p_{T} with hits in at least 7 consecutive layers; #it{p}_{T} (GeV/#it{c}); Counts", nb, xbins);
181+
TH1D goodTracks("goodTracks", "Good Tracks; p_{T} (GeV/c); Counts", nb, xbins);
182+
TH1D fakeTracks("fakeTracks", "Fake Tracks; p_{T} (GeV/c); Counts", nb, xbins);
183+
184+
std::array<TH1D, 5> goodTracksMatching, fakeTracksMatching;
185+
for (int i = 0; i < 5; ++i) {
186+
goodTracksMatching[i] = TH1D(Form("goodTracksMatching_%dLayers", i + 7),
187+
Form("Good Tracks with %d layer hits; p_{T} (GeV/c); Counts", i + 7),
188+
nb, xbins);
189+
fakeTracksMatching[i] = TH1D(Form("fakeTracksMatching_%dLayers", i + 7),
190+
Form("Fake Tracks with %d layer hits; p_{T} (GeV/c); Counts", i + 7),
191+
nb, xbins);
192+
}
193+
194+
TH1D numberOfClustersPerTrack("numberOfClustersPerTrack",
195+
"Number of clusters per track; N_{clusters}; Counts",
196+
12, -0.5, 11.5);
197+
198+
// First pass: identify particles with full hit coverage from kinematics
199+
std::cout << "Analyzing MC particles..." << std::endl;
200+
for (int iEvent = 0; iEvent < nEvents; ++iEvent) {
201+
const auto& mcTracks = kineReader.getTracks(iEvent);
202+
for (size_t iTrack = 0; iTrack < mcTracks.size(); ++iTrack) {
203+
const auto& mcTrack = mcTracks[iTrack];
204+
if (!mcTrack.isPrimary()) {
205+
continue;
206+
}
207+
208+
// Create label for this particle
209+
o2::MCCompLabel label(iTrack, iEvent, 0);
210+
float pt = mcTrack.GetPt();
211+
212+
// Store particle info
213+
particlePtMap[label] = pt;
214+
215+
// Check if this particle has hits
216+
auto hitIt = particleHitMap.find(label);
217+
if (hitIt != particleHitMap.end()) {
218+
// Store pT in hit info
219+
hitIt->second.pt = pt;
220+
221+
// Fill histogram for particles with hits in all 11 layers
222+
if (hitIt->second.nHits == 11) {
223+
genParticlePtHist.Fill(pt);
224+
}
225+
226+
// Fill histogram for particles with at least 7 consecutive layer hits
227+
if (hitIt->second.hasConsecutiveLayers(7)) {
228+
genParticlePt7LayersHist.Fill(pt);
229+
}
230+
}
231+
}
232+
}
233+
234+
std::cout << "Generated particles with 11 hits: " << genParticlePtHist.GetEntries() << std::endl;
235+
std::cout << "Generated particles with 7+ consecutive hits: " << genParticlePt7LayersHist.GetEntries() << std::endl;
236+
237+
// Second pass: analyze reconstructed tracks
238+
std::cout << "Analyzing reconstructed tracks..." << std::endl;
239+
int nROFs = recTree->GetEntries();
240+
int totalTracks = 0;
241+
int goodTracksCount = 0;
242+
int fakeTracksCount = 0;
243+
244+
for (int iROF = 0; iROF < nROFs; ++iROF) {
245+
recTree->GetEntry(iROF);
246+
247+
if (!recTracks || !trkLabels) {
248+
continue;
249+
}
250+
251+
totalTracks += recTracks->size();
252+
253+
for (size_t iTrack = 0; iTrack < recTracks->size(); ++iTrack) {
254+
const auto& track = recTracks->at(iTrack);
255+
const auto& label = trkLabels->at(iTrack);
256+
257+
if (!label.isSet() || !label.isValid()) {
258+
continue;
259+
}
260+
261+
int eventID = label.getEventID();
262+
int trackID = label.getTrackID();
263+
int nClusters = track.getNumberOfClusters();
264+
265+
// Get MC track info
266+
if (eventID < 0 || eventID >= nEvents) {
267+
continue;
268+
}
269+
270+
const auto& mcTracks = kineReader.getTracks(eventID);
271+
if (trackID < 0 || trackID >= (int)mcTracks.size()) {
272+
continue;
273+
}
274+
275+
float pt = mcTracks[trackID].GetPt();
276+
277+
// Fill histograms
278+
numberOfClustersPerTrack.Fill(nClusters);
279+
280+
auto key = o2::MCCompLabel(trackID, eventID, 0);
281+
if (particleHitMap.find(key) != particleHitMap.end() && particleHitMap[key].hasConsecutiveLayers(11)) {
282+
if (label.isFake()) {
283+
fakeTracks.Fill(pt);
284+
fakeTracksCount++;
285+
if (nClusters >= 7 && nClusters <= 11) {
286+
fakeTracksMatching[nClusters - 7].Fill(pt);
287+
}
288+
} else {
289+
goodTracks.Fill(pt);
290+
goodTracksCount++;
291+
if (nClusters >= 7 && nClusters <= 11) {
292+
goodTracksMatching[nClusters - 7].Fill(pt);
293+
}
294+
}
295+
}
296+
}
297+
}
298+
299+
// Create efficiency histograms
300+
std::cout << "Computing efficiencies..." << std::endl;
301+
302+
std::array<TH1D, 5> efficiencyHistograms;
303+
THStack* efficiencyStack = new THStack("efficiencyStack",
304+
"Tracking Efficiency; #it{p}_{T} (GeV/#it{c}); Efficiency");
305+
306+
int colors[5] = {kRed, kBlue, kGreen + 2, kMagenta, kOrange};
307+
for (int i = 0; i < 5; ++i) {
308+
int nClusters = i + 7;
309+
efficiencyHistograms[i] = TH1D(Form("efficiency_%dClusters", nClusters),
310+
Form("Efficiency for %d cluster tracks; #it{p}_{T} (GeV/#it{c}); Efficiency", nClusters),
311+
nb, xbins);
312+
313+
efficiencyHistograms[i].Divide(&goodTracksMatching[i], &genParticlePtHist, 1, 1, "B");
314+
315+
efficiencyHistograms[i].SetLineColor(colors[i]);
316+
efficiencyHistograms[i].SetFillColor(colors[i]);
317+
efficiencyHistograms[i].SetLineWidth(2);
318+
efficiencyHistograms[i].SetMarkerColor(colors[i]);
319+
efficiencyHistograms[i].SetMarkerStyle(20 + i);
320+
efficiencyStack->Add(&efficiencyHistograms[i]);
321+
}
322+
323+
// Write output
324+
std::cout << "Writing output to " << outputfile << std::endl;
325+
TFile outFile(outputfile.c_str(), "RECREATE");
326+
genParticlePtHist.Write();
327+
goodTracks.Write();
328+
fakeTracks.Write();
329+
for (int i = 0; i < 5; ++i) {
330+
goodTracksMatching[i].Write();
331+
fakeTracksMatching[i].Write();
332+
efficiencyHistograms[i].Write();
333+
}
334+
efficiencyStack->Write();
335+
genParticlePt7LayersHist.Write();
336+
numberOfClustersPerTrack.Write();
337+
outFile.Close();
338+
339+
// Clean up
340+
hitsFile->Close();
341+
tracFile->Close();
342+
delete efficiencyStack;
343+
delete hitsFile;
344+
delete tracFile;
345+
}

0 commit comments

Comments
 (0)