FairShip
Loading...
Searching...
No Matches
splitcalDetector.splitcalDetector Class Reference
Inheritance diagram for splitcalDetector.splitcalDetector:
Collaboration diagram for splitcalDetector.splitcalDetector:

Public Member Functions

None __init__ (self, name, intree, outtree=None)
 
None delete (self)
 
None fill (self)
 
None digitize (self)
 
- Public Member Functions inherited from BaseDetector.BaseDetector
None __init__ (self, name, intree, branchName=None, mcBranchType=None, mcBranchName=None, int splitLevel=99, outtree=None)
 
None delete (self)
 
None fill (self)
 
None digitize (self)
 
None process (self)
 

Public Attributes

 reco
 
 recoBranch
 
 step
 
 input_hits
 
 list_subclusters_of_hits
 
- Public Attributes inherited from BaseDetector.BaseDetector
 name
 
 intree
 
 outtree
 
 det
 
 MCdet
 
 mcBranch
 
 branch
 

Protected Member Functions

None _reconstruct_clusters (self)
 
def _get_subclusters_excluding_fragments (self)
 
int _get_cluster_energy (self, list_hits)
 
def _clustering (self)
 
def _get_neighbours (self, hit)
 

Detailed Description

Definition at line 7 of file splitcalDetector.py.

Constructor & Destructor Documentation

◆ __init__()

None splitcalDetector.splitcalDetector.__init__ (   self,
  name,
  intree,
  branchName = None 
)
Initialize the detector digitizer.

Reimplemented from BaseDetector.BaseDetector.

Definition at line 8 of file splitcalDetector.py.

8 def __init__(self, name, intree, outtree=None) -> None:
9 # Initialize base class for digitized hits
10 super().__init__(name, intree, "Splitcal", outtree=outtree)
11
12 # Clusters use value storage
13 self.reco = ROOT.std.vector("splitcalCluster")()
14 # Use outtree if provided, otherwise intree (backward compatibility)
15 tree_for_output = self.outtree if outtree is not None else intree
16 self.recoBranch = tree_for_output.Branch("Reco_SplitcalClusters", self.reco)
17

Member Function Documentation

◆ _clustering()

def splitcalDetector.splitcalDetector._clustering (   self)
protected
Perform clustering algorithm on input hits.

Definition at line 199 of file splitcalDetector.py.

199 def _clustering(self):
200 """Perform clustering algorithm on input hits."""
201 list_hits_in_cluster = {}
202 cluster_index = -1
203
204 for i, hit in enumerate(self.input_hits):
205 if hit.IsUsed() == 1:
206 continue
207
208 neighbours = self._get_neighbours(hit)
209
210 if len(neighbours) < 1:
211 continue
212
213 cluster_index = cluster_index + 1
214 hit.SetIsUsed(1)
215 list_hits_in_cluster[cluster_index] = []
216 list_hits_in_cluster[cluster_index].append(hit)
217
218 for neighbouringHit in neighbours:
219 if neighbouringHit.IsUsed() == 1:
220 continue
221
222 neighbouringHit.SetIsUsed(1)
223 list_hits_in_cluster[cluster_index].append(neighbouringHit)
224
225 expand_neighbours = self._get_neighbours(neighbouringHit)
226
227 if len(expand_neighbours) >= 1:
228 for additionalHit in expand_neighbours:
229 if additionalHit not in neighbours:
230 neighbours.append(additionalHit)
231
232 return list_hits_in_cluster
233

◆ _get_cluster_energy()

int splitcalDetector.splitcalDetector._get_cluster_energy (   self,
  list_hits 
)
protected
Calculate total energy of hits in a cluster.

Definition at line 192 of file splitcalDetector.py.

192 def _get_cluster_energy(self, list_hits) -> int:
193 """Calculate total energy of hits in a cluster."""
194 energy = 0
195 for hit in list_hits:
196 energy += hit.GetEnergy()
197 return energy
198

◆ _get_neighbours()

def splitcalDetector.splitcalDetector._get_neighbours (   self,
  hit 
)
protected
Find neighbouring hits for clustering.

Definition at line 234 of file splitcalDetector.py.

234 def _get_neighbours(self, hit):
235 """Find neighbouring hits for clustering."""
236 list_neighbours = []
237 err_x_1 = hit.GetXError()
238 err_y_1 = hit.GetYError()
239 err_z_1 = hit.GetZError()
240
241 # Allow one or more 'missing' hit in x/y
242 max_gap = 2.0
243 if hit.IsX():
244 err_x_1 = err_x_1 * max_gap
245 if hit.IsY():
246 err_y_1 = err_y_1 * max_gap
247
248 for hit2 in self.input_hits:
249 if hit2 is not hit:
250 Dx = fabs(hit2.GetX() - hit.GetX())
251 Dy = fabs(hit2.GetY() - hit.GetY())
252 Dz = fabs(hit2.GetZ() - hit.GetZ())
253 err_x_2 = hit2.GetXError()
254 err_y_2 = hit2.GetYError()
255 err_z_2 = hit2.GetZError()
256
257 if hit2.IsX():
258 err_x_2 = err_x_2 * max_gap
259 if hit2.IsY():
260 err_y_2 = err_y_2 * max_gap
261
262 if self.step == 1:
263 if hit.IsX() and (
264 Dx <= (err_x_1 + err_x_2)
265 and Dz <= 2 * (err_z_1 + err_z_2)
266 and ((Dy <= (err_y_1 + err_y_2) and Dz > 0.0) or (Dy == 0))
267 ):
268 list_neighbours.append(hit2)
269 if hit.IsY() and (
270 Dy <= (err_y_1 + err_y_2)
271 and Dz <= 2 * (err_z_1 + err_z_2)
272 and ((Dx <= (err_x_1 + err_x_2) and Dz > 0.0) or (Dx == 0))
273 ):
274 list_neighbours.append(hit2)
275
276 elif self.step == 2:
277 # Relax z condition for step 2
278 if hit.IsX() and (
279 Dx <= (err_x_1 + err_x_2)
280 and Dz <= 6 * (err_z_1 + err_z_2)
281 and ((Dy <= (err_y_1 + err_y_2) and Dz > 0.0) or (Dy == 0))
282 ):
283 list_neighbours.append(hit2)
284 if hit.IsY() and (
285 Dy <= (err_y_1 + err_y_2)
286 and Dz <= 6 * (err_z_1 + err_z_2)
287 and ((Dx <= (err_x_1 + err_x_2) and Dz > 0.0) or (Dx == 0))
288 ):
289 list_neighbours.append(hit2)
290 else:
291 print("-- _get_neighbours: ERROR: step not defined")
292
293 return list_neighbours

◆ _get_subclusters_excluding_fragments()

def splitcalDetector.splitcalDetector._get_subclusters_excluding_fragments (   self)
protected
Merge fragments into the closest subcluster.

Definition at line 150 of file splitcalDetector.py.

150 def _get_subclusters_excluding_fragments(self):
151 """Merge fragments into the closest subcluster."""
152 list_subclusters_excluding_fragments = {}
153 fragment_indices = []
154 subclusters_indices = []
155
156 for k in self.list_subclusters_of_hits:
157 subcluster_size = len(self.list_subclusters_of_hits[k])
158 if subcluster_size < 5:
159 fragment_indices.append(k)
160 else:
161 subclusters_indices.append(k)
162
163 # Merge all fragments if no subclusters exist
164 if len(subclusters_indices) == 0 and len(fragment_indices) != 0:
165 subclusters_indices.append(0)
166
167 # Merge fragments into closest subcluster
168 for index_fragment in fragment_indices:
169 minDistance = -1
170 minIndex = -1
171 first_hit_fragment = self.list_subclusters_of_hits[index_fragment][0]
172
173 for index_subcluster in subclusters_indices:
174 first_hit_subcluster = self.list_subclusters_of_hits[index_subcluster][0]
175 if first_hit_fragment.IsX():
176 distance = fabs(first_hit_fragment.GetX() - first_hit_subcluster.GetX())
177 else:
178 distance = fabs(first_hit_fragment.GetY() - first_hit_subcluster.GetY())
179
180 if minDistance < 0 or distance < minDistance:
181 minDistance = distance
182 minIndex = index_subcluster
183
184 if minIndex != index_fragment:
185 self.list_subclusters_of_hits[minIndex] += self.list_subclusters_of_hits[index_fragment]
186
187 for counter, index_subcluster in enumerate(subclusters_indices):
188 list_subclusters_excluding_fragments[counter] = self.list_subclusters_of_hits[index_subcluster]
189
190 return list_subclusters_excluding_fragments
191

◆ _reconstruct_clusters()

None splitcalDetector.splitcalDetector._reconstruct_clusters (   self)
protected
Perform cluster reconstruction from digitized hits.

Definition at line 54 of file splitcalDetector.py.

54 def _reconstruct_clusters(self) -> None:
55 """Perform cluster reconstruction from digitized hits."""
56 # Hit selection: select hits above noise threshold
57 noise_energy_threshold = 0.002 # GeV
58 list_hits_above_threshold = []
59 hit_to_index = {}
60 for idx, hit in enumerate(self.det):
61 if hit.GetEnergy() > noise_energy_threshold:
62 hit.SetIsUsed(0)
63 list_hits_above_threshold.append(hit)
64 hit_to_index[id(hit)] = idx
65
66 if not list_hits_above_threshold:
67 return
68
69 # Step 1: group of neighbouring cells
70 self.step = 1
71 self.input_hits = list_hits_above_threshold
72 list_clusters_of_hits = self._clustering()
73
74 # Step 2: check if clusters can be split in XZ and YZ planes
75 self.step = 2
76 list_final_clusters = {}
77 index_final_cluster = 0
78
79 for i in list_clusters_of_hits:
80 list_hits_x = []
81 list_hits_y = []
82 for hit in list_clusters_of_hits[i]:
83 hit.SetIsUsed(0)
84 if hit.IsX():
85 list_hits_x.append(hit)
86 if hit.IsY():
87 list_hits_y.append(hit)
88
89 # Re-cluster in XZ plane
90 self.input_hits = list_hits_x
91 list_subclusters_of_x_hits = self._clustering()
92 cluster_energy_x = self._get_cluster_energy(list_hits_x)
93
94 self.list_subclusters_of_hits = list_subclusters_of_x_hits
95 list_of_subclusters_x = self._get_subclusters_excluding_fragments()
96
97 # Compute energy weights from X splitting
98 weights_from_x_splitting = {}
99 for index_subcluster in list_of_subclusters_x:
100 subcluster_energy_x = self._get_cluster_energy(list_of_subclusters_x[index_subcluster])
101 weight = subcluster_energy_x / cluster_energy_x if cluster_energy_x > 0 else 0
102 weights_from_x_splitting[index_subcluster] = weight
103
104 # Re-cluster in YZ plane
105 self.input_hits = list_hits_y
106 list_subclusters_of_y_hits = self._clustering()
107 cluster_energy_y = self._get_cluster_energy(list_hits_y)
108
109 self.list_subclusters_of_hits = list_subclusters_of_y_hits
110 list_of_subclusters_y = self._get_subclusters_excluding_fragments()
111
112 # Compute energy weights from Y splitting
113 weights_from_y_splitting = {}
114 for index_subcluster in list_of_subclusters_y:
115 subcluster_energy_y = self._get_cluster_energy(list_of_subclusters_y[index_subcluster])
116 weight = subcluster_energy_y / cluster_energy_y if cluster_energy_y > 0 else 0
117 weights_from_y_splitting[index_subcluster] = weight
118
119 # Build final clusters
120 if len(list_of_subclusters_x) == 1 and len(list_of_subclusters_y) == 1:
121 list_final_clusters[index_final_cluster] = [(hit, 1.0) for hit in list_clusters_of_hits[i]]
122 index_final_cluster += 1
123 else:
124 for ix in list_of_subclusters_x:
125 for iy in list_of_subclusters_y:
126 # Build list of (hit, weight) tuples
127 hit_weight_list = []
128 for hit in list_of_subclusters_y[iy]:
129 hit_weight_list.append((hit, weights_from_x_splitting[ix]))
130 for hit in list_of_subclusters_x[ix]:
131 hit_weight_list.append((hit, weights_from_y_splitting[iy]))
132
133 list_final_clusters[index_final_cluster] = hit_weight_list
134 index_final_cluster += 1
135
136 # Fill cluster objects
137 for i in list_final_clusters:
138 # Create empty cluster
139 cluster = ROOT.splitcalCluster()
140
141 # Add all hits with their weights using indices
142 for hit, weight in list_final_clusters[i]:
143 hit_index = hit_to_index[id(hit)]
144 cluster.AddHit(hit_index, weight)
145
146 cluster.SetIndex(int(i))
147 cluster.ComputeEtaPhiE(self.det)
148 self.reco.push_back(cluster)
149

◆ delete()

None splitcalDetector.splitcalDetector.delete (   self)
Clear detector hit containers.

Reimplemented from BaseDetector.BaseDetector.

Definition at line 18 of file splitcalDetector.py.

18 def delete(self) -> None:
19 # Override to also clear reconstruction branch
20 super().delete()
21 self.reco.clear()
22

◆ digitize()

None splitcalDetector.splitcalDetector.digitize (   self)
Digitize splitcal hits and perform cluster reconstruction.

Reimplemented from BaseDetector.BaseDetector.

Definition at line 31 of file splitcalDetector.py.

31 def digitize(self) -> None:
32 """Digitize splitcal hits and perform cluster reconstruction."""
33 # Digitization: group MC points by detector cell, then create one hit per cell
34 points_by_detID = {}
35 for point in self.intree.splitcalPoint:
36 detector_id = point.GetDetectorID()
37 if detector_id not in points_by_detID:
38 points_by_detID[detector_id] = []
39 points_by_detID[detector_id].append(point)
40
41 # Create one hit per detector cell from all points in that cell
42 for detector_id, points in points_by_detID.items():
43 # Convert Python list to std::vector for C++
44 point_vector = ROOT.std.vector("splitcalPoint")()
45 for point in points:
46 point_vector.push_back(point)
47
48 hit = ROOT.splitcalHit(point_vector, self.intree.t0)
49 self.det.push_back(hit)
50
51 # Cluster reconstruction
52 self._reconstruct_clusters()
53

◆ fill()

None splitcalDetector.splitcalDetector.fill (   self)
Fill detector hit branches.

Note: This method is now a no-op to prevent double-filling.
All branches are filled synchronously by recoTree.Fill() in the main loop.

Reimplemented from BaseDetector.BaseDetector.

Definition at line 23 of file splitcalDetector.py.

23 def fill(self) -> None:
24 """Fill detector hit branches.
25
26 Note: This method is now a no-op to prevent double-filling.
27 All branches are filled synchronously by recoTree.Fill() in the main loop.
28 """
29 pass
30

Member Data Documentation

◆ input_hits

splitcalDetector.splitcalDetector.input_hits

Definition at line 71 of file splitcalDetector.py.

◆ list_subclusters_of_hits

splitcalDetector.splitcalDetector.list_subclusters_of_hits

Definition at line 94 of file splitcalDetector.py.

◆ reco

splitcalDetector.splitcalDetector.reco

Definition at line 13 of file splitcalDetector.py.

◆ recoBranch

splitcalDetector.splitcalDetector.recoBranch

Definition at line 16 of file splitcalDetector.py.

◆ step

splitcalDetector.splitcalDetector.step

Definition at line 70 of file splitcalDetector.py.


The documentation for this class was generated from the following file: