ATLAS Offline Software
Loading...
Searching...
No Matches
NNClusterCalibratorToolImpl.icc
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4#ifndef NNCLSUTERCALIBRATORTOOLIMPL_ICC
5#define NNCLSUTERCALIBRATORTOOLIMPL_ICC
6
7#include <PixelReadoutGeometry/PixelModuleDesign.h>
8#include <onnxruntime_cxx_api.h>
9
10#include <cmath>
11#include <limits>
12#include <set>
13#include <stdexcept>
14
15#include "Acts/Utilities/Zip.hpp"
16#include "ActsGeometry/ActsDetectorElement.h"
17#include "AthenaBaseComps/AthCheckMacros.h"
18#include "Identifier/Identifier.h"
19#include "InDetMeasurementUtilities/Helpers.h"
20#include "InDetReadoutGeometry/SiDetectorElement.h"
21#include "NNPixelClusterCalibratorHelpers.h"
22#include "PixelReadoutGeometry/PixelModuleDesign.h"
23#include "ReadoutGeometryBase/SiCellId.h"
24#include "src/detail/TrackStateFlagHelper.h"
25#include "xAODMeasurementBase/MeasurementDefs.h"
26namespace ActsTrk::detail {
27
28template <typename calib_data_t, typename traj_t>
29StatusCode NNClusterCalibratorToolImpl<calib_data_t, traj_t>::initialize() {
30 ATH_CHECK(m_readKeyONNX.initialize());
31 ATH_MSG_DEBUG("Initializing ...");
32
33 ATH_CHECK(BASE::initialize());
34 ATH_MSG_INFO(" successfully initialized, NNClusterCalibrator implementation");
35 return StatusCode::SUCCESS;
36}
37
38template <typename calib_data_t, typename traj_t>
39NNClusterCalibratorOptions<calib_data_t, traj_t>
40NNClusterCalibratorToolImpl<calib_data_t, traj_t>::createOptions(
41 const EventContext& ctx) const {
42 NNClusterCalibratorOptions<calib_data_t, traj_t> opt;
43 opt.m_minClusterSizeForNN = m_minClusterSizeForNN;
44 opt.m_minClusterChargeForNN = m_minClusterChargeForNN;
45 SG::ReadCondHandle<OnnxNNCollection> onnxCollection(m_readKeyONNX, ctx);
46 if (!onnxCollection.isValid()) {
47 ATH_MSG_FATAL("Failed to get ONNX network collection with key "
48 << m_readKeyONNX.key());
49 return opt; // this options will be invalid
50 } else {
51 ATH_MSG_DEBUG("Obtained NN models for this event");
52 }
53
54 opt.m_models = onnxCollection.cptr();
55
56 auto barePointer = BASE::createOnTrackCalibrator(ctx).release();
57 auto downCast =
58 dynamic_cast<AnalogueClusteringCalibrator<calib_data_t, traj_t>*>(
59 barePointer);
60 opt.m_fallback =
61 std::unique_ptr<AnalogueClusteringCalibrator<calib_data_t, traj_t>>(
62 downCast);
63 return opt;
64}
65
66template <typename calib_data_t, typename traj_t>
67std::tuple<typename NNClusterCalibrator<calib_data_t, traj_t>::BASE::Pos,
68 typename NNClusterCalibrator<calib_data_t, traj_t>::BASE::Cov,
69 unsigned int>
70NNClusterCalibrator<calib_data_t, traj_t>::calibrate(
71 const EventContext& ctx, const Acts::GeometryContext& gctx,
72 const Acts::CalibrationContext& cctx, const xAOD::PixelCluster& cluster,
73 const InDetDD::SiDetectorElement& detElement,
74 const std::pair<float, float>& tan_incidence_angles) const {
75 double totalCharge = cluster.totalCharge();
76 if (totalCharge < m_options.m_minClusterChargeForNN || cluster.chargeList().size() <
77 m_options.m_minClusterSizeForNN) {
78 return m_options.m_fallback->calibrate(ctx, gctx, cctx, cluster,
79 detElement, tan_incidence_angles);
80 }
81
82 ActsTrk::NNinput nnInput;
83 fillClusterData(nnInput, cluster, detElement);
84
85 nnInput.set(NNinput::Index::phi, std::atan(tan_incidence_angles.first));
86 nnInput.set(NNinput::Index::theta, std::atan(tan_incidence_angles.second));
87
88 // 1st call to
89 std::array<float, 3> probabilities = predictNumberOfClusters(nnInput);
90 // pick max probability
91 const int maxProbNumberIndex = NumberNNoutput::maxProbIndex(probabilities);
92 const int maxProbNumberOfClusters = maxProbNumberIndex + 1;
93
94 PositionNNoutput positions =
95 predictPositions(nnInput, maxProbNumberOfClusters);
96
97 // these are deltas w.r.t. cluster center
98 typename NNClusterCalibrator<calib_data_t, traj_t>::BASE::Pos pos;
99 pos(0) = nnInput.indexCoordToRealCoord(positions.x(maxProbNumberIndex),
100 NNinput::Index::pitchXOffset);
101 pos(1) = nnInput.indexCoordToRealCoord(positions.y(maxProbNumberIndex),
102 NNinput::Index::pitchYOffset);
103
104 typename NNClusterCalibrator<calib_data_t, traj_t>::BASE::Cov cov;
105 cov(1, 0) = cov(0, 1) = 0.0f;
106 cov(0, 0) = nnInput.precisionToRealCoord(positions.xprec(maxProbNumberIndex),
107 NNinput::Index::pitchXOffset);
108 cov(1, 1) = nnInput.precisionToRealCoord(positions.yprec(maxProbNumberIndex),
109 NNinput::Index::pitchYOffset);
110
111 unsigned int splitFlag =
112 positions.numberOfSubClusters() > 1
113 ? detail::setTrackStateFlag(Acts::TrackStateFlag::IsSplitHit)
114 : 0;
115 return {pos, cov, splitFlag};
116}
117
118template <typename calib_data_t, typename traj_t>
119std::tuple<int, int, int, int>
120NNClusterCalibrator<calib_data_t, traj_t>::clusterIndexRanges(
121 const xAOD::PixelCluster& cluster) const {
122 int xMin = std::numeric_limits<int>::max();
123 int xMax = std::numeric_limits<int>::min();
124 int yMin = std::numeric_limits<int>::max();
125 int yMax = std::numeric_limits<int>::min();
126 for (const auto& rdoID : cluster.rdoList()) {
127 const Identifier singlePixelID(rdoID);
128 const int x = static_cast<int>(this->pixelID().phi_index(singlePixelID));
129 const int y = static_cast<int>(this->pixelID().eta_index(singlePixelID));
130 xMin = std::min(x, xMin);
131 xMax = std::max(x, xMax);
132 yMin = std::min(y, yMin);
133 yMax = std::max(y, yMax);
134 }
135 return {xMin, xMax, yMin, yMax};
136}
137
138template <typename calib_data_t, typename traj_t>
139InDetDD::SiCellId
140NNClusterCalibrator<calib_data_t, traj_t>::weightedClusterCenter(
141 const xAOD::PixelCluster& cluster,
142 const InDetDD::PixelModuleDesign* design) const {
143
144 // obtain weighted position
145 double sum = 0;
146 InDetDD::SiLocalPosition position(0, 0);
147 auto idvals = Acts::zip(cluster.rdoList(), cluster.chargeList());
148 for (auto [rdoID, charge] : idvals) {
149 const Identifier singlePixelID(rdoID);
150 const int x = static_cast<int>(this->pixelID().phi_index(singlePixelID));
151 const int y = static_cast<int>(this->pixelID().eta_index(singlePixelID));
152
153 InDetDD::SiLocalPosition siLocalPosition(
154 design->positionFromColumnRow(y, x));
155 position += siLocalPosition * charge;
156 sum += charge;
157 }
158 position /= sum;
159
160 return design->cellIdOfPosition(position);
161}
162
163template <typename calib_data_t, typename traj_t>
164void NNClusterCalibrator<calib_data_t, traj_t>::fillClusterData(
165 NNinput& nn, const xAOD::PixelCluster& cluster,
166 const InDetDD::SiDetectorElement& detElement) const {
167
168 const Identifier moduleID(
169 static_cast<Identifier::value_type>(cluster.identifier()));
170 nn.set(NNinput::Index::layer, (float)this->pixelID().layer_disk(moduleID));
171 nn.set(NNinput::Index::bec, (float)this->pixelID().barrel_ec(moduleID));
172
173 // need to decide how to center the cluster in 7 x 7 matrix
174 // this implementation will center it w/o taking into account the charge
175 // (will be revisited)
176 // reminder: row <-> phi <-> x, column <-> eta <-> y
177 auto [xMin, xMax, yMin, yMax] = clusterIndexRanges(cluster);
178
179 // this is a particular way of truncating cluster to 7x7
180 // TODO test if picking the center would work better for large clusters
181 xMax = std::clamp(xMax, xMin,
182 xMin + static_cast<int>(NNinput::Index::windowSize));
183 yMax = std::clamp(yMax, yMin,
184 yMin + static_cast<int>(NNinput::Index::windowSize));
185
186 const InDetDD::PixelModuleDesign* design(
187 dynamic_cast<const InDetDD::PixelModuleDesign*>(&detElement.design()));
188
189 nn.centerCell = weightedClusterCenter(cluster, design);
190 nn.centerPosition = design->localPositionOfCell(nn.centerCell);
191 const int xCenter = nn.centerCell.phiIndex();
192 const int yCenter = nn.centerCell.etaIndex();
193
194 auto idvals = Acts::zip(cluster.rdoList(), cluster.chargeList());
195 for (auto [rdoID, charge] : idvals) {
196 const Identifier singlePixelID(rdoID);
197 const int x = static_cast<int>(this->pixelID().phi_index(singlePixelID));
198 const int y = static_cast<int>(this->pixelID().eta_index(singlePixelID));
199
200 const int xNN = NNinput::toNNinputIndex(x, xCenter);
201 const int yNN = NNinput::toNNinputIndex(y, yCenter);
202
203 if (NNinput::inWindow(xNN, yNN)) {
204 nn.setPixelCharge(xNN, yNN, charge);
205 } else {
206 // this happens, we need to understand frequency and impact
207 }
208 }
209
210 for (int i = 0; i < static_cast<int>(NNinput::Index::windowSize); ++i) {
211 nn.setPixelXPitch(i, 0.1);
212 nn.setPixelYPitch(i, 0.1);
213 const int x = NNinput::toModuleIndex(i, xCenter);
214 const int y = NNinput::toModuleIndex(i, yCenter);
215 const InDetDD::SiCellId cell(x, y);
216 if (cell.isValid())
217 nn.setPixelXPitch(i, design->parameters(cell).width().xPhi());
218 }
219}
220
221template <typename calib_data_t, typename traj_t>
222std::array<float, 3>
223NNClusterCalibrator<calib_data_t, traj_t>::predictNumberOfClusters(
224 NNinput& nn) const {
225 Ort::Session& session = *m_options.m_models->numberNetwork;
226 auto inputTypeInfo = session.GetInputTypeInfo(0);
227 auto tensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
228 const int64_t expectedDim = tensorInfo.GetShape()[1];
229
230 // TODO investigate streamlining this
231 // this is copy paste from NnClusterizationFactory
232 // seems to be a lot of preps to be repeated for each invocation
233 // maybe it can be spared?
234 Ort::MemoryInfo memInfo =
235 Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
236
237 std::vector<int64_t> inputShape = {1, expectedDim};
238 Ort::Value inputTensor = Ort::Value::CreateTensor<float>(
239 memInfo, nn.payload().data(), nn.payload().size(), inputShape.data(),
240 inputShape.size());
241
242 Ort::AllocatorWithDefaultOptions allocator;
243 auto inputName = session.GetInputNameAllocated(0, allocator);
244 auto outputName = session.GetOutputNameAllocated(0, allocator);
245 std::array<const char*, 1> inputNames({inputName.get()});
246 std::array<const char*, 1> outputNames({outputName.get()});
247
248 // Run inference
249 auto outputTensors = session.Run(Ort::RunOptions{nullptr}, inputNames.data(),
250 &inputTensor, 1, outputNames.data(), 1);
251
252 // Extract output
253 const float* outputData = outputTensors[0].GetTensorData<float>();
254 return std::array<float, 3>({*outputData, *(outputData+1), *(outputData+2)});
255}
256
257template <typename calib_data_t, typename traj_t>
258Ort::Session& NNClusterCalibrator<calib_data_t, traj_t>::selectPositionNetwork(
259 int number) const {
260 if (number == 1) {
261 return *m_options.m_models->positionNetwork1;
262 } else if (number == 2) {
263 return *m_options.m_models->positionNetwork2;
264 }
265 return *m_options.m_models->positionNetwork3;
266}
267
268template <typename calib_data_t, typename traj_t>
269PositionNNoutput NNClusterCalibrator<calib_data_t, traj_t>::predictPositions(
270 NNinput& nn, int number) const {
271
272 Ort::Session& session = selectPositionNetwork(number);
273 auto inputTypeInfo = session.GetInputTypeInfo(0);
274 auto tensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();
275 const int64_t expectedDim = tensorInfo.GetShape()[1];
276
277 // TODO investigate streamlining this
278 // at least part of preparation can be shared between Number and Posion (this)
279 // this is copy paste from NnClusterizationFactory
280 // seems to be a lot of preps to be repeated for each invocation
281 // maybe it can be spared?
282 Ort::MemoryInfo memInfo =
283 Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
284
285 std::vector<int64_t> inputShape = {1, expectedDim};
286 Ort::Value inputTensor = Ort::Value::CreateTensor<float>(
287 memInfo, nn.payload().data(), nn.payload().size(), inputShape.data(),
288 inputShape.size());
289
290 Ort::AllocatorWithDefaultOptions allocator;
291 auto inputName = session.GetInputNameAllocated(0, allocator);
292 auto outputName = session.GetOutputNameAllocated(0, allocator);
293 std::array<const char*, 1> inputNames({inputName.get()});
294 std::array<const char*, 1> outputNames({outputName.get()});
295
296 Ort::TypeInfo typeInfo = session.GetOutputTypeInfo(0);
297 auto tensorInfoOutput = typeInfo.GetTensorTypeAndShapeInfo();
298 std::vector<int64_t> shape = tensorInfoOutput.GetShape();
299
300 // this shape is supposed to always be: [number*5]
301 assert(shape[1] == 5 * number);
302
303 // Run inference
304 auto outputTensors = session.Run(Ort::RunOptions{nullptr}, inputNames.data(),
305 &inputTensor, 1, outputNames.data(), 1);
306
307 // Extract output
308 const float* outputData = outputTensors[0].GetTensorData<float>();
309
310 // NN outputs 5 numbers
311 return PositionNNoutput(outputData, outputData + (number * 5));
312}
313
314
315} // namespace ActsTrk::detail
316
317#endif // NNCLUSTERINGTOOLIMPL_ICC