diff --git a/AtlasTest/CITest/Athena.cmake b/AtlasTest/CITest/Athena.cmake
index 219c9fdbfb81ca636afc4298e518d1e9ab2213c8..498fbc94e2f31e2f2a33ee5a00f424fa1b2f311b 100644
--- a/AtlasTest/CITest/Athena.cmake
+++ b/AtlasTest/CITest/Athena.cmake
@@ -291,6 +291,9 @@ atlas_add_citest( ACTS_ActsBenchmarkWithSpot
    PROPERTIES PROCESSOR 8
    LOG_IGNORE_PATTERN "ActsTrackFindingAlg.*ERROR Propagation reached the step count limit|ActsTrackFindingAlg.*ERROR Propagation failed: PropagatorError:3 Propagation reached the configured maximum number of steps with the initial parameters|ActsTrackFindingAlg.*ERROR CombinatorialKalmanFilter failed: CombinatorialKalmanFilterError:5 Propagation reaches max steps before track finding is finished with the initial parameters|ActsTrackFindingAlg.Acts.*ERROR.*SurfaceError:1" )
 
+atlas_add_citest( ACTS_ActsAnalogueClustering
+  SCRIPT ActsAnalogueClustering.sh )
+
 #################################################################################
 # Trigger
 #################################################################################
diff --git a/Tracking/Acts/ActsConfig/python/ActsConfigFlags.py b/Tracking/Acts/ActsConfig/python/ActsConfigFlags.py
index f721ab7c215c7c864f6e69a8ae6f2a50372b11c7..bef91085727e89552b9d3d5ef03c4a06eb8fd8fd 100644
--- a/Tracking/Acts/ActsConfig/python/ActsConfigFlags.py
+++ b/Tracking/Acts/ActsConfig/python/ActsConfigFlags.py
@@ -16,6 +16,10 @@ class TrackFitterType(FlagEnum):
     KalmanFitter = 'KalmanFitter' # default ACTS fitter to choose
     GaussianSumFitter = 'GaussianSumFitter' # new experimental implementation
 
+class PixelCalibrationStrategy(FlagEnum):
+    Uncalibrated = "Uncalibrated"
+    AnalogueClustering = "AnalogueClustering"
+
 def createActsConfigFlags():
     actscf = AthConfigFlags()
     
@@ -48,6 +52,7 @@ def createActsConfigFlags():
     actscf.addFlag("Acts.SeedingStrategy", SeedingStrategy.Default, type=SeedingStrategy)  # Define Seeding Strategy
 
     # Track finding
+    actscf.addFlag('Acts.PixelCalibrationStrategy', PixelCalibrationStrategy.Uncalibrated, type=PixelCalibrationStrategy)
     actscf.addFlag('Acts.doRotCorrection', True)
     actscf.addFlag('Acts.doPrintTrackStates', False)
     actscf.addFlag('Acts.skipDuplicateSeeds', True)
diff --git a/Tracking/Acts/ActsConfig/python/ActsMeasurementCalibrationConfig.py b/Tracking/Acts/ActsConfig/python/ActsMeasurementCalibrationConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..76737bb337f4d89afc5943456ee26deb0f735ae8
--- /dev/null
+++ b/Tracking/Acts/ActsConfig/python/ActsMeasurementCalibrationConfig.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+def ActsAnalogueClusteringToolCfg(flags,
+                                  name: str='ActsAnalogueClusteringTool',
+                                  **kwargs) -> ComponentAccumulator:
+
+    if not flags.Detector.GeometryITk:
+        raise Exception("Acts Analogue Clustering calibration only supports ITk!")
+
+    from PixelConditionsAlgorithms.ITkPixelConditionsConfig import ITkPixelOfflineCalibCondAlgCfg
+    from SiLorentzAngleTool.ITkPixelLorentzAngleConfig import ITkPixelLorentzAngleToolCfg
+
+    acc = ComponentAccumulator()
+
+    acc.merge(ITkPixelOfflineCalibCondAlgCfg(flags))
+
+    if 'DetEleCollKey' not in kwargs:
+        kwargs.setdefault("DetEleCollKey", "ITkPixelDetectorElementCollection")
+
+    if 'PixelOfflineCalibData' not in kwargs:
+        kwargs.setdefault("PixelOfflineCalibData", "ITkPixelOfflineCalibData")
+
+    if 'PixelLorentzAngleTool' not in kwargs:
+        kwargs.setdefault("PixelLorentzAngleTool", acc.popToolsAndMerge(ITkPixelLorentzAngleToolCfg(flags)))
+
+    if 'CalibrateErrors' not in kwargs:
+        kwargs.setdefault("CalibrateErrors", not flags.Tracking.useBroadPixClusterErrors)
+
+    acc.setPrivateTools(CompFactory.ActsTrk.ITkAnalogueClusteringTool(name, **kwargs))
+
+    return acc
diff --git a/Tracking/Acts/ActsConfig/python/ActsTrackFindingConfig.py b/Tracking/Acts/ActsConfig/python/ActsTrackFindingConfig.py
index 766421ca02ddda5385b2bba40474e85701aa7371..ee9ac463c5e03c388d497a40ca5e6ea85549b7d0 100644
--- a/Tracking/Acts/ActsConfig/python/ActsTrackFindingConfig.py
+++ b/Tracking/Acts/ActsConfig/python/ActsTrackFindingConfig.py
@@ -117,6 +117,18 @@ def ActsMainTrackFindingAlgCfg(flags,
                                                ReverseFilteringPt=0,
                                                OutlierChi2Cut=30))
         )
+
+    if 'PixelCalibrator' not in kwargs:
+        from AthenaConfiguration.Enums import BeamType
+        from ActsConfig.ActsConfigFlags import PixelCalibrationStrategy
+        from ActsConfig.ActsMeasurementCalibrationConfig import ActsAnalogueClusteringToolCfg
+
+        if flags.Beam.Type is not BeamType.Cosmics:
+            if flags.Acts.PixelCalibrationStrategy is PixelCalibrationStrategy.AnalogueClustering:
+                kwargs.setdefault(
+                    'PixelCalibrator',
+                    acc.popToolsAndMerge(ActsAnalogueClusteringToolCfg(flags))
+                )
         
     if flags.Acts.doMonitoring and 'MonTool' not in kwargs:
         from ActsConfig.ActsMonitoringConfig import ActsTrackFindingMonitoringToolCfg
diff --git a/Tracking/Acts/ActsConfig/test/ActsAnalogueClustering.sh b/Tracking/Acts/ActsConfig/test/ActsAnalogueClustering.sh
new file mode 100755
index 0000000000000000000000000000000000000000..720b529c5131a5ad1f560e4cb09a1834cc2e73a4
--- /dev/null
+++ b/Tracking/Acts/ActsConfig/test/ActsAnalogueClustering.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/bash
+# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+
+# ttbar mu=200 input
+input_rdo=/cvmfs/atlas-nightlies.cern.ch/repo/data/data-art/PhaseIIUpgrade/RDO/ATLAS-P2-RUN4-03-00-00/mc21_14TeV.601229.PhPy8EG_A14_ttbar_hdamp258p75_SingleLep.recon.RDO.e8481_s4149_r14700/RDO.33629020._000047.pool.root.1
+n_events=2
+
+Reco_tf.py --CA \
+  --preInclude "InDetConfig.ConfigurationHelpers.OnlyTrackingPreInclude,ActsConfig.ActsCIFlags.actsValidateTracksFlags" \
+  --preExec 'from ActsConfig.ActsConfigFlags import PixelCalibrationStrategy; flags.Acts.PixelCalibrationStrategy=PixelCalibrationStrategy.AnalogueClustering' \
+  --inputRDOFile ${input_rdo} \
+  --outputAODFile test.AOD.pool.root  \
+  --maxEvents ${n_events}
diff --git a/Tracking/Acts/ActsTrackReconstruction/CMakeLists.txt b/Tracking/Acts/ActsTrackReconstruction/CMakeLists.txt
index 77e23639fbc1ce241788be5a4cd8ccbf54ffe331..0048eabd60227098866fa873d5e63edaf4372723 100644
--- a/Tracking/Acts/ActsTrackReconstruction/CMakeLists.txt
+++ b/Tracking/Acts/ActsTrackReconstruction/CMakeLists.txt
@@ -33,6 +33,7 @@ atlas_add_component( ActsTrackReconstruction
 		       InDetReadoutGeometry
 		       MagFieldConditions
 		       MagFieldElements
+		       PixelConditionsData
 		       StoreGateLib
 		       TRT_ReadoutGeometry
 		       TrkEventPrimitives
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/AnalogueClusteringToolImpl.h b/Tracking/Acts/ActsTrackReconstruction/src/AnalogueClusteringToolImpl.h
new file mode 100644
index 0000000000000000000000000000000000000000..68ff2e9a40a6c5cbee037516cb69299b66073673
--- /dev/null
+++ b/Tracking/Acts/ActsTrackReconstruction/src/AnalogueClusteringToolImpl.h
@@ -0,0 +1,96 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef ACTS_ANALOGUECLUSTERING_H
+#define ACTS_ANALOGUECLUSTERING_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "InDetReadoutGeometry/SiDetectorElementCollection.h"
+#include "PixelConditionsData/ITkPixelOfflineCalibData.h"
+#include "StoreGate/ReadCondHandleKey.h"
+
+#include "ActsToolInterfaces/IOnTrackCalibratorTool.h"
+#include "OnTrackCalibrator.h"
+
+namespace ActsTrk {
+
+template <typename calib_data_t, typename traj_t>
+class AnalogueClusteringToolImpl : public extends<AthAlgTool, IOnTrackCalibratorTool<traj_t>> {
+public:
+    using base_class = typename extends<AthAlgTool, IOnTrackCalibratorTool<traj_t>>::base_class;
+    using Pos = typename OnTrackCalibrator<traj_t>::PixelPos;
+    using Cov = typename OnTrackCalibrator<traj_t>::PixelCov;
+    using TrackStateProxy = typename OnTrackCalibrator<traj_t>::TrackStateProxy;
+
+    AnalogueClusteringToolImpl(const std::string& type,
+			       const std::string& name,
+			       const IInterface* parent);
+
+    virtual StatusCode initialize() override;
+
+    std::pair<Pos, Cov> calibrate(const Acts::GeometryContext&,
+				  const Acts::CalibrationContext&,
+				  const xAOD::PixelCluster&,
+				  const TrackStateProxy&) const;
+    
+    virtual void connect(OnTrackCalibrator<traj_t>& calibrator) const override;
+
+private:
+
+    using error_data_t = typename std::remove_pointer_t<decltype(std::declval<calib_data_t>().getClusterErrorData())>;
+
+    const InDetDD::SiDetectorElement* getDetectorElement(xAOD::DetectorIDHashType id) const;
+
+    std::pair<float, float> anglesOfIncidence(const InDetDD::SiDetectorElement& element,
+					      const TrackStateProxy& state) const;
+
+    const error_data_t* getErrorData() const;
+
+    std::pair<float, float>
+    getPositionCorrection(const error_data_t& errorData,
+			  const InDetDD::SiDetectorElement& element,
+			  const std::pair<float, float>& angles,
+			  const xAOD::PixelCluster& cluster) const;
+
+    std::pair<float, float>
+    getCorrectedError(const error_data_t& errorData,
+		      const InDetDD::SiDetectorElement& element,
+		      const std::pair<float, float>& angles,
+		      const xAOD::PixelCluster& cluster) const;
+
+    SG::ReadCondHandleKey<InDetDD::SiDetectorElementCollection> m_pixelDetEleCollKey {
+	this,
+        "DetEleCollKey",
+	"",
+	"Key of SiDetectorElementCollection for Pixel"
+    };
+
+    SG::ReadCondHandleKey<calib_data_t> m_clusterErrorKey {
+	this,
+	"PixelOfflineCalibData",
+	"ITkPixelOfflineCalibData",
+	"Calibration data for pixel clusters"
+    };
+
+
+    ToolHandle<ISiLorentzAngleTool> m_lorentzAngleTool {
+	this,
+	"PixelLorentzAngleTool",
+	"",
+	"Tool to retreive Lorentz angle"
+    };
+
+    Gaudi::Property<bool> m_doErrCalib {this, "CalibrateErrors", true};
+
+    // in micrometers
+    Gaudi::Property<int> m_thickness {this, "PixelThickness", 250};
+
+
+};
+
+} // namespace ActsTrk
+
+#include "AnalogueClusteringToolImpl.icc"
+
+#endif
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/AnalogueClusteringToolImpl.icc b/Tracking/Acts/ActsTrackReconstruction/src/AnalogueClusteringToolImpl.icc
new file mode 100644
index 0000000000000000000000000000000000000000..c5ec19201d82362cca3956c770a3ec54616060f4
--- /dev/null
+++ b/Tracking/Acts/ActsTrackReconstruction/src/AnalogueClusteringToolImpl.icc
@@ -0,0 +1,242 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef ANALOGUECLUSTERINGTOOLIMPL_ICC
+#define ANALOGUECLUSTERINGTOOLIMPL_ICC
+
+namespace ActsTrk {
+
+template <typename calib_data_t, typename traj_t>
+AnalogueClusteringToolImpl<calib_data_t, traj_t>::AnalogueClusteringToolImpl(
+    const std::string& type,
+    const std::string& name,
+    const IInterface* parent)
+    : base_class(type, name, parent)
+{}
+
+template <typename calib_data_t, typename traj_t>
+StatusCode AnalogueClusteringToolImpl<calib_data_t, traj_t>::initialize()
+{
+    ATH_MSG_DEBUG("Initializing " << AthAlgTool::name() << " ...");
+    ATH_CHECK(m_pixelDetEleCollKey.initialize());
+    ATH_CHECK(m_clusterErrorKey.initialize()); 
+    ATH_CHECK(m_lorentzAngleTool.retrieve());
+    ATH_MSG_DEBUG(AthAlgTool::name() << " successfully initialized");
+    return StatusCode::SUCCESS;
+}
+
+template <typename calib_data_t, typename traj_t>
+const InDetDD::SiDetectorElement*
+AnalogueClusteringToolImpl<calib_data_t, traj_t>::getDetectorElement(xAOD::DetectorIDHashType id) const
+{
+    SG::ReadCondHandle<InDetDD::SiDetectorElementCollection> pixelDetEleHandle(
+	m_pixelDetEleCollKey,
+	Gaudi::Hive::currentContext());
+
+    const InDetDD::SiDetectorElementCollection* detElements(*pixelDetEleHandle);
+    
+    if (!pixelDetEleHandle.isValid() or detElements == nullptr) {
+	ATH_MSG_ERROR(m_pixelDetEleCollKey.fullKey() << " is not available.");
+	return nullptr;
+    }
+
+    const InDetDD::SiDetectorElement* element = detElements->getDetectorElement(id);
+    if (element == nullptr) {
+	ATH_MSG_ERROR("No element corresponding to hash " << id << " for " << m_pixelDetEleCollKey.fullKey());
+	return nullptr;
+    }
+
+    return element;
+}
+
+template <typename calib_data_t, typename traj_t>
+std::pair<float, float>
+AnalogueClusteringToolImpl<calib_data_t, traj_t>::anglesOfIncidence(const InDetDD::SiDetectorElement& element,
+					  const TrackStateProxy& state) const
+{
+    
+    Acts::Vector3 direction = Acts::makeDirectionFromPhiTheta(
+	state.parameters()[Acts::eBoundPhi],
+	state.parameters()[Acts::eBoundTheta]);
+
+    float projPhi = direction.dot(element.phiAxis());
+    float projEta = direction.dot(element.etaAxis());
+    float projNorm = direction.dot(element.normal());
+
+    float anglePhi = std::atan2(projPhi, projNorm);
+    float angleEta = std::atan2(projEta, projNorm);
+
+    // Map the angles of inward-going tracks onto [-PI/2, PI/2]
+    if (anglePhi > M_PI *0.5) {
+	anglePhi -= M_PI;
+    }
+    if (anglePhi < -M_PI *0.5) {
+	anglePhi += M_PI;
+    }
+
+    // settle the sign/pi periodicity issues
+    float thetaloc;
+    if (angleEta > -0.5 * M_PI && angleEta < M_PI / 2.) {
+	thetaloc = M_PI_2 - angleEta;
+    } else if (angleEta > M_PI_2 && angleEta < M_PI) {
+	thetaloc = 1.5 * M_PI - angleEta;
+    } else { // 3rd quadrant
+	thetaloc = -M_PI_2 - angleEta;
+    }
+    angleEta = -1 * log(tan(thetaloc * 0.5));
+
+    
+    // Subtract the Lorentz angle effect
+    float angleShift = m_lorentzAngleTool->getTanLorentzAngle(element.identifyHash());
+    anglePhi = std::atan(std::tan(anglePhi) - element.design().readoutSide() * angleShift);
+
+    return std::make_pair(anglePhi, angleEta);
+}
+
+template <typename calib_data_t, typename traj_t>
+const typename AnalogueClusteringToolImpl<calib_data_t, traj_t>::error_data_t*
+AnalogueClusteringToolImpl<calib_data_t, traj_t>::getErrorData() const
+{
+    SG::ReadCondHandle<calib_data_t> handle(
+	m_clusterErrorKey,
+	Gaudi::Hive::currentContext());
+
+    if (!handle.isValid()) {
+	ATH_MSG_ERROR(m_clusterErrorKey << " is not available.");
+	return nullptr;
+    }
+
+    const error_data_t* data = handle->getClusterErrorData();
+    if (data == nullptr) {
+	ATH_MSG_ERROR("No cluster error data corresponding to " << m_clusterErrorKey);
+	return nullptr;
+    }
+
+    return data;
+}
+
+template <typename calib_data_t, typename traj_t>
+std::pair<float, float>
+AnalogueClusteringToolImpl<calib_data_t, traj_t>::getPositionCorrection(
+    const error_data_t& errorData,
+    const InDetDD::SiDetectorElement& element,
+    const std::pair<float, float>& angles,
+    const xAOD::PixelCluster& cluster) const
+{
+    // TODO validate these angles
+    auto& [anglePhi, angleEta] = angles;
+
+    float deltaX = 0;
+    float deltaY = 0;
+    
+    float omX = cluster.omegaX();
+    float omY = cluster.omegaY();
+    int nrows = cluster.channelsInPhi();
+    int ncols = cluster.channelsInEta();
+
+    if (omX > -0.5 && omY > -0.5 && (nrows > 1 || ncols > 1)) {
+	Identifier id = element.identify();
+	std::pair<double, double> delta =
+	    errorData.getDelta(
+		&id,
+		nrows,
+		anglePhi,
+		ncols,
+		angleEta);
+
+	if (nrows > 1)
+	    deltaX = delta.first * (omX - 0.5);
+
+	if (ncols > 1)
+	    deltaY = delta.second * (omY - 0.5);
+    }
+
+    return std::make_pair(deltaX, deltaY);
+}
+
+template <typename calib_data_t, typename traj_t>
+std::pair<float, float>
+AnalogueClusteringToolImpl<calib_data_t, traj_t>::getCorrectedError(
+    const error_data_t& errorData,
+    const InDetDD::SiDetectorElement& element,
+    const std::pair<float, float>& angles,
+    const xAOD::PixelCluster& cluster) const
+{
+    float errX = 0;
+    float errY = 0;
+
+    int nrows = cluster.channelsInPhi();
+    int ncols = cluster.channelsInEta();
+
+    auto& [anglePhi, angleEta] = angles;
+
+    // Special case for very shallow tracks
+    // Error estimated from geometrical projection of
+    // the track path in silicon onto the module surface
+    if (std::abs(anglePhi) > 1) {
+	errX = m_thickness * Acts::UnitConstants::um * std::tan(std::abs(anglePhi)) / std::sqrt(12);
+	errY = m_thickness * Acts::UnitConstants::um * std::tan(std::abs(angleEta));
+	if (cluster.widthInEta() > errY) {
+	    errY = cluster.widthInEta() / std::sqrt(12);
+	} else {
+	    errY /= std::sqrt(12);
+	}
+    } else if (nrows > 1 && ncols > 1) {
+	Identifier id = element.identify();
+	std::tie(errX, errY) = errorData.getDeltaError(&id);
+    }
+
+    return std::make_pair(errX, errY);
+}
+
+template <typename calib_data_t, typename traj_t>
+std::pair<typename AnalogueClusteringToolImpl<calib_data_t, traj_t>::Pos,
+	  typename AnalogueClusteringToolImpl<calib_data_t, traj_t>::Cov>
+AnalogueClusteringToolImpl<calib_data_t, traj_t>::calibrate(
+    const Acts::GeometryContext& /*gctx*/,
+    const Acts::CalibrationContext& /*cctx*/,
+    const xAOD::PixelCluster& cluster,
+    const TrackStateProxy& state) const
+{
+    Pos pos = cluster.template localPosition<2>();
+    Cov cov = cluster.template localCovariance<2>();
+
+    assert(!cluster.rdoList().empty());
+    const InDetDD::SiDetectorElement *detElement = getDetectorElement(cluster.identifierHash());
+    if (detElement == nullptr) {
+	throw std::runtime_error("SiDetectorElement is NULL");
+    }
+
+    const error_data_t *errorData = getErrorData();
+    if (errorData == nullptr) {
+	throw std::runtime_error("PixelClusterErrorData is NULL");
+    }
+
+    std::pair<float, float> angles = anglesOfIncidence(*detElement, state);
+
+    auto [deltaX, deltaY] = getPositionCorrection(*errorData, *detElement, angles, cluster);
+    pos[Acts::eBoundLoc0] += deltaX;
+    pos[Acts::eBoundLoc1] += deltaY;
+
+    if (m_doErrCalib) {
+	auto [errX, errY] = getCorrectedError(*errorData, *detElement, angles, cluster);
+	if (errX > 0)
+	    cov(0, 0) = errX * errX;
+	if (errY > 0)
+	    cov(1, 1) = errY * errY;
+    }
+    
+    return std::make_pair(pos, cov);
+}
+
+template <typename calib_data_t, typename traj_t>
+void AnalogueClusteringToolImpl<calib_data_t, traj_t>::connect(OnTrackCalibrator<traj_t>& calibrator) const
+{
+    calibrator.pixel_calibrator. template connect<&AnalogueClusteringToolImpl<calib_data_t, traj_t>::calibrate>(this);
+}
+
+
+} // namespace ActsTrk
+
+#endif
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/ITkAnalogueClusteringTool.h b/Tracking/Acts/ActsTrackReconstruction/src/ITkAnalogueClusteringTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..8062eaad2bd178732d5dd30337bf96c4ee9f3707
--- /dev/null
+++ b/Tracking/Acts/ActsTrackReconstruction/src/ITkAnalogueClusteringTool.h
@@ -0,0 +1,23 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef ACTS_ITKANALOGUECLUSTERINGTOOL_H
+#define ACTS_ITKANALOGUECLUSTERINGTOOL_H
+
+#include "AnalogueClusteringToolImpl.h"
+
+namespace ActsTrk {
+
+class ITkAnalogueClusteringTool :
+	public AnalogueClusteringToolImpl<ITk::PixelOfflineCalibData, ActsTrk::MutableTrackStateBackend> {
+public:
+    using calib_data_t = ITk::PixelOfflineCalibData;
+    using traj_t = ActsTrk::MutableTrackStateBackend;
+    using AnalogueClusteringToolImpl<calib_data_t, traj_t>::AnalogueClusteringToolImpl;
+};
+
+} // namespace ActsTrk
+
+
+#endif
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.h b/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.h
index 00248036c2ab534d9fabc87db613085e0be054bc..fe95ca9964478ad7f4dfb391cf0d8d2bad1ae20b 100644
--- a/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.h
+++ b/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.h
@@ -1,10 +1,13 @@
 /*
-  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
 */
 
 #ifndef ONTRACKCALIBRATOR_H
 #define ONTRACKCALIBRATOR_H
 
+
+#include <GaudiKernel/ToolHandle.h>
+
 #include "MeasurementCalibrator.h"
 
 namespace ActsTrk {
@@ -39,8 +42,8 @@ public:
 
     OnTrackCalibrator(const ActsTrk::IActsToTrkConverterTool &converter_tool,
 		      const TrackingSurfaceHelper &surface_helper,
-		      IOnTrackCalibratorTool<traj_t> *pixelTool,
-		      IOnTrackCalibratorTool<traj_t> *stripTool);
+		      const ToolHandle<IOnTrackCalibratorTool<traj_t>> &pixelTool,
+		      const ToolHandle<IOnTrackCalibratorTool<traj_t>> &stripTool);
 
     void calibrate(const Acts::GeometryContext& geoctx,
 		   const Acts::CalibrationContext& cctx,
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.icc b/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.icc
index 8357bbcb791d1634e6306ac6e2c59d3cd8312f78..91be5e703b3e315a12e468d3aaa4e147ced4faaf 100644
--- a/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.icc
+++ b/Tracking/Acts/ActsTrackReconstruction/src/OnTrackCalibrator.icc
@@ -12,25 +12,26 @@ OnTrackCalibrator<traj_t> OnTrackCalibrator<traj_t>::NoCalibration(
     const ActsTrk::IActsToTrkConverterTool &converter_tool,
     const TrackingSurfaceHelper &surface_helper)
 {
-    return OnTrackCalibrator(converter_tool, surface_helper, nullptr, nullptr);
+    ToolHandle<IOnTrackCalibratorTool<traj_t>> null(nullptr);
+    return OnTrackCalibrator(converter_tool, surface_helper, null, null);
 }
 
 template <typename traj_t>
 OnTrackCalibrator<traj_t>::OnTrackCalibrator(
     const ActsTrk::IActsToTrkConverterTool &converter_tool,
     const TrackingSurfaceHelper &surface_helper,
-    IOnTrackCalibratorTool<traj_t> *pixelTool,
-    IOnTrackCalibratorTool<traj_t> *stripTool)
+    const ToolHandle<IOnTrackCalibratorTool<traj_t>>& pixelTool,
+    const ToolHandle<IOnTrackCalibratorTool<traj_t>>& stripTool)
     : MeasurementCalibratorBase(converter_tool),
       m_surfaceHelper(&surface_helper)
 {
-    if (pixelTool != nullptr) {
+    if (pixelTool.isEnabled()) {
 	pixelTool->connect(*this);
     } else {
 	pixel_calibrator.template connect<&OnTrackCalibrator<traj_t>::passthrough<2, xAOD::PixelCluster>>(this);
     }
 
-    if (stripTool != nullptr) {
+    if (stripTool.isEnabled()) {
 	stripTool->connect(*this);
     } else {
 	strip_calibrator.template connect<&OnTrackCalibrator<traj_t>::passthrough<1, xAOD::StripCluster>>(this);
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.cxx b/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.cxx
index b666278ce4a64a2a9129003a270e0f08c294b92b..d910b43a9dc53ae57aedc23a57e508deb06e9bf1 100644
--- a/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.cxx
+++ b/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.cxx
@@ -5,6 +5,7 @@
 #include "src/TrackFindingData.h"
 
 // Athena
+#include "AsgTools/ToolStore.h"
 #include "TrkParameters/TrackParameters.h"
 #include "TrkTrackSummary/TrackSummary.h"
 #include "InDetPrepRawData/PixelClusterCollection.h"
@@ -37,7 +38,7 @@
 #include "ActsInterop/Logger.h"
 
 #include "ActsInterop/TableUtils.h"
-#include "MeasurementCalibrator.h"
+#include "OnTrackCalibrator.h"
 // Other
 #include <sstream>
 #include <functional>
@@ -124,6 +125,8 @@ namespace ActsTrk
     ATH_CHECK(m_ATLASConverterTool.retrieve());
     ATH_CHECK(m_trackStatePrinter.retrieve(EnableTool{not m_trackStatePrinter.empty()}));
     ATH_CHECK(m_fitterTool.retrieve());
+    ATH_CHECK(m_pixelCalibTool.retrieve(EnableTool{not m_pixelCalibTool.empty()}));
+    ATH_CHECK(m_stripCalibTool.retrieve(EnableTool{not m_stripCalibTool.empty()}));
 
     m_logger = makeActsAthenaLogger(this, "Acts");
 
@@ -488,8 +491,14 @@ namespace ActsTrk
 
     ActsTrk::MutableTrackContainer tracksContainerTemp;
 
-    OnTrackCalibrator calibrator = OnTrackCalibrator<ActsTrk::MutableTrackStateBackend>
-	::NoCalibration(*m_ATLASConverterTool, tracking_surface_helper);
+    // Measurement calibration
+    // N.B. OnTrackCalibrator expects disabled tool handles when no calibration is requested.
+    // Therefore, passing them without checking if they are enabled is safe.
+    OnTrackCalibrator calibrator = OnTrackCalibrator<ActsTrk::MutableTrackStateBackend>(
+	*m_ATLASConverterTool,
+	tracking_surface_helper,
+	m_pixelCalibTool,
+	m_stripCalibTool);
 
     options.extensions.calibrator.connect<&OnTrackCalibrator<ActsTrk::MutableTrackStateBackend>::calibrate>(&calibrator);
 
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.h b/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.h
index 111f2c648b3a647a8d99aba4426a84fd5b324e17..de7fba0ee620ece10ff774f311203cdcd260f4af 100644
--- a/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.h
+++ b/Tracking/Acts/ActsTrackReconstruction/src/TrackFindingAlg.h
@@ -23,6 +23,7 @@
 #include "ActsEventCnv/IActsToTrkConverterTool.h"
 #include "ActsGeometry/ATLASSourceLink.h"
 #include "ActsToolInterfaces/IFitterTool.h"
+#include "ActsToolInterfaces/IOnTrackCalibratorTool.h"
 
 // Athena
 #include "AthenaMonitoringKernel/GenericMonitoringTool.h"
@@ -46,8 +47,6 @@
 #include "StoreGate/WriteHandleKey.h"
 #include "ActsEvent/TrackContainerHandlesHelper.h"
 
-#include "OnTrackCalibrator.h"
-
 class TrackingSurfaceHelper;
 namespace
 {
@@ -63,6 +62,7 @@ namespace ActsTrk
   class TrackFindingAlg : public AthReentrantAlgorithm
   {
   public:
+
     TrackFindingAlg(const std::string &name,
                     ISvcLocator *pSvcLocator);
     virtual ~TrackFindingAlg();
@@ -79,6 +79,10 @@ namespace ActsTrk
     ToolHandle<ActsTrk::IActsToTrkConverterTool> m_ATLASConverterTool{this, "ATLASConverterTool", ""};
     ToolHandle<ActsTrk::ITrackStatePrinter> m_trackStatePrinter{this, "TrackStatePrinter", "", "optional track state printer"};
     ToolHandle<ActsTrk::IFitterTool> m_fitterTool{this, "FitterTool", "", "Fitter Tool for Seeds"};
+    ToolHandle<ActsTrk::IOnTrackCalibratorTool<ActsTrk::MutableTrackStateBackend>> m_pixelCalibTool{
+      this, "PixelCalibrator", "", "Opt. pixel measurement calibrator"};
+    ToolHandle<ActsTrk::IOnTrackCalibratorTool<ActsTrk::MutableTrackStateBackend>> m_stripCalibTool{
+      this, "StripCalibrator", "", "Opt. strip measurement calibrator"};
 
     // Handle Keys
     // Seed collections. These 2 vectors must match element for element.
diff --git a/Tracking/Acts/ActsTrackReconstruction/src/components/ActsTrackReconstruction_entries.cxx b/Tracking/Acts/ActsTrackReconstruction/src/components/ActsTrackReconstruction_entries.cxx
index 30627d39e988f0f165da26b03a4b002966e3f5a6..a1312a782bc43d7e3e04ac3ab026010d9d78b258 100644
--- a/Tracking/Acts/ActsTrackReconstruction/src/components/ActsTrackReconstruction_entries.cxx
+++ b/Tracking/Acts/ActsTrackReconstruction/src/components/ActsTrackReconstruction_entries.cxx
@@ -7,6 +7,7 @@
 #include "src/ReFitterAlg.h"
 #include "src/TrackToTrackParticleCnvAlg.h"
 // Tools
+#include "src/ITkAnalogueClusteringTool.h"
 #include "src/TrackStatePrinter.h"
 #include "src/KalmanFitter.h"
 #include "src/GaussianSumFitter.h"
@@ -23,6 +24,7 @@ DECLARE_COMPONENT( ActsTrk::ProtoTrackReportingAlg )
 DECLARE_COMPONENT( ActsTrk::TrackToTrackParticleCnvAlg )
 
 // Tools
+DECLARE_COMPONENT( ActsTrk::ITkAnalogueClusteringTool )
 DECLARE_COMPONENT( ActsTrk::TrackStatePrinter )
 DECLARE_COMPONENT( ActsTrk::KalmanFitter )
 DECLARE_COMPONENT( ActsTrk::GaussianSumFitter )