2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
4#ifndef NNCLSUTERCALIBRATORTOOLIMPL_ICC
5#define NNCLSUTERCALIBRATORTOOLIMPL_ICC
7#include <PixelReadoutGeometry/PixelModuleDesign.h>
8#include <onnxruntime_cxx_api.h>
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 {
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 ...");
33 ATH_CHECK(BASE::initialize());
34 ATH_MSG_INFO(" successfully initialized, NNClusterCalibrator implementation");
35 return StatusCode::SUCCESS;
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
51 ATH_MSG_DEBUG("Obtained NN models for this event");
54 opt.m_models = onnxCollection.cptr();
56 auto barePointer = BASE::createOnTrackCalibrator(ctx).release();
58 dynamic_cast<AnalogueClusteringCalibrator<calib_data_t, traj_t>*>(
61 std::unique_ptr<AnalogueClusteringCalibrator<calib_data_t, traj_t>>(
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,
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);
82 ActsTrk::NNinput nnInput;
83 fillClusterData(nnInput, cluster, detElement);
85 nnInput.set(NNinput::Index::phi, std::atan(tan_incidence_angles.first));
86 nnInput.set(NNinput::Index::theta, std::atan(tan_incidence_angles.second));
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;
94 PositionNNoutput positions =
95 predictPositions(nnInput, maxProbNumberOfClusters);
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);
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);
111 unsigned int splitFlag =
112 positions.numberOfSubClusters() > 1
113 ? detail::setTrackStateFlag(Acts::TrackStateFlag::IsSplitHit)
115 return {pos, cov, splitFlag};
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);
135 return {xMin, xMax, yMin, yMax};
138template <typename calib_data_t, typename traj_t>
140NNClusterCalibrator<calib_data_t, traj_t>::weightedClusterCenter(
141 const xAOD::PixelCluster& cluster,
142 const InDetDD::PixelModuleDesign* design) const {
144 // obtain weighted position
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));
153 InDetDD::SiLocalPosition siLocalPosition(
154 design->positionFromColumnRow(y, x));
155 position += siLocalPosition * charge;
160 return design->cellIdOfPosition(position);
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 {
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));
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);
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));
186 const InDetDD::PixelModuleDesign* design(
187 dynamic_cast<const InDetDD::PixelModuleDesign*>(&detElement.design()));
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();
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));
200 const int xNN = NNinput::toNNinputIndex(x, xCenter);
201 const int yNN = NNinput::toNNinputIndex(y, yCenter);
203 if (NNinput::inWindow(xNN, yNN)) {
204 nn.setPixelCharge(xNN, yNN, charge);
206 // this happens, we need to understand frequency and impact
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);
217 nn.setPixelXPitch(i, design->parameters(cell).width().xPhi());
221template <typename calib_data_t, typename traj_t>
223NNClusterCalibrator<calib_data_t, traj_t>::predictNumberOfClusters(
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];
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);
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(),
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()});
249 auto outputTensors = session.Run(Ort::RunOptions{nullptr}, inputNames.data(),
250 &inputTensor, 1, outputNames.data(), 1);
253 const float* outputData = outputTensors[0].GetTensorData<float>();
254 return std::array<float, 3>({*outputData, *(outputData+1), *(outputData+2)});
257template <typename calib_data_t, typename traj_t>
258Ort::Session& NNClusterCalibrator<calib_data_t, traj_t>::selectPositionNetwork(
261 return *m_options.m_models->positionNetwork1;
262 } else if (number == 2) {
263 return *m_options.m_models->positionNetwork2;
265 return *m_options.m_models->positionNetwork3;
268template <typename calib_data_t, typename traj_t>
269PositionNNoutput NNClusterCalibrator<calib_data_t, traj_t>::predictPositions(
270 NNinput& nn, int number) const {
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];
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);
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(),
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()});
296 Ort::TypeInfo typeInfo = session.GetOutputTypeInfo(0);
297 auto tensorInfoOutput = typeInfo.GetTensorTypeAndShapeInfo();
298 std::vector<int64_t> shape = tensorInfoOutput.GetShape();
300 // this shape is supposed to always be: [number*5]
301 assert(shape[1] == 5 * number);
304 auto outputTensors = session.Run(Ort::RunOptions{nullptr}, inputNames.data(),
305 &inputTensor, 1, outputNames.data(), 1);
308 const float* outputData = outputTensors[0].GetTensorData<float>();
310 // NN outputs 5 numbers
311 return PositionNNoutput(outputData, outputData + (number * 5));
315} // namespace ActsTrk::detail
317#endif // NNCLUSTERINGTOOLIMPL_ICC