diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/data/trkAnaConfigs_example.json b/InnerDetector/InDetValidation/InDetTrackPerfMon/data/trkAnaConfigs_example.json
index cd245cc18d357fe7692cd7cc5179f266389be66d..dba349eb08ebb40fe07052de9ee7f08961cdd447 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/data/trkAnaConfigs_example.json
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/data/trkAnaConfigs_example.json
@@ -24,14 +24,15 @@
     "TrkAnaTrigEle" : {
         "enabled" : true,
         "TestType"  : "Trigger",
-        "RefType"   : "OfflineElectron", 
+        "RefType"   : "Offline", 
         "TrigTrkKey"    : "HLT_IDTrack_Electron_IDTrig",
         "ChainNames"    : [ "HLT_e.*_idperf_.*" ],
         "MatchingType"    : "DeltaRMatch",
         "dRmax"           : 0.05,
         "pTResMax"        : -9.9,
         "SubFolder"  : "TrkAnaTrigEle/",
-        "ObjectQuality" : "Tight",
+        "SelectOfflineObject" : "Electron", 
+        "ObjectQuality"       : "Tight",
         "doOfflineElectrons" : true
     },
     "TrkAnaOffl1" : {
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetAlgorithmConfig.py b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetAlgorithmConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..be97ac9825423c1376053ad48abd003a2e0e04b9
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetAlgorithmConfig.py
@@ -0,0 +1,130 @@
+#
+#  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+#
+
+'''@file InDetAlgorithmConfig.py
+@author M. Aparo
+@date 02-10-2023
+@brief CA-based python configurations for the event algorithms in this package
+'''
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+
+def TruthHitDecoratorAlgCfg( flags, name="TruthHitDecoratorAlg", **kwargs ):
+    '''
+    create decoration algorithm which decorates
+    truth particles with track parameters at the perigee.
+    '''
+    acc = ComponentAccumulator()
+
+    from TrkConfig.AtlasExtrapolatorConfig import AtlasExtrapolatorCfg
+    extrapolator = acc.popToolsAndMerge( AtlasExtrapolatorCfg(flags) )
+    acc.addPublicTool( extrapolator )
+    kwargs.setdefault( "Extrapolator", extrapolator )
+
+    acc.addEventAlgo( CompFactory.IDTPM.TruthHitDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineElectronDecoratorAlgCfg( flags, name="OfflineElectronDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineElectronDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineElectronGSFDecoratorAlgCfg( flags, name="OfflineElectronGSFDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    kwargs.setdefault( "OfflineTrkParticleContainerName", "GSFTrackParticles" )
+    kwargs.setdefault( "useGSF", True )
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineElectronDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineMuonDecoratorAlgCfg( flags, name="OfflineMuonDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineMuonDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineMuonCombDecoratorAlgCfg( flags, name="OfflineMuonDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    kwargs.setdefault( "OfflineTrkParticleContainerName", "CombinedMuonTrackParticles" )
+    kwargs.setdefault( "useCombinedMuonTracks", True )
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineMuonDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineTauBDT1prongDecoratorAlgCfg( flags, name="OfflineTauBDT1prongDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    kwargs.setdefault( "Prefix", "LinkedTauBDT1prong_" )
+    kwargs.setdefault( "TauType", "BDT" )
+    kwargs.setdefault( "TauNprongs", 1 )
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineTauDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineTauBDT3prongDecoratorAlgCfg( flags, name="OfflineTauBDT3prongDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    kwargs.setdefault( "Prefix", "LinkedTauBDT3prong_" )
+    kwargs.setdefault( "TauType", "BDT" )
+    kwargs.setdefault( "TauNprongs", 3 )
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineTauDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineTauRNN1prongDecoratorAlgCfg( flags, name="OfflineTauRNN1prongDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    kwargs.setdefault( "Prefix", "LinkedTauRNN1prong_" )
+    kwargs.setdefault( "TauType", "RNN" )
+    kwargs.setdefault( "TauNprongs", 1 )
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineTauDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineTauRNN3prongDecoratorAlgCfg( flags, name="OfflineTauRNN3prongDecoratorAlg", **kwargs ):
+    acc = ComponentAccumulator()
+    kwargs.setdefault( "Prefix", "LinkedTauRNN3prong_" )
+    kwargs.setdefault( "TauType", "RNN" )
+    kwargs.setdefault( "TauNprongs", 3 )
+    acc.addEventAlgo( CompFactory.IDTPM.OfflineTauDecoratorAlg( name, **kwargs ) )
+    return acc
+
+
+def OfflineObjectDecoratorAlgCfg( flags, name="OfflineObjectDecoratorAlg", **kwargs ):
+    '''
+    create decoration algorithm(s) to decorate offline tracks with a link to
+    the offline object they correspond to in the event reconstruction
+    '''
+    acc = ComponentAccumulator()
+
+    objStrList = []
+    tauTypeList = []
+    for trkAnaName in flags.PhysVal.IDTPM.trkAnaNames:
+        objStr = getattr( flags.PhysVal.IDTPM, trkAnaName+".SelectOfflineObject" )
+        if objStr : objStrList.append( objStr )
+        tauType = getattr( flags.PhysVal.IDTPM, trkAnaName+".TauType" )
+        if tauType : tauTypeList.append( tauType )
+
+    if "Electron" in objStrList:
+        acc.merge( OfflineElectronDecoratorAlgCfg(flags) )
+
+    if "ElectronGSF" in objStrList:
+        acc.merge( OfflineElectronGSFDecoratorAlgCfg(flags) )
+
+    if "Muon" in objStrList:
+        acc.merge( OfflineMuonDecoratorAlgCfg(flags) )
+
+    if "MuonComb" in objStrList:
+        acc.merge( OfflineMuonCombDecoratorAlgCfg(flags) )
+
+    if "Tau" in objStrList:
+        if "BDT" in tauTypeList:
+            acc.merge( OfflineTauBDT1prongDecoratorAlgCfg(flags) )
+            acc.merge( OfflineTauBDT3prongDecoratorAlgCfg(flags) )
+        if "RNN" in tauTypeList:
+            acc.merge( OfflineTauRNN1prongDecoratorAlgCfg(flags) )
+            acc.merge( OfflineTauRNN3prongDecoratorAlgCfg(flags) )
+
+    return acc
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetSelectionConfig.py b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetSelectionConfig.py
index 5be95a37c936442212a9dbb3117b4f036e1e7f95..73ff69e103b2ad17446ba22b0aa5c5e3f60b7fd4 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetSelectionConfig.py
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetSelectionConfig.py
@@ -1,5 +1,5 @@
 #
-#  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+#  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
 #
 
 '''@file InDetSelectionConfig.py
@@ -40,35 +40,32 @@ def TrackRoiSelectionToolCfg( flags, name="TrackRoiSelectionTool", **kwargs ):
     return acc
 
 
+def TrackObjectSelectionToolCfg( flags, name="TrackObjectSelectionTool", **kwargs ):
+    acc = ComponentAccumulator()
+
+    objStr = flags.PhysVal.IDTPM.currentTrkAna.SelectOfflineObject
+    kwargs.setdefault( "ObjectType",    objStr )
+    kwargs.setdefault( "ObjectQuality", flags.PhysVal.IDTPM.currentTrkAna.ObjectQuality )
+
+    if "Tau" in objStr:
+        kwargs.setdefault( "TauType",    flags.PhysVal.IDTPM.currentTrkAna.TauType )
+        kwargs.setdefault( "TauNprongs", flags.PhysVal.IDTPM.currentTrkAna.TauNprongs )
+
+    acc.setPrivateTools( CompFactory.IDTPM.TrackObjectSelectionTool( name, **kwargs ) )
+    return acc
+
+
 def TrackQualitySelectionToolCfg( flags, name="TrackQualitySelectionTool", **kwargs ):
     acc = ComponentAccumulator()
 
-    ## TODO - to be uncommented in later MRs
     ## offline track-object selection
-    #from InDetTrackPerfMon.ConfigUtils import getObjectStr
-    #if getObjectStr( flags ):
-    #    kwargs.setdefault( "DoObjectSelection", True )
-    #
-    #    if "TrackObjectSelectionTool" not in kwargs:
-    #       kwargs.setdefault( "TrackObjectSelectionTool", acc.popToolsAndMerge(
-    #           InDetTrackObjectSelectionToolCfg( flags,
-    #               name="TrackObjectSelectionTool" + flags.PhysVal.IDTPM.currentTrkAna.anaTag ) ) )
+    if flags.PhysVal.IDTPM.currentTrkAna.SelectOfflineObject:
+        kwargs.setdefault( "DoObjectSelection", True )
+    
+        if "TrackObjectSelectionTool" not in kwargs:
+           kwargs.setdefault( "TrackObjectSelectionTool", acc.popToolsAndMerge(
+               TrackObjectSelectionToolCfg( flags,
+                   name="TrackObjectSelectionTool" + flags.PhysVal.IDTPM.currentTrkAna.anaTag ) ) )
 
     acc.setPrivateTools( CompFactory.IDTPM.TrackQualitySelectionTool( name, **kwargs ) )
     return acc
-
-
-## TODO - to be uncommented in later MRs
-#def InDetTrackObjectSelectionToolCfg( flags, name="TrackObjectSelectionTool", **kwargs ):
-#    acc = ComponentAccumulator()
-#
-#    from InDetTrackPerfMon.ConfigUtils import getObjectStr
-#    objectStr = getObjectStr( flags )
-#    kwargs.setdefault( "ObjectType",     objectStr )
-#    kwargs.setdefault( "ObjectQuality",  flags.PhysVal.IDTPM.currentTrkAna.ObjectQuality )
-#    if "Tau" in objectStr:
-#        kwargs.setdefault( "TauType",    flags.PhysVal.IDTPM.currentTrkAna.TauType )
-#        kwargs.setdefault( "TauNprongs", flags.PhysVal.IDTPM.currentTrkAna.TauNprongs )
-#
-#    acc.setPrivateTools( CompFactory.IDTPM.InDetTrackObjectSelectionTool( name, **kwargs ) )
-#    return acc
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonConfig.py b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonConfig.py
index 88d367a05f3e763cfb00602462edff4a6fbfeec4..3c25188b77aee98db5d997b9eac8b413c24d0ecf 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonConfig.py
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonConfig.py
@@ -128,19 +128,27 @@ def InDetTrackPerfMonCfg( flags ):
     ## Configuring IDTPM tool instances
     tools = [] 
 
-    ## TODO - to be uncommented in future MRs
-    #useTruth = False
-    #for trkAnaName in flags.PhysVal.IDTPM.trkAnaNames :
-    #    if "Truth" in getattr( flags.PhysVal.IDTPM, trkAnaName+".RefType" ) :
-    #        useTruth = True
-    #if useTruth:
-    #    acc.merge( InDetTruthDecoratorAlgCfg(flags) )
-
-    ## TODO - to be uncommented in future MRs
-    ## true only if offline objects are requested in any scheduled trkAnalysis
-    #if getObjectStrList(flags):
-    #    from InDetTrackPerfMon.InDetOfflineObjectDecoratorAlgConfig import InDetOfflineObjectDecoratorAlgCfg
-    #    acc.merge( InDetOfflineObjectDecoratorAlgCfg(flags) )
+    useTruth = False
+    useOfflineObject = False
+    for trkAnaName in flags.PhysVal.IDTPM.trkAnaNames :
+        if "Truth" in getattr( flags.PhysVal.IDTPM, trkAnaName+".RefType" ):
+            useTruth = True
+            break
+        if getattr( flags.PhysVal.IDTPM, trkAnaName+".SelectOfflineObject" ):
+            if "Truth" in getattr( flags.PhysVal.IDTPM, trkAnaName+".SelectOfflineObject" ):
+                continue # Do not schedule algorithm for Truth-match offline selection
+            useOfflineObject = True
+            break
+
+    ## Truth-hit decorator
+    if useTruth:
+        from InDetTrackPerfMon.InDetAlgorithmConfig import TruthHitDecoratorAlgCfg
+        acc.merge( TruthHitDecoratorAlgCfg(flags) )
+
+    ## Offline track-object decorator
+    if useOfflineObject:
+        from InDetTrackPerfMon.InDetAlgorithmConfig import OfflineObjectDecoratorAlgCfg
+        acc.merge( OfflineObjectDecoratorAlgCfg(flags) )
 
     for trkAnaName in flags.PhysVal.IDTPM.trkAnaNames:
         ## cloning flags of current TrackAnalysis to PhysVal.IDTPM.currentTrkAna
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonFlags.py b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonFlags.py
index 607803e358f72f02f3fc143933086717b6875083..3eece2d72c3df760e71bff3ef8077425ddb3e0e9 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonFlags.py
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/python/InDetTrackPerfMonFlags.py
@@ -7,12 +7,12 @@ def createIDTPMConfigFlags():
     from AthenaConfiguration.AthConfigFlags import AthConfigFlags
     icf = AthConfigFlags()
 
-    icf.addFlag("DirName", "InDetTrackPerfMonPlots/")
-    icf.addFlag("trkAnaNames", ["Default"])
-    icf.addFlag("unpackTrigChains", False)
-    icf.addFlag("histoDefFormat", "JSON")
-    icf.addFlag("HistoDefFileList" , "InDetTrackPerfMon/HistoDefFileList_default.txt")
-    icf.addFlag("plotsCommonValuesFile", "InDetTrackPerfMon/IDTPMPlotCommonValues.json")
+    icf.addFlag( "DirName", "InDetTrackPerfMonPlots/" )
+    icf.addFlag( "trkAnaNames", ["Default"] )
+    icf.addFlag( "unpackTrigChains", False )
+    icf.addFlag( "histoDefFormat", "JSON" )
+    icf.addFlag( "HistoDefFileList" , "InDetTrackPerfMon/HistoDefFileList_default.txt" )
+    icf.addFlag( "plotsCommonValuesFile", "InDetTrackPerfMon/IDTPMPlotCommonValues.json" )
     
     return icf
 
@@ -24,39 +24,39 @@ def createIDTPMTrkAnaConfigFlags():
     icf = AthConfigFlags()
 
     # General properties
-    icf.addFlag("enabled", True)
-    icf.addFlag("anaTag", "")
-    icf.addFlag("SubFolder", "IDTPM/")
+    icf.addFlag( "enabled", True )
+    icf.addFlag( "anaTag", "" )
+    icf.addFlag( "SubFolder", "IDTPM/" )
     # Test-Reference collections properties
-    icf.addFlag("TestType", "Offline")
-    icf.addFlag("RefType", "Truth")
-    icf.addFlag("TrigTrkKey"    , "HLT_IDTrack_Electron_IDTrig")
-    icf.addFlag("OfflineTrkKey" , "InDetTrackParticles")
-    icf.addFlag("TruthPartKey"  , "TruthParticles")
+    icf.addFlag( "TestType", "Offline" )
+    icf.addFlag( "RefType", "Truth" )
+    icf.addFlag( "TrigTrkKey"    , "HLT_IDTrack_Electron_IDTrig" )
+    icf.addFlag( "OfflineTrkKey" , "InDetTrackParticles" )
+    icf.addFlag( "TruthPartKey"  , "TruthParticles" )
     # Matching properties
-    icf.addFlag("MatchingType"    , "DeltaRMatch")
-    icf.addFlag("dRmax"           , 0.05)
-    icf.addFlag("pTResMax"        , -9.9)
+    icf.addFlag( "MatchingType"    , "DeltaRMatch" )
+    icf.addFlag( "dRmax"           , 0.05 )
+    icf.addFlag( "pTResMax"        , -9.9 )
     # Trigger-specific properties
-    icf.addFlag("ChainNames"    , [])
-    icf.addFlag("RoiKey"        , "")
-    icf.addFlag("ChainLeg"      , -1)
-    icf.addFlag("doTagNProbe"   , False)
-    icf.addFlag("RoiKeyTag"     , "")
-    icf.addFlag("ChainLegTag"   , 0)
-    icf.addFlag("RoiKeyProbe"   , "")
-    icf.addFlag("ChainLegProbe" , 1)
+    icf.addFlag( "ChainNames"    , [] )
+    icf.addFlag( "RoiKey"        , "" )
+    icf.addFlag( "ChainLeg"      , -1 )
+    icf.addFlag( "doTagNProbe"   , False )
+    icf.addFlag( "RoiKeyTag"     , "" )
+    icf.addFlag( "ChainLegTag"   , 0 )
+    icf.addFlag( "RoiKeyProbe"   , "" )
+    icf.addFlag( "ChainLegProbe" , 1 )
     # Offline tracks selection properties
-    icf.addFlag("ObjectQuality" , "Medium")
-    icf.addFlag("TauType"       , "RNN")
-    icf.addFlag("TauNprongs"    , 1)
-    icf.addFlag("TruthMatchedOnly" , False)
+    icf.addFlag( "SelectOfflineObject", "" )
+    icf.addFlag( "ObjectQuality"      , "Medium" )
+    icf.addFlag( "TauType"            , "RNN" )
+    icf.addFlag( "TauNprongs"         , 1 )
     # ...
     # Truth particles selection properties
     # ...
     # Histogram properties
-    icf.addFlag("doTrackParameters"   , True)
-    icf.addFlag("doEfficiencies"      , True)
-    icf.addFlag("doOfflineElectrons"  , False)
+    icf.addFlag( "doTrackParameters"   , True )
+    icf.addFlag( "doEfficiencies"      , True )
+    icf.addFlag( "doOfflineElectrons"  , False )
     
     return icf
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/InDetTrackPerfMonTool.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/InDetTrackPerfMonTool.cxx
index 2ee8a2134d3d3348b4d925353d16fcf53eba9de5..a0e105c41cec115067bab6ea5b67103b9634fb50 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/InDetTrackPerfMonTool.cxx
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/InDetTrackPerfMonTool.cxx
@@ -8,7 +8,7 @@
  **/
 
 /// local include
-#include "InDetTrackPerfMon/InDetTrackPerfMonTool.h"
+#include "InDetTrackPerfMonTool.h"
 
 /// gaudi includes
 #include "GaudiKernel/SystemOfUnits.h"
@@ -231,7 +231,7 @@ StatusCode InDetTrackPerfMonTool::fillHistograms() {
     unsigned decisionType = TrigDefs::Physics; // TrigDefs::includeFailedDecisions;
 
     /// skipping TrkAnalysis if chain is not passed for this event
-    if( thisChain != "" and thisChain != "Offline" and m_trkAnaDefSvc->useTrigger() ) {
+    if( !thisChain.empty() and thisChain != "Offline" and m_trkAnaDefSvc->useTrigger() ) {
       if( not m_trigDecTool->isPassed( thisChain, decisionType ) ) { 
         ATH_MSG_DEBUG( "Trigger chain " << thisChain << " is not fired. Skipping" );
         continue;
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/InDetTrackPerfMonTool.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/InDetTrackPerfMonTool.h
similarity index 95%
rename from InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/InDetTrackPerfMonTool.h
rename to InnerDetector/InDetValidation/InDetTrackPerfMon/src/InDetTrackPerfMonTool.h
index 7d41950ae1c2c4781708111840755adc327aacfc..939bb090996f76c28fa2bfa4fafd58b7a20da174 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/InDetTrackPerfMonTool.h
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/InDetTrackPerfMonTool.h
@@ -25,12 +25,10 @@
 
 /// local includes
 #include "InDetTrackPerfMon/ITrackAnalysisDefinitionSvc.h"
-#include "InDetTrackPerfMon/TrackAnalysisCollections.h"
-#include "InDetTrackPerfMon/RoiSelectionTool.h"
+#include "../src/TrackAnalysisCollections.h"
+#include "../src/RoiSelectionTool.h"
 #include "InDetTrackPerfMon/ITrackSelectionTool.h"
 /// TODO - To be included in later MRs
-//#include "InDetTrackPerfMon/InDetObjectDecorHelper.h"
-//#include "InDetTrackPerfMon/TrackRoiSelectionTool.h"
 //#include "InDetTrackPerfMon/ITrackMatchingTool.h"
 //#include "InDetTrackPerfMon/TrackAnalysisPlotsMgr.h"
 
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineElectronDecoratorAlg.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineElectronDecoratorAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..a8dfbe7acd215d0bd7dddb593ffde7069e4d1afd
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineElectronDecoratorAlg.cxx
@@ -0,0 +1,154 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+/**
+ * @file OfflineElectronDecoratorAlg.cxx
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ **/
+
+/// Local includes
+#include "OfflineElectronDecoratorAlg.h"
+
+/// xAOD includes
+#include "xAODEgamma/ElectronxAODHelpers.h" // xAOD::EgammaHelpers::getOriginalTrackParticle
+
+
+///----------------------------------------
+///------- Parametrized constructor -------
+///----------------------------------------
+IDTPM::OfflineElectronDecoratorAlg::OfflineElectronDecoratorAlg(
+    const std::string& name,
+    ISvcLocator* pSvcLocator ) :
+  AthReentrantAlgorithm( name, pSvcLocator ) { }
+
+
+///--------------------------
+///------- initialize -------
+///--------------------------
+StatusCode IDTPM::OfflineElectronDecoratorAlg::initialize() {
+
+  ATH_CHECK( m_offlineTrkParticlesName.initialize(
+                not m_offlineTrkParticlesName.key().empty() ) );
+
+  ATH_CHECK( m_electronsName.initialize( not m_electronsName.key().empty() ) );
+
+  /// Create decorations for original (non GSF) ID tracks
+  IDTPM::createDecoratorKeysAndAccessor( 
+      *this, m_offlineTrkParticlesName,
+      m_prefix.value(), m_decor_ele_names, m_decor_ele );
+
+  if( m_decor_ele.size() != NDecorations ) {
+    ATH_MSG_ERROR( "Incorrect booking of electron decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-----------------------
+///------- execute -------
+///-----------------------
+StatusCode IDTPM::OfflineElectronDecoratorAlg::execute( const EventContext& ctx ) const {
+
+  /// retrieve offline track particle container
+  SG::ReadHandle< xAOD::TrackParticleContainer > ptracks( m_offlineTrkParticlesName, ctx );
+  if( not ptracks.isValid() ) {
+    ATH_MSG_ERROR( "Failed to retrieve track particles container" );
+    return StatusCode::FAILURE;
+  }
+
+  /// retrieve electron container
+  SG::ReadHandle< xAOD::ElectronContainer > pelectrons( m_electronsName, ctx );
+  if( not pelectrons.isValid() ) {
+    ATH_MSG_ERROR( "Failed to retrieve electrons container" );
+    return StatusCode::FAILURE;
+  }
+
+  std::vector< IDTPM::OptionalDecoration<xAOD::TrackParticleContainer, ElementElectronLink_t> >
+      ele_decor( IDTPM::createDecoratorsIfNeeded( *ptracks, m_decor_ele, ctx ) );
+
+  if( ele_decor.empty() ) {
+    ATH_MSG_ERROR( "Failed to book electron decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  for( const xAOD::TrackParticle* track : *ptracks ) {
+    /// decorate current track with electron ElementLink(s)
+    ATH_CHECK( decorateElectronTrack( *track, ele_decor, *pelectrons.ptr() ) );
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-------------------------------
+///---- decorateElectronTrack ----
+///-------------------------------
+StatusCode IDTPM::OfflineElectronDecoratorAlg::decorateElectronTrack(
+                const xAOD::TrackParticle& track,
+                std::vector< IDTPM::OptionalDecoration< xAOD::TrackParticleContainer,
+                                                        ElementElectronLink_t > >& ele_decor,
+                const xAOD::ElectronContainer& electrons ) const {
+
+  /// loop electron container to look for electron reconstructed with track
+  for( const xAOD::Electron* electron : electrons ) {
+
+    /// retrieve TrackParticle from electron
+    const xAOD::TrackParticle* eleTrack = m_useGSF.value() ?
+                        electron->trackParticle() : // with GSF
+                        xAOD::EgammaHelpers::getOriginalTrackParticle( electron ); // no GSF
+
+    if( not eleTrack ) {
+      ATH_MSG_ERROR( "Corrupted matching electron ID track" );
+      return StatusCode::FAILURE;
+    }
+
+    if( eleTrack == &track ) {
+      /// Create electron element link
+      ElementElectronLink_t eleLink;
+      eleLink.toContainedElement( electrons, electron );
+
+      ATH_MSG_DEBUG( "Found matching electron (e=" << electron->e() << "). Decorating track." );
+
+      /// Decoration for All electron (no quality selection)
+      IDTPM::decorateOrRejectQuietly( track, ele_decor[All], eleLink );
+
+      /// Decoration for Tight electron
+      if( electron->passSelection("Tight") ) {
+        IDTPM::decorateOrRejectQuietly( track, ele_decor[Tight], eleLink );
+      }
+
+      /// Decoration for Medium electron
+      if( electron->passSelection("Medium") ) {
+        IDTPM::decorateOrRejectQuietly( track, ele_decor[Medium], eleLink );
+      }
+
+      /// Decoration for Loose electron
+      if( electron->passSelection("Loose") ) {
+        IDTPM::decorateOrRejectQuietly( track, ele_decor[Loose], eleLink );
+      }
+
+      /// Decoration for LHTight electron
+      if( electron->passSelection("LHTight") ) {
+        IDTPM::decorateOrRejectQuietly( track, ele_decor[LHTight], eleLink );
+      }
+
+      /// Decoration for LHMedium electron
+      if( electron->passSelection("LHMedium") ) {
+        IDTPM::decorateOrRejectQuietly( track, ele_decor[LHMedium], eleLink );
+      }
+
+      /// Decoration for LHLoose electron
+      if( electron->passSelection("LHLoose") ) {
+        IDTPM::decorateOrRejectQuietly( track, ele_decor[LHLoose], eleLink );
+      }
+
+      return StatusCode::SUCCESS;
+    } // if( eleTrack == &track ) 
+
+  } // close electron loop
+
+  return StatusCode::SUCCESS;
+}
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineElectronDecoratorAlg.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineElectronDecoratorAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..a02f6f70d2b21b83c9cec683ee8244c07aeead25
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineElectronDecoratorAlg.h
@@ -0,0 +1,94 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_OFFLINEELECTRONDECORATORALG_H
+#define INDETTRACKPERFMON_OFFLINEELECTRONDECORATORALG_H
+
+/**
+ * @file OfflineElectronDecoratorAlg.h
+ * @brief Algorithm to decorate offline tracks with the corresponding
+ *        offline electron object (if required for trigger analysis) 
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ * @date 25 September 2023
+ **/
+
+/// Athena includes
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+
+/// xAOD includes
+#include "xAODTracking/TrackParticleContainer.h"
+#include "xAODEgamma/ElectronContainer.h"
+
+/// STL includes
+#include <string>
+#include <vector>
+
+/// Local includes
+#include "SafeDecorator.h"
+
+
+namespace IDTPM {
+
+  class OfflineElectronDecoratorAlg :
+      public AthReentrantAlgorithm {
+
+  public:
+
+    typedef ElementLink<xAOD::ElectronContainer> ElementElectronLink_t;
+
+    OfflineElectronDecoratorAlg( const std::string& name, ISvcLocator* pSvcLocator );
+
+    virtual ~OfflineElectronDecoratorAlg() = default;
+
+    virtual StatusCode initialize() override;
+
+    virtual StatusCode execute( const EventContext& ctx ) const override;
+
+  private:
+
+    SG::ReadHandleKey<xAOD::TrackParticleContainer> m_offlineTrkParticlesName {
+        this, "OfflineTrkParticleContainerName", "InDetTrackParticles", "Name of container of offline tracks" };
+
+    StringProperty m_prefix{ this, "Prefix", "LinkedElectron_", "Decoration prefix to avoid clashes" };
+
+    StatusCode decorateElectronTrack(
+        const xAOD::TrackParticle& track,
+        std::vector< IDTPM::OptionalDecoration< xAOD::TrackParticleContainer,
+                                                ElementElectronLink_t > >& ele_decor,
+        const xAOD::ElectronContainer& electrons ) const;
+
+    SG::ReadHandleKey<xAOD::ElectronContainer> m_electronsName {
+        this, "ElectronContainerName", "Electrons", "Name of container of offline electrons" };
+
+    BooleanProperty m_useGSF { this, "useGSF", false, "Match electron with original ID track after GSF" };
+   
+    enum ElectronDecorations : size_t {
+      All,
+      Tight,
+      Medium,
+      Loose,
+      LHTight,
+      LHMedium,
+      LHLoose,
+      NDecorations
+    };
+
+    const std::vector< std::string > m_decor_ele_names {
+      "All",
+      "Tight",
+      "Medium",
+      "Loose",
+      "LHTight",
+      "LHMedium",
+      "LHLoose"
+    };
+  
+    std::vector< IDTPM::WriteKeyAccessorPair< xAOD::TrackParticleContainer,
+                                              ElementElectronLink_t > > m_decor_ele{};
+
+};
+
+} // namespace IDTPM
+
+#endif // > !INDETTRACKPERFMON_OFFLINEELECTRONDECORATORALG_H
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineMuonDecoratorAlg.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineMuonDecoratorAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..3f802ac778bc961f0896803e1e7043fcb22ced87
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineMuonDecoratorAlg.cxx
@@ -0,0 +1,145 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+/**
+ * @file OfflineMuonDecoratorAlg.cxx
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ **/
+
+/// Local includes
+#include "OfflineMuonDecoratorAlg.h"
+
+
+///----------------------------------------
+///------- Parametrized constructor -------
+///----------------------------------------
+IDTPM::OfflineMuonDecoratorAlg::OfflineMuonDecoratorAlg(
+    const std::string& name,
+    ISvcLocator* pSvcLocator ) :
+  AthReentrantAlgorithm( name, pSvcLocator ) { }
+
+
+///----------------------------------
+///------- General initialize -------
+///----------------------------------
+StatusCode IDTPM::OfflineMuonDecoratorAlg::initialize() {
+
+  ATH_CHECK( m_offlineTrkParticlesName.initialize(
+                not m_offlineTrkParticlesName.key().empty() ) );
+
+  /// Muon collection, if required
+  ATH_CHECK( m_muonsName.initialize( not m_muonsName.key().empty() ) );
+
+  /// Create decorations for original ID tracks
+  IDTPM::createDecoratorKeysAndAccessor( 
+      *this, m_offlineTrkParticlesName,
+      m_prefix.value(), m_decor_mu_names, m_decor_mu );
+
+  if( m_decor_mu.size() != NDecorations ) {
+    ATH_MSG_ERROR( "Incorrect booking of muon decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-----------------------
+///------- execute -------
+///-----------------------
+StatusCode IDTPM::OfflineMuonDecoratorAlg::execute( const EventContext& ctx ) const {
+
+  /// retrieve offline track particle container
+  SG::ReadHandle< xAOD::TrackParticleContainer > ptracks( m_offlineTrkParticlesName, ctx );
+  if( not ptracks.isValid() ) {
+    ATH_MSG_ERROR( "Failed to retrieve track particles container" );
+    return StatusCode::FAILURE;
+  }
+
+  /// retrieve muon container
+  SG::ReadHandle< xAOD::MuonContainer > pmuons( m_muonsName, ctx );
+  if( not pmuons.isValid() ) {
+    ATH_MSG_ERROR( "Failed to retrieve muons container" );
+    return StatusCode::FAILURE;
+  }
+
+  std::vector< IDTPM::OptionalDecoration<xAOD::TrackParticleContainer, ElementMuonLink_t> >
+      mu_decor( IDTPM::createDecoratorsIfNeeded( *ptracks, m_decor_mu, ctx ) );
+
+  if( mu_decor.empty() ) {
+    ATH_MSG_ERROR( "Failed to book muon decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  for( const xAOD::TrackParticle* track : *ptracks ) {
+    /// decorate current track with muon ElementLink(s)
+    ATH_CHECK( decorateMuonTrack( *track, mu_decor, *pmuons.ptr() ) );
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///---------------------------
+///---- decorateMuonTrack ----
+///---------------------------
+StatusCode IDTPM::OfflineMuonDecoratorAlg::decorateMuonTrack(
+                const xAOD::TrackParticle& track,
+                std::vector< IDTPM::OptionalDecoration< xAOD::TrackParticleContainer,
+                                                        ElementMuonLink_t > >& mu_decor,
+                const xAOD::MuonContainer& muons ) const {
+
+  /// loop muon container to look for muon reconstructed with current ID track
+  for( const xAOD::Muon* muon : muons ) {
+
+    /// Exclude non-Combined muons
+    if( muon->muonType() != xAOD::Muon::Combined ) continue;
+
+    /// retrieve ID TrackParticle from muon
+    const xAOD::TrackParticle* muTrack = m_useCombinedMuonTracks.value() ?
+                          *( muon->combinedTrackParticleLink() ) : // Combined track
+                          *( muon->inDetTrackParticleLink() );  // ID track
+
+    if( not muTrack ) {
+      ATH_MSG_ERROR( "Corrupted matched muon ID track" );
+      return StatusCode::FAILURE;
+    }
+
+    if( muTrack == &track ) {
+      /// Create muon element link
+      ElementMuonLink_t muLink;
+      muLink.toContainedElement( muons, muon );
+
+      ATH_MSG_DEBUG( "Found matched muon (pt=" << muon->pt() << "). Decorating track." );
+
+      /// Decoration for All muon (no quality selection)
+      IDTPM::decorateOrRejectQuietly( track, mu_decor[All], muLink );
+
+      /// Decoration for Tight muon
+      if( muon->quality() <= xAOD::Muon::Tight ) {
+        IDTPM::decorateOrRejectQuietly( track, mu_decor[Tight], muLink );
+      }
+
+      /// Decoration for Medium muon
+      if( muon->quality() <= xAOD::Muon::Medium ) {
+        IDTPM::decorateOrRejectQuietly( track, mu_decor[Medium], muLink );
+      }
+
+      /// Decoration for Loose muon
+      if( muon->quality() <= xAOD::Muon::Loose ) {
+        IDTPM::decorateOrRejectQuietly( track, mu_decor[Loose], muLink );
+      }
+
+      /// Decoration for VeryLoose muon
+      if( muon->quality() <= xAOD::Muon::VeryLoose ) {
+        IDTPM::decorateOrRejectQuietly( track, mu_decor[VeryLoose], muLink );
+      }
+
+      return StatusCode::SUCCESS;
+    } // if( muTrack == &track )
+
+  } // close muon loop
+
+  return StatusCode::SUCCESS;
+}
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineMuonDecoratorAlg.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineMuonDecoratorAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..da39a658d4a11647e43e74fd238e63554b1a4103
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineMuonDecoratorAlg.h
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_OFFLINEMUONDECORATORALG_H
+#define INDETTRACKPERFMON_OFFLINEMUONDECORATORALG_H
+
+/**
+ * @file OfflineMuonDecoratorAlg.h
+ * @brief Algorithm to decorate offline tracks with the corresponding
+ *        offline muon object (if required for trigger analysis) 
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ * @date 25 September 2023
+ **/
+
+/// Athena includes
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+
+/// xAOD includes
+#include "xAODTracking/TrackParticleContainer.h"
+#include "xAODMuon/MuonContainer.h"
+
+/// STL includes
+#include <string>
+#include <vector>
+
+/// Local includes
+#include "SafeDecorator.h"
+
+
+namespace IDTPM {
+
+  class OfflineMuonDecoratorAlg :
+      public AthReentrantAlgorithm {
+
+  public:
+
+    typedef ElementLink<xAOD::MuonContainer> ElementMuonLink_t;
+
+    OfflineMuonDecoratorAlg( const std::string& name, ISvcLocator* pSvcLocator );
+
+    virtual ~OfflineMuonDecoratorAlg() = default;
+
+    virtual StatusCode initialize() override;
+
+    virtual StatusCode execute( const EventContext& ctx ) const override;
+
+  private:
+
+    SG::ReadHandleKey<xAOD::TrackParticleContainer> m_offlineTrkParticlesName {
+        this, "OfflineTrkParticleContainerName", "InDetTrackParticles", "Name of container of offline tracks" };
+
+    StringProperty m_prefix { this, "Prefix", "LinkedMuon_", "Decoration prefix to avoid clashes" };
+
+    StatusCode decorateMuonTrack(
+        const xAOD::TrackParticle& track,
+        std::vector< IDTPM::OptionalDecoration< xAOD::TrackParticleContainer,
+                                                ElementMuonLink_t > >& mu_decor,
+        const xAOD::MuonContainer& muons ) const;
+
+    SG::ReadHandleKey<xAOD::MuonContainer> m_muonsName {
+        this, "MuonContainerName", "Muons", "Name of container of offline muons" };
+
+    BooleanProperty m_useCombinedMuonTracks {
+        this, "useCombinedMuonTracks", false, "Match combined muon track to muons instead of ID tracks" };
+
+    enum MuonDecorations : size_t {
+      All,
+      Tight,
+      Medium,
+      Loose,
+      VeryLoose,
+      NDecorations
+    };
+
+    const std::vector< std::string > m_decor_mu_names {
+      "All",
+      "Tight",
+      "Medium",
+      "Loose",
+      "VeryLoose"
+    };
+
+    std::vector< IDTPM::WriteKeyAccessorPair< xAOD::TrackParticleContainer,
+                                              ElementMuonLink_t > > m_decor_mu{};
+
+  };
+
+} // namespace IDTPM
+
+#endif // > ! INDETTRACKPERFMON_OFFLINEMUONDECORATORALG_H
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineObjectDecorHelper.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineObjectDecorHelper.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..a295e76b0a505a98a4225c9e787ddc4638480e2f
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineObjectDecorHelper.cxx
@@ -0,0 +1,67 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+/**
+ * @file OfflineObjectDecorHelper.cxx
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ **/
+
+/// local includes
+#include "OfflineObjectDecorHelper.h"
+
+
+namespace IDTPM {
+
+  /// getLinkedElectron
+  const xAOD::Electron* getLinkedElectron( const xAOD::TrackParticle& track,
+                                           const std::string& quality ) {
+    std::string decoName = "LinkedElectron_" + quality;
+    return getLinkedObject< xAOD::ElectronContainer >( track, decoName );
+  }
+
+
+  /// getLinkedMuon
+  const xAOD::Muon* getLinkedMuon( const xAOD::TrackParticle& track,
+                                   const std::string& quality ) {
+    std::string decoName = "LinkedMuon_" + quality;
+    return getLinkedObject< xAOD::MuonContainer >( track, decoName );
+  }
+
+
+  /// getLinkedTau
+  const xAOD::TauJet* getLinkedTau( const xAOD::TrackParticle& track,
+                                    const int requiredNtracks,
+                                    const std::string& type,
+                                    const std::string& quality ) {
+    std::string decoName = "LinkedTau" + type +
+        std::to_string( requiredNtracks ) + "_" + quality;
+    return getLinkedObject< xAOD::TauJetContainer >( track, decoName );
+  }
+
+
+  /// isUnlinkedTruth
+  bool isUnlinkedTruth( const xAOD::TrackParticle& track ) {
+    return isUnlinkedObject< xAOD::TruthParticleContainer >(
+        track, "truthParticleLink" );
+  }
+
+
+  /// getTruthMatchProb
+  float getTruthMatchProb( const xAOD::TrackParticle& track ) {
+    return ( track.isAvailable< float >( "truthMatchProbability" ) ?
+        track.auxdata< float >( "truthMatchProbability" ) : -1. );
+  }
+
+
+  /// getLinkedTruth
+  const xAOD::TruthParticle* getLinkedTruth( const xAOD::TrackParticle& track,
+                                             const float truthProbCut ) {
+    float prob = getTruthMatchProb( track );
+    if( prob < truthProbCut ) return nullptr;
+
+    return getLinkedObject< xAOD::TruthParticleContainer >(
+        track, "truthParticleLink" );
+  }
+
+} // namespace IDTPM
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineObjectDecorHelper.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineObjectDecorHelper.h
new file mode 100644
index 0000000000000000000000000000000000000000..8583e9239f4db4048efa03b4b547ceb6bdcda047
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineObjectDecorHelper.h
@@ -0,0 +1,79 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_OFFLINEOBJECTDECORHELPER_H
+#define INDETTRACKPERFMON_OFFLINEOBJECTDECORHELPER_H
+
+/**
+ * @file OfflineObjectDecorHelper.h
+ * @brief Utility methods to access
+ *        offline object decorations
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ * @date 25 September 2023
+ **/
+
+/// xAOD includes
+#include "xAODTracking/TrackParticle.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+#include "xAODTau/TauJetContainer.h"
+#include "xAODTruth/TruthParticleContainer.h"
+
+/// STL includes
+#include <string>
+
+
+namespace IDTPM {
+
+  /// Templated method to check if a track is not linked to an object
+  template < typename container_t >
+  bool isUnlinkedObject( const xAOD::TrackParticle& track,
+                         const std::string& decoName ) {
+    using elementLink_t = ElementLink< container_t >;
+    return ( not track.isAvailable< elementLink_t >( decoName ) );
+  }
+
+  /// Templated method to retrieve object linked to a track
+  template < typename container_t >
+  typename container_t::const_value_type getLinkedObject(
+      const xAOD::TrackParticle& track,
+      const std::string& decoName )
+  {
+    using elementLink_t = ElementLink< container_t >;
+
+    if( isUnlinkedObject< container_t >( track, decoName ) ) return nullptr;
+
+    elementLink_t eleLink = track.auxdata< elementLink_t >( decoName );
+
+    if( not eleLink.isValid() ) return nullptr;
+
+    return *eleLink;
+  }
+
+  /// Non-templated methods
+  /// For offline electrons
+  const xAOD::Electron* getLinkedElectron( const xAOD::TrackParticle& track,
+                                           const std::string& quality="All" );
+
+  /// For offline muons
+  const xAOD::Muon* getLinkedMuon( const xAOD::TrackParticle& track,
+                                   const std::string& quality="All" );
+
+  /// For offline hadronic taus
+  const xAOD::TauJet* getLinkedTau( const xAOD::TrackParticle& track,
+                                    const int requiredNtracks,
+                                    const std::string& type="RNN",
+                                    const std::string& quality="" );
+
+  /// For truth particles
+  bool isUnlinkedTruth( const xAOD::TrackParticle& track );
+
+  float getLinkedTruthMatchProb( const xAOD::TrackParticle& track );
+
+  const xAOD::TruthParticle* getLinkedTruth( const xAOD::TrackParticle& track,
+                                             const float truthProbCut=0. );
+
+} // namespace IDTPM
+
+#endif // > ! INDETTRACKPERFMON_OFFLINEOBJECTDECORHELPER_H
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineTauDecoratorAlg.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineTauDecoratorAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..030a1212de3b5f8d3c14f6ec4def269a082c7176
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineTauDecoratorAlg.cxx
@@ -0,0 +1,194 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+/**
+ * @file OfflineTauDecoratorAlg.cxx
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ **/
+
+/// Local includes
+#include "OfflineTauDecoratorAlg.h"
+
+
+///----------------------------------------
+///------- Parametrized constructor -------
+///----------------------------------------
+IDTPM::OfflineTauDecoratorAlg::OfflineTauDecoratorAlg(
+    const std::string& name,
+    ISvcLocator* pSvcLocator ) :
+  AthReentrantAlgorithm( name, pSvcLocator ) { }
+
+
+///--------------------------
+///------- initialize -------
+///--------------------------
+StatusCode IDTPM::OfflineTauDecoratorAlg::initialize() {
+
+  ATH_CHECK( m_offlineTrkParticlesName.initialize(
+                not m_offlineTrkParticlesName.key().empty() ) );
+
+  ATH_CHECK( m_tausName.initialize( not m_tausName.key().empty() ) );
+
+  /// Create decorations for original ID tracks
+  IDTPM::createDecoratorKeysAndAccessor( 
+      *this, m_offlineTrkParticlesName,
+      m_prefix.value(), m_decor_tau_names, m_decor_tau );
+
+  if( m_decor_tau.size() != NDecorations ) {
+    ATH_MSG_ERROR( "Incorrect booking of tau " <<
+                   m_tauType.value() << " " <<
+                   m_tauNprongs.value() << 
+                   "prong decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-----------------------
+///------- execute -------
+///-----------------------
+StatusCode IDTPM::OfflineTauDecoratorAlg::execute( const EventContext& ctx ) const {
+
+  /// retrieve offline track particle container
+  SG::ReadHandle< xAOD::TrackParticleContainer > ptracks( m_offlineTrkParticlesName, ctx );
+  if( not ptracks.isValid() ) {
+    ATH_MSG_ERROR( "Failed to retrieve track particles container" );
+    return StatusCode::FAILURE;
+  }
+
+  /// retrieve tau container
+  SG::ReadHandle< xAOD::TauJetContainer > ptaus( m_tausName, ctx );
+  if( not ptaus.isValid() ) {
+    ATH_MSG_ERROR( "Failed to retrieve taus container" );
+    return StatusCode::FAILURE;
+  }
+
+  std::vector< IDTPM::OptionalDecoration<xAOD::TrackParticleContainer, ElementTauLink_t> >
+      tau_decor( IDTPM::createDecoratorsIfNeeded( *ptracks, m_decor_tau, ctx ) );
+
+  if( tau_decor.empty() ) {
+    ATH_MSG_ERROR( "Failed to book tau " <<
+                   m_tauType.value() << " " <<
+                   m_tauNprongs.value() << 
+                   "prong decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  for( const xAOD::TrackParticle* track : *ptracks ) {
+    /// decorate current track with tau ElementLink(s)
+    ATH_CHECK( decorateTauTrack( *track, tau_decor, *ptaus.ptr() ) );
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///--------------------------
+///---- decorateTauTrack ----
+///--------------------------
+StatusCode IDTPM::OfflineTauDecoratorAlg::decorateTauTrack(
+                const xAOD::TrackParticle& track,
+                std::vector< IDTPM::OptionalDecoration< xAOD::TrackParticleContainer,
+                                                        ElementTauLink_t > >& tau_decor,
+                const xAOD::TauJetContainer& taus ) const { 
+
+  /// loop muon container to look for muon reconstructed with current ID track
+  for( const xAOD::TauJet* tau : taus ) {
+
+    /// Select number of tau tracks (1prong or 3prong)
+    int NTauTracks = tau->nTracks();
+    if( m_tauNprongs.value() > 0 and NTauTracks != (int)m_tauNprongs.value() ) continue;
+
+    /// retrieve ID TrackParticles from jet
+    std::vector< const xAOD::TrackParticle* > tauTracks;
+    for( size_t iprong=0; iprong<(size_t)NTauTracks; iprong++ ) {
+
+      std::vector< ElementTrackLink_t > tracklink = tau->track( iprong )->trackLinks();
+      ATH_MSG_DEBUG( "TauLinkVec size (" << m_tauNprongs.value() <<
+                     "prong " << m_tauType.value() << "tau) = " << tracklink.size() );
+
+      /// adding found tau tracks to tauTracks vector 
+      for( size_t ilink=0; ilink<tracklink.size() ; ilink++ ) {
+        if( tracklink.at(ilink).isValid() ) tauTracks.push_back( *(tracklink.at(ilink)) );
+      }
+
+    } // close NTauTracks loop
+
+    ATH_MSG_DEBUG( "TauVec size (" << m_tauNprongs.value() << "prong " <<
+                   m_tauType.value() << "tau) = " << tauTracks.size() );
+
+    /// Loop over all the ID tracks of this hadronic tau
+    for( const xAOD::TrackParticle* tauTrack : tauTracks ) {
+
+      if( not tauTrack ) {
+        ATH_MSG_ERROR( "Corrupted matching tau ID track" );
+        continue;
+      }
+
+      if( tauTrack == &track ) {
+        /// Createn tau element link
+        ElementTauLink_t tauLink;
+        tauLink.toContainedElement( taus, tau );
+
+        bool isTight( false ), isMedium( false );
+        bool isLoose( false ), isVeryLoose( false );
+
+        if( m_tauType.value() == "BDT" ) {
+          isTight     = tau->isTau( xAOD::TauJetParameters::JetBDTSigTight );
+          isMedium    = tau->isTau( xAOD::TauJetParameters::JetBDTSigMedium );
+          isLoose     = tau->isTau( xAOD::TauJetParameters::JetBDTSigLoose );
+          isVeryLoose = tau->isTau( xAOD::TauJetParameters::JetBDTSigVeryLoose );
+
+        } else if( m_tauType.value() == "RNN" ) {
+          isTight     = tau->isTau( xAOD::TauJetParameters::JetRNNSigTight );
+          isMedium    = tau->isTau( xAOD::TauJetParameters::JetRNNSigMedium );
+          isLoose     = tau->isTau( xAOD::TauJetParameters::JetRNNSigLoose );
+          isVeryLoose = tau->isTau( xAOD::TauJetParameters::JetRNNSigVeryLoose );
+
+        } else {
+          ATH_MSG_ERROR( "Unknown Tau type: " << m_tauType.value() );
+          return StatusCode::FAILURE;
+        }
+
+        /// Decoration for All tau (no quality selection)
+        if( isTight or isMedium or isLoose or isVeryLoose ) {
+          ATH_MSG_DEBUG( "Found matching tau " <<
+                         m_tauType.value() << " " <<
+                         m_tauNprongs.value() << 
+                         "prong (pt=" << tau->pt() <<
+                         "). Decorating track." );
+          IDTPM::decorateOrRejectQuietly( track, tau_decor[All], tauLink );
+          return StatusCode::SUCCESS;
+        }
+
+        /// Decoration for Tight tau
+        if( isTight ) {
+          IDTPM::decorateOrRejectQuietly( track, tau_decor[Tight], tauLink );
+        }
+
+        /// Decoration for Medium tau
+        if( isMedium ) {
+          IDTPM::decorateOrRejectQuietly( track, tau_decor[Medium], tauLink );
+        }
+
+        /// Decoration for Loose tau
+        if( isLoose ) {
+          IDTPM::decorateOrRejectQuietly( track, tau_decor[Loose], tauLink );
+        }
+
+        /// Decoration for VeryLoose tau
+        if( isVeryLoose ) {
+          IDTPM::decorateOrRejectQuietly( track, tau_decor[VeryLoose], tauLink );
+        }
+
+      } // if( tauTrack == &track ) 
+
+    } // close tauTracks loop
+
+  } // close tau loop
+
+  return StatusCode::SUCCESS;
+}
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineTauDecoratorAlg.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineTauDecoratorAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..3eef241ab07a2d7df4c92322f5c4f7d8f64fced1
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/OfflineTauDecoratorAlg.h
@@ -0,0 +1,93 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_OFFLINETAUDECORATORALG_H
+#define INDETTRACKPERFMON_OFFLINETAUDECORATORALG_H
+
+/**
+ * @file OfflineTauDecoratorAlg.h
+ * @brief Algorithm to decorate offline tracks with the corresponding
+ *        offline tau object (if required for trigger analysis) 
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ * @date 25 September 2023
+ **/
+
+/// Athena includes
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+
+/// xAOD includes
+#include "xAODTracking/TrackParticleContainer.h"
+#include "xAODTau/TauJetContainer.h"
+
+/// STL includes
+#include <string>
+#include <vector>
+
+/// Local includes
+#include "SafeDecorator.h"
+
+
+namespace IDTPM {
+
+  class OfflineTauDecoratorAlg :
+      public AthReentrantAlgorithm {
+
+  public:
+
+    typedef ElementLink<xAOD::TauJetContainer> ElementTauLink_t;
+    typedef ElementLink<xAOD::TrackParticleContainer> ElementTrackLink_t;
+
+    OfflineTauDecoratorAlg( const std::string& name, ISvcLocator* pSvcLocator );
+
+    virtual ~OfflineTauDecoratorAlg() = default;
+
+    virtual StatusCode initialize() override;
+
+    virtual StatusCode execute( const EventContext& ctx ) const override;
+
+  private:
+
+    SG::ReadHandleKey<xAOD::TrackParticleContainer> m_offlineTrkParticlesName {
+        this, "OfflineTrkParticleContainerName", "InDetTrackParticles", "Name of container of offline tracks" };
+
+    StringProperty m_prefix { this, "Prefix", "LinkedTau_", "Decoration prefix to avoid clashes" };
+
+    StringProperty m_tauType { this, "TauType", "RNN", "Type of reconstructed hadronic Tau (BDT or RNN)" };
+
+    UnsignedIntegerProperty m_tauNprongs { this, "TauNprongs", 1, "Number of prongs in hadronic Tau decay (1 or 3)" };
+
+    StatusCode decorateTauTrack(
+        const xAOD::TrackParticle& track,
+        std::vector< IDTPM::OptionalDecoration< xAOD::TrackParticleContainer,
+                                                ElementTauLink_t > >& tau_decor,
+        const xAOD::TauJetContainer& taus ) const;
+
+    SG::ReadHandleKey<xAOD::TauJetContainer> m_tausName {
+        this, "TauContainerName", "TauJets", "Name of container of offline hadronic taus" };
+
+    enum TauDecorations : size_t {
+      All,
+      Tight,
+      Medium,
+      Loose,
+      VeryLoose,
+      NDecorations
+    };
+
+    const std::vector< std::string > m_decor_tau_names {
+      "All",
+      "Tight",
+      "Medium",
+      "Loose",
+      "VeryLoose"
+    };
+
+    std::vector< IDTPM::WriteKeyAccessorPair< xAOD::TrackParticleContainer,
+                                              ElementTauLink_t > > m_decor_tau{};
+
+  };
+
+} // namespace IDTPM
+
+#endif // > ! INDETTRACKPERFMON_OFFLINETAUDECORATORALG_H
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/RoiSelectionTool.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/RoiSelectionTool.cxx
index 28e7f371962580a6eb0da6c700c71a86c10d4674..fd249d2028651712a73225f387538f482e0acf47 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/RoiSelectionTool.cxx
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/RoiSelectionTool.cxx
@@ -7,7 +7,7 @@
  * @author marco aparo
  **/
 
-#include "InDetTrackPerfMon/RoiSelectionTool.h"
+#include "RoiSelectionTool.h"
 
 
 ///----------------------------------------
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/RoiSelectionTool.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/RoiSelectionTool.h
similarity index 97%
rename from InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/RoiSelectionTool.h
rename to InnerDetector/InDetValidation/InDetTrackPerfMon/src/RoiSelectionTool.h
index 51e635a7bf4ccfce4c281f971b354c62d692717f..41b71f83ff5610c3ca6255449a5f374344298b37 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/RoiSelectionTool.h
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/RoiSelectionTool.h
@@ -11,7 +11,7 @@
 #include "TrigSteeringEvent/TrigRoiDescriptor.h"
 
 /// local includes
-//#include "InDetTrackPerfMon/TrackRoiSelectionTool.h"
+//#include "TrackRoiSelectionTool.h" // TODO - to be included in later MRs
 
 /// STL includes
 #include <string>
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/SafeDecorator.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/SafeDecorator.h
new file mode 100644
index 0000000000000000000000000000000000000000..bf0d6c9bd45c01857adf6b2b72920ab6a6dd8c18
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/SafeDecorator.h
@@ -0,0 +1,219 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_SAFEDECORATOR_H
+#define INDETTRACKPERFMON_SAFEDECORATOR_H
+
+/**
+ * @file SafeDecorator.h
+ * header file for function of same name
+ * adapted from original IDPVM implementation
+ * @author shaun roe
+ * @date 20 July 2016
+ * @brief Helper functions to perform safe decoration
+ *        of xAOD objects in this package
+ */
+
+#include "AthContainers/AuxElement.h"
+#include "StoreGate/WriteDecorHandleKey.h"
+#include "StoreGate/WriteDecorHandle.h"
+#include "StoreGate/ReadDecorHandleKey.h"
+#include "StoreGate/ReadDecorHandle.h"
+#include "GaudiKernel/EventContext.h"
+#include "CxxUtils/fpcompare.h" // for equal
+
+#include <iostream>
+#include <utility>
+#include <vector>
+#include <cmath>
+#include <cstdlib>
+
+namespace IDTPM {
+
+  /// Useful declarations
+  template< class ContainerType, class VariableType >
+  using WriteKeyAccessorPair =
+      std::pair< SG::WriteDecorHandleKey<ContainerType>,
+                 SG::AuxElement::ConstAccessor<VariableType> >;
+  //
+  template< class ContainerType, class VariableType >
+  using WriteAccessorRefPair =
+      std::pair< SG::WriteDecorHandle<ContainerType, VariableType>,
+                 SG::AuxElement::ConstAccessor<VariableType>& >;
+  //
+  template< class ContainerType, class VariableType >
+  using OptionalDecoration =
+      std::pair< SG::WriteDecorHandle<ContainerType, VariableType>, bool >;
+
+  /// create a pair composed of a WriteDecorHandleKey to create a decorator handle
+  /// and an accessor to check the availablilty of a decoration
+  template< class T_Parent, class T_Cont, class T >
+  void createDecoratorKeysAndAccessor(
+      T_Parent& parent,
+      const SG::ReadHandleKey< T_Cont >& container_key,
+      const std::string& prefix,
+      const std::vector< std::string >& decor_names,
+      std::vector< WriteKeyAccessorPair<T_Cont, T > >& decor_out )
+  {
+    decor_out.clear();
+    decor_out.reserve( decor_names.size() );
+    for( const std::string& a_decor_name: decor_names ) {
+      decor_out.emplace_back(
+          SG::WriteDecorHandleKey< T_Cont >( container_key.key()+"."+prefix+a_decor_name ),
+          SG::AuxElement::ConstAccessor<T>( prefix+a_decor_name )
+      );
+      parent.declare( decor_out.back().first );
+      decor_out.back().first.setOwner( &parent );
+      decor_out.back().first.initialize().ignore();
+    }
+  }
+
+  /// like createDecoratorKeysAndAccessor but without the accessor
+  /// to check the availablilty of a decoration
+  template< class T_Parent, class T_Cont >
+  void createDecoratorKeys(
+      T_Parent& parent,
+      const SG::ReadHandleKey< T_Cont >& container_key,
+      const std::string& prefix,
+      const std::vector< std::string >& decor_names,
+      std::vector< SG::WriteDecorHandleKey< T_Cont > >& decor_out)
+  {
+    decor_out.clear();
+    decor_out.reserve( decor_names.size() );
+    for (const std::string &a_decor_name : decor_names) {
+      assert( !a_decor_name.empty() );
+      decor_out.emplace_back( container_key.key()+"."+prefix+a_decor_name );
+      // need to declare handles, otherwise the scheduler would not
+      // pick up the data dependencies introduced by the decorations
+      parent.declare( decor_out.back() );
+      decor_out.back().setOwner(&parent);
+      decor_out.back().initialize().ignore();
+    }
+  }
+
+  /// Like above - FIXME: maybe not needed
+  /*template<class T_Parent, class T_Cont>
+  void addReadDecoratorHandleKeys(T_Parent &parent,
+                                  const SG::ReadHandleKey<T_Cont> &container_key,
+                                  const std::string &prefix,
+                                  const std::vector<std::string> &decor_names,
+                                  std::vector<SG::ReadDecorHandleKey<T_Cont> > &decor_out) {
+    decor_out.reserve(decor_out.size() + decor_names.size());
+    for (const std::string &a_decor_name : decor_names) {
+      decor_out.emplace_back( container_key.key()+"."+prefix+a_decor_name);
+      parent.declare(decor_out.back());
+      decor_out.back().setOwner(&parent);
+      decor_out.back().initialize().ignore();
+    }
+  }*/
+
+  /// create/book the decorations if they do not exist already
+  template< class T_Cont, class T >
+  std::vector< OptionalDecoration< T_Cont,T > >
+  createDecoratorsIfNeeded(
+      const T_Cont& container,
+      const std::vector< WriteKeyAccessorPair<T_Cont, T > >& keys,
+      const EventContext& ctx,
+      bool verbose=false ) 
+  {
+    std::vector< OptionalDecoration< T_Cont, T > > out;
+    bool all_available = true;
+    if( !container.empty() ) {
+      std::vector<bool> decorate;
+      decorate.reserve( keys.size() );
+      for( const WriteKeyAccessorPair< T_Cont, T >& a_key : keys ) {
+        decorate.push_back( !a_key.second.isAvailable( *container[0] ) );
+        all_available &= !decorate.back();
+        if( verbose && !decorate.back() ) {
+          std::cout << "WARNING IDTPM::createDecoratorsIfNeeded: Decoration "
+                    << a_key.first.key() << " already exists; reject update."
+                    << std::endl;
+        } // close if( verbose && !decorate.back() )
+      } // close WriteKeyAccessorPair loop
+
+      if( !all_available ) {
+        std::size_t idx = 0;
+        out.reserve( keys.size() );
+        for( const WriteKeyAccessorPair< T_Cont, T >& a_key : keys ) {
+          assert( idx < decorate.size() );
+          out.emplace_back(
+            SG::WriteDecorHandle< T_Cont,T >( a_key.first, ctx ),
+            decorate[idx++]
+          );
+          if( not out.back().first.isPresent() ) {
+            std::stringstream msg;
+            msg << "Container " << a_key.first.key()
+                << " to be decorated does not exist.";
+            throw std::runtime_error( msg.str() );
+          } // close if( not out.back().first.isPresent() )
+        } // close WriteKeyAccessorPair loop
+      } // close if( !all_available )
+    } // close if( !container.empty() )
+    return out;
+  }
+
+  /// similar to createDecoratorsIfNeeded, but without the checking if decorations already exist
+  template< class T_Cont, class T >
+  std::vector< SG::WriteDecorHandle< T_Cont, T > >
+  createDecorators(
+      const std::vector< SG::WriteDecorHandleKey< T_Cont > >& keys,
+      const EventContext& ctx )
+  {
+    std::vector< SG::WriteDecorHandle< T_Cont, T > > out;
+    out.reserve( keys.size() );
+    for( const SG::WriteDecorHandleKey< T_Cont >& a_key : keys ) {
+      out.emplace_back( a_key, ctx );
+      if( not out.back().isValid() ) {
+        std::stringstream msg;
+        msg << "Failed to create decorator handle " << a_key.key();
+        throw std::runtime_error( msg.str() );
+      }
+    } // close SG::WriteDecorHandleKey loop
+    return out;
+  }
+
+  /// Fill the decoration if it deas not exist or it has a different value
+  template< class T_Cont, class T_Cont_Elm, class T >
+  void decorateOrWarnIfUnequal(
+      const T_Cont_Elm& particle,
+      WriteAccessorRefPair< T_Cont, T >& decorator,
+      const T& value )
+  {
+    if( !decorator.second.isAvailable( particle ) ) {
+      const T existing = decorator.second( particle );
+      if( not CxxUtils::fpcompare::equal( existing, value ) ) {
+        std::cout << "WARNING IDTPM::safeDecorator: " << decorator.first.decorKey()
+                  << " Already exists on this object with a different value."
+                  << std::endl;
+      }
+    } else {
+      decorator.first( particle ) = value;
+    }
+  }
+
+  /// Safe method to fill the decoration if decor flag is true
+  template< class T_Cont, class T_Cont_Elm, class T >
+  void decorateOrRejectQuietly(
+      const T_Cont_Elm& particle,
+      OptionalDecoration< T_Cont, T >& decorator,
+      const T& value)
+  {
+    if( decorator.second ) {
+      decorator.first( particle ) = value;
+    }
+  }
+
+  /// unsafe fill decoration method for convenience
+  template< class T_Cont, class T_Cont_Elm, class T >
+  void decorate(
+    const T_Cont_Elm& particle,
+    OptionalDecoration< T_Cont, T >& decorator,
+    const T& value)
+  {
+    decorator.first( particle ) = value;
+  }
+
+} // namespace IDTPM
+
+#endif // > !INDETTRACKPERFMON_SAFEDECORATOR_H
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisCollections.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisCollections.cxx
index d3f2f13147716b4c3831b5f991b20c9dc25e6e2d..9905e93aa3ca55b0cd36b984862b29ad8c86eaa8 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisCollections.cxx
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisCollections.cxx
@@ -7,7 +7,8 @@
  * @author Marco Aparo <marco.aparo@cern.ch>
  */
 
-#include "InDetTrackPerfMon/TrackAnalysisCollections.h"
+#include "TrackAnalysisCollections.h"
+#include "TrackParmetersHelper.h"
 
 /// -------------------
 /// --- Constructor ---
@@ -500,9 +501,9 @@ std::string IDTPM::TrackAnalysisCollections::printInfo(
   size_t it(0);
   for( const xAOD::TrackParticle* thisOfflineTrack : m_offlTrackVec[ stage ] ) {
     ss << "Offline track" 
-       << " : pt = "  << thisOfflineTrack->pt() 
-       << " : eta = " << thisOfflineTrack->eta() 
-       << " : phi = " << thisOfflineTrack->phi() 
+       << " : pt = "  << pT( *thisOfflineTrack )
+       << " : eta = " << eta( *thisOfflineTrack )
+       << " : phi = " << phi( *thisOfflineTrack )
        << std::endl;
     if( it > 20 ) { ss << "et al...." << std::endl; break; }
     it++;
@@ -514,9 +515,9 @@ std::string IDTPM::TrackAnalysisCollections::printInfo(
   it = 0;
   for( const xAOD::TruthParticle* thisTruthParticle : m_truthTrackVec[ stage ] ) {
     ss << "Truth particle"
-       << " : pt = "  << thisTruthParticle->pt()
-       << " : eta = " << thisTruthParticle->eta()
-       << " : phi = " << thisTruthParticle->phi()
+       << " : pt = "  << pT( *thisTruthParticle )
+       << " : eta = " << eta( *thisTruthParticle )
+       << " : phi = " << phi( *thisTruthParticle )
        << std::endl;
     if( it > 20 ) { ss << "et al...." << std::endl; break; }
     it++;
@@ -528,9 +529,9 @@ std::string IDTPM::TrackAnalysisCollections::printInfo(
   it = 0;
   for( const xAOD::TrackParticle* thisTriggerTrack : m_trigTrackVec[ stage ] ) {
     ss << "Trigger track"
-       << " : pt = "  << thisTriggerTrack->pt()
-       << " : eta = " << thisTriggerTrack->eta()
-       << " : phi = " << thisTriggerTrack->phi()
+       << " : pt = "  << pT( *thisTriggerTrack )
+       << " : eta = " << eta( *thisTriggerTrack )
+       << " : phi = " << phi( *thisTriggerTrack )
        << std::endl;
     if( it > 20 ) { ss << "et al...." << std::endl; break; }
     it++;
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackAnalysisCollections.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisCollections.h
similarity index 100%
rename from InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackAnalysisCollections.h
rename to InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisCollections.h
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisDefinitionSvc.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisDefinitionSvc.cxx
index d603f0536571418e5a6bfeba48fa2f9e8843af9b..98b95a01736d27a349b6494829d9e114f75ac2ca 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisDefinitionSvc.cxx
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisDefinitionSvc.cxx
@@ -9,7 +9,7 @@
 **/
 
 /// local includes
-#include "InDetTrackPerfMon/TrackAnalysisDefinitionSvc.h"
+#include "TrackAnalysisDefinitionSvc.h"
 
 /// STL includes 
 #include <algorithm>
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackAnalysisDefinitionSvc.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisDefinitionSvc.h
similarity index 100%
rename from InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackAnalysisDefinitionSvc.h
rename to InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackAnalysisDefinitionSvc.h
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackObjectSelectionTool.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackObjectSelectionTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..b02aa4775c674e980493b270e2dc1d297ade9622
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackObjectSelectionTool.cxx
@@ -0,0 +1,134 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+/**
+ * @file    InDetTrackObjectSelectionTool.cxx
+ * @author  Marco Aparo <marco.aparo@cern.ch>
+ **/
+
+/// Local include(s)
+#include "TrackObjectSelectionTool.h"
+#include "TrackAnalysisCollections.h"
+#include "OfflineObjectDecorHelper.h"
+#include "TrackParmetersHelper.h"
+
+
+///----------------------------------------
+///------- Parametrized constructor -------
+///----------------------------------------
+IDTPM::TrackObjectSelectionTool::TrackObjectSelectionTool( 
+    const std::string& name ) :
+  asg::AsgTool( name ) { }
+
+
+///--------------------------
+///------- Initialize -------
+///--------------------------
+StatusCode IDTPM::TrackObjectSelectionTool::initialize() {
+
+  ATH_CHECK( asg::AsgTool::initialize() );
+
+  ATH_MSG_INFO( "Initializing " << name() << "..." );
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-------------------------
+///----- selectTracks ------
+///-------------------------
+StatusCode IDTPM::TrackObjectSelectionTool::selectTracks(
+    IDTPM::TrackAnalysisCollections& trkAnaColls ) {
+
+  ATH_MSG_DEBUG( "Selecting offline tracks matched to an offline " << m_objectType.value() );
+
+  ITrackAnalysisDefinitionSvc* trkAnaDefSvc( nullptr );
+  ISvcLocator* svcLoc = Gaudi::svcLocator();
+  ATH_CHECK( svcLoc->service( "TrkAnaDefSvc" + trkAnaColls.anaTag(), trkAnaDefSvc ) );
+
+  if( not trkAnaDefSvc->useOffline() ) {
+    ATH_MSG_DEBUG( "Tool not enabled if offline tracks are not used." );
+    return StatusCode::SUCCESS;
+  }
+
+  /// started loop over offline tracks
+  std::vector< const xAOD::TrackParticle* > newVec;
+  for( const xAOD::TrackParticle* thisTrack :
+       trkAnaColls.offlTrackVec( IDTPM::TrackAnalysisCollections::FS ) ) {
+    if( accept( *thisTrack ) ) newVec.push_back( thisTrack );
+  }
+
+  /// update selected Full-Scan offline track vector
+  ATH_CHECK( trkAnaColls.fillOfflTrackVec( newVec,
+                IDTPM::TrackAnalysisCollections::FS ) );
+
+  /// Debug printout
+  ATH_MSG_DEBUG( "Tracks after offline object-matching: " << 
+      trkAnaColls.printInfo( IDTPM::TrackAnalysisCollections::FS ) );
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-----------------------
+///------- accept --------
+///-----------------------
+bool IDTPM::TrackObjectSelectionTool::accept(
+    const xAOD::TrackParticle& offTrack ) const {
+
+  /// Electron
+  if( m_objectType.value().find("Electron") != std::string::npos ) {
+
+    const xAOD::Electron* ele = IDTPM::getLinkedElectron(
+        offTrack, m_objectQuality.value() );
+
+    if( not ele ) return false;
+
+    ATH_MSG_DEBUG( "Offline Track with pt = " << pT( offTrack ) <<
+                   " matches with " << m_objectQuality.value() << 
+                   " Electron with transverse energy = " << ET( *ele ) );
+  }
+
+  /// Muon
+  if( m_objectType.value().find("Muon") != std::string::npos ) {
+    const xAOD::Muon* mu = IDTPM::getLinkedMuon(
+        offTrack, m_objectQuality.value() );
+
+    if( not mu ) return false;
+
+    ATH_MSG_DEBUG( "Offline Track with pt = " << pT( offTrack ) <<
+                   " matches with " << m_objectQuality.value() <<
+                   " Muon with transverse energy = " << ET( *mu ) );
+  }
+
+  /// Tau
+  if( m_objectType.value().find("Tau") != std::string::npos ) {
+
+    const xAOD::TauJet* tau = IDTPM::getLinkedTau(
+        offTrack, m_tauNprongs.value(),
+        m_tauType.value(), m_objectQuality.value() );
+
+    if( not tau ) return false;
+
+    ATH_MSG_DEBUG( "Offline Track with pt = " << pT( offTrack ) <<
+                   " matches with " << m_objectQuality.value() <<
+                   " hadronic " << m_tauNprongs.value() << "prong " <<
+                   m_tauType.value() << " Tau with transverse energy = " <<
+                   ET( *tau ) );
+  }
+
+  /// Truth
+  if( m_objectType.value().find("Truth") != std::string::npos ) {
+
+    const xAOD::TruthParticle* truth = IDTPM::getLinkedTruth(
+        offTrack, m_truthProbCut.value() );
+
+    if( not truth ) return false;
+
+    ATH_MSG_DEBUG( "Offline Track with pt = " << pT( offTrack ) <<
+                   " matches with truth particle with pt = " << pT( *truth ) );
+  }
+
+  return true;
+}
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackObjectSelectionTool.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackObjectSelectionTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..f94af828e138ea8099e9c6f3aa9e0fc4ced6656e
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackObjectSelectionTool.h
@@ -0,0 +1,84 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_TRACKOBJECTSELECTIONTOOL_H
+#define INDETTRACKPERFMON_TRACKOBJECTSELECTIONTOOL_H
+
+/**
+ * @file    TrackObjectSelectionTool.h
+ * @author  Marco Aparo <marco.aparo@cern.ch>
+ * @date    02 October 2023
+ * @brief   Tool to select offline ID tracks which asociated
+ *          with fully-reconstructed object with specific
+ *          quality criteria
+ */
+
+/// Athena include(s)
+#include "AsgTools/AsgTool.h"
+#include "TrigSteeringEvent/TrigRoiDescriptor.h"
+
+/// Local include(s)
+#include "InDetTrackPerfMon/ITrackSelectionTool.h"
+
+/// EDM includes
+#include "xAODTracking/TrackParticle.h"
+
+
+namespace IDTPM {
+
+  class TrackObjectSelectionTool :
+      public virtual IDTPM::ITrackSelectionTool,
+      public asg::AsgTool {
+
+  public:
+
+    ASG_TOOL_CLASS( TrackObjectSelectionTool, ITrackSelectionTool );
+   
+    /// Constructor 
+    TrackObjectSelectionTool( const std::string& name );
+
+    /// Destructor
+    virtual ~TrackObjectSelectionTool() = default;
+
+    /// Initialize
+    virtual StatusCode initialize() override;
+
+    /// Main Track selection method
+    virtual StatusCode selectTracks(
+        IDTPM::TrackAnalysisCollections& trkAnaColls ) override;
+
+    /// Dummy method - unused
+    virtual StatusCode selectTracksInRoI(
+        IDTPM::TrackAnalysisCollections&,
+        const ElementLink< TrigRoiDescriptorCollection >& ) override {
+      ATH_MSG_WARNING( "selectTracksInRoI method is disabled" );
+      return StatusCode::SUCCESS;
+    }
+
+    bool accept( const xAOD::TrackParticle& offTrack ) const;
+
+  private:
+
+    StringProperty m_objectType {
+        this, "ObjectType", "Electron", "Offline object type requested" };
+
+    StringProperty m_objectQuality {
+        this, "ObjectQuality", "", "Quality-based object selection" };
+
+    StringProperty m_tauType {
+        this, "TauType", "RNN", "Request RNN or BDT taus" };
+
+    IntegerProperty m_tauNprongs {
+        this, "TauNprongs", 1, "Request 1- or 3- prong taus" };
+
+    FloatProperty m_truthProbCut {
+        this, "MatchingTruthProb", 0.5, "Minimal truthProbability for valid matching" };
+
+  }; // class TrackObjectSelectionTool
+
+} // namespace IDTPM
+
+
+
+#endif // > ! INDETTRACKPERFMON_INDETTRACKOBJECTSELECTIONTOOL_H
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackParmetersHelper.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackParmetersHelper.h
new file mode 100644
index 0000000000000000000000000000000000000000..212252040f8235436c31eee262bc8a536375f962
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackParmetersHelper.h
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_TRKPARAMETERSHELPER_H
+#define INDETTRACKPERFMON_TRKPARAMETERSHELPER_H
+
+/**
+ * @file TrackParmetersHelper.h
+ * @brief Utility methods to access 
+ *        track/truth particles parmeters in
+ *        a consitent way in this package
+ * @author Marco Aparo <marco.aparo@cern.ch>
+ * @date 25 September 2023
+ **/
+
+/// xAOD includes
+#include "xAODTracking/TrackParticle.h"
+#include "xAODTruth/TruthParticle.h"
+
+/// STD includes
+#include <cmath> // std::fabs
+
+
+namespace IDTPM {
+
+  /// Accessor utility function for getting the value of pT
+  template< class U >
+  inline float pT( const U& p ) { return p.pt(); }
+
+  /// Accessor utility function for getting the value of signed pT
+  template< class U >
+  inline float pTsig( const U& p ) {
+    float pT = std::fabs( p.pt() );
+    if( p.charge() < 0 )  pT *= -1;
+    if( p.charge() == 0 ) pT = 0.;
+    return pT;
+  }
+
+  /// Accessor utility function for getting the value of eta
+  template< class U >
+  inline float eta( const U& p ) { return p.eta(); }
+
+  /// Accessor utility function for getting the value of phi
+  template< class U >
+  inline float phi( const U& p ) { return p.phi(); }
+
+  /// Accessor utility function for getting the value of z0
+  inline float getZ0( const xAOD::TrackParticle& p ) { return p.z0(); }
+  ///
+  inline float getZ0( const xAOD::TruthParticle& p ) {
+    return ( p.isAvailable<float>("z0") ) ?
+           p.auxdata<float>("z0") : -9999.;
+  }
+  ///
+  template< class U >
+  inline float z0( const U& p ) { return getZ0( p ); }
+
+  /// Accessor utility function for getting the value of d0
+  inline float getD0( const xAOD::TrackParticle& p ) { return p.d0(); }
+  ///
+  inline float getD0( const xAOD::TruthParticle& p ) {
+    return ( p.isAvailable<float>("d0") ) ?
+           p.auxdata<float>("d0") : -9999.;
+  }
+  ///
+  template< class U >
+  inline float d0( const U& p ) { return getD0( p ); }
+
+  /// Accessor utility function for getting the value of qOverP
+  inline float getQoverP( const xAOD::TrackParticle& p ) { return p.qOverP(); }
+  ///
+  inline float getQoverP( const xAOD::TruthParticle& p ) {
+    return ( p.isAvailable<float>("qOverP") ) ?
+           p.auxdata<float>("qOverP") : -9999.;
+  }
+  ///
+  template< class U >
+  inline float qOverP( const U& p ) { return getQoverP( p ); }
+
+  /// Accessor utility function for getting the value of Energy
+  template< class U >
+  inline float Etot( const U& p ) { return p.e(); }
+
+  /// Accessor utility function for getting the value of Tranverse energy
+  template< class U >
+  inline float ET( const U& p ) { return p.p4().Et(); }
+
+} // namespace IDTPM
+
+#endif // > ! INDETTRACKPERFMON_TRKPARAMETERSHELPER_H
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackQualitySelectionTool.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackQualitySelectionTool.cxx
index 158831e826acac5870198a6d6ba1866146ad22a3..3bc590b071c6309b6680d0cdb1b6cf6aa73fb372 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackQualitySelectionTool.cxx
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackQualitySelectionTool.cxx
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
 */
 
 /**
@@ -8,8 +8,8 @@
  **/
 
 /// Local include(s)
-#include "InDetTrackPerfMon/TrackQualitySelectionTool.h"
-#include "InDetTrackPerfMon/TrackAnalysisCollections.h"
+#include "TrackQualitySelectionTool.h"
+#include "TrackAnalysisCollections.h"
 
 
 ///----------------------------------------
@@ -29,8 +29,7 @@ StatusCode IDTPM::TrackQualitySelectionTool::initialize() {
 
   ATH_MSG_INFO( "Initializing " << name() );
 
-  /// TODO - to be included in later MRs
-  //ATH_CHECK( m_objSelectionTool.retrieve( EnableTool{ m_doObjSelection.value() } ) );
+  ATH_CHECK( m_objSelectionTool.retrieve( EnableTool{ m_doObjSelection.value() } ) );
 
   return StatusCode::SUCCESS;
 }
@@ -65,11 +64,10 @@ StatusCode IDTPM::TrackQualitySelectionTool::selectTracks(
   ATH_MSG_DEBUG( "Tracks after initial FullScan copy: " << 
       trkAnaColls.printInfo( IDTPM::TrackAnalysisCollections::FS ) );
 
-  /// TODO - To be included in later MRs
   /// Select offline tracks matched to offline objects
-  //if( trkAnaDefSvc->useOffline() and m_doObjSelection.value() ) {
-  //  ATH_CHECK( m_objSelectionTool->selectTracks( trkAnaColls ) );
-  //}
+  if( trkAnaDefSvc->useOffline() and m_doObjSelection.value() ) {
+    ATH_CHECK( m_objSelectionTool->selectTracks( trkAnaColls ) );
+  }
 
   /// TODO - put offline and truth selections here...
 
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackQualitySelectionTool.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackQualitySelectionTool.h
similarity index 82%
rename from InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackQualitySelectionTool.h
rename to InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackQualitySelectionTool.h
index 969984f5523f8ea4d7569fc52a67da5c3bc852d4..da48c49a4938ac0c2e9ed9bad3c69728f61c4c50 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackQualitySelectionTool.h
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackQualitySelectionTool.h
@@ -1,5 +1,5 @@
 /*
-  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 INDETTRACKPERFMON_TRACKQUALITYSELECTIONTOOL_H
@@ -56,15 +56,14 @@ namespace IDTPM {
       return StatusCode::SUCCESS;
     }
 
-/* TODO - To be included in later MRs
   private:
 
-    BooleanProperty m_doObjSelection{ this, "DoObjectSelection", false, "Perform track-object selection" };
+    BooleanProperty m_doObjSelection {
+        this, "DoObjectSelection", false, "Perform track-object selection" };
 
-    ToolHandle< IDTPM::IInDetSelectionTool > m_objSelectionTool{
-        this, "TrackObjectSelectionTool", "IDTPM::InDetTrackPerfMon/IInDetSelectionTool", 
-        "Tool to perform track-object quality selection" };
-*/
+    ToolHandle< IDTPM::ITrackSelectionTool > m_objSelectionTool {
+        this, "TrackObjectSelectionTool", "IDTPM::InDetTrackPerfMon/ITrackSelectionTool", 
+        "Tool to perform track-object selection" };
 
   }; // class InDetGeneralSelectionTool
 
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackRoiSelectionTool.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackRoiSelectionTool.cxx
index a017eac89476b41a32c07285db1133576bea99ca..beef82bc6d9ccdea229f4887937dcb12a15916b3 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackRoiSelectionTool.cxx
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackRoiSelectionTool.cxx
@@ -11,65 +11,14 @@
 #include "TrigSteeringEvent/TrigRoiDescriptor.h"
 
 /// Local include(s)
-#include "InDetTrackPerfMon/TrackRoiSelectionTool.h"
-#include "InDetTrackPerfMon/TrackAnalysisCollections.h"
+#include "TrackRoiSelectionTool.h"
+#include "TrackAnalysisCollections.h"
+#include "TrackParmetersHelper.h"
 
 /// STD includes
 #include <cmath> // std::fabs
 
 
-///---------------------------------------------------
-/// Placing utility functions in anonymous namespace
-///---------------------------------------------------
-namespace {
-
-  /// Accessor utility function for getting the value of pT
-  template< class U >
-  float pT( const U* p ) {
-    return p->pt();
-  }
-
-  /// Accessor utility function for getting the value of pTsig
-  template< class U >
-  float pTsig( const U* p ) {
-    float pT = std::fabs( p->pt() );
-    if( p->charge() < 0 )  pT *= -1;
-    if( p->charge() == 0 ) pT = 0.;
-    return pT;
-  }
-
-  /// Accessor utility function for getting the value of eta
-  template< class U >
-  float eta( const U* p ) {
-    return p->eta();
-  }
-
-  /// Accessor utility function for getting the value of phi
-  template< class U >
-  float phi( const U* p ) {
-    return p->phi();
-  }
-
-  /// Accessor utility function for getting the value of z0
-  float getZ0( const xAOD::TrackParticle* p ) { 
-    return p->z0();
-  }
-  ///
-  float getZ0( const xAOD::TruthParticle* /*p*/ ) {
-    /// TODO - To be included in later MRs
-    //return ( p->isAvailable<float>("z0") ) ?
-    //       p->auxdata<float>("z0") : -9999.;
-    return 0.; // TODO - to be removed in later MRs
-  }
-  ///
-  template< class U >
-  float z0( const U* p ) {
-    return getZ0( p );
-  }
-
-} // namespace
-
-
 ///----------------------------------------
 ///------- Parametrized constructor -------
 ///----------------------------------------
@@ -101,7 +50,7 @@ StatusCode IDTPM::TrackRoiSelectionTool::initialize() {
 ///-----------------------
 template< class T >
 bool IDTPM::TrackRoiSelectionTool::accept(
-    const T* t, const TrigRoiDescriptor* r ) const {
+    const T& t, const TrigRoiDescriptor* r ) const {
 
   if( r==0 ) { 
     ATH_MSG_ERROR( "Called with null RoiDescriptor" );
@@ -119,7 +68,7 @@ bool IDTPM::TrackRoiSelectionTool::accept(
     if( r->isFullscan() ) return true;
 
     /// NB: This isn't actually correct - the tracks can bend out of the 
-    ///     RoI even if the perigee phi is withing the Roi
+    ///     RoI even if the perigee phi is within the Roi
     bool contained_phi = ( r->phiMinus() < r->phiPlus() ) ?
                          ( phi(t) > r->phiMinus() && phi(t) < r->phiPlus() ) :
                          ( phi(t) > r->phiMinus() || phi(t) < r->phiPlus() ); 
@@ -242,7 +191,7 @@ std::vector< const T* > IDTPM::TrackRoiSelectionTool::getTracks(
 
   for( size_t it=0 ; it<tvec.size() ; it++ ) {
     const T* thisTrack = tvec.at(it);
-    if( accept<T>( thisTrack, r ) ) {
+    if( accept<T>( *thisTrack, r ) ) {
       selectedTracks.push_back( thisTrack );
     }
   }
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackRoiSelectionTool.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackRoiSelectionTool.h
similarity index 97%
rename from InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackRoiSelectionTool.h
rename to InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackRoiSelectionTool.h
index 56aa366ef341834f339d72e2765e155f22da1023..51dc37f1c5e4566cf200c98e7b819aa39ba49157 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/InDetTrackPerfMon/TrackRoiSelectionTool.h
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TrackRoiSelectionTool.h
@@ -65,7 +65,7 @@ namespace IDTPM {
   
     /// geometric RoI filters - for non-trigger tracks (e.g. offline, truth, etc.)
     template< class T >
-    bool accept( const T* t, const TrigRoiDescriptor* r ) const;
+    bool accept( const T& t, const TrigRoiDescriptor* r ) const;
 
     /// track getter function (for offline tracks or truth particles)
     template< class T >
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TruthHitDecoratorAlg.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TruthHitDecoratorAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ce3c63880f8a4cec96c740575107d803d22e6137
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TruthHitDecoratorAlg.cxx
@@ -0,0 +1,233 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+/**
+ * @file TruthHitDecoratorAlg.cxx
+ * @author shaun roe
+ **/
+
+/// Local includes
+#include "TruthHitDecoratorAlg.h"
+
+/// EDM includes
+#include "xAODTruth/TruthVertex.h"
+#include "TrkParameters/TrackParameters.h" // Contains typedef to Trk::CurvilinearParameters
+
+/// ROOT includes
+#include "TDatabasePDG.h"
+#include "TParticlePDG.h"
+
+/// STD includes
+#include <cmath>
+//#include <limits>
+
+
+///---------------------------
+///------- Constructor -------
+///---------------------------
+IDTPM::TruthHitDecoratorAlg::TruthHitDecoratorAlg(
+    const std::string& name,
+    ISvcLocator* pSvcLocator) :
+  AthReentrantAlgorithm( name, pSvcLocator ) { }
+
+
+///--------------------------
+///------- initialize -------
+///--------------------------
+StatusCode IDTPM::TruthHitDecoratorAlg::initialize() {
+
+  ATH_CHECK( m_extrapolator.retrieve() );
+  ATH_CHECK( m_beamSpotDecoKey.initialize() );
+  ATH_CHECK( m_truthPixelClusterName.initialize() );
+  ATH_CHECK( m_truthSCTClusterName.initialize() );
+  ATH_CHECK( m_truthParticleName.initialize() );
+
+  /// Creating/booking decorations for truth particles
+  IDTPM::createDecoratorKeysAndAccessor(
+      *this, m_truthParticleName, 
+      m_prefix.value(), m_decor_names, m_decor );
+
+  if( m_decor.size() != NDecorations ) {
+    ATH_MSG_ERROR( "Incorrect booking of truth hits decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-----------------------
+///------- execute -------
+///-----------------------
+StatusCode IDTPM::TruthHitDecoratorAlg::execute(
+    const EventContext& ctx ) const {
+
+  /// retrieve truth particle container
+  SG::ReadHandle< xAOD::TruthParticleContainer > ptruth( m_truthParticleName, ctx );
+  if( not ptruth.isValid() ) {
+    ATH_MSG_ERROR( "Failed to retrieve truth particle container" );
+    return StatusCode::FAILURE;
+  }
+
+  std::vector< IDTPM::OptionalDecoration< xAOD::TruthParticleContainer, float > >
+      float_decor( IDTPM::createDecoratorsIfNeeded(
+          *ptruth, m_decor, ctx, msgLvl(MSG::DEBUG) ) );
+
+  /// truthbarcode-cluster maps to be pre-stored at event level
+  std::unordered_map< int, float > barcodeSCTclustercount;
+  std::unordered_map< int, float > barcodePIXclustercount;
+  
+  /// Loop over the pixel and sct clusters
+  /// to fill the truth barcode - cluster count maps
+  SG::ReadHandle< xAOD::TrackMeasurementValidationContainer > sctClusters( m_truthSCTClusterName, ctx );
+  SG::ReadHandle< xAOD::TrackMeasurementValidationContainer > pixelClusters( m_truthPixelClusterName, ctx );
+
+  //only decorate the truth particles with truth silicon hits if both containers are available 
+  if( sctClusters.isValid() && pixelClusters.isValid() ) {
+
+    static const SG::AuxElement::ConstAccessor< std::vector<int> > barcodeAcc( "truth_barcode" );
+
+    /// Loop over truth SCT clusters
+    for( const xAOD::TrackMeasurementValidation* sctCluster : *sctClusters ) {
+      std::vector<int> truth_barcode;
+      if( barcodeAcc.isAvailable( *sctCluster ) ) {
+        truth_barcode = barcodeAcc( *sctCluster );
+        for( const int& barcode : truth_barcode ) {
+          auto result = barcodeSCTclustercount.emplace( std::pair<int, float>(barcode, 0.0) ); 
+          if( !result.second ) ++( result.first->second );
+        }
+      }
+    } // close loop over truth SCT clusters
+
+    /// Loop over truth Pixel clusters
+    for( const xAOD::TrackMeasurementValidation* pixCluster : *pixelClusters ) {
+      std::vector<int> truth_barcode;
+      if( barcodeAcc.isAvailable( *pixCluster ) ) {
+        truth_barcode = barcodeAcc( *pixCluster );
+        for( const int& barcode : truth_barcode ) {
+          auto result = barcodePIXclustercount.emplace( std::pair<int, float>(barcode, 0.0) ); 
+          if( !result.second ) ++( result.first->second );
+        }
+      }
+    } // close loop over truth Pixel clusters
+  } // close if sctClusters and pixelClusters isValid
+
+  if( float_decor.empty() ) {
+    ATH_MSG_ERROR( "Failed to book Truth particles Hit decorations" );
+    return StatusCode::FAILURE;
+  }
+
+  /// Retrieving BeamSpot info
+  SG::ReadDecorHandle< xAOD::EventInfo, float > beamPosX( m_beamSpotDecoKey[0], ctx );
+  SG::ReadDecorHandle< xAOD::EventInfo, float > beamPosY( m_beamSpotDecoKey[1], ctx );
+  SG::ReadDecorHandle< xAOD::EventInfo, float > beamPosZ( m_beamSpotDecoKey[2], ctx );
+  if( (not beamPosX.isValid()) or (not beamPosY.isValid()) or (not beamPosZ.isValid()) ) {
+    ATH_MSG_WARNING( "Failed to retrieve beam position" );
+    return StatusCode::RECOVERABLE;
+  }
+  Amg::Vector3D beamPos = Amg::Vector3D( beamPosX(0), beamPosY(0), beamPosZ(0) );
+
+  for( const xAOD::TruthParticle* truth_particle : *ptruth ) {
+    /// decorate current truth particle with hit info
+    ATH_CHECK( decorateTruth( 
+        *truth_particle, float_decor, beamPos,
+        barcodePIXclustercount, barcodeSCTclustercount, ctx ) );
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+///-----------------------
+///---- decorateTruth ----
+///-----------------------
+StatusCode IDTPM::TruthHitDecoratorAlg::decorateTruth(
+    const xAOD::TruthParticle& particle,
+		std::vector< IDTPM::OptionalDecoration< xAOD::TruthParticleContainer, float > >& float_decor,
+		const Amg::Vector3D& beamPos,
+		std::unordered_map<int, float>& pixelMap,
+		std::unordered_map<int, float>& sctMap,
+    const EventContext& ctx ) const {
+
+  /// Skip neutral truth particles
+  if( particle.isNeutral() ) {
+    return StatusCode::SUCCESS;
+  }
+
+  const Amg::Vector3D momentum( particle.px(), particle.py(), particle.pz() );
+  const int pid( particle.pdgId() );
+  double charge = particle.charge();
+
+  if( std::isnan(charge) ) {
+    ATH_MSG_DEBUG( "Charge not found on particle with pid " << pid );
+    return StatusCode::SUCCESS;
+  }
+   
+  /// Retrieve the cluster count from the pre-filled maps   
+  std::unordered_map< int, float >::iterator it1, it2;
+  it1 = pixelMap.find( particle.barcode() );
+  it2 = sctMap.find( particle.barcode() );
+  float nSiHits = 0;
+  if( it1 !=pixelMap.end() )  nSiHits += (*it1).second; 
+  if( it2 !=sctMap.end() )    nSiHits += (*it2).second; 
+ 
+  /// Decoration for NSiHits
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[NSilHits], nSiHits );
+
+  const xAOD::TruthVertex* ptruthVertex(nullptr);
+  try {
+    ptruthVertex = particle.prodVtx();
+  } catch( const std::exception& e ) {
+    if ( not m_errorEmitted ) {
+      ATH_MSG_WARNING( "A non existent production vertex was requested in calculating the track parameters d0 etc" );
+    }
+    m_errorEmitted = true;
+    return StatusCode::RECOVERABLE; //SUCCESS;
+  }
+
+  if( not ptruthVertex ) {
+    ATH_MSG_DEBUG( "A production vertex pointer was retrieved, but it is NULL" );
+    return StatusCode::SUCCESS;
+  }
+
+  const auto xPos = ptruthVertex->x();
+  const auto yPos = ptruthVertex->y();
+  const auto z_truth = ptruthVertex->z();
+  const Amg::Vector3D position( xPos, yPos, z_truth );
+  const float prodR_truth = std::sqrt( xPos * xPos + yPos * yPos );
+  const Trk::CurvilinearParameters cParameters( position, momentum, charge );
+
+  Trk::PerigeeSurface persf( beamPos );
+
+  std::unique_ptr< const Trk::TrackParameters > tP(
+      m_extrapolator->extrapolate( ctx, cParameters,
+                                   persf, Trk::anyDirection, false ) );
+  if( not tP ) {
+    ATH_MSG_DEBUG( "The TrackParameters pointer for this TruthParticle is NULL" );
+    return StatusCode::SUCCESS;
+  }
+
+  /// Other hit decorations
+  float d0_truth     = tP->parameters()[ Trk::d0 ];
+  float theta_truth  = tP->parameters()[ Trk::theta ];
+  float z0_truth     = tP->parameters()[ Trk::z0 ];
+  float phi_truth    = tP->parameters()[ Trk::phi ];
+  float qOverP_truth = tP->parameters()[ Trk::qOverP ]; // P or Pt ??
+  float z0st_truth   = z0_truth * std::sin( theta_truth );
+
+  ATH_MSG_DEBUG( "Truth particle (pT = " << particle.pt() <<
+                 " has impact parameter (d0, z0) = (" << d0_truth <<
+                 " ," << z0_truth << ")" );
+
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[D0],     d0_truth );
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[Z0],     z0_truth );
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[Phi],    phi_truth );
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[Theta],  theta_truth );
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[Z0st],   z0st_truth );
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[QOverP], qOverP_truth );
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[ProdR],  prodR_truth );
+  IDTPM::decorateOrRejectQuietly( particle, float_decor[ProdZ],  z_truth );
+
+  return StatusCode::SUCCESS;
+}
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TruthHitDecoratorAlg.h b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TruthHitDecoratorAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..48bf7b8c053f109ba9c407689a23041c7acf5eed
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/TruthHitDecoratorAlg.h
@@ -0,0 +1,122 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef INDETTRACKPERFMON_TRUTHHITDECORATORALG_H
+#define INDETTRACKPERFMON_TRUTHHITDECORATORALG_H
+
+/**
+ * @file TruthHitDecoratorAlg.h
+ * header file for class of same name
+ * adapted from original IDPVM InDetPhysValTruthDecoratorAlg
+ * @author shaun roe
+ * @date 27 March 2014
+ * @brief Algorithm to decorate xAOD::TruthParticles with additional
+ *        information regarding the corresponding track hits
+ *        required for validation
+ **/
+
+/// Gaudi includes
+#include "GaudiKernel/ToolHandle.h"
+#include "StoreGate/ReadDecorHandleKeyArray.h"
+
+/// Athena includes
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "TrkExInterfaces/IExtrapolator.h"
+
+/// EDM includes
+#include "xAODTruth/TruthParticleContainer.h"
+#include "xAODEventInfo/EventInfo.h"
+#include "xAODTracking/TrackMeasurementValidationContainer.h"
+
+/// STD includes
+#include <string>
+#include <atomic>
+#include <unordered_map>
+#include <vector>
+
+/// Local includes
+#include "SafeDecorator.h"
+
+
+namespace IDTPM {
+
+  class TruthHitDecoratorAlg :
+      public AthReentrantAlgorithm {
+
+  public:
+
+    TruthHitDecoratorAlg( const std::string& name, ISvcLocator* pSvcLocator );
+
+    virtual ~TruthHitDecoratorAlg() = default;
+
+    virtual StatusCode initialize() override;
+
+    virtual StatusCode execute( const EventContext& ctx ) const override;
+
+  private:
+
+    StatusCode decorateTruth(
+      const xAOD::TruthParticle& particle,
+      std::vector< IDTPM::OptionalDecoration< xAOD::TruthParticleContainer,
+                                              float > >& float_decor,
+      const Amg::Vector3D& beamPos, 
+      std::unordered_map< int, float >& pixelMap,
+      std::unordered_map< int, float >& sctMap,
+      const EventContext& ctx ) const;
+
+    PublicToolHandle< Trk::IExtrapolator > m_extrapolator {
+        this, "Extrapolator", "Trk::Extrapolator/AtlasExtrapolator", "" };
+
+    SG::ReadDecorHandleKeyArray< xAOD::EventInfo > m_beamSpotDecoKey {
+        this, "BeamSpotDecoKeys", 
+        { "EventInfo.beamPosX", "EventInfo.beamPosY", "EventInfo.beamPosZ" },
+        "Beamspot position decoration keys" };
+
+    mutable std::atomic<bool> m_errorEmitted{ false };
+
+    ///TruthParticle container's name needed to create decorators
+    SG::ReadHandleKey< xAOD::TruthParticleContainer > m_truthParticleName {
+        this, "TruthParticleContainerName",  "TruthParticles", "" };
+
+    StringProperty m_prefix { this, "Prefix", "", "Decoration prefix to avoid clashes." };
+  
+    SG::ReadHandleKey< xAOD::TrackMeasurementValidationContainer > m_truthPixelClusterName {
+        this, "PixelClusterContainerName", "PixelClusters", "" };
+  
+    SG::ReadHandleKey< xAOD::TrackMeasurementValidationContainer > m_truthSCTClusterName {
+        this, "SCTClusterContainerName",  "SCT_Clusters", "" };
+   
+    enum TruthDecorations {
+      D0,
+      Z0,
+      Phi,
+      Theta,
+      Z0st,
+      QOverP,
+      ProdR,
+      ProdZ,
+      NSilHits,
+      NDecorations
+    };
+
+    const std::vector< std::string > m_decor_names {
+      "d0",
+      "z0",
+      "phi",
+      "theta",
+      "z0st",
+      "qOverP",
+      "prodR",
+      "prodZ",
+      "nSilHits"
+    };
+
+    std::vector< std::pair< 
+        SG::WriteDecorHandleKey< xAOD::TruthParticleContainer >,
+        SG::AuxElement::ConstAccessor< float > > > m_decor{};
+  };
+
+} // namespace IDTPM
+
+#endif // > !INDETTRACKPERFMON_TRUTHHITDECORATORALG_H 
diff --git a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/components/InDetTrackPerfMon_entries.cxx b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/components/InDetTrackPerfMon_entries.cxx
index 48400ad798fe9f335336fc33460ff609a0a57f69..79c7e4340ffc107cb9ba9debf3ab84a20c9f624d 100644
--- a/InnerDetector/InDetValidation/InDetTrackPerfMon/src/components/InDetTrackPerfMon_entries.cxx
+++ b/InnerDetector/InDetValidation/InDetTrackPerfMon/src/components/InDetTrackPerfMon_entries.cxx
@@ -2,36 +2,36 @@
   Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 */
 
-#include "InDetTrackPerfMon/InDetTrackPerfMonTool.h"
-#include "InDetTrackPerfMon/TrackAnalysisDefinitionSvc.h"
-#include "InDetTrackPerfMon/TrackQualitySelectionTool.h"
-#include "InDetTrackPerfMon/RoiSelectionTool.h"
-#include "InDetTrackPerfMon/TrackRoiSelectionTool.h"
+#include "../InDetTrackPerfMonTool.h"
+#include "../TrackAnalysisDefinitionSvc.h"
+#include "../TrackQualitySelectionTool.h"
+#include "../RoiSelectionTool.h"
+#include "../TrackRoiSelectionTool.h"
+#include "../TruthHitDecoratorAlg.h"
+#include "../OfflineElectronDecoratorAlg.h"
+#include "../OfflineMuonDecoratorAlg.h"
+#include "../OfflineTauDecoratorAlg.h"
+#include "../TrackObjectSelectionTool.h"
 /// TODO - To be included in later MRs
-//#include "InDetTrackPerfMon/InDetTrackObjectSelectionTool.h"
 //#include "InDetTrackPerfMon/DeltaRtrackMatchingTool_trk.h"
 //#include "InDetTrackPerfMon/DeltaRtrackMatchingTool_trkTruth.h"
 //#include "InDetTrackPerfMon/TrackTruthMatchingTool.h"
 //#include "InDetTrackPerfMon/HistogramDefinitionSvc.h"
 //#include "InDetTrackPerfMon/ReadJsonHistoDefTool.h"
-//#include "InDetTrackPerfMon/InDetTruthDecoratorAlg.h"
-//#include "InDetTrackPerfMon/InDetOfflineElectronDecoratorAlg.h"
-//#include "InDetTrackPerfMon/InDetOfflineMuonDecoratorAlg.h"
-//#include "InDetTrackPerfMon/InDetOfflineTauDecoratorAlg.h"
 
 DECLARE_COMPONENT( InDetTrackPerfMonTool )
 DECLARE_COMPONENT( TrackAnalysisDefinitionSvc )
 DECLARE_COMPONENT( IDTPM::TrackQualitySelectionTool )
 DECLARE_COMPONENT( IDTPM::RoiSelectionTool )
 DECLARE_COMPONENT( IDTPM::TrackRoiSelectionTool )
+DECLARE_COMPONENT( IDTPM::TruthHitDecoratorAlg )
+DECLARE_COMPONENT( IDTPM::OfflineElectronDecoratorAlg )
+DECLARE_COMPONENT( IDTPM::OfflineMuonDecoratorAlg )
+DECLARE_COMPONENT( IDTPM::OfflineTauDecoratorAlg )
+DECLARE_COMPONENT( IDTPM::TrackObjectSelectionTool )
 /// TODO - To be included in later MRs
 //DECLARE_COMPONENT( ReadJsonHistoDefTool )
 //DECLARE_COMPONENT( IDTPM::HistogramDefinitionSvc )
-//DECLARE_COMPONENT( IDTPM::InDetTrackObjectSelectionTool )
 //DECLARE_COMPONENT( IDTPM::DeltaRtrackMatchingTool_trk )
 //DECLARE_COMPONENT( IDTPM::DeltaRtrackMatchingTool_trkTruth )
 //DECLARE_COMPONENT( IDTPM::TrackTruthMatchingTool )
-//DECLARE_COMPONENT( InDetTruthDecoratorAlg )
-//DECLARE_COMPONENT( InDetOfflineElectronDecoratorAlg )
-//DECLARE_COMPONENT( InDetOfflineMuonDecoratorAlg )
-//DECLARE_COMPONENT( InDetOfflineTauDecoratorAlg )