diff --git a/Calorimeter/CaloDigiAlgs/CMakeLists.txt b/Calorimeter/CaloDigiAlgs/CMakeLists.txt
index 42991aa03cc03af6e51ab161d36d1d61df1a7956..89d20eae8e67a52daa6a23b60825e06f43610ec3 100644
--- a/Calorimeter/CaloDigiAlgs/CMakeLists.txt
+++ b/Calorimeter/CaloDigiAlgs/CMakeLists.txt
@@ -9,7 +9,9 @@ atlas_subdir( CaloDigiAlgs )
 atlas_add_component( CaloDigiAlgs
                      src/*.cxx src/*.h
                      src/components/*.cxx
-                     LINK_LIBRARIES AthenaBaseComps Identifier StoreGateLib WaveRawEvent FaserCaloSimEvent WaveDigiToolsLib)
+                     LINK_LIBRARIES AthenaBaseComps Identifier FaserCaloIdentifier  
+		     WaveformConditionsToolsLib StoreGateLib WaveRawEvent 
+		     FaserCaloSimEvent WaveDigiToolsLib)
 
 atlas_install_python_modules( python/*.py )
 
diff --git a/Calorimeter/CaloDigiAlgs/python/CaloDigiAlgsConfig.py b/Calorimeter/CaloDigiAlgs/python/CaloDigiAlgsConfig.py
index ce45e6257885956f5edb6284780ff9756b51b9fc..793f73401b54c91724bdb6283fa4c6099811628c 100644
--- a/Calorimeter/CaloDigiAlgs/python/CaloDigiAlgsConfig.py
+++ b/Calorimeter/CaloDigiAlgs/python/CaloDigiAlgsConfig.py
@@ -6,6 +6,7 @@ from AthenaConfiguration.ComponentFactory import CompFactory
 
 from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
 
+from WaveformConditionsTools.WaveformCableMappingConfig import WaveformCableMappingCfg
 
 # One stop shopping for normal FASER data
 def CaloWaveformDigitizationCfg(flags):
@@ -17,6 +18,8 @@ def CaloWaveformDigitizationCfg(flags):
 
     acc.merge(CaloWaveformDigiCfg(flags, "CaloWaveformDigiAlg"))
     acc.merge(CaloWaveformDigitizationOutputCfg(flags))
+    acc.merge(WaveformCableMappingCfg(flags))
+
     return acc
 
 # Return configured digitization algorithm from SIM hits
@@ -54,6 +57,8 @@ def CaloWaveformDigitizationOutputCfg(flags, **kwargs):
     ]
     acc.merge(OutputStreamCfg(flags, "RDO"))
     ostream = acc.getEventAlgo("OutputStreamRDO")
-    ostream.TakeItemsFromInput = True # Copies all data from input file to output
+    # ostream.TakeItemsFromInput = True # Copies all data from input file to output
+    # ostream.TakeItemsFromInput = False
+    # Try turning this off
     ostream.ItemList += ItemList
     return acc
diff --git a/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.cxx b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.cxx
index 1abfcf9f11ecf71c135723f51d799828205ea2be..3e10288eaa2e68f82f1c72be38fde04cb73c66fd 100644
--- a/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.cxx
+++ b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.cxx
@@ -2,13 +2,15 @@
 
 #include "Identifier/Identifier.h"
 
-#include <vector>
+#include "FaserCaloSimEvent/CaloHitIdHelper.h"
+
 #include <map>
 #include <utility>
 
 CaloWaveformDigiAlg::CaloWaveformDigiAlg(const std::string& name, 
 					 ISvcLocator* pSvcLocator)
-  : AthReentrantAlgorithm(name, pSvcLocator) { 
+  : AthReentrantAlgorithm(name, pSvcLocator)
+{ 
 
 }
 
@@ -18,7 +20,7 @@ CaloWaveformDigiAlg::initialize() {
 
   // Initalize tools
   ATH_CHECK( m_digiTool.retrieve() );
-
+  ATH_CHECK( m_mappingTool.retrieve() );
 
   // Set key to read waveform from
   ATH_CHECK( m_caloHitContainerKey.initialize() );
@@ -26,19 +28,19 @@ CaloWaveformDigiAlg::initialize() {
   // Set key to write container
   ATH_CHECK( m_waveformContainerKey.initialize() );
 
-  // Will eventually depend on the type of detector
-  // TODO: Vary time at which centre it?
-  // TODO: Change params compared to scint
-  //  m_kernel = new TF1("PDF", " ROOT::Math::crystalball_pdf(x, -0.9, 10, 4, 900)", 0, 1200);
-  
+  // Set up  helper
+  ATH_CHECK(detStore()->retrieve(m_ecalID, "EcalID"));
+
+  // Create CB time kernel and pre-evaluate for number of samples
   m_kernel = new TF1("PDF", "[4] * ROOT::Math::crystalball_pdf(x, [0],[1],[2],[3])", 0, 1200);
-  //m_kernel->SetParameters(-0.25,10,4,900);                                                      
   m_kernel->SetParameter(0, m_CB_alpha);
   m_kernel->SetParameter(1, m_CB_n);
   m_kernel->SetParameter(2, m_CB_sigma);
   m_kernel->SetParameter(3, m_CB_mean);
   m_kernel->SetParameter(4, m_CB_norm);
 
+  // Pre-evaluate time kernel for each bin
+  m_timekernel = m_digiTool->evaluate_timekernel(m_kernel);
 
   return StatusCode::SUCCESS;
 }
@@ -55,12 +57,10 @@ CaloWaveformDigiAlg::finalize() {
 StatusCode 
 CaloWaveformDigiAlg::execute(const EventContext& ctx) const {
   ATH_MSG_DEBUG("Executing");
-
-  ATH_MSG_DEBUG("Run: " << ctx.eventID().run_number() 
-		<< " Event: " << ctx.eventID().event_number());
+  ATH_MSG_DEBUG("Run: " << ctx.eventID().run_number() << " Event: " << ctx.eventID().event_number());
 
   // Find the input HIT collection
-  SG::ReadHandle<CaloHitCollection> caloHitHandle(m_caloHitContainerKey, ctx);
+   SG::ReadHandle<CaloHitCollection> caloHitHandle(m_caloHitContainerKey, ctx);
 
   ATH_CHECK( caloHitHandle.isValid() );
   ATH_MSG_DEBUG("Found ReadHandle for CaloHitCollection " << m_caloHitContainerKey);
@@ -75,11 +75,67 @@ CaloWaveformDigiAlg::execute(const EventContext& ctx) const {
     ATH_MSG_DEBUG("CaloHitCollection found with zero length!");
     return StatusCode::SUCCESS;
   }
-  
-  // Digitise the hits
-  CHECK( m_digiTool->digitise<CaloHitCollection>(caloHitHandle.ptr(),
-						 waveformContainerHandle.ptr(), m_kernel,
-						 std::pair<float, float>(m_base_mean, m_base_rms)) );
+
+  // Create structure to store pulse for each channel
+  std::map<Identifier, std::vector<uint16_t>> waveforms = m_digiTool->create_waveform_map(m_ecalID);
+
+  for (const auto& tk : m_timekernel) {
+    std::map<unsigned int, float> counts;    
+
+    // Convolve hit energy with evaluated kernel and sum for each hit id (i.e. channel)
+    for (const auto& hit : *caloHitHandle) { 
+      counts[hit.identify()] += tk * hit.energyLoss();
+    }
+
+    // Subtract count from basleine and add result to correct waveform vector
+    for (const auto& c : counts) {
+
+      unsigned int baseline = m_digiTool->generate_baseline(m_base_mean, m_base_rms);
+      int value = baseline - c.second;
+
+      if (value < 0) {
+	ATH_MSG_WARNING("Found pulse " << c.second << " larger than baseline " << c.first);
+	value = 0; // Protect against scaling signal above baseline
+      }
+
+      // Convert hit id to Identifier and store 
+      Identifier id = CaloHitIdHelper::GetHelper()->getIdentifier(c.first);
+      waveforms[id].push_back(value);
+    }
+  }
+
+
+  // This is a bit of a hack to make sure all waveforms have
+  // at least baseline entries.  Should really add this to the 
+  // logic above
+  for (const auto& w : waveforms) {
+    if (w.second.size() > 0) continue;
+
+    // Waveform was empty, fill with baseline
+    int channel = m_mappingTool->getChannelMapping(w.first);
+    ATH_MSG_DEBUG("Writing baseline into empty waveform in channel "<< channel);
+    int i = m_digiTool->nsamples();
+    while(i--) {  // Use while to avoid unused variable warning with for
+      int baseline = m_digiTool->generate_baseline(m_base_mean, m_base_rms);
+      waveforms[w.first].push_back(baseline);
+    }
+  }
+
+  //m_chrono->chronoStop("Digit");
+  //m_chrono->chronoStart("Write");
+
+  // Loop over wavefrom vectors to make and store waveform
+  unsigned int nsamples = m_digiTool->nsamples();
+  for (const auto& w : waveforms) {
+    RawWaveform* wfm = new RawWaveform();
+    wfm->setWaveform(0, w.second);
+    wfm->setIdentifier(w.first);
+    wfm->setChannel(m_mappingTool->getChannelMapping(w.first));
+    wfm->setSamples(nsamples);
+    waveformContainerHandle->push_back(wfm);
+  }
+
+  //m_chrono->chronoStop("Write");
 
   ATH_MSG_DEBUG("WaveformsHitContainer " << waveformContainerHandle.name() << "' filled with "<< waveformContainerHandle->size() <<" items");
 
diff --git a/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.h b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.h
index f99d13b5565712045bcef7507fd04bca6bf0d07a..25111bc9655ae99d97e7ea56a98524102ce151f3 100644
--- a/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.h
+++ b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.h
@@ -10,6 +10,7 @@
 
 // Tool classes
 #include "WaveDigiTools/IWaveformDigitisationTool.h"
+#include "WaveformConditionsTools/IWaveformCableMappingTool.h"
 
 // Handles
 #include "StoreGate/ReadHandleKey.h"
@@ -19,11 +20,15 @@
 #include "GaudiKernel/ServiceHandle.h"
 #include "GaudiKernel/ToolHandle.h"
 
+// Helpers
+#include "FaserCaloIdentifier/EcalID.h"
+
 // ROOT
 #include "TF1.h"
 
 // STL
 #include <string>
+#include <vector>
 
 class CaloWaveformDigiAlg : public AthReentrantAlgorithm {
 
@@ -48,6 +53,8 @@ class CaloWaveformDigiAlg : public AthReentrantAlgorithm {
   CaloWaveformDigiAlg &operator=(const CaloWaveformDigiAlg&) = delete;
   //@}
 
+  /** @name Steerable pameters for crystal ball and baseline **/
+  //@{
   Gaudi::Property<double> m_CB_alpha {this, "CB_alpha", 0, "Alpha of the crystal ball function"};
   Gaudi::Property<double> m_CB_n {this, "CB_n", 0, "n of the crystal ball function"};
   Gaudi::Property<double> m_CB_mean {this, "CB_mean", 0, "Mean of the crystal ball function"};
@@ -56,10 +63,16 @@ class CaloWaveformDigiAlg : public AthReentrantAlgorithm {
 
   Gaudi::Property<double> m_base_mean {this, "base_mean", 0, "Mean of the baseline"};
   Gaudi::Property<double> m_base_rms {this, "base_rms", 0, "RMS of the baseline"};
+  //@}
 
-  /// Kernel PDF
-  TF1* m_kernel;
+  /** Kernel PDF and evaluated values **/
+  //@{
+  TF1*                            m_kernel;
+  std::vector<float>              m_timekernel;
+  //@}
 
+  /// Detector ID helper
+  const EcalID* m_ecalID{nullptr};
 
   /**
    * @name Digitisation tool
@@ -67,6 +80,11 @@ class CaloWaveformDigiAlg : public AthReentrantAlgorithm {
   ToolHandle<IWaveformDigitisationTool> m_digiTool
     {this, "WaveformDigitisationTool", "WaveformDigitisationTool"};
 
+  /**
+   * @name Mapping tool
+   */
+  ToolHandle<IWaveformCableMappingTool> m_mappingTool
+    {this, "WaveformCableMappingTool", "WaveformCableMappingTool"};
 
   /**
    * @name Input HITS using SG::ReadHandleKey
@@ -89,4 +107,6 @@ class CaloWaveformDigiAlg : public AthReentrantAlgorithm {
 
 };
 
+
+
 #endif // CALODIGIALGS_CALODIGIALG_H
diff --git a/Calorimeter/FaserCaloSimEvent/CMakeLists.txt b/Calorimeter/FaserCaloSimEvent/CMakeLists.txt
index 9777d4178e6e465f0f57de0c82173b0620fbe46a..2b034a3189207f186984b163c0c5a034bee7f956 100644
--- a/Calorimeter/FaserCaloSimEvent/CMakeLists.txt
+++ b/Calorimeter/FaserCaloSimEvent/CMakeLists.txt
@@ -18,11 +18,11 @@ atlas_add_library( FaserCaloSimEvent
                    PRIVATE_INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} ${GEANT4_INCLUDE_DIRS}
                    DEFINITIONS ${CLHEP_DEFINITIONS}
                    LINK_LIBRARIES ${CLHEP_LIBRARIES} AthAllocators AthenaKernel CxxUtils GeneratorObjects HitManagement StoreGateLib SGtests
-                   PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES} FaserCaloIdentifier )
+                   PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES} FaserCaloIdentifier Identifier)
 
 atlas_add_dictionary( FaserCaloSimEventDict
                       FaserCaloSimEvent/CaloSimEventDict.h
                       FaserCaloSimEvent/selection.xml
                       INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} ${CLHEP_INCLUDE_DIRS} ${GEANT4_INCLUDE_DIRS}
-                      LINK_LIBRARIES ${ROOT_LIBRARIES} ${CLHEP_LIBRARIES} AthAllocators CxxUtils GeneratorObjects HitManagement StoreGateLib SGtests FaserCaloIdentifier FaserCaloSimEvent )
+                      LINK_LIBRARIES ${ROOT_LIBRARIES} ${CLHEP_LIBRARIES} AthAllocators CxxUtils GeneratorObjects HitManagement StoreGateLib SGtests FaserCaloIdentifier FaserCaloSimEvent Identifier)
 
diff --git a/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHit.h b/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHit.h
index b1e955f845d8f82ea8dcff82a6bbd5883f96b4b1..0b6fbe42ad0dd24cb4adbcb8072a65055d6ad2be 100644
--- a/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHit.h
+++ b/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHit.h
@@ -16,6 +16,8 @@
 #include "CLHEP/Geometry/Point3D.h"
 #include "GeneratorObjects/HepMcParticleLink.h"
 
+class Identifier;
+
 class CaloHit  {
 
   ///////////////////////////////////////////////////////////////////
@@ -80,8 +82,13 @@ public:
   // Const methods:
   ///////////////////////////////////////////////////////////////////
 
+  // This returns the HitId, used in Geant.  This is not the detector Identifier
   unsigned int identify() const;
 
+  // This is the detector readout Identifier (pmt Identifier)
+  // provided by ScintHitIdHelper::getIdentifier
+  Identifier getIdentifier() const;
+
   // local start position of the energy deposit:
   HepGeom::Point3D<double> localStartPosition() const;
 
diff --git a/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHitIdHelper.h b/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHitIdHelper.h
index 5500649bc0c789aa5119503ee357839ee7e11b9e..e94581470c0fa76dddbec99c44aa93ba61484cdb 100644
--- a/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHitIdHelper.h
+++ b/Calorimeter/FaserCaloSimEvent/FaserCaloSimEvent/CaloHitIdHelper.h
@@ -23,6 +23,11 @@
 
 // This class is singleton and static method and variable are used.
 #include "CxxUtils/checker_macros.h"
+
+#include "Identifier/Identifier.h"
+
+#include "FaserCaloIdentifier/EcalID.h"
+
 ATLAS_NO_CHECK_FILE_THREAD_SAFETY;
 
 class CaloHitIdHelper : HitIdHelper {
@@ -43,6 +48,8 @@ class CaloHitIdHelper : HitIdHelper {
   // Left or Right
   int getModule(const int& hid) const;
 
+  Identifier getIdentifier(const int& hid) const;
+
   //
   // Info packing:
   int buildHitId(const int, const int) const;
@@ -54,6 +61,9 @@ class CaloHitIdHelper : HitIdHelper {
   //
   // Initialize the helper, only called by the constructor
   void Initialize();
+
+  /// Detector ID helper
+  const EcalID* m_ecalID{nullptr};
 };
 
 #endif // CALOSIMEVENT_CALOHITIDHELPER
diff --git a/Calorimeter/FaserCaloSimEvent/src/CaloHit.cxx b/Calorimeter/FaserCaloSimEvent/src/CaloHit.cxx
index c8e18d7fc737268e388bf469be037ea43655a6d3..86e895117d03eb537127c68d74efef7b08bf8788 100644
--- a/Calorimeter/FaserCaloSimEvent/src/CaloHit.cxx
+++ b/Calorimeter/FaserCaloSimEvent/src/CaloHit.cxx
@@ -142,6 +142,10 @@ int CaloHit::getModule() const {
   return  CaloHitIdHelper::GetHelper()->getModule(m_ID);
 }
 
+Identifier CaloHit::getIdentifier() const {
+  return CaloHitIdHelper::GetHelper()->getIdentifier(m_ID);
+}
+
 void CaloHit::print() const {
   std::cout << "*** Calorimeter Hit" << std::endl;
   std::cout << "          Station Number " << getRow() << std::endl;
diff --git a/Calorimeter/FaserCaloSimEvent/src/CaloHitIdHelper.cxx b/Calorimeter/FaserCaloSimEvent/src/CaloHitIdHelper.cxx
index eec88553dae53bc6e415b385494b32a7f83645ad..0e98fbd6aa3c1be718b049ab91e74ae92b682ed4 100644
--- a/Calorimeter/FaserCaloSimEvent/src/CaloHitIdHelper.cxx
+++ b/Calorimeter/FaserCaloSimEvent/src/CaloHitIdHelper.cxx
@@ -6,8 +6,6 @@
 
 #include "FaserCaloSimEvent/CaloHitIdHelper.h"
 #include "StoreGate/StoreGateSvc.h"
-#include "StoreGate/StoreGateSvc.h"
-#include "FaserCaloIdentifier/EcalID.h"
 
 #include "G4Types.hh"
 #ifdef G4MULTITHREADED
@@ -42,10 +40,9 @@ void CaloHitIdHelper::Initialize() {
   // determine whether hits were created with an SLHC dictionary
   // in which case eta module field is expanded.
   // Need to lock this thread-unsafe retrieval
-  const EcalID* pix;
   ServiceHandle<StoreGateSvc> detStore ("DetectorStore", "CaloHitIdHelper");
   if (detStore.retrieve().isSuccess()) {
-    if (detStore->retrieve(pix, "EcalID").isFailure()) { pix = 0; }
+    if (detStore->retrieve(m_ecalID, "EcalID").isFailure()) { m_ecalID = 0; }
   }
 
   InitializeField("Row", 0, 2);
@@ -64,6 +61,14 @@ int CaloHitIdHelper::getModule(const int& hid) const
   return this->GetFieldValue("Module", hid);
 }
 
+// identifier
+Identifier CaloHitIdHelper::getIdentifier(const int& hid) const
+{
+  return m_ecalID->pmt_id(getRow(hid), getModule(hid), 0);
+
+}
+
+
 //
 // Info packing:
 int CaloHitIdHelper::buildHitId( const int row, 
diff --git a/Control/CalypsoConfiguration/python/DetectorConfigFlags.py b/Control/CalypsoConfiguration/python/DetectorConfigFlags.py
index 763f1176951ecef9e3ecb3a57d7b8a607d449bbb..b2588ba84ef16b18af9084a029cdccbcbe8fae8f 100644
--- a/Control/CalypsoConfiguration/python/DetectorConfigFlags.py
+++ b/Control/CalypsoConfiguration/python/DetectorConfigFlags.py
@@ -7,15 +7,16 @@ from CalypsoConfiguration.AutoConfigFlags import DetDescrInfo, getDefaultDetecto
 
 
 allDetectors = [
-    'Emulsion', 'Veto', 'Trigger', 'Preshower', 'FaserSCT', 'Ecal', 'Dipole', 
+    'Emulsion', 'Veto', 'Trigger', 'Preshower', 'VetoNu', 'FaserSCT', 'Ecal', 'Dipole', 'Trench'
 ]
 
 allGroups = {
     'Neutrino' : [ 'Emulsion' ],
     'Tracker' : ['SCT'],
-    'Scintillator' : ['Veto', 'Trigger', 'Preshower'],
+    'Scintillator' : ['Veto', 'Trigger', 'Preshower', 'VetoNu'],
     'FaserCalo'  : ['Ecal'],
-    'Magnet'     : ['Dipole']
+    'Magnet'     : ['Dipole'],
+    'Cavern'     : ['Trench']
 }
 
 def createDetectorConfigFlags():
@@ -32,17 +33,20 @@ def createDetectorConfigFlags():
     dcf.addFlag('Detector.GeometryVeto',            False)
     dcf.addFlag('Detector.GeometryTrigger',         False)
     dcf.addFlag('Detector.GeometryPreshower',       False)
+    dcf.addFlag('Detector.GeometryVetoNu',          False)
     dcf.addFlag('Detector.GeometryScintillator',    lambda prevFlags : (prevFlags.Detector.GeometryVeto or
                                                                         prevFlags.Detector.GeometryTrigger or
-                                                                        prevFlags.Detector.GeometryPreshower))
+                                                                        prevFlags.Detector.GeometryPreshower or
+                                                                        prevFlags.Detector.GeometryVetoNu))
     dcf.addFlag('Detector.GeometryFaserSCT',        False)
     dcf.addFlag('Detector.GeometryTracker',         lambda prevFlags : prevFlags.Detector.GeometryFaserSCT )
     
     dcf.addFlag('Detector.GeometryEcal',            False)
     dcf.addFlag('Detector.GeometryFaserCalo',       lambda prevFlags : prevFlags.Detector.GeometryEcal)
 
-    # Cavern (disabled by default)
-    # dcf.addFlag('Detector.GeometryCavern',False)
+    # Trench (disabled by default)
+    dcf.addFlag('Detector.GeometryTrench',False)
+    dcf.addFlag('Detector.GeometryFaserCavern',     lambda prevFlags : (prevFlags.Detector.GeometryTrench ))
 
     # dcf.addFlag('Detector.GeometryFaser',           lambda prevFlags : (prevFlags.Detector.GeometryDecayVolume or
     #                                                                     prevFlags.Detector.GeometryScintillator or
@@ -66,9 +70,11 @@ def createDetectorConfigFlags():
     dcf.addFlag('Detector.EnableVeto',        lambda prevFlags : 'Veto' in getDefaultDetectors(prevFlags.GeoModel.FaserVersion))
     dcf.addFlag('Detector.EnableTrigger',     lambda prevFlags : 'Trigger' in getDefaultDetectors(prevFlags.GeoModel.FaserVersion))
     dcf.addFlag('Detector.EnablePreshower',   lambda prevFlags : 'Preshower' in getDefaultDetectors(prevFlags.GeoModel.FaserVersion))
+    dcf.addFlag('Detector.EnableVetoNu',      lambda prevFlags : 'VetoNu' in getDefaultDetectors(prevFlags.GeoModel.FaserVersion))
     dcf.addFlag('Detector.EnableScintillator',lambda prevFlags : (prevFlags.Detector.EnableVeto or
                                                                     prevFlags.Detector.EnableTrigger or
-                                                                    prevFlags.Detector.EnablePreshower))
+                                                                    prevFlags.Detector.EnablePreshower or
+                                                                    prevFlags.Detector.EnableVetoNu))
     dcf.addFlag('Detector.EnableFaserSCT',    lambda prevFlags : 'FaserSCT' in getDefaultDetectors(prevFlags.GeoModel.FaserVersion))
     dcf.addFlag('Detector.EnableTracker',     lambda prevFlags : prevFlags.Detector.EnableFaserSCT )
     dcf.addFlag('Detector.EnableEcal',        lambda prevFlags : 'Ecal' in getDefaultDetectors(prevFlags.GeoModel.FaserVersion))
@@ -86,8 +92,9 @@ def createDetectorConfigFlags():
     dcf.addFlag('Detector.RecoTrigger', False)
     dcf.addFlag('Detector.RecoPreshower', False)
     dcf.addFlag('Detector.RecoEcal', False)
+    dcf.addFlag('Detector.RecoVetoNu', False)
     dcf.addFlag('Detector.RecoWaveform', lambda prevFlags : (prevFlags.Detector.RecoVeto or prevFlags.Detector.RecoTrigger or 
-                                                             prevFlags.Detector.RecoPreshower or prevFlags.Detector.RecoEcal))
+                                                             prevFlags.Detector.RecoPreshower or prevFlags.Detector.RecoVetoNu or prevFlags.Detector.RecoEcal))
     dcf.addFlag('Detector.RecoFaserSCT', False)
     dcf.addFlag('Detector.RecoTracker', lambda prevFlags : (prevFlags.Detector.RecoFaserSCT))
 
diff --git a/Control/CalypsoConfiguration/python/testDetectorFlags.py b/Control/CalypsoConfiguration/python/testDetectorFlags.py
index 9a555b4c637e5699aefd8516874286f1e127b51c..ab8c1e99cf6373e09dc02b94fd72d2560df187d2 100644
--- a/Control/CalypsoConfiguration/python/testDetectorFlags.py
+++ b/Control/CalypsoConfiguration/python/testDetectorFlags.py
@@ -54,11 +54,13 @@ assert flags.Detector.GeometryDipole
 assert not flags.Detector.EnableFaserSCT
 assert not flags.Detector.EnableEcal
 assert not flags.Detector.EnableVeto
+assert not flags.Detector.EnableVetoNu
 assert not flags.Detector.EnableTrigger
 assert not flags.Detector.EnablePreshower
 assert not flags.Detector.GeometryFaserSCT
 assert not flags.Detector.GeometryEcal
 assert not flags.Detector.GeometryVeto
+assert not flags.Detector.GeometryVetoNu
 assert not flags.Detector.GeometryTrigger
 assert not flags.Detector.GeometryPreshower
 print()
diff --git a/Control/CalypsoExample/Digitization/scripts/faserMDC_digi.py b/Control/CalypsoExample/Digitization/scripts/faserMDC_digi.py
new file mode 100755
index 0000000000000000000000000000000000000000..0d94cf0c344b4010627ad63826c4d7aa4732a580
--- /dev/null
+++ b/Control/CalypsoExample/Digitization/scripts/faserMDC_digi.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+# Run with:
+# ./faser_digi.py filepath runtype
+# 
+# filepath - fully qualified path, including url if needed, to the input HITS file
+#   example: "root://eospublic.cern.ch//eos/experiment/faser/sim/GeniePilot/HITS/1/faser.150fbInv.1.001.HITS.pool.root"
+# 
+# For MDC, we assume this is TI12 geometry
+#
+import sys
+import time
+import argparse
+
+a = time.time()
+
+parser = argparse.ArgumentParser(description="Run FASER reconstruction")
+
+parser.add_argument("file_path",
+                    help="Fully qualified path of the raw input file")
+parser.add_argument("run_type", nargs="?", default="",
+                    help="Specify run type (if it can't be parsed from path)")
+parser.add_argument("-t", "--tag", default="",
+                    help="Specify digi tag (to append to output filename)")
+parser.add_argument("-n", "--nevts", type=int, default=-1,
+                    help="Specify number of events to process (default: all)")
+parser.add_argument("-v", "--verbose", action='store_true', 
+                    help="Turn on DEBUG output")
+
+args = parser.parse_args()
+
+from pathlib import Path
+
+filepath=Path(args.file_path)
+
+# runtype has been provided
+if len(args.run_type) > 0:
+    runtype=args.run_type
+
+else:
+    runtype = "TI12MC"
+    print(f"Assuming {runtype} geometry for MDC")
+
+print(f"Starting digitization of {filepath.name} with type {runtype}")
+if args.nevts > 0:
+    print(f"Reconstructing {args.nevts} events by command-line option")
+
+# Start digitization
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from AthenaCommon.Constants import VERBOSE, INFO
+
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+
+Configurable.configurableRun3Behavior = True
+    
+# Flags for this job
+ConfigFlags.Input.isMC = True                    # Needed to bypass autoconfig
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"   # Use MC conditions for now
+
+ConfigFlags.Input.ProjectName = "mc20"
+ConfigFlags.GeoModel.Align.Dynamic    = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Digitization.TruthOutput = True
+
+# TI12 old geometry
+if runtype == "TI12OldMC":
+    ConfigFlags.GeoModel.FaserVersion = "FASER-01" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+
+# Testbeam setup 
+elif runtype == "TestBeamMC" :
+    ConfigFlags.GeoModel.FaserVersion = "FASER-TB00" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-TB00"
+
+# New TI12 geometry (ugh)
+elif runtype == "TI12MC":
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+
+else:
+    print("Invalid run type found:", runtype)
+    print("Specify correct type or update list")
+    sys.exit(-1)
+
+
+# Must use original input string here, as pathlib mangles double // in path names
+ConfigFlags.Input.Files = [ args.file_path ]
+
+filestem = filepath.stem
+# Remove any filetype modifier
+if filestem[-5:] == "-HITS":
+    filestem = filestem[:-5]
+
+if len(args.tag) > 0:
+    print(f"{args.tag} in {filestem}?")
+    if args.tag in filestem:
+        print(f"Not adding tag {args.tag} to file {filestem}")
+    else:
+        filestem += f"-{args.tag}"
+
+ConfigFlags.Output.RDOFileName = f"{filestem}-RDO.root"
+
+#
+# Play around with this?
+# ConfigFlags.Concurrency.NumThreads = 2
+# ConfigFlags.Concurrency.NumConcurrentEvents = 2
+ConfigFlags.lock()
+
+#
+# Configure components
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Needed, or move to MainServicesCfg?
+from FaserGeoModel.FaserGeoModelConfig import FaserGeometryCfg
+acc.merge(FaserGeometryCfg(ConfigFlags))
+
+# Set up algorithms
+from FaserSCT_Digitization.FaserSCT_DigitizationConfigNew import FaserSCT_DigitizationCfg
+acc.merge(FaserSCT_DigitizationCfg(ConfigFlags))
+
+from CaloDigiAlgs.CaloDigiAlgsConfig import CaloWaveformDigitizationCfg
+acc.merge(CaloWaveformDigitizationCfg(ConfigFlags))
+
+from ScintDigiAlgs.ScintDigiAlgsConfig import ScintWaveformDigitizationCfg
+acc.merge(ScintWaveformDigitizationCfg(ConfigFlags))
+
+# Configure verbosity    
+# ConfigFlags.dump()
+if args.verbose:
+    acc.foreach_component("*").OutputLevel = VERBOSE
+
+else:
+    acc.foreach_component("*").OutputLevel = INFO
+
+acc.foreach_component("*ClassID*").OutputLevel = INFO
+
+acc.getService("MessageSvc").Format = "% F%40W%S%7W%R%T %0W%M"
+
+# Execute and finish
+sc = acc.run(maxEvents=args.nevts)
+
+b = time.time()
+from AthenaCommon.Logging import log
+log.info(f"Finish execution in {b-a} seconds")
+
+sys.exit(not sc.isSuccess())
diff --git a/Control/CalypsoExample/Digitization/scripts/faserMDC_digi_merge.py b/Control/CalypsoExample/Digitization/scripts/faserMDC_digi_merge.py
new file mode 100755
index 0000000000000000000000000000000000000000..9c656de660dc7957aa222b691e50af457a870bf0
--- /dev/null
+++ b/Control/CalypsoExample/Digitization/scripts/faserMDC_digi_merge.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+# Run with:
+# ./faser_digi.py filepath runtype
+# 
+# filepath - fully qualified path, including url if needed, to the input HITS file
+#   example: "root://eospublic.cern.ch//eos/experiment/faser/sim/GeniePilot/HITS/1/faser.150fbInv.1.001.HITS.pool.root"
+# 
+# For MDC, we assume this is TI12 geometry
+#
+import sys
+import time
+import argparse
+
+a = time.time()
+
+parser = argparse.ArgumentParser(description="Run FASER reconstruction")
+
+parser.add_argument("dir_path",
+                    help="Fully qualified path of the input file directory")
+parser.add_argument("run_type", nargs="?", default="",
+                    help="Specify run type (if it can't be parsed from path)")
+parser.add_argument("-s", "--slice", type=int, default=0,
+                    help="Specify file slice to produce")
+parser.add_argument("-f", "--files", type=int, default=5,
+                    help="Specify number of input files to run in one batch")
+parser.add_argument("-t", "--tag", default="",
+                    help="Specify digi tag (to append to output filename)")
+parser.add_argument("-n", "--nevts", type=int, default=-1,
+                    help="Specify number of events to process (default: all)")
+parser.add_argument("-v", "--verbose", action='store_true', 
+                    help="Turn on DEBUG output")
+
+args = parser.parse_args()
+
+from pathlib import Path
+
+dirpath = Path(args.dir_path)
+
+# runtype has been provided
+if len(args.run_type) > 0:
+    runtype=args.run_type
+
+else:
+    runtype = "TI12MC"
+    print(f"Assuming {runtype} geometry for MDC")
+
+# Does the directory exist?
+if not (dirpath.exists() and dirpath.is_dir()):
+    print(f"Problem with directory {args.dir_path}")
+    sys.exit(1)
+
+# Create segment list
+seglist = list(range(args.slice*args.files, (args.slice+1)*args.files))
+
+# Now build file list
+filelist = []
+dirlist = list(dirpath.glob('FaserMC-*-HITS.root'))
+if len(dirlist) == 0:
+    print(f"No HITS file found in directory {args.dir_path}")
+    sys.exit(1)
+
+for seg in seglist:
+    # Assume these are in numerical order from 0
+    if seg >= len(dirlist):
+        print(f"Requested file segment {seg} but only {len(dirlist)} files found")
+        break
+    filelist.append(dirlist[seg])  
+
+if len(filelist) == 0:
+    # Asked for range that doesn't exist
+    print(f"No files found for slice {args.slice} with Nfiles={args.files}")
+    sys.exit(1)
+
+# Figure out the file pattern for the output
+stem = filelist[0].stem
+spl = stem.split('-')
+short = spl[1]
+run = spl[2]
+seglo = int(spl[3])
+# Can be multiple tags
+tagstr = ''
+for tag in spl[4:]:
+    if tag == "HITS": break
+    if len(tagstr) > 0:
+        tagstr += "-"
+    tagstr += tag
+
+# Also find the largest file number
+stem = filelist[-1].stem
+spl = stem.split('-')
+seghi = int(spl[3])
+
+# Build output filename
+if seglo == 0 and (seghi+1) == len(dirlist):  # Full run
+    outfile = f"FaserMC-{short}-{run}"
+elif seglo == seghi:  # Single segment
+    outfile = f"FaserMC-{short}-{run}-{seglo:05}"
+else:
+    outfile = f"FaserMC-{short}-{run}-{seglo:05}-{seghi:05}"
+
+# Add existing tag
+if len(tagstr) > 0:
+    outfile += f"-{tagstr}"
+
+# Was a tag requested?  
+if len(args.tag) > 0:
+    if args.tag in tagstr:
+        print(f"Not adding tag {args.tag} to file {filelist[0]}")
+    else:
+        outfile += f"-{args.tag}"
+
+# Finish output file
+outfile += "-RDO.root"
+
+print(f"Found files from {seglo} to {seghi}")
+print(f"Starting digitization of outfile {outfile} with type {runtype}")
+if args.nevts > 0:
+    print(f"Reconstructing {args.nevts} events by command-line option")
+
+# Start digitization
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from AthenaCommon.Constants import VERBOSE, INFO
+
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+
+Configurable.configurableRun3Behavior = True
+    
+# Flags for this job
+ConfigFlags.Input.isMC = True                    # Needed to bypass autoconfig
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"   # Use MC conditions for now
+
+ConfigFlags.Input.ProjectName = "mc20"
+ConfigFlags.GeoModel.Align.Dynamic    = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Digitization.TruthOutput = True
+
+# TI12 old geometry
+if runtype == "TI12OldMC":
+    ConfigFlags.GeoModel.FaserVersion = "FASER-01" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+
+# Testbeam setup 
+elif runtype == "TestBeamMC" :
+    ConfigFlags.GeoModel.FaserVersion = "FASER-TB00" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-TB00"
+
+# New TI12 geometry (ugh)
+elif runtype == "TI12MC":
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+
+else:
+    print("Invalid run type found:", runtype)
+    print("Specify correct type or update list")
+    sys.exit(-1)
+
+
+# Try just passing the filelist
+ConfigFlags.Input.Files = [str(file) for file in filelist]
+ConfigFlags.Output.RDOFileName = outfile
+
+#
+# Play around with this?
+# ConfigFlags.Concurrency.NumThreads = 2
+# ConfigFlags.Concurrency.NumConcurrentEvents = 2
+ConfigFlags.lock()
+
+#
+# Configure components
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Needed, or move to MainServicesCfg?
+from FaserGeoModel.FaserGeoModelConfig import FaserGeometryCfg
+acc.merge(FaserGeometryCfg(ConfigFlags))
+
+# Set up algorithms
+from FaserSCT_Digitization.FaserSCT_DigitizationConfigNew import FaserSCT_DigitizationCfg
+acc.merge(FaserSCT_DigitizationCfg(ConfigFlags))
+
+from CaloDigiAlgs.CaloDigiAlgsConfig import CaloWaveformDigitizationCfg
+acc.merge(CaloWaveformDigitizationCfg(ConfigFlags))
+
+from ScintDigiAlgs.ScintDigiAlgsConfig import ScintWaveformDigitizationCfg
+acc.merge(ScintWaveformDigitizationCfg(ConfigFlags))
+
+# Configure verbosity    
+# ConfigFlags.dump()
+if args.verbose:
+    acc.foreach_component("*").OutputLevel = VERBOSE
+
+else:
+    acc.foreach_component("*").OutputLevel = INFO
+
+acc.foreach_component("*ClassID*").OutputLevel = INFO
+
+acc.getService("MessageSvc").Format = "% F%40W%S%7W%R%T %0W%M"
+
+# Execute and finish
+sc = acc.run(maxEvents=args.nevts)
+
+b = time.time()
+from AthenaCommon.Logging import log
+log.info(f"Finish execution in {b-a} seconds")
+
+sys.exit(not sc.isSuccess())
diff --git a/Control/CalypsoExample/Digitization/scripts/faser_digi.py b/Control/CalypsoExample/Digitization/scripts/faser_digi.py
index 5ef818999634d592b21142b88ad2cee5c1a324f1..91de9cad754ec1bb739a3f689f27911139e48882 100755
--- a/Control/CalypsoExample/Digitization/scripts/faser_digi.py
+++ b/Control/CalypsoExample/Digitization/scripts/faser_digi.py
@@ -82,7 +82,7 @@ elif runtype == "TestBeamMC" :
 
 # New TI12 geometry (ugh)
 elif runtype == "TI12MC":
-    ConfigFlags.GeoModel.FaserVersion = "FASERNU-02" 
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03" 
     ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
 
 else:
diff --git a/Control/CalypsoExample/Digitization/scripts/submit_faserMDC_digi.sh b/Control/CalypsoExample/Digitization/scripts/submit_faserMDC_digi.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a8e1b9059aa32d4e91bafe923797d262aa2b84ca
--- /dev/null
+++ b/Control/CalypsoExample/Digitization/scripts/submit_faserMDC_digi.sh
@@ -0,0 +1,135 @@
+#!/bin/bash
+# Used with a condor file to submit to vanilla universe
+#
+# Usage:
+# submit_faserMDC_digi.sh filepath [release_directory] [working_directory]
+# 
+# filepath - full file name (with path)
+# release_directory - optional path to release install directory (default pwd)
+# working_directory - optional path to output directory location (default pwd)
+#
+# The release directory must already be set up 
+# (so an unqualified asetup can set up the release properly)
+#
+# Script will use git describe to find the release tag.  
+# If this matches gen/g???? it will be passed to the job
+#
+#----------------------------------------
+# Keep track of time
+SECONDS=0
+#
+# Parse command-line options
+file_path=${1}
+release_directory=${2}
+working_directory=${3}
+#
+# Set defaults if arguments aren't provided
+if [ -z "$file_path" ]
+then
+  echo "No file specified!"
+  echo "Usage: submit_faserMDC_digi.sh file [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$release_directory" ]
+then
+  release_directory=`pwd`
+fi
+#
+if [ -z "$working_directory" ]
+then
+  working_directory=`pwd`
+fi
+#
+starting_directory=`pwd`
+#
+# Now extract the run number and file stem
+#
+# First, get the filename
+file_name=$(basename "$file_path")
+# 
+# Now split based on '.' to get stem
+defaultIFS=$IFS
+IFS='.'
+read file_stem ext <<< "$file_name"
+#
+# Finally extract the run number
+IFS='-'
+# Read the split words into an array based on delimiter
+read faser short run_number segment <<< "$file_stem"
+#
+# Set the IFS delimeter back or else echo doesn't work...
+IFS=$defaultIFS
+#
+# Make output directory if needed
+output_directory="$working_directory/$run_number"
+mkdir -p "$output_directory"
+#
+# This magic redirects everything in this script to our log file
+exec >& "$output_directory/$file_stem.log"
+echo `date` - $HOSTNAME
+echo "File: $file_name"
+echo "Release: $release_directory"
+echo "Output: $output_directory"
+echo "Starting: $starting_directory"
+#
+# Set up the release (do this automatically)?
+export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
+source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh 
+#
+# Try automatic
+# Always go back to the starting directory in case paths are relative
+cd "$starting_directory"
+cd "$release_directory"
+# asetup
+# source build/x8*/setup.sh
+#
+# Do this by hand
+asetup --input=calypso/asetup.faser Athena,22.0.49
+source build/x86*/setup.sh
+#
+#
+# Try to find a release tag
+cd calypso
+recotag=`git describe`
+if [[ "$recotag" == "reco/r"???? ]]; then
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found reco tag: $tag"
+fi
+if [[ "$recotag" == "digi/d"???? ]]; then
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found digi tag: $tag"
+fi
+if [[ "$recotag" == "sim/s"???? ]]; then
+  tag=`echo "$recotag" | cut -c 5-10`
+  echo "Found sim tag: $tag"
+fi
+#
+# Move to the run directory
+cd "$starting_directory"
+cd "$output_directory"
+#
+# Remove any previous directory if it exists
+#if [[ -e "$file_stem" ]]; then
+#    echo "Remove previous directory $file_stem"
+#    rm -rf "$file_stem"
+#fi
+#
+# Make run directory
+if [[ -e "$file_stem" ]]; then
+    echo "Directory $file_stem already exists"
+else
+    mkdir "$file_stem"
+fi
+cd "$file_stem"
+#
+# Run job
+if [[ -z "$tag" ]]; then
+    faserMDC_digi.py "$file_path"
+else
+    faserMDC_digi.py "--tag=$tag" "$file_path"
+fi
+#
+# Print out ending time
+date
+echo "Job finished after $SECONDS seconds"
diff --git a/Control/CalypsoExample/Digitization/scripts/submit_faserMDC_digi_merge.sh b/Control/CalypsoExample/Digitization/scripts/submit_faserMDC_digi_merge.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3c500905965fc48e7242838f284226516c03b8f2
--- /dev/null
+++ b/Control/CalypsoExample/Digitization/scripts/submit_faserMDC_digi_merge.sh
@@ -0,0 +1,159 @@
+#!/bin/bash
+# Used with a condor file to submit to vanilla universe
+#
+# Usage:
+# submit_faserMDC_digi_merge.sh dirpath slice nfiles [release_directory] [working_directory] 
+# 
+# dirpath - full directory path to HITS files
+# slice - ordinal output file number
+# nfiles - number of HITS files to process per slice
+# release_directory - optional path to release install directory (default pwd)
+# working_directory - optional path to output directory location (default pwd)
+#
+# The release directory must already be set up 
+# (so an unqualified asetup can set up the release properly)
+#
+# Script will use git describe to find the release tag.  
+# If this matches sim/s???? or digi/d???? it will be passed to the job
+#
+#----------------------------------------
+# Keep track of time
+SECONDS=0
+#
+# Parse command-line options
+dir_path=${1}
+slice=${2}
+nfiles=${3}
+release_directory=${4}
+working_directory=${5}
+#
+# Set defaults if arguments aren't provided
+if [ -z "$dir_path" ]
+then
+  echo "No directory specified!"
+  echo "Usage: submit_faserMDC_digi_merge.sh directory slice nfiles [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$slice" ]
+then
+  echo "Slice number not specified!"
+  echo "Usage: submit_faserMDC_digi_merge.sh directory slice nfiles [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$nfiles" ]
+then
+  echo "Files per slice not specified!"
+  echo "Usage: submit_faserMDC_digi_merge.sh directory slice nfiles [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$release_directory" ]
+then
+  release_directory=`pwd`
+fi
+#
+if [ -z "$working_directory" ]
+then
+  working_directory=`pwd`
+fi
+#
+starting_directory=`pwd`
+#
+# Now extract the run number and file stem
+#
+# First, get an example filename
+file_name=`ls -1 $dir_path | head -1`
+# 
+# Now split based on '.' to get stem
+defaultIFS=$IFS
+IFS='.'
+read file_stem ext <<< "$file_name"
+#
+# Finally extract the run number
+IFS='-'
+# Read the split words into an array based on delimiter
+read faser short run_number segment <<< "$file_stem"
+#
+# Set the IFS delimeter back or else echo doesn't work...
+IFS=$defaultIFS
+#
+# Make output directory if needed
+output_directory="$working_directory/$run_number"
+mkdir -p "$output_directory"
+#
+# Need to make up an output name
+file_stem="$faser-$short-$run_number-RDO-merge-$slice"
+#
+# This magic redirects everything in this script to our log file
+exec >& "$output_directory/$file_stem.log"
+echo `date` - $HOSTNAME
+echo "Directory: $dir_path"
+echo "Slice: $slice"
+echo "NFiles: $nfiles"
+echo "Release: $release_directory"
+echo "Output: $output_directory"
+echo "Starting: $starting_directory"
+echo "job: $file_stem"
+#
+# Set up the release (do this automatically)?
+export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
+source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh 
+#
+# Try automatic
+# Always go back to the starting directory in case paths are relative
+cd "$starting_directory"
+cd "$release_directory"
+# asetup
+# source build/x8*/setup.sh
+#
+# Do this by hand
+asetup --input=calypso/asetup.faser Athena,22.0.49
+source build/x86*/setup.sh
+#
+#
+# Try to find a release tag
+cd calypso
+recotag=`git describe`
+if [[ "$recotag" == "reco/r"???? ]]; then
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found reco tag: $tag"
+fi
+if [[ "$recotag" == "digi/d"???? ]]; then
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found digi tag: $tag"
+fi
+if [[ "$recotag" == "sim/s"???? ]]; then
+  tag=`echo "$recotag" | cut -c 5-10`
+  echo "Found sim tag: $tag"
+fi
+#
+# Move to the run directory
+cd "$starting_directory"
+cd "$output_directory"
+#
+# Remove any previous directory if it exists
+#if [[ -e "$file_stem" ]]; then
+#    echo "Remove previous directory $file_stem"
+#    rm -rf "$file_stem"
+#fi
+#
+# Make run directory
+if [[ -e "$file_stem" ]]; then
+    echo "Directory $file_stem already exists"
+else
+    mkdir "$file_stem"
+fi
+cd "$file_stem"
+#
+# Run job
+if [[ -z "$tag" ]]; then
+    faserMDC_digi_merge.py --slice $slice --files $nfiles $dir_path
+else
+    faserMDC_digi_merge.py --slice $slice --files $nfiles --tag $tag $dir_path
+fi
+#
+# Print out ending time
+date
+echo "Job finished after $SECONDS seconds"
diff --git a/Control/CalypsoExample/Generation/CMakeLists.txt b/Control/CalypsoExample/Generation/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4ffb523fe90a093c43dc84e121ae7792078e8047
--- /dev/null
+++ b/Control/CalypsoExample/Generation/CMakeLists.txt
@@ -0,0 +1,12 @@
+################################################################################
+# Package: Generation
+################################################################################
+
+# Declare the package name:
+atlas_subdir( Generation )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py )
+atlas_install_scripts( scripts/*.sh scripts/*.py )
+
+
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_100MeV_1Em5-110001.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_100MeV_1Em5-110001.json
new file mode 100644
index 0000000000000000000000000000000000000000..782dda26b0ebb02c6553cfeed61daf0bfd59c563
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_100MeV_1Em5-110001.json
@@ -0,0 +1,14 @@
+{
+    "run": 110001,
+    "segment": 0,
+    "file_length": 100,
+    "model": "DarkPhoton",
+    "model_path": "/eos/experiment/faser/gen/ForeseeMDC/DarkPhoton/npy/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy",
+    "short": "MDC_FS_Aee_100MeV_1Em5",
+    "tag": null,
+    "pid": [
+        -11,
+        11
+    ],
+    "mass": 100.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_10MeV_1Em4-110003.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_10MeV_1Em4-110003.json
new file mode 100644
index 0000000000000000000000000000000000000000..41326579b17bf5befc48f03423749770b12e8a68
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_10MeV_1Em4-110003.json
@@ -0,0 +1,14 @@
+{
+    "run": 110003,
+    "segment": 0,
+    "file_length": 100,
+    "model": "DarkPhoton",
+    "model_path": "/eos/experiment/faser/gen/ForeseeMDC/DarkPhoton/npy/events_14TeV_m0.01GeV_c0.0001to_11_-11.npy",
+    "short": "MDC_FS_Aee_10MeV_1Em4",
+    "tag": null,
+    "pid": [
+        -11,
+        11
+    ],
+    "mass": 10.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_10MeV_1Em5-110002.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_10MeV_1Em5-110002.json
new file mode 100644
index 0000000000000000000000000000000000000000..112a4e79f625481e9524b6ac04d939d618a3171f
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Aee_10MeV_1Em5-110002.json
@@ -0,0 +1,14 @@
+{
+    "run": 110002,
+    "segment": 0,
+    "file_length": 100,
+    "model": "DarkPhoton",
+    "model_path": "/eos/experiment/faser/gen/ForeseeMDC/DarkPhoton/npy/events_14TeV_m0.01GeV_c1e-05to_11_-11.npy",
+    "short": "MDC_FS_Aee_10MeV_1Em5",
+    "tag": null,
+    "pid": [
+        -11,
+        11
+    ],
+    "mass": 10.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_100MeV_1Em4-110007.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_100MeV_1Em4-110007.json
new file mode 100644
index 0000000000000000000000000000000000000000..044d1840123099f44b7f208f0a0d95e52717ff2e
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_100MeV_1Em4-110007.json
@@ -0,0 +1,14 @@
+{
+    "run": 110007,
+    "segment": 0,
+    "file_length": 100,
+    "model": "DarkPhoton",
+    "model_path": "/eos/experiment/faser/gen/ForeseeMDC/ALP-W/npy/events_14TeV_m0.1GeV_c0.0001to_22_22.npy",
+    "short": "MDC_FS_Alp_100MeV_1Em4",
+    "tag": null,
+    "pid": [
+        22,
+        22
+    ],
+    "mass": 100.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_10MeV_1Em2-110006.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_10MeV_1Em2-110006.json
new file mode 100644
index 0000000000000000000000000000000000000000..7d4d8496ec2270679c8e41f082083057723dfaa9
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_10MeV_1Em2-110006.json
@@ -0,0 +1,14 @@
+{
+    "run": 110006,
+    "segment": 0,
+    "file_length": 100,
+    "model": "DarkPhoton",
+    "model_path": "/eos/experiment/faser/gen/ForeseeMDC/ALP-W/npy/events_14TeV_m0.01GeV_c0.01to_22_22.npy",
+    "short": "MDC_FS_Alp_10MeV_1Em2",
+    "tag": null,
+    "pid": [
+        22,
+        22
+    ],
+    "mass": 10.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_10MeV_1Em4-110005.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_10MeV_1Em4-110005.json
new file mode 100644
index 0000000000000000000000000000000000000000..6bdc7701c5c18ea683f4311d90ada6440a94c693
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Alp_10MeV_1Em4-110005.json
@@ -0,0 +1,14 @@
+{
+    "run": 110005,
+    "segment": 0,
+    "file_length": 100,
+    "model": "DarkPhoton",
+    "model_path": "/eos/experiment/faser/gen/ForeseeMDC/ALP-W/npy/events_14TeV_m0.01GeV_c0.0001to_22_22.npy",
+    "short": "MDC_FS_Alp_10MeV_1Em4",
+    "tag": null,
+    "pid": [
+        22,
+        22
+    ],
+    "mass": 10.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Amm_316MeV_2Em6-110004.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Amm_316MeV_2Em6-110004.json
new file mode 100644
index 0000000000000000000000000000000000000000..998137d88277e5a4dd579e8dac02382ca525e9bf
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_FS_Amm_316MeV_2Em6-110004.json
@@ -0,0 +1,14 @@
+{
+    "run": 110004,
+    "segment": 0,
+    "file_length": 1000,
+    "model": "DarkPhoton",
+    "model_path": "/eos/experiment/faser/gen/ForeseeMDC/DarkPhoton/npy/events_14TeV_m0.3162GeV_c2e-06to_13_-13.npy",
+    "short": "MDC_FS_Amm_316MeV_2Em6",
+    "tag": null,
+    "pid": [
+        -13,
+        13
+    ],
+    "mass": 316.2
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_elec_100GeV-101103.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_elec_100GeV-101103.json
new file mode 100644
index 0000000000000000000000000000000000000000..5d894348c01aabba890fadcc2e5ab29b21feb166
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_elec_100GeV-101103.json
@@ -0,0 +1,13 @@
+{
+    "file_length": 1000,
+    "mass": 0.511,
+    "maxE": 100.0,
+    "minE": 100.0,
+    "pid": [-11, 11],
+    "radius": -100.0,
+    "run": 101103,
+    "sampler": "const",
+    "segment": 0,
+    "short": "MDC_PG_elec_100GeV",
+    "zpos": -1000.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_elec_logE-101101.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_elec_logE-101101.json
new file mode 100644
index 0000000000000000000000000000000000000000..4ced9578a31bc74d2b04056600a5f4d988ceeb5a
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_elec_logE-101101.json
@@ -0,0 +1,13 @@
+{
+    "file_length": 500,
+    "mass": 0.511,
+    "maxE": 5000.0,
+    "minE": 10.0,
+    "pid": [-11, 11],
+    "radius": -100.0,
+    "run": 101101,
+    "sampler": "log",
+    "segment": 0,
+    "short": "MDC_PG_elec_logE",
+    "zpos": -1000.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_gam_100GeV-102201.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_gam_100GeV-102201.json
new file mode 100644
index 0000000000000000000000000000000000000000..9fd3bac93171aacaaa85380e825cc0a75e2da340
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_gam_100GeV-102201.json
@@ -0,0 +1,13 @@
+{
+    "file_length": 1000,
+    "mass": 0.0,
+    "maxE": 100.0,
+    "minE": 100.0,
+    "pid":  22,
+    "radius": -100.0,
+    "run": 102201,
+    "sampler": "const",
+    "segment": 0,
+    "short": "MDC_PG_gam_100GeV",
+    "zpos": -1000.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_100GeV-101303.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_100GeV-101303.json
new file mode 100644
index 0000000000000000000000000000000000000000..f2bb15dbf45d165c53fced996db34c028b91c60b
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_100GeV-101303.json
@@ -0,0 +1,13 @@
+{
+    "file_length": 2000,
+    "mass": 105.66,
+    "maxE": 100.0,
+    "minE": 100.0,
+    "pid": [-13, 13],
+    "radius": -100.0,
+    "run": 101303,
+    "sampler": "const",
+    "segment": 0,
+    "short": "MDC_PG_muon_100GeV",
+    "zpos": -1000.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_fasernu_logE-101302.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_fasernu_logE-101302.json
new file mode 100644
index 0000000000000000000000000000000000000000..76ba19e1146b9107c3365cc5c5dd867ff26e6c94
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_fasernu_logE-101302.json
@@ -0,0 +1,13 @@
+{
+    "file_length": 2000,
+    "mass": 105.66,
+    "maxE": 5000.0,
+    "minE": 10.0,
+    "pid": [-13, 13],
+    "radius": -100.0,
+    "run": 101302,
+    "sampler": "log",
+    "segment": 0,
+    "short": "MDC_PG_muon_fasernu_logE",
+    "zpos": -4000.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_logE-101301.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_logE-101301.json
new file mode 100644
index 0000000000000000000000000000000000000000..c462453d04dfef9a93b310c8d782574efb687d9b
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_muon_logE-101301.json
@@ -0,0 +1,13 @@
+{
+    "file_length": 2000,
+    "mass": 105.66,
+    "maxE": 5000.0,
+    "minE": 10.0,
+    "pid": [-13, 13],
+    "radius": -100.0,
+    "run": 101301,
+    "sampler": "log",
+    "segment": 0,
+    "short": "MDC_PG_muon_logE",
+    "zpos": -1000.0
+}
diff --git a/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_pion_100GeV-121101.json b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_pion_100GeV-121101.json
new file mode 100644
index 0000000000000000000000000000000000000000..d6fa243102ae6edd11144bf3297992a014f23cbc
--- /dev/null
+++ b/Control/CalypsoExample/Generation/data/mdc/FaserMC-MDC_PG_pion_100GeV-121101.json
@@ -0,0 +1,13 @@
+{
+    "file_length": 1000,
+    "mass": 139.6,
+    "maxE": 100.0,
+    "minE": 100.0,
+    "pid":  [211, -211],
+    "radius": -100.0,
+    "run": 121101,
+    "sampler": "const",
+    "segment": 0,
+    "short": "MDC_PG_pion_100GeV",
+    "zpos": -1000.0
+}
diff --git a/Control/CalypsoExample/Generation/python/__init__.py b/Control/CalypsoExample/Generation/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f078213647f851a7dfbd2d5ca63157209ff91b22
--- /dev/null
+++ b/Control/CalypsoExample/Generation/python/__init__.py
@@ -0,0 +1,2 @@
+# Generation
+
diff --git a/Control/CalypsoExample/Generation/python/faserMDC_parser.py b/Control/CalypsoExample/Generation/python/faserMDC_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..583b04199c9d7799215a6cd79539865141a4347a
--- /dev/null
+++ b/Control/CalypsoExample/Generation/python/faserMDC_parser.py
@@ -0,0 +1,172 @@
+#
+# Copyright (C) 2022 CERN for the benefit of the ATLAS collaboration
+# Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+#
+# Parser function for MDC particle gun samples
+#
+def faserMDC_pgparser():
+
+    import sys
+    import json
+    import argparse
+
+    parser = argparse.ArgumentParser(description="Run FASER ParticleGun Simulation")
+
+    parser.add_argument("--conf", action='append',
+                        help="Specify configuration file with default values")
+    parser.add_argument("--run", default=123456, type=int,
+                        help="Run number to generate")
+    parser.add_argument("--segment", default=00000, type=int,
+                        help="Segment number to generate")
+    parser.add_argument("--file_length", default=1000, type=int,
+                        help="Total events per file segement")
+
+    parser.add_argument("--short", default="MDCPG_logE",
+                        help="Short description for filename")
+    parser.add_argument("--tag", default=None,
+                        help="Generator tag (g0000)")
+
+    parser.add_argument("--pid", default=[-13, 13], type=int, nargs='*',
+                        help="Specify PDG ID of particle (note plus/minus different) or list (e.g.: --pid -13 13)")
+    parser.add_argument("--mass", default=105.66, type=float,
+                        help="Specify particle mass (in MeV)")
+    parser.add_argument("--radius", default=100., type=float,
+                        help="Specify radius (in mm)")
+    parser.add_argument("--angle", default=0.015, type=float,
+                        help="Specify angular width (in Rad)")
+    parser.add_argument("--zpos", default=None, type=float,
+                        help="Specify z position of particles (in mm) (helpful to avoid FASERnu)")
+
+    parser.add_argument("--sampler", default="log",
+                        help="Specify energy sampling (log, lin, const)")
+    parser.add_argument("--minE", default=10., type=float,
+                        help="Minimum energy in GeV (for log or lin sampler)")
+    parser.add_argument("--maxE", default=1000., type=float,
+                        help="Maximum energy (or constant) in GeV")
+
+    parser.add_argument("--nevts", default=-1, type=int,
+                        help="Number of events to generate (for debugging)")
+    parser.add_argument("--dump", action='store_true',
+                        help="Write out full configuration")
+    parser.add_argument("--noexec", action='store_true',
+                        help="Exit after parsing configuration (no execution)")
+
+    pg_args = parser.parse_args()
+
+    # Get defaults
+    if pg_args.conf is not None:
+        for conf_fname in pg_args.conf:
+            with open(conf_fname, 'r') as f:
+                parser.set_defaults(**json.load(f))
+
+        # Reload arguments to override config file with command line
+        pg_args = parser.parse_args()
+
+    # Print out configuration if requested
+    if pg_args.dump:
+        tmp_args = vars(pg_args).copy()
+        del tmp_args['dump']  # Don't dump the dump value
+        del tmp_args['conf']  # Don't dump the conf file name either
+        del tmp_args['nevts'] # Debugging, not part of configuration
+        del tmp_args['noexec'] # Debugging, not part of configuration
+        print("Configuration:")
+        print(json.dumps(tmp_args, indent=4, sort_keys=False))
+
+    if pg_args.noexec:
+        sys.exit(0)
+
+    #
+    # Add some derived quantities
+    #
+
+    # Create the file name also (could add gentag here)
+    pg_args.outfile = f"FaserMC-{pg_args.short}-{pg_args.run:06}-{pg_args.segment:05}"
+
+    if pg_args.tag:
+        pg_args.outfile += f"-{pg_args.tag}"
+
+    pg_args.outfile += "-HITS.root"
+
+    return pg_args
+
+# All done
+
+#
+# Parser function for MDC Foresee samples
+#
+def faserMDC_fsparser():
+
+    import sys
+    import json
+    import argparse
+
+    parser = argparse.ArgumentParser(description="Run FASER ParticleGun Simulation")
+
+    parser.add_argument("--conf", action='append',
+                        help="Specify configuration file with default values")
+    parser.add_argument("--run", default=123456, type=int,
+                        help="Run number to generate")
+    parser.add_argument("--segment", default=00000, type=int,
+                        help="Segment number to generate")
+    parser.add_argument("--file_length", default=1000, type=int,
+                        help="Total events per file segement")
+
+    parser.add_argument("--model", help="Model name")
+    parser.add_argument("--model_path", help="Path to model phase space file")
+
+    parser.add_argument("--short", default="MDCPG_logE",
+                        help="Short description for filename")
+    parser.add_argument("--tag", default=None,
+                        help="Generator tag (g0000)")
+
+    parser.add_argument("--pid", default=[-13, 13], type=int, nargs=2,
+                        help="Specify PDG ID of daughter particles")
+    parser.add_argument("--mass", default=105.66, type=float,
+                        help="Specify particle mass (in MeV)")
+
+    parser.add_argument("--nevts", default=-1, type=int,
+                        help="Number of events to generate (for debugging)")
+    parser.add_argument("--dump", action='store_true',
+                        help="Write out full configuration")
+    parser.add_argument("--noexec", action='store_true',
+                        help="Exit after parsing configuration (no execution)")
+
+    fs_args = parser.parse_args()
+
+    # Get defaults
+    if fs_args.conf is not None:
+        for conf_fname in fs_args.conf:
+            with open(conf_fname, 'r') as f:
+                parser.set_defaults(**json.load(f))
+
+        # Reload arguments to override config file with command line
+        fs_args = parser.parse_args()
+
+    # Print out configuration if requested
+    if fs_args.dump:
+        tmp_args = vars(fs_args).copy()
+        del tmp_args['dump']  # Don't dump the dump value
+        del tmp_args['conf']  # Don't dump the conf file name either
+        del tmp_args['nevts'] # Debugging, not part of configuration
+        del tmp_args['noexec'] # Debugging, not part of configuration
+        print("Configuration:")
+        print(json.dumps(tmp_args, indent=4, sort_keys=False))
+
+    if fs_args.noexec:
+        sys.exit(0)
+
+    #
+    # Add some derived quantities
+    #
+
+    # Create the file name also (could add gentag here)
+    fs_args.outfile = f"FaserMC-{fs_args.short}-{fs_args.run:06}-{fs_args.segment:05}"
+
+    if fs_args.tag:
+        fs_args.outfile += f"-{fs_args.tag}"
+
+    fs_args.outfile += "-HITS.root"
+
+    return fs_args
+
+# All done
diff --git a/Control/CalypsoExample/Generation/scripts/faserMDC_foresee.py b/Control/CalypsoExample/Generation/scripts/faserMDC_foresee.py
new file mode 100755
index 0000000000000000000000000000000000000000..b48bbf861ed4bbac6e0a6fb6cc9fd95da5049138
--- /dev/null
+++ b/Control/CalypsoExample/Generation/scripts/faserMDC_foresee.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python
+"""
+Produce particle gun samples
+Derived from G4FaserAlgConfigNew
+
+Usage:
+faserMDC_particlegun.py --conf=<config_file>
+
+Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
+"""
+
+if __name__ == '__main__':
+
+    import sys
+    import time
+    a = time.time()
+#
+# Parse command-line options
+#
+    from Generation.faserMDC_parser import faserMDC_fsparser
+    args = faserMDC_fsparser()
+#
+# Figure out events to run and skip
+#
+    nskipped = args.segment*args.file_length
+    if args.nevts > 0:
+        nevents = args.nevts
+    else:
+        nevents = args.file_length
+#
+# Print out what we are doing
+#
+    print(f"Generating {nevents} in file {args.outfile}")
+#
+# Set up logging and config behaviour
+#
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG, VERBOSE
+    from AthenaCommon.Configurable import Configurable
+    log.setLevel(DEBUG)
+    Configurable.configurableRun3Behavior = 1
+#
+# Import and set config flags
+#
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Exec.MaxEvents = nevents
+    ConfigFlags.Exec.SkipEvents = nskipped
+    from AthenaConfiguration.Enums import ProductionStep
+    ConfigFlags.Common.ProductionStep = ProductionStep.Simulation
+#
+# All these must be specified to avoid auto-configuration
+#
+    ConfigFlags.Input.RunNumber = [args.run] #Isn't updating - todo: investigate
+    ConfigFlags.Input.OverrideRunNumber = True
+    ConfigFlags.Input.LumiBlockNumber = [(args.segment+1)]
+    ConfigFlags.Input.isMC = True
+#
+# Output file name
+# 
+    ConfigFlags.Output.HITSFileName = args.outfile
+#
+# Sim ConfigFlags
+#
+    ConfigFlags.Sim.Layout = "FASER"
+    ConfigFlags.Sim.PhysicsList = "FTFP_BERT"
+    ConfigFlags.Sim.ReleaseGeoModel = False
+    ConfigFlags.Sim.IncludeParentsInG4Event = True # Controls whether BeamTruthEvent is written to output HITS file
+
+    # Property bag for particle gun keyword:argument pairs
+    # Apply +12mm vertical shift (to get to FASER CL)
+    # And 150 uRad crossing angle (Jamie claims this is half-angle)
+    ConfigFlags.addFlag("Sim.Gun",{"Generator" : "Foresee"})  
+    ConfigFlags.addFlag("Sim.Beam.xangle", 0)  # Potential beam crossing angles
+    ConfigFlags.addFlag("Sim.Beam.yangle", -0.000150)    
+    ConfigFlags.addFlag("Sim.Beam.xshift", 0)  # Potential beam shift
+    ConfigFlags.addFlag("Sim.Beam.yshift", 12.) 
+
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"   # Geometry set-up
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"   # Conditions set-up
+    ConfigFlags.addFlag("Input.InitialTimeStamp", 0)   # To avoid autoconfig 
+    ConfigFlags.GeoModel.Align.Dynamic = False
+
+#
+# Preset particle gun parameters
+#
+    from math import atan
+    from AthenaCommon.SystemOfUnits import GeV, TeV, cm, m
+    from AthenaCommon.PhysicalConstants import pi
+
+    print(f"Using pid: {args.pid}")
+    if len(args.pid) != 2:
+        print(f"Need 2 particles defined for Foresee generator!")
+        sys.exit(1)
+
+    # String seeding doesn't work for numpy, make integer by hashing instead
+    import hashlib
+
+    # Note the mother mass here is in GeV for some reason
+    import ParticleGun as PG
+    ConfigFlags.Sim.Gun = {
+        "Generator" : "Foresee",
+        "model_path" : args.model_path,
+        "model_name" : args.model,
+        "daughter1_pid" : args.pid[0],
+        "daughter2_pid" : args.pid[1],
+        "mother_mass" : args.mass/1000., 
+        "randomSeed" : int(hashlib.sha512(args.outfile.encode()).hexdigest(), 16) }
+
+    # Note the nominal z position is -3.75m, which is a bit upstream of vetoNu
+    # The decay volume is approximately -1.5 - 0 m, so -1m is safely inside
+    # To get all the material currently defined in front, specify -5m.
+    # Note zpos is in mm!
+    #if args.zpos:
+    #    ConfigFlags.Sim.Gun["z"] = args.zpos
+
+    doShiftLOS = (ConfigFlags.Sim.Beam.xangle or ConfigFlags.Sim.Beam.yangle or
+                  ConfigFlags.Sim.Beam.xshift or ConfigFlags.Sim.Beam.yshift)
+
+    if doShiftLOS:
+        pgConfig = ConfigFlags.Sim.Gun
+        pgConfig["McEventKey"] = "BeamTruthEvent_ATLASCoord"
+        ConfigFlags.Sim.Gun = pgConfig
+
+#
+# MDC geometry configuration
+#
+    detectors = ['Veto', 'VetoNu', 'Preshower', 'FaserSCT', 'Ecal', 'Trigger', 'Dipole', 'Emulsion']
+#
+# Setup detector flags
+#
+    from CalypsoConfiguration.DetectorConfigFlags import setupDetectorsFromList
+    setupDetectorsFromList(ConfigFlags, detectors, toggle_geometry=True)
+#
+# Finalize flags
+#
+    ConfigFlags.lock()
+#
+# Initialize a new component accumulator
+#
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+#
+# Configure the particle gun as requested, or using defaults
+#
+
+#
+# Particle gun generators - the generator, energy, angle, particle type, position, etc can be modified by passing keyword arguments
+#
+    from FaserParticleGun.FaserParticleGunConfig import FaserParticleGunCfg
+    cfg.merge(FaserParticleGunCfg(ConfigFlags))
+    from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+    cfg.merge(McEventSelectorCfg(ConfigFlags))
+
+#
+# Output file
+#
+    from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    cfg.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Shift LOS
+#
+
+    if doShiftLOS:
+        import McParticleEvent.Pythonizations
+        from GeneratorUtils.ShiftLOSConfig import ShiftLOSCfg
+
+        cfg.merge(ShiftLOSCfg(ConfigFlags, 
+                              xcross = ConfigFlags.Sim.Beam.xangle, 
+                              ycross = ConfigFlags.Sim.Beam.yangle,
+                              xshift = ConfigFlags.Sim.Beam.xshift,
+                              yshift = ConfigFlags.Sim.Beam.yshift))
+
+    
+#
+# Add the G4FaserAlg
+#
+    from G4FaserAlg.G4FaserAlgConfigNew import G4FaserAlgCfg
+    cfg.merge(G4FaserAlgCfg(ConfigFlags))
+#
+# Dump config
+#
+    from AthenaConfiguration.ComponentFactory import CompFactory
+    cfg.addEventAlgo(CompFactory.JobOptsDumperAlg(FileName="G4FaserTestConfig.txt"))
+    cfg.getService("StoreGateSvc").Dump = True
+    cfg.getService("ConditionStore").Dump = True
+    cfg.printConfig(withDetails=True, summariseProps = False)  # gags on ParticleGun if summariseProps = True?
+
+    ConfigFlags.dump()
+    #f = open("test.pkl","wb")
+    #cfg.store(f)
+    #f.close()
+#
+# Execute and finish
+#
+
+    #cfg.foreach_component("*").OutputLevel = "INFO"  # Use warning for production
+
+    sc = cfg.run()
+
+    b = time.time()
+    log.info("Run G4FaserAlg in " + str(b-a) + " seconds")
+#
+# Success should be 0
+#
+    sys.exit(not sc.isSuccess())
+
diff --git a/Control/CalypsoExample/Generation/scripts/faserMDC_particlegun.py b/Control/CalypsoExample/Generation/scripts/faserMDC_particlegun.py
new file mode 100755
index 0000000000000000000000000000000000000000..20970fdf37f5fb28f102949e5ef35cd1372f193c
--- /dev/null
+++ b/Control/CalypsoExample/Generation/scripts/faserMDC_particlegun.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+"""
+Produce particle gun samples
+Derived from G4FaserAlgConfigNew
+
+Usage:
+faserMDC_particlegun.py --conf=<config_file>
+
+Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
+"""
+
+if __name__ == '__main__':
+
+    import sys
+    import time
+    a = time.time()
+#
+# Parse command-line options
+#
+    from Generation.faserMDC_parser import faserMDC_pgparser
+    args = faserMDC_pgparser()
+#
+# Figure out events to run and skip
+#
+    nskipped = args.segment*args.file_length
+    if args.nevts > 0:
+        nevents = args.nevts
+    else:
+        nevents = args.file_length
+#
+# Print out what we are doing
+#
+    print(f"Generating {nevents} in file {args.outfile}")
+#
+# Set up logging and config behaviour
+#
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG, VERBOSE
+    from AthenaCommon.Configurable import Configurable
+    log.setLevel(DEBUG)
+    Configurable.configurableRun3Behavior = 1
+#
+# Import and set config flags
+#
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Exec.MaxEvents = nevents
+    ConfigFlags.Exec.SkipEvents = nskipped
+    from AthenaConfiguration.Enums import ProductionStep
+    ConfigFlags.Common.ProductionStep = ProductionStep.Simulation
+#
+# All these must be specified to avoid auto-configuration
+#
+    ConfigFlags.Input.RunNumber = [args.run] #Isn't updating - todo: investigate
+    ConfigFlags.Input.OverrideRunNumber = True
+    ConfigFlags.Input.LumiBlockNumber = [(args.segment+1)]
+    ConfigFlags.Input.isMC = True
+#
+# Output file name
+# 
+    ConfigFlags.Output.HITSFileName = args.outfile
+#
+# Sim ConfigFlags
+#
+    ConfigFlags.Sim.Layout = "FASER"
+    ConfigFlags.Sim.PhysicsList = "FTFP_BERT"
+    ConfigFlags.Sim.ReleaseGeoModel = False
+    ConfigFlags.Sim.IncludeParentsInG4Event = True # Controls whether BeamTruthEvent is written to output HITS file
+    ConfigFlags.addFlag("Sim.Gun",{"Generator" : "SingleParticle"})  # Property bag for particle gun keyword:argument pairs
+    ConfigFlags.addFlag("Sim.Beam.xangle", 0)  # Potential beam crossing angles
+    ConfigFlags.addFlag("Sim.Beam.yangle", 0)    
+    ConfigFlags.addFlag("Sim.Beam.xshift", 0)  # Potential beam shift
+    ConfigFlags.addFlag("Sim.Beam.yshift", 0)        
+
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"   # Geometry set-up
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"   # Conditions set-up
+    ConfigFlags.addFlag("Input.InitialTimeStamp", 0)   # To avoid autoconfig 
+    ConfigFlags.GeoModel.Align.Dynamic = False
+
+#
+# Preset particle gun parameters
+#
+    import ParticleGun as PG
+    from AthenaCommon.SystemOfUnits import GeV, TeV, cm, m
+    from AthenaCommon.PhysicalConstants import pi
+
+    if isinstance(args.pid, list):
+        # Note args.pid is a list, must make this a set for ParticleGun
+        pidarg = set(args.pid)
+    else:
+        # Just pass a single value
+        pidarg = args.pid
+
+    print(f"Using pid: {args.pid} => {pidarg}")
+
+    # Create the simgun dictionary
+    # Negative radius gives uniform sampling
+    # Positive radius gives Gaussian sampling
+    sg_dict = {
+        "Generator" : "SingleParticle", 
+        "pid" : pidarg, "mass" : args.mass, 
+        "theta" :  PG.GaussianSampler(0, args.angle, oneside = True), 
+        "phi" : [0, 2*pi], "radius" : args.radius, 
+        "randomSeed" : args.outfile 
+    }
+
+    # Note the nominal z position is -3.75m, which is a bit upstream of vetoNu
+    # The decay volume is approximately -1.5 - 0 m, so -1m is safely inside
+    # To get all the material currently defined in front, specify -5m.
+    # Note zpos is in mm!
+    if args.zpos:
+        sg_dict["z"] = args.zpos
+
+    # Determine energy sampling
+    if args.sampler == "lin":
+        sg_dict["energy"] = PG.UniformSampler(args.minE*GeV, args.maxE*GeV)
+    elif args.sampler == "log":
+        sg_dict["energy"] = PG.LogSampler(args.minE*GeV, args.maxE*GeV)
+    elif args.sampler == "const":
+        sg_dict["energy"] = PG.ConstSampler(args.maxE*GeV)
+    else:
+        print(f"Sampler {args.sampler} not known!")
+        sys.exit(1)
+
+    # Pass this in one go to ConfigFlags
+    ConfigFlags.Sim.Gun = sg_dict
+
+    doShiftLOS = (ConfigFlags.Sim.Beam.xangle or ConfigFlags.Sim.Beam.yangle or
+                  ConfigFlags.Sim.Beam.xshift or ConfigFlags.Sim.Beam.yshift)
+
+    if doShiftLOS:
+        pgConfig = ConfigFlags.Sim.Gun
+        pgConfig["McEventKey"] = "BeamTruthEvent_ATLASCoord"
+        ConfigFlags.Sim.Gun = pgConfig
+
+#
+# MDC geometry configuration
+#
+    detectors = ['Veto', 'VetoNu', 'Preshower', 'FaserSCT', 'Ecal', 'Trigger', 'Dipole', 'Emulsion']
+#
+# Setup detector flags
+#
+    from CalypsoConfiguration.DetectorConfigFlags import setupDetectorsFromList
+    setupDetectorsFromList(ConfigFlags, detectors, toggle_geometry=True)
+#
+# Finalize flags
+#
+    ConfigFlags.lock()
+#
+# Initialize a new component accumulator
+#
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+#
+# Configure the particle gun as requested, or using defaults
+#
+
+#
+# Particle gun generators - the generator, energy, angle, particle type, position, etc can be modified by passing keyword arguments
+#
+    from FaserParticleGun.FaserParticleGunConfig import FaserParticleGunCfg
+    cfg.merge(FaserParticleGunCfg(ConfigFlags))
+    from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+    cfg.merge(McEventSelectorCfg(ConfigFlags))
+
+#
+# Output file
+#
+    from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    cfg.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Shift LOS
+#
+
+    if doShiftLOS:
+        import McParticleEvent.Pythonizations
+        from GeneratorUtils.ShiftLOSConfig import ShiftLOSCfg
+
+        cfg.merge(ShiftLOSCfg(ConfigFlags, 
+                              xcross = ConfigFlags.Sim.Beam.xangle, 
+                              ycross = ConfigFlags.Sim.Beam.yangle,
+                              xshift = ConfigFlags.Sim.Beam.xshift,
+                              yshift = ConfigFlags.Sim.Beam.yshift))
+
+    
+#
+# Add the G4FaserAlg
+#
+    from G4FaserAlg.G4FaserAlgConfigNew import G4FaserAlgCfg
+    cfg.merge(G4FaserAlgCfg(ConfigFlags))
+#
+# Dump config
+#
+    from AthenaConfiguration.ComponentFactory import CompFactory
+    cfg.addEventAlgo(CompFactory.JobOptsDumperAlg(FileName="G4FaserTestConfig.txt"))
+    cfg.getService("StoreGateSvc").Dump = True
+    cfg.getService("ConditionStore").Dump = True
+    cfg.printConfig(withDetails=True, summariseProps = False)  # gags on ParticleGun if summariseProps = True?
+
+    ConfigFlags.dump()
+    #f = open("test.pkl","wb")
+    #cfg.store(f)
+    #f.close()
+#
+# Execute and finish
+#
+
+    #cfg.foreach_component("*").OutputLevel = "INFO"  # Use warning for production
+
+    sc = cfg.run()
+
+    b = time.time()
+    log.info("Run G4FaserAlg in " + str(b-a) + " seconds")
+#
+# Success should be 0
+#
+    sys.exit(not sc.isSuccess())
+
diff --git a/Control/CalypsoExample/Generation/scripts/faser_particlegun.py b/Control/CalypsoExample/Generation/scripts/faser_particlegun.py
new file mode 100755
index 0000000000000000000000000000000000000000..ee38005df79c3f6aab97b5c800f3949521110a02
--- /dev/null
+++ b/Control/CalypsoExample/Generation/scripts/faser_particlegun.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+"""
+Produce particle gun samples
+Derived from G4FaserAlgConfigNew
+This is a general low-level script, 
+although this could be useful for testing.
+
+Usage:
+faser_particlegun.py <options>
+
+Control using command-line options:
+--evtMax=1000
+--skipEvt=1000
+
+Output.HITSFileName=<name>
+Sim.Gun='{"pid" : 11, "z": -1500.}'
+
+Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
+"""
+
+if __name__ == '__main__':
+
+    import time
+    a = time.time()
+#
+# Set up logging and config behaviour
+#
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG, VERBOSE
+    from AthenaCommon.Configurable import Configurable
+    log.setLevel(DEBUG)
+    Configurable.configurableRun3Behavior = 1
+#
+# Import and set config flags
+#
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Exec.MaxEvents = 10  # can be overridden from command line with --evtMax=<number>
+    ConfigFlags.Exec.SkipEvents = 0  # can be overridden from command line with --skipEvt=<number>
+    from AthenaConfiguration.Enums import ProductionStep
+    ConfigFlags.Common.ProductionStep = ProductionStep.Simulation
+#
+# All these must be specified to avoid auto-configuration
+#
+    ConfigFlags.Input.RunNumber = [12345] #Isn't updating - todo: investigate
+    ConfigFlags.Input.OverrideRunNumber = True
+    ConfigFlags.Input.LumiBlockNumber = [1]
+    ConfigFlags.Input.isMC = True
+#
+# Output file name
+# 
+    ConfigFlags.Output.HITSFileName = "my.HITS.pool.root" # can be overridden from command line with Output.HITSFileName=<name>
+#
+# Sim ConfigFlags
+#
+    ConfigFlags.Sim.Layout = "FASER"
+    ConfigFlags.Sim.PhysicsList = "FTFP_BERT"
+    ConfigFlags.Sim.ReleaseGeoModel = False
+    ConfigFlags.Sim.IncludeParentsInG4Event = True # Controls whether BeamTruthEvent is written to output HITS file
+    ConfigFlags.addFlag("Sim.Gun",{"Generator" : "SingleParticle"})  # Property bag for particle gun keyword:argument pairs
+    ConfigFlags.addFlag("Sim.Beam.xangle", 0)  # Potential beam crossing angles
+    ConfigFlags.addFlag("Sim.Beam.yangle", 0)    
+
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-02"             # Geometry set-up
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"             # Conditions set-up
+    ConfigFlags.addFlag("Input.InitialTimeStamp", 0)             # To avoid autoconfig 
+    ConfigFlags.GeoModel.Align.Dynamic = False
+
+#
+# Preset particle gun parameters
+#
+    from math import atan
+    from AthenaCommon.SystemOfUnits import GeV, TeV, cm, m
+    from AthenaCommon.PhysicalConstants import pi
+
+    # 11 - electron, 13 - muon, 22 - photon
+    import ParticleGun as PG
+    ConfigFlags.Sim.Gun = {
+        "Generator" : "SingleParticle", "pid" : 13, 
+        "energy" : PG.LogSampler(10*GeV, 1*TeV), 
+        "theta" :  PG.GaussianSampler(0, atan((10*cm)/(7*m)), oneside = True), 
+        "phi" : [0, 2*pi], "mass" : 0.511, "radius" : -10*cm, #"z": -2.0*m,
+        "randomSeed" : 12345}
+
+#
+# Command-line overrides
+#
+# These have the format: Sim.Gun='{"pid" : 11}'
+# Filename: Output.HITSFileName="test.muon.001.root"
+# Also number of events: --evtMax 100
+# Starting at z = -1.5m (-1500.) will miss the veto (for electrons)
+
+    import sys
+    ConfigFlags.fillFromArgs(sys.argv[1:])
+
+#
+# By being a little clever, we can steer the geometry setup from the command line using GeoModel.FaserVersion
+#
+# MDC configuration
+#
+    detectors = ['Veto', 'Preshower', 'FaserSCT', 'Ecal', 'Trigger', 'Dipole', 'Emulsion']
+#
+# Setup detector flags
+#
+    from CalypsoConfiguration.DetectorConfigFlags import setupDetectorsFromList
+    setupDetectorsFromList(ConfigFlags, detectors, toggle_geometry=True)
+#
+# Finalize flags
+#
+    ConfigFlags.lock()
+#
+# Initialize a new component accumulator
+#
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+#
+# Configure the particle gun as requested, or using defaults
+#
+
+#
+# Particle gun generators - the generator, energy, angle, particle type, position, etc can be modified by passing keyword arguments
+#
+    from FaserParticleGun.FaserParticleGunConfig import FaserParticleGunCfg
+    cfg.merge(FaserParticleGunCfg(ConfigFlags))
+    from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+    cfg.merge(McEventSelectorCfg(ConfigFlags))
+
+#
+# Output file
+#
+    from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    cfg.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Shift LOS
+#
+
+    if ConfigFlags.Sim.Beam.xangle or ConfigFlags.Sim.Beam.yangle:
+        MCEventKey = "BeamTruthEventShifted"
+        import McParticleEvent.Pythonizations
+        from GeneratorUtils.ShiftLOSConfig import ShiftLOSCfg
+        cfg.merge(ShiftLOSCfg(ConfigFlags, OutputMCEventKey = MCEventKey,
+                              xcross = ConfigFlags.Sim.Beam.xangle, ycross = ConfigFlags.Sim.Beam.yangle))
+    else:    
+        MCEventKey = "BeamTruthEvent"
+    
+#
+# Add the G4FaserAlg
+#
+    from G4FaserAlg.G4FaserAlgConfigNew import G4FaserAlgCfg
+    cfg.merge(G4FaserAlgCfg(ConfigFlags, InputTruthCollection = MCEventKey))
+#
+# Dump config
+#
+    from AthenaConfiguration.ComponentFactory import CompFactory
+    cfg.addEventAlgo(CompFactory.JobOptsDumperAlg(FileName="G4FaserTestConfig.txt"))
+    cfg.getService("StoreGateSvc").Dump = True
+    cfg.getService("ConditionStore").Dump = True
+    cfg.printConfig(withDetails=True, summariseProps = False)  # gags on ParticleGun if summariseProps = True?
+
+    ConfigFlags.dump()
+    #f = open("test.pkl","wb")
+    #cfg.store(f)
+    #f.close()
+#
+# Execute and finish
+#
+
+    #cfg.foreach_component("*").OutputLevel = "INFO"  # Use warning for production
+
+    sc = cfg.run()
+
+    b = time.time()
+    log.info("Run G4FaserAlg in " + str(b-a) + " seconds")
+#
+# Success should be 0
+#
+    sys.exit(not sc.isSuccess())
+
diff --git a/Control/CalypsoExample/Generation/scripts/submit_faserMDC_foresee.sh b/Control/CalypsoExample/Generation/scripts/submit_faserMDC_foresee.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6278fddba9c504ae360c0947c445553b455cdccc
--- /dev/null
+++ b/Control/CalypsoExample/Generation/scripts/submit_faserMDC_foresee.sh
@@ -0,0 +1,150 @@
+#!/bin/bash
+# Used with a condor file to submit to vanilla universe
+#
+# Usage:
+# submit_faserMDC_foresee.sh config_file segment [release_directory] [working_directory] 
+# 
+# config_file - full file name (with path)
+# segment - segment number (file segment)
+# release_directory - optional path to release install directory (default pwd)
+# working_directory - optional path to output directory location (default pwd)
+#
+# The release directory must already be set up 
+# (so an unqualified asetup can set up the release properly)
+#
+# Script will use git describe to find the release tag.  
+# If this matches gen/g???? or sim/s???? it will be passed to the job
+#
+#----------------------------------------
+# Keep track of time
+SECONDS=0
+#
+# Parse command-line options
+config_path=${1}
+segment=${2}
+release_directory=${3}
+working_directory=${4}
+#
+# Set defaults if arguments aren't provided
+if [ -z "$config_path" ]
+then
+  echo "No config_path specified!"
+  echo "Usage: submit_faserMDC_foresee.sh config_file segment [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$segment" ]
+then
+  segment=0
+fi
+#
+if [ -z "$release_directory" ]
+then
+  release_directory=`pwd`
+fi
+#
+if [ -z "$working_directory" ]
+then
+  working_directory=`pwd`
+fi
+# 
+# Apply padding to segment number
+printf -v seg_str "%05d" $segment
+#
+starting_directory=`pwd`
+#
+# Now extract the file stem
+#
+# First, get the filename
+config_file=$(basename "$config_path")
+# 
+# Now split based on '.' to get stem
+defaultIFS=$IFS
+IFS='.'
+read config_file_stem ext <<< "$config_file"
+#
+# Try to find the run number
+IFS='-'
+# Read the split words into an array based on delimeter
+read faser short run_number <<< "$config_file_stem"
+#
+# Set the IFS delimeter back or else echo doesn't work...
+IFS=$defaultIFS
+#
+# Check if we found a number, use full config name if not
+output_directory="$working_directory/${run_number}"
+re='^[0-9]+$'
+if ! [[ $run_number =~ $re ]] ; then
+    # Not a number...
+    output_directory="$working_directory/${config_file_stem}"
+fi
+#
+# Make output directory if needed
+mkdir -p "$output_directory"
+#
+# This magic redirects everything in this script to our log file
+exec >& "$output_directory/${config_file_stem}-${seg_str}.log"
+echo `date` - $HOSTNAME
+echo "File: $config_file"
+echo "Segment: $seg_str"
+echo "Release: $release_directory"
+echo "Output: $output_directory"
+echo "Starting: $starting_directory"
+#
+# Set up the release (do this automatically)?
+export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
+source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh 
+#
+# Try automatic
+# Always go back to the starting directory in case paths are relative
+cd "$starting_directory"
+cd "$release_directory"
+# This doesn't seem to work, as we need the --input argument
+#asetup 
+#source build/x8*/setup.sh
+#
+# Do this by hand
+asetup --input=calypso/asetup.faser Athena,22.0.49
+source build/x86*/setup.sh
+#
+#
+# Try to find a release tag
+cd calypso
+gentag=`git describe`
+if [[ "$gentag" == "gen/g"???? ]]; then
+  tag=`echo "$gentag" | cut -c 5-10`
+  echo "Found gen tag: $tag"
+fi
+if [[ "$gentag" == "sim/s"???? ]]; then
+  tag=`echo "$gentag" | cut -c 5-10`
+  echo "Found sim tag: $tag"
+fi
+#
+# Move to the run directory
+cd "$starting_directory"
+cd "$output_directory"
+#
+# Remove any previous directory if it exists
+#if [[ -e "$file_stem" ]]; then
+#    echo "Remove previous directory $file_stem"
+#    rm -rf "$file_stem"
+#fi
+#
+# Make run directory
+if [[ -e "${config_file_stem}-${seg_str}" ]]; then
+    echo "Directory ${config_file_stem}-${seg_str} already exists"
+else
+    mkdir "${config_file_stem}-${seg_str}"
+fi
+cd "${config_file_stem}-${seg_str}"
+#
+# Run job
+if [[ -z "$tag" ]]; then
+    faserMDC_foresee.py  "--conf=$config_path" "--segment=$seg_str"
+else
+    faserMDC_foresee.py  "--conf=$config_path" "--segment=$seg_str" "--tag=$tag"
+fi
+#
+# Print out ending time
+date
+echo "Job finished after $SECONDS seconds"
diff --git a/Control/CalypsoExample/Generation/scripts/submit_faserMDC_particlegun.sh b/Control/CalypsoExample/Generation/scripts/submit_faserMDC_particlegun.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c85fa4803a7fe9d2acfd0d3e29beaf26e515afe6
--- /dev/null
+++ b/Control/CalypsoExample/Generation/scripts/submit_faserMDC_particlegun.sh
@@ -0,0 +1,152 @@
+#!/bin/bash
+# Used with a condor file to submit to vanilla universe
+#
+# Usage:
+# submit_faserMDC_particlegun.sh config_file segment [release_directory] [working_directory] 
+# 
+# config_file - full file name (with path)
+# segment - segment number (file segment)
+# release_directory - optional path to release install directory (default pwd)
+# working_directory - optional path to output directory location (default pwd)
+#
+# The release directory must already be set up 
+# (so an unqualified asetup can set up the release properly)
+#
+# Script will use git describe to find the release tag.  
+# If this matches gen/g???? or sim/s???? it will be passed to the job
+#
+#----------------------------------------
+# Keep track of time
+SECONDS=0
+#
+# Parse command-line options
+config_path=${1}
+segment=${2}
+release_directory=${3}
+working_directory=${4}
+#
+# Set defaults if arguments aren't provided
+if [ -z "$config_path" ]
+then
+  echo "No config_path specified!"
+  echo "Usage: submit_faserMDC_particlegun.sh config_file segment [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$segment" ]
+then
+  segment=0
+fi
+#
+if [ -z "$release_directory" ]
+then
+  release_directory=`pwd`
+fi
+#
+if [ -z "$working_directory" ]
+then
+  working_directory=`pwd`
+fi
+# 
+# Apply padding to segment number
+printf -v seg_str "%05d" $segment
+#
+starting_directory=`pwd`
+#
+# Now extract the file stem
+#
+# First, get the filename
+config_file=$(basename "$config_path")
+# 
+# Now split based on '.' to get stem
+defaultIFS=$IFS
+IFS='.'
+read config_file_stem ext <<< "$config_file"
+#
+# Try to find the run number
+IFS='-'
+# Read the split words into an array based on delimeter
+read faser short run_number <<< "$config_file_stem"
+#
+# Set the IFS delimeter back or else echo doesn't work...
+IFS=$defaultIFS
+#
+# Check if we found a number, use full config name if not
+output_directory="$working_directory/${run_number}"
+re='^[0-9]+$'
+if ! [[ $run_number =~ $re ]] ; then
+    # Not a number...
+    output_directory="$working_directory/${config_file_stem}"
+fi
+#
+# Make output directory if needed
+mkdir -p "$output_directory"
+#
+# This magic redirects everything in this script to our log file
+exec >& "$output_directory/${config_file_stem}-${seg_str}.log"
+echo `date` - $HOSTNAME
+echo "File: $config_file"
+echo "Segment: $seg_str"
+echo "Release: $release_directory"
+echo "Output: $output_directory"
+echo "Starting: $starting_directory"
+#
+# Set up the release (do this automatically)?
+export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
+source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh 
+#
+# Try automatic
+# Always go back to the starting directory in case paths are relative
+cd "$starting_directory"
+cd "$release_directory"
+# This doesn't seem to work, as we need the --input argument
+#asetup 
+#source build/x8*/setup.sh
+#
+# Do this by hand
+asetup --input=calypso/asetup.faser Athena,22.0.49
+source build/x86*/setup.sh
+#
+#
+# Try to find a release tag
+cd calypso
+gentag=`git describe`
+if [[ "$gentag" == "gen/g"???? ]]; then
+  tag=`echo "$gentag" | cut -c 5-10`
+  echo "Found gen tag: $tag"
+fi
+if [[ "$gentag" == "sim/s"???? ]]; then
+  tag=`echo "$gentag" | cut -c 5-10`
+  echo "Found sim tag: $tag"
+fi
+#
+# Move to the run directory
+cd "$starting_directory"
+cd "$output_directory"
+#
+# Remove any previous directory if it exists
+#if [[ -e "$file_stem" ]]; then
+#    echo "Remove previous directory $file_stem"
+#    rm -rf "$file_stem"
+#fi
+#
+# Make run directory
+if [[ -e "${config_file_stem}-${seg_str}" ]]; then
+    echo "Directory ${config_file_stem}-${seg_str} already exists"
+else
+    mkdir "${config_file_stem}-${seg_str}"
+fi
+cd "${config_file_stem}-${seg_str}"
+#
+# Run job
+if [[ -z "$tag" ]]; then
+    faserMDC_particlegun.py  "--conf=$config_path" "--segment=$seg_str"
+else
+    faserMDC_particlegun.py  "--conf=$config_path" "--segment=$seg_str" "--tag=$tag"
+fi
+#
+# Print out ending time
+date
+echo "Job finished after $SECONDS seconds"
+#
+
diff --git a/Control/CalypsoExample/GeoModelTest/CMakeLists.txt b/Control/CalypsoExample/GeoModelTest/CMakeLists.txt
index 9db978f852f7d3ae840297d164670b9c6ba9aa00..772543c3ad28c4f31b5d42e382cff2cc79215192 100644
--- a/Control/CalypsoExample/GeoModelTest/CMakeLists.txt
+++ b/Control/CalypsoExample/GeoModelTest/CMakeLists.txt
@@ -25,6 +25,11 @@ atlas_add_test( Faser02GeoCheck
                 PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
                 PROPERTIES TIMEOUT 300 )
 
+atlas_add_test( Faser03GeoCheck
+                SCRIPT python ${CMAKE_CURRENT_SOURCE_DIR}/python/Faser03TestConfig.py
+                PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+                PROPERTIES TIMEOUT 300 )
+
 
 atlas_add_test( TestBeamGeoCheck
                 SCRIPT python ${CMAKE_CURRENT_SOURCE_DIR}/python/TestBeamTestConfig.py
diff --git a/Control/CalypsoExample/GeoModelTest/python/Faser01TestConfig.py b/Control/CalypsoExample/GeoModelTest/python/Faser01TestConfig.py
index 69a57833523e460b1ab5465312f410ae9e2509d6..e85fd9e4b3f2529de19b5d4d6e323275dbc5b5e6 100644
--- a/Control/CalypsoExample/GeoModelTest/python/Faser01TestConfig.py
+++ b/Control/CalypsoExample/GeoModelTest/python/Faser01TestConfig.py
@@ -19,6 +19,7 @@ def GeoModelTestCfg(flags, name="GeoModelTestAlg", **kwargs):
     GeoModelTestAlg = CompFactory.GeoModelTestAlg
     a.addEventAlgo(GeoModelTestAlg(name, FirstSCTStation=1, 
                                          LastSCTStation=3,
+                                         NumVetoNuStations=0,
                                          **kwargs))
 
     return a
diff --git a/Control/CalypsoExample/GeoModelTest/python/Faser02TestConfig.py b/Control/CalypsoExample/GeoModelTest/python/Faser02TestConfig.py
index 03913db74587b020b24c290fc6a2928eb965e22d..ba3f91b75be389c3c4053b6f694ee1bdbde0fa99 100644
--- a/Control/CalypsoExample/GeoModelTest/python/Faser02TestConfig.py
+++ b/Control/CalypsoExample/GeoModelTest/python/Faser02TestConfig.py
@@ -19,6 +19,7 @@ def GeoModelTestCfg(flags, name="GeoModelTestAlg", **kwargs):
     GeoModelTestAlg = CompFactory.GeoModelTestAlg
     a.addEventAlgo(GeoModelTestAlg(name, FirstSCTStation=0, 
                                          LastSCTStation=3,
+                                         NumVetoNuStations=0,
                                          PrintSctIDs=True,
                                          **kwargs))
 
@@ -35,7 +36,7 @@ if __name__ == "__main__":
     ConfigFlags.Input.isMC = True                                # Needed to bypass autoconfig
     ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"             # Always needed; must match FaserVersion
     ConfigFlags.GeoModel.FaserVersion     = "FASERNU-02"         # Default FASER geometry
-    # ConfigFlags.GeoModel.GeoExportFile    = "faserGeoNu02.db"        # Writes out a GeoModel file with the full geometry tree (optional, comment out to skip)
+    ConfigFlags.GeoModel.GeoExportFile    = "FaserNu02.db"        # Writes out a GeoModel file with the full geometry tree (optional, comment out to skip)
     # ConfigFlags.Detector.EnableVeto     = True
     # ConfigFlags.Detector.EnableTrigger  = True
     # ConfigFlags.Detector.EnablePreshower= True
diff --git a/Control/CalypsoExample/GeoModelTest/python/Faser03TestConfig.py b/Control/CalypsoExample/GeoModelTest/python/Faser03TestConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..70322eb150975256868a84bf7bd2174697d0a0da
--- /dev/null
+++ b/Control/CalypsoExample/GeoModelTest/python/Faser03TestConfig.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+#!/usr/bin/env python
+import sys
+from AthenaCommon.Constants import VERBOSE, INFO
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+def GeoModelTestCfg(flags, name="GeoModelTestAlg", **kwargs):
+
+    # Initialize GeoModel
+    from FaserGeoModel.FaserGeoModelConfig import FaserGeometryCfg
+    a = FaserGeometryCfg(flags)
+
+    # Initialize field service
+    from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
+    a.merge(MagneticFieldSvcCfg(flags))
+
+    # Configure the algorithm itself
+    GeoModelTestAlg = CompFactory.GeoModelTestAlg
+    a.addEventAlgo(GeoModelTestAlg(name, FirstSCTStation=0, 
+                                         LastSCTStation=3,
+                                         PrintSctIDs=True,
+                                         **kwargs))
+
+    return a
+
+if __name__ == "__main__":
+    from AthenaCommon.Logging import log#, logging
+    from AthenaCommon.Configurable import Configurable
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+
+    Configurable.configurableRun3Behavior = True
+    
+# Flags for this job
+    ConfigFlags.Input.isMC = True                                # Needed to bypass autoconfig
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"             # Always needed; must match FaserVersion
+    ConfigFlags.GeoModel.FaserVersion     = "FASERNU-03"           # Default FASER geometry
+    ConfigFlags.GeoModel.GeoExportFile    = "FaserNu03.db"        # Writes out a GeoModel file with the full geometry tree (optional, comment out to skip)
+    ConfigFlags.Detector.GeometryEmulsion = True
+    ConfigFlags.Detector.GeometryTrench   = True
+    # ConfigFlags.Detector.EnableVeto     = True
+    # ConfigFlags.Detector.EnableTrigger  = True
+    # ConfigFlags.Detector.EnablePreshower= True
+    # ConfigFlags.Detector.EnableFaserSCT = True
+    # ConfigFlags.Detector.EnableUpstreamDipole = True
+    # ConfigFlags.Detector.EnableCentralDipole = True
+    # ConfigFlags.Detector.EnableDownstreamDipole = True
+    # ConfigFlags.Detector.EnableEcal = True
+    ConfigFlags.lock()
+
+# Configure components
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    acc = MainServicesCfg(ConfigFlags)
+
+# Set up algorithm
+    acc.merge(GeoModelTestCfg(ConfigFlags))
+
+# Configure verbosity    
+    msgSvc = acc.getService("MessageSvc")
+    msgSvc.Format = "% F%30W%S%7W%R%T %0W%M"
+    # ConfigFlags.dump()
+    # logging.getLogger('forcomps').setLevel(VERBOSE)
+    acc.foreach_component("*").OutputLevel = VERBOSE
+    acc.foreach_component("*ClassID*").OutputLevel = INFO
+    log.setLevel(VERBOSE)
+    
+# Execute and finish
+    sys.exit(int(acc.run(maxEvents=1).isFailure()))
diff --git a/Control/CalypsoExample/GeoModelTest/python/TestBeamTestConfig.py b/Control/CalypsoExample/GeoModelTest/python/TestBeamTestConfig.py
index d590b125b0570e1628481ecbcaad312c357aa021..5dd299336c754e97a9de0fe8ec7b798c79a30f75 100644
--- a/Control/CalypsoExample/GeoModelTest/python/TestBeamTestConfig.py
+++ b/Control/CalypsoExample/GeoModelTest/python/TestBeamTestConfig.py
@@ -25,6 +25,7 @@ def GeoModelTestCfg(flags, name="GeoModelTestAlg", **kwargs):
                                          LastSCTStation=0,
                                          NumVetoStations=1, 
                                          NumVetoPlatesPerStation=2,
+                                         NumVetoNuStations=0,
                                          NumTriggerStations=0,
                                          PrintSctIDs=True,
                                          **kwargs))
diff --git a/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.cxx b/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.cxx
index 0865640503cfd4d2862f643acb6cbe38a7828dc4..0db09483da97480f304421db1ff6a1e464e0b0c5 100644
--- a/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.cxx
+++ b/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.cxx
@@ -4,6 +4,7 @@
 #include "GeoModelFaserUtilities/GeoModelExperiment.h"
 
 #include "ScintReadoutGeometry/VetoDetectorManager.h"
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h"
 #include "ScintReadoutGeometry/TriggerDetectorManager.h"
 #include "ScintReadoutGeometry/PreshowerDetectorManager.h"
 #include "TrackerReadoutGeometry/SCT_DetectorManager.h"
@@ -12,6 +13,7 @@
 #include "TrackerReadoutGeometry/SiDetectorElement.h"
 
 #include "ScintIdentifier/VetoID.h"
+#include "ScintIdentifier/VetoNuID.h"
 #include "ScintIdentifier/TriggerID.h"
 #include "ScintIdentifier/PreshowerID.h"
 #include "TrackerIdentifier/FaserSCT_ID.h"
@@ -68,6 +70,8 @@ StatusCode GeoModelTestAlg::execute(const EventContext& ctx) const
 
     ATH_CHECK(testVeto());
 
+    if (m_numVetoNuStations > 0) ATH_CHECK(testVetoNu());
+
     ATH_CHECK(testTrigger());
 
     ATH_CHECK(testPreshower());
@@ -469,6 +473,160 @@ StatusCode GeoModelTestAlg::testSCT() const
     return StatusCode::SUCCESS;
 }
 
+StatusCode GeoModelTestAlg::testVetoNu() const
+{
+    // Test retrieval of helper object directly from store
+    const VetoNuID* helper = nullptr;
+    ATH_CHECK(detStore()->retrieve(helper, "VetoNuID"));
+    if (helper != nullptr)
+    {
+        // Test neighbors with helper function
+        const IdContext& context = helper->plate_context();
+        ATH_MSG_ALWAYS("Retrieved VetoNuID helper from DetStore.");
+        for (int iStation = 0; iStation < m_numVetoNuStations; iStation++)
+        {
+            for (int iPlate = 0; iPlate < m_numVetoNuPlatesPerStation; iPlate++)
+            {
+                Identifier thisId = helper->plate_id(iStation, iPlate, true);
+                IdentifierHash thisHash = helper->plate_hash(thisId);
+                IdentifierHash prevHash;
+                IdentifierHash nextHash;
+                Identifier prevId;
+                Identifier nextId;
+                int prevStation {-1};
+                int prevPlate {-1};
+                int nextStation {-1};
+                int nextPlate {-1};
+                int prevStat = helper->get_prev_in_z(thisHash, prevHash);
+                if (prevStat == 0) 
+                {
+                    IdentifierHash testHash;
+                    int nextStat = helper->get_next_in_z(prevHash, testHash);
+                    if (nextStat != 0 || testHash != thisHash)
+                    {
+                        ATH_MSG_FATAL("Next VetoNu plate (" << testHash << ") of previous (" << prevHash << ") is not the original (" << thisHash <<")" );
+                        return StatusCode::FAILURE;
+                    }
+                    prevStat = helper->get_id(prevHash, prevId, &context);
+                    if (prevStat == 0)
+                    {
+                        prevStation = helper->station(prevId);
+                        prevPlate   = helper->plate(prevId);
+                    }
+                }
+                int nextStat = helper->get_next_in_z(thisHash, nextHash);
+                if (nextStat == 0) 
+                {
+                    IdentifierHash testHash;
+                    prevStat = helper->get_prev_in_z(nextHash, testHash);
+                    if (prevStat != 0 || testHash != thisHash)
+                    {
+                        ATH_MSG_FATAL("Previous veto plate (" << testHash << ") of next (" << nextHash << ") is not the original (" << thisHash <<")" );
+                        return StatusCode::FAILURE;
+                    }
+                    nextStat = helper->get_id(nextHash, nextId, &context);
+                    if (nextStat == 0)
+                    {
+                        nextStation = helper->station(nextId);
+                        nextPlate   = helper->plate(nextId);
+                    }
+                }
+                ATH_MSG_ALWAYS("Station/Plate " << iStation << "/" << iPlate << 
+                               " (" << thisHash << ") " <<
+                               " : prev = " << prevStation << "/" << prevPlate <<
+                               " , next = " << nextStation << "/" << nextPlate );
+            }
+        }
+    }
+    else
+    {
+        ATH_MSG_FATAL("Failed to retrieve VetoNuID helper from DetStore.");
+        return StatusCode::FAILURE;
+    }
+
+    // Test direct retrieval of typed managers from DetStore
+    const ScintDD::VetoNuDetectorManager* vetoMgr = nullptr;
+    ATH_CHECK(detStore()->retrieve(vetoMgr, "VetoNu"));
+    if (vetoMgr != nullptr)
+    {
+        ATH_MSG_ALWAYS("Retrieved (typed) VetoNu detector manager with " << vetoMgr->getNumTreeTops() << " treetops directly from DetStore.");
+        // Compare numerology with the "right" answers from our properties
+        if (vetoMgr->numerology().numStations() != m_numVetoNuStations)
+        {
+            ATH_MSG_FATAL("Disagreement in number of veto stations.");
+            return StatusCode::FAILURE;
+        }
+        if (m_numVetoNuStations > 0 && vetoMgr->numerology().numPlatesPerStation() != m_numVetoNuPlatesPerStation)
+        {
+            ATH_MSG_FATAL("Disagreement in number of plates per veto station.");
+            return StatusCode::FAILURE;
+        }
+        if (m_numVetoNuStations > 0 && m_numVetoNuPlatesPerStation > 0 && vetoMgr->numerology().numPmtsPerPlate() != m_numVetoNuPmtsPerPlate)
+        {
+            ATH_MSG_FATAL("Disagreement in number of pmts per veto plate.");
+            return StatusCode::FAILURE;
+        }
+        // Test detector elements
+        const ScintDD::ScintDetectorElementCollection* elements = vetoMgr->getDetectorElementCollection();
+        for (int station = 0; station < m_numVetoNuStations; station++)
+        {
+            for (int plate = 0; plate < m_numVetoNuPlatesPerStation; plate++)
+            {
+                Identifier id = helper->plate_id(station, plate, true);
+                if (!vetoMgr->identifierBelongs(id))
+                {
+                    ATH_MSG_FATAL("Valid VetoNu identifier does not pass identifierBelongs.");
+                    return StatusCode::FAILURE;
+                }
+                IdentifierHash hash = helper->plate_hash(id);
+                ScintDD::ScintDetectorElement* elementByLevels = vetoMgr->getDetectorElement(station, plate);
+                ScintDD::ScintDetectorElement* elementById = vetoMgr->getDetectorElement(id);
+                ScintDD::ScintDetectorElement* elementByHash = vetoMgr->getDetectorElement(hash);
+                ScintDD::ScintDetectorElement* element = (*elements)[hash];
+                if (elementByLevels != element || elementById != element || elementByHash != element)
+                {
+                    ATH_MSG_FATAL("Inconsistent retrieval of VetoNu detector elements");
+                    return StatusCode::FAILURE;
+                }
+                ATH_MSG_ALWAYS("Found VetoNu plate (" << station << ", " << plate << ") with global center at (" << 
+                    element->center().x() << ", " <<
+                    element->center().y() << ", " <<
+                    element->center().z() << ")."
+                    );
+                const ScintDD::ScintDetectorElement* next = element->nextInZ();
+                if (next != nullptr)
+                {
+                    if (next->prevInZ() != element)
+                    {
+                        ATH_MSG_FATAL("Previous neighbor of next VetoNu element is not this element.");
+                        return StatusCode::FAILURE;
+                    }
+                }
+                const ScintDD::ScintDetectorElement* prev = element->prevInZ();
+                if (prev != nullptr)
+                {
+                    if (prev->nextInZ() != element)
+                    {
+                        ATH_MSG_FATAL("Next neighbor of previous VetoNu element is not this element.");
+                        return StatusCode::FAILURE;
+                    }
+                }
+                if (next == nullptr && prev == nullptr && m_numVetoPlatesPerStation > 1)
+                {
+                    ATH_MSG_FATAL("Veto element " << hash << " has no previous OR next neighbor.");
+                    return StatusCode::FAILURE;
+                }
+            }
+        }
+    }
+    else
+    {
+        ATH_MSG_FATAL("Failed to retrieve (typed) Veto detector manager directly from DetStore.");
+        return StatusCode::FAILURE;
+    }
+    return StatusCode::SUCCESS;
+}
+
 StatusCode GeoModelTestAlg::testVeto() const
 {
     // Test retrieval of helper object directly from store
@@ -623,6 +781,10 @@ StatusCode GeoModelTestAlg::testVeto() const
     return StatusCode::SUCCESS;
 }
 
+
+
+
+
 StatusCode GeoModelTestAlg::testTrigger() const
 {
     // Test retrieval of helper object directly from store
diff --git a/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.h b/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.h
index 010be38c93980f6bb9db5f3f4779b5bc6144b559..9fe9ab00f3ca5fc31ca2208b2f5ae72024829636 100644
--- a/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.h
+++ b/Control/CalypsoExample/GeoModelTest/src/GeoModelTestAlg.h
@@ -26,6 +26,7 @@ class GeoModelTestAlg : public AthReentrantAlgorithm
 
     private:  
     StatusCode testVeto() const;
+    StatusCode testVetoNu() const;
     StatusCode testTrigger() const;
     StatusCode testPreshower() const;
     StatusCode testSCT() const;
@@ -45,6 +46,10 @@ class GeoModelTestAlg : public AthReentrantAlgorithm
     Gaudi::Property<int>            m_numPreshowerPlatesPerStation   {this, "NumPreshowerPlatesPerStation", 2, "Number of plates per station in the Preshower detector"};
     Gaudi::Property<int>            m_numPreshowerPmtsPerPlate       {this, "NumPreshowerPmtsPerPlate", 1, "Number of pmts per plate in the Preshower detector"};
 
+    Gaudi::Property<int>            m_numVetoNuStations           {this, "NumVetoNuStations", 1, "Number of stations in the VetoNu detector"};
+    Gaudi::Property<int>            m_numVetoNuPlatesPerStation   {this, "NumVetoNuPlatesPerStation", 2, "Number of plates per station in the VetoNu detector"};
+    Gaudi::Property<int>            m_numVetoNuPmtsPerPlate       {this, "NumVetoNuPmtsPerPlate", 1, "Number of pmts per plate in the VetoNu detector"};
+
     Gaudi::Property<int>            m_firstSctStation           {this, "FirstSCTStation", 1, "Identifier of the first SCT station (0 w/FaserNu, 1 otherwise)"};
     Gaudi::Property<int>            m_lastSctStation            {this, "LastSCTStation", 3, "Identifier of the last SCT station (normally 3)"};
     Gaudi::Property<int>            m_numSctPlanesPerStation    {this, "NumSCTPlanesPerStation", 3, "Number of planes per station in the SCT detector"};
diff --git a/Control/CalypsoExample/Reconstruction/CMakeLists.txt b/Control/CalypsoExample/Reconstruction/CMakeLists.txt
index dddd2f621013501750a82d0569e2714bbb20c9db..f821257234e5758a0c92a1d13e1b0d5df44f2843 100644
--- a/Control/CalypsoExample/Reconstruction/CMakeLists.txt
+++ b/Control/CalypsoExample/Reconstruction/CMakeLists.txt
@@ -20,7 +20,17 @@ atlas_install_scripts( scripts/*.sh scripts/*.py )
 atlas_add_test( ProdRecoTI12
     SCRIPT scripts/faser_reco.py ${CMAKE_CURRENT_SOURCE_DIR}/../rawdata/Faser-Physics-001920-filtered.raw TI12Data
     PROPERTIES TIMEOUT 300 )
+
+# Turn this off until we figure out the CKF behavior on testbeam data
 atlas_add_test( ProdRecoTestBeam
     SCRIPT scripts/faser_reco.py ${CMAKE_CURRENT_SOURCE_DIR}/../RAWDATA/Faser-Physics-003613-filtered.raw TestBeamData
     PROPERTIES TIMEOUT 300 )
 
+atlas_add_test( ProdRecoPilotTracks
+    SCRIPT scripts/faser_reco.py ${CMAKE_CURRENT_SOURCE_DIR}/../RAWDATA/Faser-Physics-pilot_tracks-filtered.raw TI12Data
+    PROPERTIES TIMEOUT 300 )
+
+atlas_add_test( ProdRecoTI12-2022
+    SCRIPT scripts/faser_reco.py ${CMAKE_CURRENT_SOURCE_DIR}/../rawdata/Faser-Physics-006525-filtered.raw TI12Data02
+    PROPERTIES TIMEOUT 300 )
+
diff --git a/Control/CalypsoExample/Reconstruction/scripts/faserMDC_reco.py b/Control/CalypsoExample/Reconstruction/scripts/faserMDC_reco.py
new file mode 100755
index 0000000000000000000000000000000000000000..942340f12c97db7e80850e0ede3be55d44bda54d
--- /dev/null
+++ b/Control/CalypsoExample/Reconstruction/scripts/faserMDC_reco.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+# Run with:
+# ./faser_reco.py filepath [runtype]
+# 
+# filepath - fully qualified path, including url if needed, to the input raw data file
+#   example: "root://hepatl30//atlas/local/torrence/faser/commissioning/TestBeamData/Run-004150/Faser-Physics-004150-00000.raw"
+# 
+# runtype - optionally specify the data type (TI12Data, TI12Data02, TI12Data03 or TestBeamData).
+#   In a normal file system location, this will be extracted from the directory name,
+#   but runtype will override this assignment. 
+#   > TI12Data02 is needed for the IFT geometry.  
+#   MDC will assume TI12Data03 geometry.
+#
+import sys
+import time
+import argparse
+
+a = time.time()
+
+parser = argparse.ArgumentParser(description="Run FASER reconstruction")
+
+parser.add_argument("file_path",
+                    help="Fully qualified path of the raw input file")
+parser.add_argument("run_type", nargs="?", default="",
+                    help="Specify run type (if it can't be parsed from path)")
+parser.add_argument("-r", "--reco", default="",
+                    help="Specify reco tag (to append to output filename)")
+parser.add_argument("-n", "--nevents", type=int, default=-1,
+                    help="Specify number of events to process (default: all)")
+parser.add_argument("-v", "--verbose", action='store_true', 
+                    help="Turn on DEBUG output")
+
+
+args = parser.parse_args()
+
+from pathlib import Path
+
+filepath=Path(args.file_path)
+
+# runtype has been provided
+if len(args.run_type) > 0:
+    runtype=args.run_type
+
+# Assume based on MDC reco
+else:
+    runtype = "TI12Data03"
+
+# Assume this is MC
+args.isMC = True
+
+print(f"Starting reconstruction of {filepath.name} with type {runtype}")
+if args.nevents > 0:
+    print(f"Reconstructing {args.nevents} events by command-line option")
+
+# Start reconstruction
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from AthenaCommon.Constants import VERBOSE, INFO
+
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+
+Configurable.configurableRun3Behavior = True
+    
+# Flags for this job
+ConfigFlags.Input.isMC = args.isMC               # Needed to bypass autoconfig
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"   # Use MC conditions for now
+
+ConfigFlags.Input.ProjectName = "data20"
+ConfigFlags.GeoModel.Align.Dynamic    = False
+
+# For tracking
+ConfigFlags.TrackingGeometry.MaterialSource = "/cvmfs/faser.cern.ch/repo/sw/database/DBRelease/current/acts/material-maps.json"
+
+# TI12 Cosmics geometry
+if runtype == "TI12Data":
+    ConfigFlags.GeoModel.FaserVersion = "FASER-01" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+
+# Testbeam setup 
+elif runtype == "TestBeamData" or runtype == "TestBeam2021":
+    ConfigFlags.GeoModel.FaserVersion = "FASER-TB00" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-TB00"
+
+# New TI12 geometry (ugh)
+elif runtype == "TI12Data02":
+    ConfigFlags.GeoModel.FaserVersion = "FASER-02" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+
+# Final 2022 TI12 geometry
+elif runtype == "TI12Data03":
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+
+else:
+    print("Invalid run type found:", runtype)
+    print("Specify correct type or update list")
+    sys.exit(-1)
+
+
+# Must use original input string here, as pathlib mangles double // in path names
+ConfigFlags.Input.Files = [ args.file_path ]
+
+filestem = filepath.stem
+if len(args.reco) > 0:
+    filestem += f"-{args.reco}"
+
+ConfigFlags.addFlag("Output.xAODFileName", f"{filestem}-xAOD.root")
+ConfigFlags.Output.ESDFileName = f"{filestem}-ESD.root"
+ConfigFlags.Output.doWriteESD = False
+
+#
+# Play around with this?
+# ConfigFlags.Concurrency.NumThreads = 2
+# ConfigFlags.Concurrency.NumConcurrentEvents = 2
+ConfigFlags.lock()
+
+#
+# Configure components
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Set up RAW data access
+
+if args.isMC:
+    from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+    acc.merge(PoolReadCfg(ConfigFlags))
+else:    
+    from FaserByteStreamCnvSvc.FaserByteStreamCnvSvcConfig import FaserByteStreamCnvSvcCfg
+    acc.merge(FaserByteStreamCnvSvcCfg(ConfigFlags))
+
+#
+# Needed, or move to MainServicesCfg?
+from FaserGeoModel.FaserGeoModelConfig import FaserGeometryCfg
+acc.merge(FaserGeometryCfg(ConfigFlags))
+
+# Set up algorithms
+from WaveRecAlgs.WaveRecAlgsConfig import WaveformReconstructionCfg    
+acc.merge(WaveformReconstructionCfg(ConfigFlags))
+
+# Not ready for primetime
+# from CaloRecAlgs.CaloRecAlgsConfig import CalorimeterReconstructionCfg
+# acc.merge(CalorimeterReconstructionCfg(ConfigFlags))
+
+# Tracker clusters
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, DataObjectName="SCT_RDOs"))
+
+#
+# SpacePoints
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+
+# Try Dave's new fitter
+print("Configuring TrackerSegmentFit (new)")
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+acc.merge(SegmentFitAlgCfg(ConfigFlags,
+                           SharedHitFraction=0.61, 
+                           MinClustersPerFit=5, 
+                           TanThetaXZCut=0.083))
+# 
+# Ghost removal
+from FaserActsKalmanFilter.GhostBustersConfig import GhostBustersCfg
+acc.merge(GhostBustersCfg(ConfigFlags))
+
+#
+# Kalman Filter for tracking
+from FaserActsKalmanFilter.CKF2Config import CKF2Cfg
+acc.merge(CKF2Cfg(ConfigFlags, noDiagnostics=True))
+
+#
+# Configure output
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+itemList = [ "xAOD::EventInfo#*"
+             , "xAOD::EventAuxInfo#*"
+             , "xAOD::FaserTriggerData#*"
+             , "xAOD::FaserTriggerDataAux#*"
+             , "FaserSiHitCollection#*"  # Strip hits, do we want this?
+             , "FaserSCT_RDO_Container#*" 
+             , "FaserSCT_SpacePointContainer#*"
+             , "Tracker::FaserSCT_ClusterContainer#*"
+             , "TrackCollection#*"
+]
+#
+if args.isMC:
+    # Add truth records here?
+    itemList.extend( ["McEventCollection#*", "TrackerSimDataCollection#*"] )
+
+acc.merge(OutputStreamCfg(ConfigFlags, "xAOD", itemList))
+
+# Waveform reconstruction output
+from WaveRecAlgs.WaveRecAlgsConfig import WaveformReconstructionOutputCfg    
+acc.merge(WaveformReconstructionOutputCfg(ConfigFlags))
+
+# Calorimeter reconstruction output
+# from CaloRecAlgs.CaloRecAlgsConfig import CalorimeterReconstructionOutputCfg
+# acc.merge(CalorimeterReconstructionOutputCfg(ConfigFlags))
+
+# Check what we have
+print( "Writing out xAOD objects:" )
+print( acc.getEventAlgo("OutputStreamxAOD").ItemList )
+
+# Hack to avoid problem with our use of MC databases when isMC = False
+if not args.isMC:
+    replicaSvc = acc.getService("DBReplicaSvc")
+    replicaSvc.COOLSQLiteVetoPattern = ""
+    replicaSvc.UseCOOLSQLite = True
+    replicaSvc.UseCOOLFrontier = False
+    replicaSvc.UseGeomSQLite = True
+
+# Configure verbosity    
+ConfigFlags.dump()
+if args.verbose:
+    acc.foreach_component("*").OutputLevel = VERBOSE
+else:
+    acc.foreach_component("*").OutputLevel = INFO
+
+acc.foreach_component("*ClassID*").OutputLevel = INFO
+
+acc.getService("MessageSvc").Format = "% F%40W%S%7W%R%T %0W%M"
+
+# Execute and finish
+sc = acc.run(maxEvents=args.nevents)
+
+b = time.time()
+from AthenaCommon.Logging import log
+log.info(f"Finish execution in {b-a} seconds")
+
+sys.exit(int(sc.isFailure()))
+
diff --git a/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py b/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py
index 1579f4c1b19b83a3bf7b024060122ba7ce69746b..2063b729cb1c5a09ac4a2e90c914fc7096a85412 100755
--- a/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py
+++ b/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py
@@ -7,15 +7,18 @@
 # filepath - fully qualified path, including url if needed, to the input raw data file
 #   example: "root://hepatl30//atlas/local/torrence/faser/commissioning/TestBeamData/Run-004150/Faser-Physics-004150-00000.raw"
 # 
-# runtype - optionally specify the data type (TI12Data, TI12Data02, or TestBeamData).
+# runtype - optionally specify the data type (TI12Data, TI12Data02, TI12Data03 or TestBeamData).
 #   In a normal file system location, this will be extracted from the directory name,
 #   but runtype will override this assignment. 
 #   TI12Data02 is needed for the IFT geometry.  Script will auto-detect this if read
 #   from normal file system location.
 #
 import sys
+import time
 import argparse
 
+a = time.time()
+
 parser = argparse.ArgumentParser(description="Run FASER reconstruction")
 
 parser.add_argument("file_path",
@@ -28,8 +31,6 @@ parser.add_argument("-n", "--nevents", type=int, default=-1,
                     help="Specify number of events to process (default: all)")
 parser.add_argument("-v", "--verbose", action='store_true', 
                     help="Turn on DEBUG output")
-parser.add_argument("--clusterFit", action='store_true',
-                    help="Use ClusterFit (old) track finder - default: SegmentFit(new)")
 parser.add_argument("--isMC", action='store_true',
                     help="Running on digitised MC rather than data")
 
@@ -95,6 +96,10 @@ ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"   # Use MC conditions for now
 ConfigFlags.Input.ProjectName = "data20"
 ConfigFlags.GeoModel.Align.Dynamic    = False
 
+useCKF = True
+# Enable ACTS material corrections, this crashes testbeam geometries
+ConfigFlags.TrackingGeometry.MaterialSource = "/cvmfs/faser.cern.ch/repo/sw/database/DBRelease/current/acts/material-maps.json"
+
 # TI12 Cosmics geometry
 if runtype == "TI12Data":
     ConfigFlags.GeoModel.FaserVersion = "FASER-01" 
@@ -104,12 +109,18 @@ if runtype == "TI12Data":
 elif runtype == "TestBeamData" or runtype == "TestBeam2021":
     ConfigFlags.GeoModel.FaserVersion = "FASER-TB00" 
     ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-TB00"
+    useCKF = False
 
 # New TI12 geometry (ugh)
 elif runtype == "TI12Data02":
     ConfigFlags.GeoModel.FaserVersion = "FASER-02" 
     ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
 
+# Final 2022 TI12 geometry
+elif runtype == "TI12Data03":
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03" 
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+
 else:
     print("Invalid run type found:", runtype)
     print("Specify correct type or update list")
@@ -125,6 +136,7 @@ if len(args.reco) > 0:
 
 ConfigFlags.addFlag("Output.xAODFileName", f"{filestem}-xAOD.root")
 ConfigFlags.Output.ESDFileName = f"{filestem}-ESD.root"
+ConfigFlags.Output.doWriteESD = False
 
 #
 # Play around with this?
@@ -165,24 +177,32 @@ acc.merge(WaveformReconstructionCfg(ConfigFlags))
 
 # Tracker clusters
 from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
-acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, DataObjectName="SCT_RDOs"))
 
+#
 # SpacePoints
 from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
 acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
 
-# Can't use both in the same job, as they write to the same output histograms
-if args.clusterFit:
-    print("Configuring TrackerClusterFit (old)")
-    # Try Dave's fitter
-    from TrackerClusterFit.TrackerClusterFitConfig import ClusterFitAlgCfg
-    acc.merge(ClusterFitAlgCfg(ConfigFlags))
-
-else:
-    print("Configuring TrackerSegmentFit (new)")
-    # Try Dave's new fitter
-    from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
-    acc.merge(SegmentFitAlgCfg(ConfigFlags))
+# Try Dave's new fitter
+print("Configuring TrackerSegmentFit (new)")
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+acc.merge(SegmentFitAlgCfg(ConfigFlags,
+                           SharedHitFraction=0.61, 
+                           MinClustersPerFit=5, 
+                           TanThetaXZCut=0.083))
+
+# Turn on CKF track finding 
+if useCKF:
+    # 
+    # Ghost removal
+    from FaserActsKalmanFilter.GhostBustersConfig import GhostBustersCfg
+    acc.merge(GhostBustersCfg(ConfigFlags))
+
+    #
+    # Kalman Filter for tracking
+    from FaserActsKalmanFilter.CKF2Config import CKF2Cfg
+    acc.merge(CKF2Cfg(ConfigFlags, noDiagnostics=True))
 
 #
 # Configure output
@@ -191,12 +211,17 @@ itemList = [ "xAOD::EventInfo#*"
              , "xAOD::EventAuxInfo#*"
              , "xAOD::FaserTriggerData#*"
              , "xAOD::FaserTriggerDataAux#*"
-             , "FaserSCT_RDO_Container#*"
-             , "Tracker::FaserSCT_ClusterContainer#*"
+             , "FaserSiHitCollection#*"  # Strip hits, do we want this?
+             , "FaserSCT_RDO_Container#*" 
              , "FaserSCT_SpacePointContainer#*"
-             #, "FaserSCT_SpacePointOverlapCollection#*"
+             , "Tracker::FaserSCT_ClusterContainer#*"
              , "TrackCollection#*"
 ]
+#
+if args.isMC:
+    # Add truth records here?
+    itemList.extend( ["McEventCollection#*"] )
+
 acc.merge(OutputStreamCfg(ConfigFlags, "xAOD", itemList))
 
 # Waveform reconstruction output
@@ -223,13 +248,6 @@ if not args.isMC:
 # ConfigFlags.dump()
 if args.verbose:
     acc.foreach_component("*").OutputLevel = VERBOSE
-
-    #acc.getService("FaserByteStreamInputSvc").DumpFlag = True
-    #acc.getService("FaserEventSelector").OutputLevel = VERBOSE
-    #acc.getService("FaserByteStreamInputSvc").OutputLevel = VERBOSE
-    #acc.getService("FaserByteStreamCnvSvc").OutputLevel = VERBOSE
-    #acc.getService("FaserByteStreamAddressProviderSvc").OutputLevel = VERBOSE
-
 else:
     acc.foreach_component("*").OutputLevel = INFO
 
@@ -238,4 +256,11 @@ acc.foreach_component("*ClassID*").OutputLevel = INFO
 acc.getService("MessageSvc").Format = "% F%40W%S%7W%R%T %0W%M"
 
 # Execute and finish
-sys.exit(int(acc.run(maxEvents=args.nevents).isFailure()))
+sc = acc.run(maxEvents=args.nevents)
+
+b = time.time()
+from AthenaCommon.Logging import log
+log.info(f"Finish execution in {b-a} seconds")
+
+sys.exit(int(sc.isFailure()))
+
diff --git a/Control/CalypsoExample/Reconstruction/scripts/submit_faserMDC_reco.sh b/Control/CalypsoExample/Reconstruction/scripts/submit_faserMDC_reco.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b04d56aa9d13f3bac39f02213e0f47625714c689
--- /dev/null
+++ b/Control/CalypsoExample/Reconstruction/scripts/submit_faserMDC_reco.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+# Used with a condor file to submit to vanilla universe
+#
+# Usage:
+# submit_faser_reco.sh file_path [release_directory] [working_directory] [nevents]
+# 
+# file_path - full file name (with path)
+# release_directory - optional path to release install directory (default pwd)
+# working_directory - optional path to output directory location (default pwd)
+# nevents - optional number of events to process (default: -1 - all)
+#
+# The release directory must already be set up 
+# (so an unqualified asetup can set up the release properly)
+#
+# Script will use git describe to find the release tag.  
+# If this matches reco/r???? it will be passed to the reco job
+#
+#----------------------------------------
+#
+# Parse command-line options
+file_path=${1}
+release_directory=${2}
+working_directory=${3}
+nevents=${4}
+#
+# Set defaults if arguments aren't provided
+if [ -z "$file_path" ]
+then
+  echo "No file_path specified!"
+  exit 1
+fi
+#
+if [ -z "$release_directory" ]
+then
+  release_directory=`pwd`
+fi
+#
+if [ -z "$working_directory" ]
+then
+  working_directory=`pwd`
+fi
+#
+if [ -z "$nevents" ]
+then
+  nevents="-1"
+fi
+#
+starting_directory=`pwd`
+#
+# Now extract the run number and file stem
+#
+# First, get the filename
+file_name=$(basename "$file_path")
+# 
+# Now split based on '.' to get stem
+defaultIFS=$IFS
+IFS='.'
+read file_stem ext <<< "$file_name"
+#
+# Finally extract the run number
+IFS='-'
+# Read the split words into an array based on delimiter
+read faser desc run_number segment <<< "$file_stem"
+#
+# Set the IFS delimeter back or else echo doesn't work...
+IFS=$defaultIFS
+#
+# Make output directory if needed
+output_directory="$working_directory/${run_number}"
+mkdir -p "$output_directory"
+#
+# This magic redirects everything in this script to our log file
+exec >& "$output_directory/$file_stem.log"
+echo `date` - $HOSTNAME
+echo "File: $file_name"
+echo "Release: $release_directory"
+echo "Output: $output_directory"
+echo "Starting: $starting_directory"
+#
+# Set up the release (do this automatically)?
+export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
+source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh 
+#
+# Try automatic
+# Always go back to the starting directory in case paths are relative
+cd "$starting_directory"
+cd "$release_directory"
+#asetup
+#source build/x8*/setup.sh
+#
+# Do this by hand
+asetup --input=calypso/asetup.faser Athena,22.0.49
+source build/x86*/setup.sh
+#
+#
+# Try to find a release tag
+cd calypso
+recotag=`git describe`
+if [[ "$recotag" == "reco/r"???? ]]; then
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found reco tag: $tag"
+fi
+if [[ "$recotag" == "reco/p"???? ]]; then
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found reco tag: $tag"
+fi
+#
+# Move to the run directory
+cd "$starting_directory"
+cd "$output_directory"
+#
+# Remove any previous directory if it exists
+#if [[ -e "$file_stem" ]]; then
+#    echo "Remove previous directory $file_stem"
+#    rm -rf "$file_stem"
+#fi
+#
+# Make run directory
+if [[ -e "$file_stem" ]]; then
+    echo "Directory $file_stem already exists"
+else
+    mkdir "$file_stem"
+fi
+cd "$file_stem"
+#
+# Run job
+if [[ -z "$tag" ]]; then
+    faserMDC_reco.py "--nevents=$nevents" "$file_path"
+else
+    faserMDC_reco.py "--nevents=$nevents" "--reco=$tag" "$file_path"
+fi
+#
+# Print out ending time
+date
diff --git a/Control/CalypsoExample/Reconstruction/scripts/submit_faser_reco.sh b/Control/CalypsoExample/Reconstruction/scripts/submit_faser_reco.sh
index 64cca5567690c0680a4966b7e64f1d8ea869b1ca..277d6eaa79a3bc510cb413aa742b292837e28403 100755
--- a/Control/CalypsoExample/Reconstruction/scripts/submit_faser_reco.sh
+++ b/Control/CalypsoExample/Reconstruction/scripts/submit_faser_reco.sh
@@ -97,8 +97,12 @@ source build/x8*/setup.sh
 cd calypso
 recotag=`git describe`
 if [[ "$recotag" == "reco/r"???? ]]; then
-  rtag=`echo "$recotag" | cut -c 6-11`
-  echo "Found reco tag: $rtag"
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found reco tag: $tag"
+fi
+if [[ "$recotag" == "reco/p"???? ]]; then
+  tag=`echo "$recotag" | cut -c 6-11`
+  echo "Found reco tag: $tag"
 fi
 #
 # Move to the run directory
@@ -123,7 +127,7 @@ cd "$file_stem"
 if [[ -z "$rtag" ]]; then
     faser_reco.py "--nevents=$nevents" "$file_path"
 else
-    faser_reco.py "--nevents=$nevents" "--reco=$rtag" "$file_path"
+    faser_reco.py "--nevents=$nevents" "--reco=$tag" "$file_path"
 fi
 #
 # Print out ending time
diff --git a/Control/CalypsoExample/Simulation/CMakeLists.txt b/Control/CalypsoExample/Simulation/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..440e12c8c1df89217ed1e7df9b3c7314ff4303d4
--- /dev/null
+++ b/Control/CalypsoExample/Simulation/CMakeLists.txt
@@ -0,0 +1,12 @@
+################################################################################
+# Package: Simulation
+################################################################################
+
+# Declare the package name:
+atlas_subdir( Simulation )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py )
+atlas_install_scripts( scripts/*.sh scripts/*.py )
+
+
diff --git a/Control/CalypsoExample/Simulation/python/__init__.py b/Control/CalypsoExample/Simulation/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1019c31f8428bc651bbfdb56065098ecc35f870c
--- /dev/null
+++ b/Control/CalypsoExample/Simulation/python/__init__.py
@@ -0,0 +1 @@
+# Simulation.py
diff --git a/Control/CalypsoExample/Simulation/scripts/faserMDC_simulate.py b/Control/CalypsoExample/Simulation/scripts/faserMDC_simulate.py
new file mode 100755
index 0000000000000000000000000000000000000000..12de492f4070237b59dda273673b994bbc275d37
--- /dev/null
+++ b/Control/CalypsoExample/Simulation/scripts/faserMDC_simulate.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python
+"""
+Produce simulated hits from input 4-vectors
+Derived from G4FaserAlgConfigNew
+
+Usage:
+faserMDC_simulate.py filepath outfile
+
+filepath - full path, including url if needed, to the input 4-vector file
+outfile - output filename, parameters will be inferred from this name
+
+Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
+"""
+
+if __name__ == '__main__':
+
+    import sys
+    import time
+    a = time.time()
+#
+# Parse command-line options
+#
+    import sys
+    import argparse
+
+    parser = argparse.ArgumentParser(description="Run FASER simulation")
+
+    parser.add_argument("file_path",
+                        help="Fully qualified path of the raw input file")
+
+    parser.add_argument("output",
+                        help="Output file name")
+
+    #parser.add_argument("--run", type=int, default=123456,
+    #                    help="Specify run number to use in simulated data")
+
+    parser.add_argument("--xangle", type=float, default=0.0,
+                        help="Specify H crossing angle (in Radians)")
+    parser.add_argument("--yangle", type=float, default=0.0,
+                        help="Specify V crossing angle (in Radians)")
+    parser.add_argument("--xshift", type=float, default=0.0,
+                        help="Specify H shift of events wrt FASER (in mm)")
+    parser.add_argument("--yshift", type=float, default=0.0,
+                        help="Specify V shift of events wrt FASER (in mm)")
+
+    parser.add_argument("-t", "--tag", default="",
+                        help="Specify sim tag (to append to output filename)")
+    parser.add_argument("-s", "--skip", type=int, default=0,
+                        help="Specify number of events to skip (default: none)")
+    parser.add_argument("-n", "--nevents", type=int, default=-1,
+                        help="Specify number of events to process (default: all)")
+    parser.add_argument("-v", "--verbose", action='store_true', 
+                        help="Turn on DEBUG output")
+
+    args = parser.parse_args()
+
+    from pathlib import Path
+
+    filepath = Path(args.file_path)
+
+#
+# Parse input file
+#
+    print(f"Starting digitization of {filepath.name}")
+
+    filestem = filepath.stem
+    if len(args.tag) > 0:
+        if args.tag in filestem:
+            print(f"Not adding tag {args.tag} to {filestem}")
+        else:
+            filestem += f"-{args.tag}"
+
+    if args.output:
+        # Just directly specify the output filename
+        outfile = args.output
+
+        spl = outfile.split('-')
+        if len(spl) < 4:
+            print(f"Can't get run number from {outfile}!")
+            sys.exit(1)
+
+        runnum = int(spl[2])
+        segnum = int(spl[3])
+
+    else:
+        outfile = f"{filestem}-HITS.root"
+        print(f"Output file name not specified")
+        sys.exit(1)
+
+    print(f"Outfile: {outfile}")
+
+#
+# Figure out events to run
+#
+    if args.skip > 0:
+        print(f"Skipping {args.skip} events by command-line option")
+
+    if args.nevents > 0:
+        print(f"Reconstructing {args.nevents} events by command-line option")
+#
+# Set up logging and config behaviour
+#
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG, VERBOSE
+    from AthenaCommon.Configurable import Configurable
+    log.setLevel(DEBUG)
+    Configurable.configurableRun3Behavior = 1
+#
+# Import and set config flags
+#
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    from AthenaConfiguration.Enums import ProductionStep
+    ConfigFlags.Common.ProductionStep = ProductionStep.Simulation
+#
+# All these must be specified to avoid auto-configuration
+#
+    ConfigFlags.Input.RunNumber = [ runnum ] 
+    ConfigFlags.Input.OverrideRunNumber = True
+    ConfigFlags.Input.LumiBlockNumber = [ (segnum+1) ]
+    ConfigFlags.Input.isMC = True
+#
+# Input file name
+# 
+    # Path mangles // in url, so use direct file_path here
+    ConfigFlags.Input.Files = [ args.file_path ]
+#
+# Skip events
+#
+    ConfigFlags.Exec.SkipEvents = args.skip
+    ConfigFlags.Exec.MaxEvents = args.nevents
+#
+# Output file name
+# 
+    ConfigFlags.Output.HITSFileName = outfile
+#
+# Sim ConfigFlags
+#
+    ConfigFlags.Sim.Layout = "FASER"
+    ConfigFlags.Sim.PhysicsList = "FTFP_BERT"
+    ConfigFlags.Sim.ReleaseGeoModel = False
+    ConfigFlags.Sim.IncludeParentsInG4Event = True # Controls whether BeamTruthEvent is written to output HITS file
+
+    # Units are radians and mm
+    ConfigFlags.addFlag("Sim.Beam.xangle", args.xangle)  # Potential beam crossing angles
+    ConfigFlags.addFlag("Sim.Beam.yangle", args.yangle)    
+    ConfigFlags.addFlag("Sim.Beam.xshift", args.xshift)  # Potential beam shift
+    ConfigFlags.addFlag("Sim.Beam.yshift", args.yshift)    
+
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"     # Geometry set-up
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"     # Conditions set-up
+    ConfigFlags.addFlag("Input.InitialTimeStamp", 0)     # To avoid autoconfig 
+    ConfigFlags.GeoModel.Align.Dynamic = False
+
+    # import sys
+    # ConfigFlags.fillFromArgs(sys.argv[1:])
+
+    doShiftLOS = (ConfigFlags.Sim.Beam.xangle or ConfigFlags.Sim.Beam.yangle or
+                  ConfigFlags.Sim.Beam.xshift or ConfigFlags.Sim.Beam.yshift)
+
+#
+# MDC geometry configuration
+#
+    detectors = ['Veto', 'VetoNu', 'Preshower', 'FaserSCT', 'Ecal', 'Trigger', 'Dipole', 'Emulsion']
+#
+# Setup detector flags
+#
+    from CalypsoConfiguration.DetectorConfigFlags import setupDetectorsFromList
+    setupDetectorsFromList(ConfigFlags, detectors, toggle_geometry=True)
+#
+# Finalize flags
+#
+    ConfigFlags.lock()
+#
+# Initialize a new component accumulator
+#
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+
+#
+# Check whether a known input file was specified
+#
+    if ConfigFlags.Input.Files[0].endswith(".events") or ConfigFlags.Input.Files[0].endswith(".hepmc"):
+
+        from HEPMCReader.HepMCReaderConfig import HepMCReaderCfg
+
+        if doShiftLOS:
+            cfg.merge(HepMCReaderCfg(ConfigFlags, McEventKey = "BeamTruthEvent_ATLASCoord"))
+        else:
+            cfg.merge(HepMCReaderCfg(ConfigFlags))
+
+        from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+        cfg.merge(McEventSelectorCfg(ConfigFlags))
+#
+# Else, set up to read it as a pool.root file
+#
+    else:
+        from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+        cfg.merge(PoolReadCfg(ConfigFlags))
+
+        if doShiftLOS:
+            from SGComps.AddressRemappingConfig import InputOverwriteCfg
+            # Rename old truth collection to add ATLAS coord to can still use BeamTruthEvent for the one in FASER Coords
+            cfg.merge(InputOverwriteCfg("McEventCollection", "BeamTruthEvent", "McEventCollection", "BeamTruthEvent_ATLASCoord"))
+
+#
+# Output file
+#
+    from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    cfg.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Shift LOS
+#
+    if doShiftLOS:
+
+        import McParticleEvent.Pythonizations
+        from GeneratorUtils.ShiftLOSConfig import ShiftLOSCfg
+        cfg.merge(ShiftLOSCfg(ConfigFlags, 
+                              xcross = ConfigFlags.Sim.Beam.xangle, 
+                              ycross = ConfigFlags.Sim.Beam.yangle,
+                              xshift = ConfigFlags.Sim.Beam.xshift,
+                              yshift = ConfigFlags.Sim.Beam.yshift))
+
+
+#
+# Add the G4FaserAlg
+#
+    from G4FaserAlg.G4FaserAlgConfigNew import G4FaserAlgCfg
+    cfg.merge(G4FaserAlgCfg(ConfigFlags))
+#
+# Dump config
+#
+    from AthenaConfiguration.ComponentFactory import CompFactory
+    cfg.addEventAlgo(CompFactory.JobOptsDumperAlg(FileName="G4FaserTestConfig.txt"))
+    cfg.getService("StoreGateSvc").Dump = True
+    cfg.getService("ConditionStore").Dump = True
+    cfg.printConfig(withDetails=True, summariseProps = False)  # gags on ParticleGun if summariseProps = True?
+
+    ConfigFlags.dump()
+    #f = open("test.pkl","wb")
+    #cfg.store(f)
+    #f.close()
+#
+# Execute and finish
+#
+
+    # This fails with ShiftLOSCfg...
+    #if args.verbose:
+    #    cfg.foreach_component("*").OutputLevel = "DEBUG" 
+    #else:
+    #    cfg.foreach_component("*").OutputLevel = "INFO"  
+
+    sc = cfg.run(maxEvents=args.nevents)
+
+    b = time.time()
+    log.info("Finish execution in " + str(b-a) + " seconds")
+#
+# Success should be 0
+#
+    sys.exit(int(sc.isFailure()))
+
diff --git a/Control/CalypsoExample/Simulation/scripts/submit_faserMDC_simulate.sh b/Control/CalypsoExample/Simulation/scripts/submit_faserMDC_simulate.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3c175d72519eda6d9f973c84ea3c2b1af00b7dba
--- /dev/null
+++ b/Control/CalypsoExample/Simulation/scripts/submit_faserMDC_simulate.sh
@@ -0,0 +1,177 @@
+#!/bin/bash
+# Used with a condor file to submit to vanilla universe
+#
+# Usage:
+# submit_faserMDC_simluate.sh [--shift] input_file output_file [release_directory] [working_directory] [skip] [nevts]
+#
+# Options:
+# --shift - apply crossing angle (and FASER shift)
+# 
+# input_file - full file name (with path)
+# output_file - full output file name
+# release_directory - optional path to release install directory (default pwd)
+# working_directory - optional path to output directory location (default pwd)
+# skip - events in input file to skip
+# nevts = events in input file to process
+#
+# The release directory must already be set up 
+# (so an unqualified asetup can set up the release properly)
+#
+# Script will use git describe to find the release tag.  
+# If this matches gen/g???? or sim/s???? it will be passed to the job
+#
+#----------------------------------------
+# Keep track of time
+SECONDS=0
+#
+# Parse command-line options
+while [ -n "$1" ]
+do 
+  case "$1" in
+    -s | --shift) 
+	  echo "Applying crossing-angle shift" 
+	  xangle=1 
+	  shift;;  # This 'eats' the argument
+
+    -*) 
+	  echo "Unknown option $1"
+	  shift;;
+
+    *) break;;  # Not an option, don't shift
+  esac
+done
+
+#
+# Parse command-line arguments
+infile=${1}
+outfile=${2}
+release_directory=${3}
+working_directory=${4}
+skip_events=${5}
+nevts=${6}
+#
+# Set defaults if arguments aren't provided
+if [ -z "$infile" ]
+then
+  echo "No input file specified!"
+  echo "Usage: submit_faserMDC_simulate.sh input_file output_file [release dir] [output dir]"
+  exit 1
+fi
+#
+if [ -z "$outfile" ]
+then
+  outfile="FaserMC-Test-123456-00000-HITS.root"
+  echo "No output file specified, using $outfile !"
+fi
+#
+if [ -z "$release_directory" ]
+then
+  release_directory=`pwd`
+fi
+#
+if [ -z "$working_directory" ]
+then
+  working_directory=`pwd`
+fi
+#
+if [ -z "$skip_events" ]
+then
+  skip_events=0
+fi
+#
+if [ -z "$nevts" ]
+then
+  nevts=-1
+fi
+#
+starting_directory=`pwd`
+#
+# Now extract the file information
+# Here we do this on the output file, as the input files can be non-standard
+#
+# First, get the filename
+outfilename=$(basename "$outfile")
+# 
+# Now split based on '.' to get stem
+defaultIFS=$IFS
+IFS='.'
+read file_stem ext <<< "$outfilename"
+#
+# Try to find the run number
+IFS='-'
+# Read the split words into an array based on delimeter
+read faser short run_number seg <<< "$file_stem"
+#
+# Set the IFS delimeter back or else echo doesn't work...
+IFS=$defaultIFS
+#
+# Check if we found a number, use full input file name if not
+output_directory="$working_directory/${run_number}"
+re='^[0-9]+$'
+if ! [[ $run_number =~ $re ]] ; then
+    # Not a number...
+    output_directory="$working_directory/${file_stem}"
+fi
+#
+# Make output directory if needed
+mkdir -p "$output_directory"
+#
+# This magic redirects everything in this script to our log file
+exec >& "$output_directory/${file_stem}.log"
+echo `date` - $HOSTNAME
+echo "Input File: $infile"
+echo "Output File: $outfilename"
+echo "Release: $release_directory"
+echo "Output: $output_directory"
+echo "Starting: $starting_directory"
+echo "Skip: $skip_events"
+echo "Nevts: $nevts"
+#
+# Set up the release (do this automatically)?
+export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
+source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh 
+#
+# Try automatic
+# Always go back to the starting directory in case paths are relative
+cd "$starting_directory"
+cd "$release_directory"
+# This doesn't seem to work, as we need the --input argument
+#asetup 
+#source build/x8*/setup.sh
+#
+# Do this by hand
+asetup --input=calypso/asetup.faser Athena,22.0.49
+source build/x86*/setup.sh
+#
+# Move to the run directory
+cd "$starting_directory"
+cd "$output_directory"
+#
+# Remove any previous directory if it exists
+#if [[ -e "$file_stem" ]]; then
+#    echo "Remove previous directory $file_stem"
+#    rm -rf "$file_stem"
+#fi
+#
+# Make run directory
+if [[ -e "${file_stem}" ]]; then
+    echo "Directory ${file_stem} already exists"
+else
+    mkdir "${file_stem}"
+fi
+cd "${file_stem}"
+#
+# Run job
+#if [[ -z "$tag" ]]; then
+#fi
+if [[ -z "$xangle" ]]; then
+    faserMDC_simulate.py  --skip "$skip_events" -n "$nevts" "$infile" "$outfile"
+else
+    faserMDC_simulate.py  --yangle -0.000150 --yshift 12.0 --skip "$skip_events" -n "$nevts" "$infile" "$outfile"
+fi
+#
+# Print out ending time
+date
+echo "Job finished after $SECONDS seconds"
+#
+
diff --git a/Control/CalypsoExample/rawdata/Faser-Physics-006525-filtered.raw b/Control/CalypsoExample/rawdata/Faser-Physics-006525-filtered.raw
new file mode 100644
index 0000000000000000000000000000000000000000..e7e7da6e539a0f05a064c58c1b58d7c6a1081e7e
Binary files /dev/null and b/Control/CalypsoExample/rawdata/Faser-Physics-006525-filtered.raw differ
diff --git a/Control/CalypsoExample/rawdata/Faser-Physics-pilot_tracks-filtered.raw b/Control/CalypsoExample/rawdata/Faser-Physics-pilot_tracks-filtered.raw
new file mode 100644
index 0000000000000000000000000000000000000000..3079490b5b1d0110887cb2a7de7591e0bf2b0cc2
Binary files /dev/null and b/Control/CalypsoExample/rawdata/Faser-Physics-pilot_tracks-filtered.raw differ
diff --git a/Database/ConnectionManagement/FaserAuthentication/data/dblookup.xml b/Database/ConnectionManagement/FaserAuthentication/data/dblookup.xml
index 52542a2837125d540824617b1085a5d710c3507f..9cebc2e82b233d6191d7671cf6d9d6997cad23f9 100644
--- a/Database/ConnectionManagement/FaserAuthentication/data/dblookup.xml
+++ b/Database/ConnectionManagement/FaserAuthentication/data/dblookup.xml
@@ -26,4 +26,9 @@
    <service name="sqlite_file:///cvmfs/faser.cern.ch/repo/sw/database/DBRelease/current/sqlite200/CABP200.db" accessMode="read" />
 </logicalservice>
 
+<logicalservice name="COOLOFL_TRIGGER">
+   <service name="sqlite_file:data/sqlite200/waveform_reco.db" accessMode="read" />
+   <service name="sqlite_file:///cvmfs/faser.cern.ch/repo/sw/database/DBRelease/current/sqlite200/waveform_reco.db" accessMode="read" />
+</logicalservice>
+
 </servicelist>
diff --git a/DetectorDescription/DetDescrCnvSvc/src/DetDescrCnvSvc.cxx b/DetectorDescription/DetDescrCnvSvc/src/DetDescrCnvSvc.cxx
index 50884e7221086392e645855bd885e6f19ee821e0..cc2fca779acd953d177963424389477baa30c856 100644
--- a/DetectorDescription/DetDescrCnvSvc/src/DetDescrCnvSvc.cxx
+++ b/DetectorDescription/DetDescrCnvSvc/src/DetDescrCnvSvc.cxx
@@ -125,6 +125,8 @@ DetDescrCnvSvc::initialize()     {
     if (status != StatusCode::SUCCESS) return status;
     status =  addToDetStore(55179317, "PreshowerID");
     if (status != StatusCode::SUCCESS) return status;
+    status =  addToDetStore(247779284, "VetoNuID");
+    if (status != StatusCode::SUCCESS) return status;
     status =  addToDetStore(205618430, "FaserSCT_ID");
     if (status != StatusCode::SUCCESS) return status;
     status =  addToDetStore(113753346, "EcalID");
diff --git a/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetTechnology.h b/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetTechnology.h
index 6c0a2b2919f4ef4bc8f16b4519bdd82dbc45bade..0ee1ecc5d64e53a1d5cd0e04ad969421f8344c59 100644
--- a/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetTechnology.h
+++ b/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetTechnology.h
@@ -35,20 +35,21 @@ namespace FaserDetDescr {
             fLastFaserNeutrinoTechnology  = 1,
         // Scintillator
             fFirstFaserScintillatorTechnology  = 2,
-            fFaserVeto                         = 2,
-            fFaserTrigger                      = 3,
-            fFaserPreshower                    = 4,
-            fLastFaserScintillatorTechnology   = 4,
+            fFaserVetoNu                       = 2,
+            fFaserVeto                         = 3,
+            fFaserTrigger                      = 4,
+            fFaserPreshower                    = 5,
+            fLastFaserScintillatorTechnology   = 5,
         // Tracker
-            fFirstFaserTrackerTechnology = 5,
-            fFaserSCT                    = 5,
-            fLastFaserTrackerTechnology  = 5,
+            fFirstFaserTrackerTechnology = 6,
+            fFaserSCT                    = 6,
+            fLastFaserTrackerTechnology  = 6,
         // Calorimeter
-            fFirstFaserCalorimeterTechnology    = 6,
-            fFaserECAL                          = 6,
-            fLastFaserCalorimeterTechnology     = 6,
+            fFirstFaserCalorimeterTechnology    = 7,
+            fFaserECAL                          = 7,
+            fLastFaserCalorimeterTechnology     = 7,
         // number of defined detector technologies
-            fNumFaserDetTechnologies    = 7
+            fNumFaserDetTechnologies    = 8
    };
 
 } // end of namespace
diff --git a/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetectorID.h b/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetectorID.h
index 3de4b6428fa65ae333a6bebe92aef8334f78a7b8..3ac1160a9e0c12935f522b0750b09a74fe4d4983 100644
--- a/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetectorID.h
+++ b/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserDetectorID.h
@@ -82,6 +82,7 @@ public:
     /// @name Scintillator subsystem ids
     //@{
     Identifier          veto            (void) const;
+    Identifier          vetonu          (void) const;
     Identifier          trigger         (void) const;
     Identifier          preshower       (void) const;
     //@}
@@ -157,6 +158,7 @@ public:
     bool                is_calo         (Identifier id) const;
     bool                is_emulsion     (Identifier id) const;
     bool                is_veto         (Identifier id) const;
+    bool                is_vetonu       (Identifier id) const;
     bool                is_trigger      (Identifier id) const;
     bool                is_preshower    (Identifier id) const;
     bool                is_sct          (Identifier id) const;
@@ -172,6 +174,7 @@ public:
     bool                is_calo         (const ExpandedIdentifier& id) const;
     bool                is_emulsion     (const ExpandedIdentifier& id) const;
     bool                is_veto         (const ExpandedIdentifier& id) const;
+    bool                is_vetonu       (const ExpandedIdentifier& id) const;
     bool                is_trigger      (const ExpandedIdentifier& id) const;
     bool                is_preshower    (const ExpandedIdentifier& id) const;
     bool                is_sct          (const ExpandedIdentifier& id) const;
@@ -230,6 +233,7 @@ protected:
 
     /// Scintillator:
     ExpandedIdentifier          veto_exp            (void) const;
+    ExpandedIdentifier          vetonu_exp          (void) const;
     ExpandedIdentifier          trigger_exp         (void) const;
     ExpandedIdentifier          preshower_exp       (void) const;
 
@@ -247,6 +251,7 @@ protected:
     int                 calo_field_value         () const;
     int                 emulsion_field_value     () const;
     int                 veto_field_value         () const;     
+    int                 vetonu_field_value       () const;     
     int                 trigger_field_value      () const;       
     int                 preshower_field_value    () const;       
     int                 sct_field_value          () const;
@@ -306,6 +311,7 @@ private:
     int                 m_CALO_ID;
     int                 m_EMULSION_ID;
     int                 m_VETO_ID;     
+    int                 m_VETONU_ID;     
     int                 m_TRIGGER_ID;       
     int                 m_PRESHOWER_ID;       
     int                 m_SCT_ID;
@@ -382,6 +388,13 @@ FaserDetectorID::veto_exp           (void) const
     return (result << m_VETO_ID);
 }
 
+inline ExpandedIdentifier          
+FaserDetectorID::vetonu_exp           (void) const
+{
+    ExpandedIdentifier result(scint_exp());
+    return (result << m_VETONU_ID);
+}
+
 inline ExpandedIdentifier          
 FaserDetectorID::trigger_exp             (void) const
 {
@@ -428,6 +441,9 @@ FaserDetectorID::emulsion_field_value     () const {return (m_EMULSION_ID);}
 inline int                 
 FaserDetectorID::veto_field_value         () const {return (m_VETO_ID);}     
 
+inline int                 
+FaserDetectorID::vetonu_field_value       () const {return (m_VETONU_ID);}     
+
 inline int                 
 FaserDetectorID::trigger_field_value      () const {return (m_TRIGGER_ID);}       
 
@@ -484,6 +500,16 @@ FaserDetectorID::is_veto       (Identifier id) const
     return result;
 }
 
+inline bool               
+FaserDetectorID::is_vetonu     (Identifier id) const
+{
+    bool result = false;
+    if(is_scint(id)) {
+        result = (m_scint_part_impl.unpack(id) == m_VETONU_ID);
+    }
+    return result;
+}
+
 inline bool               
 FaserDetectorID::is_trigger     (Identifier id) const
 {
diff --git a/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserRegion.h b/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserRegion.h
index fdefad244797c043e1d6048dd0e4e416c60fba7f..86b9324d943ef75f1eea2d2c45eaf8521981be5e 100644
--- a/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserRegion.h
+++ b/DetectorDescription/FaserDetDescr/FaserDetDescr/FaserRegion.h
@@ -35,8 +35,9 @@ namespace FaserDetDescr {
             fFaserDipole          = 4,  // this means in the metal of the magnet, not the bore
             fFaserCalorimeter     = 5,
             fFaserCavern          = 6,
+            fFaserTrench          = 7,  // i.e. the concrete around the trench
         // number of defined GeoIDs
-            fNumFaserRegions      = 7
+            fNumFaserRegions      = 8
    };
 
 } // end of namespace
diff --git a/DetectorDescription/FaserDetDescr/src/FaserDetectorID.cxx b/DetectorDescription/FaserDetDescr/src/FaserDetectorID.cxx
index 9e8dfbb0dcf18ea616c4e35725b28cbb27e8151a..59d81a8d8006bdde022b1db76092a6f97567e85e 100644
--- a/DetectorDescription/FaserDetDescr/src/FaserDetectorID.cxx
+++ b/DetectorDescription/FaserDetDescr/src/FaserDetectorID.cxx
@@ -42,6 +42,7 @@ FaserDetectorID::FaserDetectorID()
         m_CALO_ID(4),
         m_EMULSION_ID(1),
         m_VETO_ID(1),
+    	m_VETONU_ID(4),
         m_TRIGGER_ID(2),
         m_PRESHOWER_ID(3),
         m_SCT_ID(1),
@@ -77,6 +78,7 @@ FaserDetectorID::FaserDetectorID(const FaserDetectorID& other)
         m_CALO_ID                 (other.m_CALO_ID),
         m_EMULSION_ID             (other.m_EMULSION_ID),
         m_VETO_ID                 (other.m_VETO_ID),
+        m_VETONU_ID               (other.m_VETONU_ID),
         m_TRIGGER_ID              (other.m_TRIGGER_ID),
         m_PRESHOWER_ID            (other.m_PRESHOWER_ID),
         m_SCT_ID                  (other.m_SCT_ID),
@@ -122,6 +124,7 @@ FaserDetectorID::operator= (const FaserDetectorID& other)
         m_VETO_ID               = other.m_VETO_ID;
         m_TRIGGER_ID            = other.m_TRIGGER_ID;
         m_PRESHOWER_ID          = other.m_PRESHOWER_ID;
+        m_VETONU_ID             = other.m_VETONU_ID;
         m_SCT_ID                = other.m_SCT_ID;
         m_ECAL_ID               = other.m_ECAL_ID;
         m_faser_dict            = other.m_faser_dict;
@@ -204,6 +207,16 @@ FaserDetectorID::veto        (void) const
     return (result);
 }
 
+Identifier
+FaserDetectorID::vetonu      (void) const
+{
+    Identifier result((Identifier::value_type)0);
+    // Pack field
+    m_det_impl.pack       (scint_field_value(), result);
+    m_scint_part_impl.pack(m_VETONU_ID, result);
+    return (result);
+}
+
 Identifier
 FaserDetectorID::trigger          (void) const
 {
@@ -444,6 +457,16 @@ FaserDetectorID::is_veto       (const ExpandedIdentifier& id) const
     return result;
 }
 
+bool
+FaserDetectorID::is_vetonu       (const ExpandedIdentifier& id) const
+{
+    bool result = false;
+    if ( is_scint(id) && id.fields() > 1 ){
+        if ( id[1] == m_VETONU_ID ) result = true;
+    }
+    return result;
+}
+
 bool
 FaserDetectorID::is_trigger         (const ExpandedIdentifier& id) const
 {
@@ -729,6 +752,7 @@ FaserDetectorID::initLevelsFromDict(const IdDictMgr& dict_mgr)
     m_VETO_ID               = -1;
     m_TRIGGER_ID            = -1;
     m_PRESHOWER_ID          = -1;
+    m_VETONU_ID             = -1;
     m_SCT_ID                = -1;
     m_ECAL_ID               = -1;
 
@@ -935,6 +959,38 @@ FaserDetectorID::initLevelsFromDict(const IdDictMgr& dict_mgr)
             return (1);
         }
 
+        label = field->find_label("VetoNu");
+        if (label) {
+            if (label->m_valued) {
+                m_VETONU_ID = label->m_value;
+            }
+            else {
+                if(m_msgSvc) {
+                    MsgStream log(m_msgSvc, "FaserDetectorID" );
+                    log << MSG::ERROR << "initLevelsFromDict - label VetoNu does NOT have a value "
+                        << endmsg;
+                }
+                else {
+                    std::cout << "FaserDetectorID::initLevelsFromDict - label VetoNu does NOT have a value "
+                              << std::endl;
+                }
+                return (1);
+            }
+        }
+        else {
+            if(m_msgSvc) {
+                MsgStream log(m_msgSvc, "FaserDetectorID" );
+                log << MSG::ERROR << "initLevelsFromDict - unable to find 'VetoNu' label "
+                    << endmsg;
+            }
+            else {
+                std::cout << "FaserDetectorID::initLevelsFromDict - unable to find 'VetoNu' label "
+                          << std::endl;
+            }
+            return (1);
+        }
+
+
     }
 
     // Initialize ids for Tracker
diff --git a/DetectorDescription/FaserDetDescr/src/FaserRegionHelper.cxx b/DetectorDescription/FaserDetDescr/src/FaserRegionHelper.cxx
index 35362e28494447026cb03495568f67bc8a1066ed..b2af1fbef778f1fc224dd09417eb90a350c6d60b 100644
--- a/DetectorDescription/FaserDetDescr/src/FaserRegionHelper.cxx
+++ b/DetectorDescription/FaserDetDescr/src/FaserRegionHelper.cxx
@@ -22,6 +22,7 @@ namespace FaserDetDescr {
     else if ( region == FaserDetDescr::fFaserCalorimeter    ) return "FaserCalo";
     else if ( region == FaserDetDescr::fFaserNeutrino       ) return "FaserNeutrino";
     else if ( region == FaserDetDescr::fFaserCavern         ) return "FaserCavern";
+    else if ( region == FaserDetDescr::fFaserTrench         ) return "FaserTrench";
     else                                                      return "UndefinedFaserRegion";
 
   }
diff --git a/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.h b/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.h
index b64fe128eac2531f3a4c44b98dbb2e19f2f9d301..3b212bbd59e5503dd7a6f14b55cc805fb6f22bb1 100644
--- a/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.h
+++ b/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.h
@@ -20,10 +20,12 @@ class ScintHit;
 class VetoID;
 class TriggerID;
 class PreshowerID;
+class VetoNuID;
 namespace ScintDD {
   class VetoDetectorManager;
   class TriggerDetectorManager;
   class PreshowerDetectorManager;
+  class VetoNuDetectorManager;
 }
 
 class GeoScintHit {
@@ -41,7 +43,7 @@ class GeoScintHit {
 
   // Is this hit ok?
 
-  operator bool () const { return s_veto || s_trigger || s_preshower; }
+  operator bool () const { return s_veto || s_trigger || s_preshower || s_vetonu; }
 
  private:
   
@@ -49,9 +51,11 @@ class GeoScintHit {
 
   const ScintHit                                  *m_hit;
   static const ScintDD::VetoDetectorManager       *s_veto;
+  static const ScintDD::VetoNuDetectorManager     *s_vetonu;
   static const ScintDD::TriggerDetectorManager    *s_trigger;
   static const ScintDD::PreshowerDetectorManager  *s_preshower;
   static const VetoID                             *s_vID;
+  static const VetoNuID                           *s_vnID;
   static const TriggerID                          *s_tID;
   static const PreshowerID                        *s_pID;
 };
diff --git a/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.icc b/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.icc
index 47610f91a19cded0a84bb7e6c3c955d3bf4faaa9..ad0534374be219ff4a41f944a7ebd3de52f5c090 100644
--- a/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.icc
+++ b/DetectorDescription/GeoModel/FaserGeoAdaptors/FaserGeoAdaptors/GeoScintHit.icc
@@ -5,11 +5,13 @@
 #include "ScintSimEvent/ScintHit.h"
 #include "ScintReadoutGeometry/ScintDetectorElement.h"
 #include "ScintReadoutGeometry/VetoDetectorManager.h"
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h"
 #include "ScintReadoutGeometry/TriggerDetectorManager.h"
 #include "ScintReadoutGeometry/PreshowerDetectorManager.h"
 #include "StoreGate/StoreGateSvc.h"
 #include "StoreGate/StoreGateSvc.h"
 #include "ScintIdentifier/VetoID.h"
+#include "ScintIdentifier/VetoNuID.h"
 #include "ScintIdentifier/TriggerID.h"
 #include "ScintIdentifier/PreshowerID.h"
 #include "GeoPrimitives/CLHEPtoEigenConverter.h"
@@ -20,12 +22,16 @@ inline void GeoScintHit::init() {
   if (detStore.retrieve().isSuccess()) {
     if(detStore->retrieve(s_veto,"Veto").isFailure())
       s_veto = 0;
+    if(detStore->retrieve(s_vetonu,"VetoNu").isFailure())
+      s_vetonu = 0;
     if(detStore->retrieve(s_trigger,"Trigger").isFailure())
       s_trigger = 0;
     if(detStore->retrieve(s_preshower,"Preshower").isFailure())
       s_preshower = 0;
     if(detStore->retrieve(s_vID,"VetoID").isFailure())
       s_vID = 0;
+    if(detStore->retrieve(s_vnID,"VetoNuID").isFailure())
+      s_vnID = 0;
     if(detStore->retrieve(s_tID,"TriggerID").isFailure())
       s_tID = 0;
     if(detStore->retrieve(s_pID,"PreshowerID").isFailure())
@@ -35,7 +41,7 @@ inline void GeoScintHit::init() {
 
 inline GeoScintHit::GeoScintHit (const ScintHit & h) {
   m_hit = &h;
-  if (!s_veto || ! s_trigger || ! s_preshower ) init();
+  if (!s_veto || ! s_trigger || ! s_preshower || ! s_vetonu) init();
 }
 
 inline HepGeom::Point3D<double> GeoScintHit::getGlobalPosition() const {
@@ -48,6 +54,11 @@ inline HepGeom::Point3D<double> GeoScintHit::getGlobalPosition() const {
 			m_hit->getPlate());
     geoelement = s_veto->getDetectorElement(id);      
   }
+  else if (m_hit->isVetoNu()) {
+    id = s_vnID->plate_id(Station,
+			m_hit->getPlate());
+    geoelement = s_vetonu->getDetectorElement(id);      
+  }
   else if (m_hit->isTrigger()) {
     id = s_tID->plate_id(Station,
 			m_hit->getPlate());
diff --git a/DetectorDescription/GeoModel/FaserGeoAdaptors/src/statics.cxx b/DetectorDescription/GeoModel/FaserGeoAdaptors/src/statics.cxx
index ca6dd213cbe3a3782bbe5a45500b9a09e48eee09..ab41b1e48be0304a68b0a79e846096bbd60f2acd 100644
--- a/DetectorDescription/GeoModel/FaserGeoAdaptors/src/statics.cxx
+++ b/DetectorDescription/GeoModel/FaserGeoAdaptors/src/statics.cxx
@@ -9,12 +9,14 @@
 
 const NeutrinoDD::EmulsionDetectorManager *GeoNeutrinoHit::s_emulsion = 0;
 const ScintDD::VetoDetectorManager      *GeoScintHit::s_veto = 0;
+const ScintDD::VetoNuDetectorManager    *GeoScintHit::s_vetonu = 0;
 const ScintDD::TriggerDetectorManager   *GeoScintHit::s_trigger = 0;
 const ScintDD::PreshowerDetectorManager *GeoScintHit::s_preshower = 0;
 const TrackerDD::SCT_DetectorManager    *GeoFaserSiHit::s_sct;
 const CaloDD::EcalDetectorManager       *GeoFaserCaloHit::s_ecal = 0;
 const EmulsionID                        *GeoNeutrinoHit::s_nID = 0;
 const VetoID                            *GeoScintHit::s_vID = 0;
+const VetoNuID                          *GeoScintHit::s_vnID = 0;
 const TriggerID                         *GeoScintHit::s_tID = 0;
 const PreshowerID                       *GeoScintHit::s_pID = 0;
 const FaserSCT_ID                       *GeoFaserSiHit::s_sID = 0;
diff --git a/DetectorDescription/GeoModel/FaserGeoModel/CMakeLists.txt b/DetectorDescription/GeoModel/FaserGeoModel/CMakeLists.txt
index 2086d60b12f50e223aa3bf0f0230ca8717aee6c7..38a8976dafe047cce2da022e6e1cee2267ef2aad 100644
--- a/DetectorDescription/GeoModel/FaserGeoModel/CMakeLists.txt
+++ b/DetectorDescription/GeoModel/FaserGeoModel/CMakeLists.txt
@@ -8,17 +8,17 @@ atlas_subdir( FaserGeoModel )
 
 if (INSTALL_GEOMDB)
 add_custom_command (
-   OUTPUT ${CMAKE_BINARY_DIR}/data/geomDB_sqlite
+   OUTPUT ${CMAKE_BINARY_DIR}/data/geomDB/geomDB_sqlite
    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/data/geomDB.sql
-   COMMAND mkdir -p ${CMAKE_BINARY_DIR}/data
-   COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/data/geomDB.sql | sqlite3 ${CMAKE_BINARY_DIR}/data/geomDB_sqlite
+   COMMAND mkdir -p ${CMAKE_BINARY_DIR}/data/geomDB
+   COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/data/geomDB.sql | sqlite3 ${CMAKE_BINARY_DIR}/data/geomDB/geomDB_sqlite
    )
 
-   add_custom_target( geomDB ALL DEPENDS ${CMAKE_BINARY_DIR}/data/geomDB_sqlite )
+   add_custom_target( geomDB ALL DEPENDS ${CMAKE_BINARY_DIR}/data/geomDB/geomDB_sqlite )
 
    # Install the generated file:
-   install( FILES ${CMAKE_BINARY_DIR}/data/geomDB_sqlite
-            DESTINATION ${CMAKE_INSTALL_DATADIR} )
+   install( FILES ${CMAKE_BINARY_DIR}/data/geomDB/geomDB_sqlite
+            DESTINATION ${CMAKE_INSTALL_DATADIR}/geomDB/ )
 endif()
 
 # Install python files from the package:
diff --git a/DetectorDescription/GeoModel/FaserGeoModel/data/geomDB.sql b/DetectorDescription/GeoModel/FaserGeoModel/data/geomDB.sql
index 25673485d439d673d60366b80f62285228601f98..81a979c3cc714898a733bc15ffa8896047c0d5a9 100644
--- a/DetectorDescription/GeoModel/FaserGeoModel/data/geomDB.sql
+++ b/DetectorDescription/GeoModel/FaserGeoModel/data/geomDB.sql
@@ -144,6 +144,25 @@ CREATE TABLE IF NOT EXISTS "EMULSIONGENERAL_DATA2TAG" (
 	"EMULSIONGENERAL_DATA_ID" SLONGLONG
 );
 --
+DROP TABLE IF EXISTS "EMULSIONSUPPORT_DATA";
+CREATE TABLE IF NOT EXISTS "EMULSIONSUPPORT_DATA" (
+	"EMULSIONSUPPORT_DATA_ID" SLONGLONG UNIQUE,
+    "MATERIAL" TEXT,
+	"DX" DOUBLE,
+	"DY" DOUBLE,
+    "DZ" DOUBLE,
+	"X" DOUBLE,
+	"Y" DOUBLE,
+	"Z" DOUBLE,
+	"LABEL" TEXT
+);
+--
+DROP TABLE IF EXISTS "EMULSIONSUPPORT_DATA2TAG";
+CREATE TABLE IF NOT EXISTS "EMULSIONSUPPORT_DATA2TAG" (
+	"EMULSIONSUPPORT_TAG_ID" SLONGLONG,
+	"EMULSIONSUPPORT_DATA_ID" SLONGLONG
+);
+--
 DROP TABLE IF EXISTS "EMULSIONFILM_DATA";
 CREATE TABLE IF NOT EXISTS "EMULSIONFILM_DATA" (
 	"EMULSIONFILM_DATA_ID" SLONGLONG UNIQUE,
@@ -250,6 +269,75 @@ CREATE TABLE IF NOT EXISTS "VETORADIATORGENERAL_DATA2TAG" (
 	"VETORADIATORGENERAL_DATA_ID" SLONGLONG
 );
 --
+-- Tables for describing VetoNu scintillator plates (and passive radiators)
+--
+DROP TABLE IF EXISTS "VETONUTOPLEVEL_DATA";
+CREATE TABLE IF NOT EXISTS "VETONUTOPLEVEL_DATA" (
+	"VETONUTOPLEVEL_DATA_ID"	SLONGLONG UNIQUE,
+	"POSX" DOUBLE,
+	"POSY" DOUBLE,
+	"POSZ" DOUBLE,
+	"ROTX" DOUBLE,
+	"ROTY" DOUBLE,
+	"ROTZ" DOUBLE,
+	"ROTORDER" INT,
+	"LABEL" TEXT
+);
+--
+-- The DATA2TAG tables associate specific rows of the corresponding
+-- _DATA table with the referenced tag (from the HVS_TAG2NODE table).
+-- This is a many-to-many relationship: each row may belong to
+-- several tags, and each tag may apply to several rows.
+DROP TABLE IF EXISTS "VETONUTOPLEVEL_DATA2TAG";
+CREATE TABLE IF NOT EXISTS "VETONUTOPLEVEL_DATA2TAG" (
+	"VETONUTOPLEVEL_TAG_ID" SLONGLONG,
+	"VETONUTOPLEVEL_DATA_ID" SLONGLONG
+);
+--
+DROP TABLE IF EXISTS "VETONUSTATIONGENERAL_DATA";
+CREATE TABLE IF NOT EXISTS "VETONUSTATIONGENERAL_DATA" (
+	"VETONUSTATIONGENERAL_DATA_ID" SLONGLONG UNIQUE,
+	"NUMPLATES" INT,
+	"PLATEPITCH" DOUBLE
+);
+--
+DROP TABLE IF EXISTS "VETONUSTATIONGENERAL_DATA2TAG";
+CREATE TABLE IF NOT EXISTS "VETONUSTATIONGENERAL_DATA2TAG" (
+	"VETONUSTATIONGENERAL_TAG_ID" SLONGLONG,
+	"VETONUSTATIONGENERAL_DATA_ID" SLONGLONG
+);
+--
+DROP TABLE IF EXISTS "VETONUPLATEGENERAL_DATA";
+CREATE TABLE IF NOT EXISTS "VETONUPLATEGENERAL_DATA" (
+	"VETONUPLATEGENERAL_DATA_ID" SLONGLONG UNIQUE,
+	"NUMPMTS" INT,
+	"WIDTH" DOUBLE,
+	"LENGTH" DOUBLE,
+	"THICKNESS" DOUBLE,
+	"MATERIAL" TEXT
+);
+--
+DROP TABLE IF EXISTS "VETONUPLATEGENERAL_DATA2TAG";
+CREATE TABLE IF NOT EXISTS "VETONUPLATEGENERAL_DATA2TAG" (
+	"VETONUPLATEGENERAL_TAG_ID" SLONGLONG,
+	"VETONUPLATEGENERAL_DATA_ID" SLONGLONG
+);
+--
+DROP TABLE IF EXISTS "VETONURADIATORGENERAL_DATA";
+CREATE TABLE IF NOT EXISTS "VETONURADIATORGENERAL_DATA" (
+	"VETONURADIATORGENERAL_DATA_ID" SLONGLONG UNIQUE,
+	"WIDTH" DOUBLE,
+	"LENGTH" DOUBLE,
+	"THICKNESS" DOUBLE,
+	"MATERIAL" TEXT
+);
+--
+DROP TABLE IF EXISTS "VETONURADIATORGENERAL_DATA2TAG";
+CREATE TABLE IF NOT EXISTS "VETONURADIATORGENERAL_DATA2TAG" (
+	"VETONURADIATORGENERAL_TAG_ID" SLONGLONG,
+	"VETONURADIATORGENERAL_DATA_ID" SLONGLONG
+);
+--
 -- Tables for describing Trigger scintillator plates (and passive radiators)
 --
 DROP TABLE IF EXISTS "TRIGGERTOPLEVEL_DATA";
@@ -583,6 +671,22 @@ CREATE TABLE IF NOT EXISTS "VETOSWITCHES_DATA2TAG" (
 	"VETOSWITCHES_DATA_ID" SLONGLONG
 );
 --
+DROP TABLE IF EXISTS "VETONUSWITCHES_DATA";
+CREATE TABLE IF NOT EXISTS "VETONUSWITCHES_DATA" ( 
+	"VETONUSWITCHES_DATA_ID" SLONGLONG UNIQUE,
+	"DETECTORNAME" TEXT ,
+	"USEMAGFIELDSVC" INT ,
+	"COSMICLAYOUT" INT ,
+	"VERSIONNAME" TEXT ,
+	"LAYOUT" TEXT ,
+	"DESCRIPTION" TEXT
+);
+DROP TABLE IF EXISTS "VETONUSWITCHES_DATA2TAG";
+CREATE TABLE IF NOT EXISTS "VETONUSWITCHES_DATA2TAG" (
+	"VETONUSWITCHES_TAG_ID" SLONGLONG,
+	"VETONUSWITCHES_DATA_ID" SLONGLONG
+);
+--
 DROP TABLE IF EXISTS "TRIGGERSWITCHES_DATA";
 CREATE TABLE IF NOT EXISTS "TRIGGERSWITCHES_DATA" ( 
 	"TRIGGERSWITCHES_DATA_ID" SLONGLONG UNIQUE,
@@ -631,6 +735,24 @@ CREATE TABLE IF NOT EXISTS "ECALSWITCHES_DATA2TAG" (
 	"ECALSWITCHES_TAG_ID" SLONGLONG,
 	"ECALSWITCHES_DATA_ID" SLONGLONG
 );
+--
+DROP TABLE IF EXISTS "TRENCHSWITCHES_DATA";
+CREATE TABLE IF NOT EXISTS "TRENCHSWITCHES_DATA" ( 
+	"TRENCHSWITCHES_DATA_ID" SLONGLONG UNIQUE,
+	"DETECTORNAME" TEXT ,
+	"USEMAGFIELDSVC" INT ,
+	"COSMICLAYOUT" INT ,
+	"VERSIONNAME" TEXT ,
+	"LAYOUT" TEXT ,
+	"DESCRIPTION" TEXT,
+	"GDMLFILE" TEXT
+);
+DROP TABLE IF EXISTS "TRENCHSWITCHES_DATA2TAG";
+CREATE TABLE IF NOT EXISTS "TRENCHSWITCHES_DATA2TAG" (
+	"TRENCHSWITCHES_TAG_ID" SLONGLONG,
+	"TRENCHSWITCHES_DATA_ID" SLONGLONG
+);
+
 --
 DROP TABLE IF EXISTS "NEUTRINOIDENTIFIER_DATA";
 CREATE TABLE IF NOT EXISTS "NEUTRINOIDENTIFIER_DATA" ( 
@@ -689,6 +811,9 @@ CREATE TABLE IF NOT EXISTS "CALOIDENTIFIER_DATA2TAG" (
 -- Data for the HVS_NODE table
 INSERT INTO "HVS_NODE" VALUES (0,    "FASER", 0, 1, NULL);
 INSERT INTO "HVS_NODE" VALUES (90,   "FaserCommon", 0, 0, NULL);
+INSERT INTO "HVS_NODE" VALUES (5,    "Cavern", 0, 1, NULL);
+INSERT INTO "HVS_NODE" VALUES (51,   "Trench", 5, 1, NULL);
+INSERT INTO "HVS_NODE" VALUES (514,  "TrenchSwitches", 0, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (9000, "Materials", 0, 1, NULL);
 INSERT INTO "HVS_NODE" VALUES (9001, "StdMaterials", 9000, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (9002, "StdMatComponents", 9000, 0, NULL);
@@ -703,6 +828,7 @@ INSERT INTO "HVS_NODE" VALUES (111,  "EmulsionGeneral", 11, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (112,  "EmulsionFilm", 11, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (113,  "EmulsionPlates", 11, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (114,  "EmulsionSwitches", 11, 0, NULL);
+INSERT INTO "HVS_NODE" VALUES (115,  "EmulsionSupport", 11, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (2,    "Scintillator", 0, 1, NULL);
 INSERT INTO "HVS_NODE" VALUES (2003, "ScintMaterials", 2, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (2004, "ScintMatComponents", 2, 0, NULL);
@@ -725,6 +851,12 @@ INSERT INTO "HVS_NODE" VALUES (232,  "PreshowerPlateGeneral", 23, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (234,  "PreshowerSwitches", 23, 0, NULL );
 INSERT INTO "HVS_NODE" VALUES (235,  "PreshowerRadiatorGeneral", 23, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (236,  "PreshowerAbsorberGeneral", 23, 0, NULL);
+INSERT INTO "HVS_NODE" VALUES (24,   "VetoNu", 2, 1, NULL);
+INSERT INTO "HVS_NODE" VALUES (240,  "VetoNuTopLevel", 24, 0, NULL);
+INSERT INTO "HVS_NODE" VALUES (241,  "VetoNuStationGeneral", 24, 0, NULL);
+INSERT INTO "HVS_NODE" VALUES (242,  "VetoNuPlateGeneral", 24, 0, NULL);
+INSERT INTO "HVS_NODE" VALUES (244,  "VetoNuSwitches", 24, 0, NULL );
+INSERT INTO "HVS_NODE" VALUES (245,  "VetoNuRadiatorGeneral", 24, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (3,    "Tracker", 0, 1, NULL);
 INSERT INTO "HVS_NODE" VALUES (3003, "TrackerMaterials", 3, 0, NULL);
 INSERT INTO "HVS_NODE" VALUES (3004, "TrackerMatComponents", 3, 0, NULL);
@@ -762,30 +894,40 @@ INSERT INTO "HVS_TAG2NODE" VALUES (0, "FASER-CR", 107784, NULL, 0, 0, 1598400000
 INSERT INTO "HVS_TAG2NODE" VALUES (0, "FASER-02", 107788, NULL, 0, 0, 1619222400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (0, "FASERNU-02", 107804, NULL, 0, 0, 1619308800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (0, "FASER-TB00", 107834, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (0, "FASERNU-03", 107835, NULL, 0, 0,   1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (90,   "FaserCommon-00", 100013, NULL, 0, 0, 1549324800000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (5,  "Cavern-00", 107850, NULL, 0, 0, 1652313600000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (51, "Trench-00", 107851, NULL, 0, 0, 1652313600000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (514,"TrenchSwitches-00", 107852, NULL, 0, 0, 1652313600000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (9000, "Materials-00", 100005, NULL, 0, 0, 1549238400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (9001, "StdMaterials-00", 100006, NULL, 0, 0, 1549238400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (9002, "StdMatComponents-00", 100007, NULL, 0, 0, 1549238400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (9003, "Elements-00", 100008, NULL, 0, 0, 1549238400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (1, "Neutrino-00", 100031, NULL, 0, 0, 1582416000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (1, "Neutrino-TB00", 107811, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (1, "Neutrino-01", 107839, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (11,"Emulsion-00", 100034, NULL, 0, 0, 1582416000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (11,"Emulsion-TB00", 107812, NULL, 0, 0, 1582416000000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (11,"Emulsion-01", 107840, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (110, "EmulsionTopLevel-00", 100035, NULL, 0, 0, 1582416000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (110, "EmulsionTopLevel-TB00", 107813, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (110, "EmulsionTopLevel-01", 107841, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (111, "EmulsionGeneral-00", 107805, NULL, 0, 0, 1619308800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (112, "EmulsionFilm-00", 107806, NULL, 0, 0, 1619308800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (113, "EmulsionPlates-00", 107807, NULL, 0, 0, 1619308800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (114, "EmulsionSwitches-00", 100036, NULL, 0, 0, 1582416000000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (115, "EmulsionSupport-00", 107842, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (2, "Scintillator-00", 100001, NULL, 0, 0, 1549238400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (2, "Scintillator-01", 100042, NULL, 0, 0, 1590796800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (2, "Scintillator-02", 107789, NULL, 0, 0, 1619222400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (2, "Scintillator-TB00", 107814, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (2, "Scintillator-03", 107843, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (3, "Tracker-00", 100002, NULL, 0, 0, 1549238400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (3, "Tracker-01", 100038, NULL, 0, 0, 1590796800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (3, "Tracker-CR", 107783, NULL, 0, 0, 1598400000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (3, "Tracker-02", 107790, NULL, 0, 0, 1619222400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (3, "Tracker-TB00", 107815, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (3, "Tracker-03", 107836, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (31, "SCT-00", 100026, NULL, 0, 0, 1567987200000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (31, "SCT-01", 100037, NULL, 0, 0, 1159079680000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (31, "SCT-CR", 107781, NULL, 0, 0, 1598400000000000000, NULL, 22);
@@ -795,6 +937,7 @@ INSERT INTO "HVS_TAG2NODE" VALUES (32, "Dipole-00", 100027, NULL, 0, 0, 15686784
 INSERT INTO "HVS_TAG2NODE" VALUES (32, "Dipole-01", 100041, NULL, 0, 0, 1590796800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (32, "Dipole-02", 107792, NULL, 0, 0, 1619222400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (32, "Dipole-TB00", 107817, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (32, "Dipole-03", 107837, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (310, "SctTopLevel-00", 106788, NULL, 0, 0, 1567987200000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (310, "SctTopLevel-01", 106790, NULL, 0, 0, 1590796800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (310, "SctTopLevel-CR", 107786, NULL, 0, 0, 1598400000000000000, NULL, 22);
@@ -816,6 +959,7 @@ INSERT INTO "HVS_TAG2NODE" VALUES (320, "DipoleTopLevel-00", 100029, NULL, 0, 0,
 INSERT INTO "HVS_TAG2NODE" VALUES (320, "DipoleTopLevel-01", 100040, NULL, 0, 0, 1590796800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (320, "DipoleTopLevel-02", 107794, NULL, 0, 0, 1619222400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (320, "DipoleTopLevel-TB00", 107819, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (320, "DipoleTopLevel-03", 107838, NULL, 0, 0, 1652054400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (321, "DipoleGeneral-00", 100004, NULL, 0, 0, 1568678400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (324, "DipoleSwitches-00", 100028, NULL, 0, 0, 1568678400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (4, "Calorimeter-00", 100003, NULL, 0, 0, 1549238400000000000, NULL, 22);
@@ -856,6 +1000,12 @@ INSERT INTO "HVS_TAG2NODE" VALUES (231,  "PreshowerStationGeneral-01", 100052, N
 INSERT INTO "HVS_TAG2NODE" VALUES (232,  "PreshowerPlateGeneral-00", 120025, NULL, 0, 0, 1581292800000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (235,  "PreshowerRadatorGeneral-00", 107808, NULL, 0, 0, 1627776000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (236,  "PreshowerAbsorberGeneral-00", 107809, NULL, 0, 0, 1627776000000000000, NULL, 22);
+
+INSERT INTO "HVS_TAG2NODE" VALUES (240,  "VetoNuTopLevel-00", 107845, NULL, 0, 0, 1652054400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (241,  "VetoNuStationGeneral-00", 107846, NULL, 0, 0, 1652054400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (242,  "VetoNuPlateGeneral-00", 107847, NULL, 0, 0, 1652054400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (245,  "VetoNuRadiatorGeneral-00", 107849, NULL, 0, 0, 1652054400000000000, NULL, 22);
+
 INSERT INTO "HVS_TAG2NODE" VALUES (1003, "NeutrinoMaterials-00", 100032, NULL, 0, 0, 1582416000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (1004, "NeutrinoMatComponents-00", 100033, NULL, 0, 0, 1582416000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (2003, "ScintMaterials-00", 100011, NULL, 0, 0, 1549238400000000000, NULL, 22);
@@ -867,6 +1017,7 @@ INSERT INTO "HVS_TAG2NODE" VALUES (4004, "CaloMatComponents-00", 100024, NULL, 0
 INSERT INTO "HVS_TAG2NODE" VALUES (214,  "VetoSwitches-00", 100014, NULL, 0, 0, 1550361600000000000 ,NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (224,  "TriggerSwitches-00", 110014, NULL, 0, 0, 1581292800000000000 ,NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (234,  "PreshowerSwitches-00", 120014, NULL, 0, 0, 1550361600000000000 ,NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (244,  "VetoNuSwitches-00", 107848, NULL, 0, 0, 1550361600000000000 ,NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (21, "Veto-00", 100015, NULL, 0, 0, 1550448000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (22, "Trigger-00", 100019, NULL, 0, 0, 1550448000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (23, "Preshower-00", 100020, NULL, 0, 0, 1550448000000000000, NULL, 22);
@@ -879,6 +1030,8 @@ INSERT INTO "HVS_TAG2NODE" VALUES (23, "Preshower-02", 107803, NULL, 0, 0, 16192
 INSERT INTO "HVS_TAG2NODE" VALUES (21, "Veto-TB00", 107826, NULL, 0, 0, 1627862400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (22, "Trigger-TB00", 107827, NULL, 0, 0, 1627862400000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (23, "Preshower-TB00", 107828, NULL, 0, 0, 1627862400000000000, NULL, 22);
+INSERT INTO "HVS_TAG2NODE" VALUES (24, "VetoNu-00", 107844, NULL, 0, 0, 1652054400000000000, NULL, 22);
+
 INSERT INTO "HVS_TAG2NODE" VALUES (1005, "NeutrinoIdentifier-00", 100030, NULL, 0, 0, 1582416000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (2005, "ScintIdentifier-00", 100016, NULL, 0, 0, 1550448000000000000, NULL, 22);
 INSERT INTO "HVS_TAG2NODE" VALUES (2005, "ScintIdentifier-TB00", 107832, NULL, 0, 0, 1627862400000000000, NULL, 22);
@@ -890,36 +1043,51 @@ INSERT INTO "HVS_TAG2NODE" VALUES (4005, "CaloIdentifier-00", 100018, NULL, 0, 0
 INSERT INTO "HVS_TAG2NODE" VALUES (4005, "CaloIdentifier-TB00", 107831, NULL, 0, 0, 1627862400000000000, NULL, 22);
 -- Data for the HVS_LTAG2LTAG table
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107804, 1,    100031);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107835, 1,    107839);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107834, 1,    107811);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100000, 2,    100001);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100039, 2,    100042);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107784, 2,    100042);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107788, 2,    107789);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107804, 2,    107789);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107834, 2,    107814);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107835, 2,    107843);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100000, 3,    100002);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100039, 3,    100038);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107784, 3,    107783);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107788, 3,    107790);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107804, 3,    107790);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107834, 3,    107815);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107835, 3,    107836);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100000, 4,    100003);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100039, 4,    100003);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107784, 4,    100003);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107788, 4,    107795);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107804, 4,    107795);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107834, 4,    107820);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107835, 4,    107795);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100000, 90,   100013);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100039, 90,   100013);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107788, 90,   100013);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107804, 90,   100013);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107834, 90,   100013);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107835, 90,   100013);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107835, 5,    107850);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (5,   107850, 51,   107851);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (51,  107835, 514,  107852);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100000, 9000,  100005);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   100039, 9000,  100005);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107784, 9000,  100005);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107788, 9000,  100005);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107804, 9000,  100005);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107834, 9000,  100005);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (0,   107835, 9000,  100005);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (9000, 100005, 9001,  100006);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (9000, 100005, 9002,  100007);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (9000, 100005, 9003,  100008);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   100031,  11,  100034);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107811,  11,  107812);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107839,  11,  107840);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  100034, 110,  100035);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  100034, 111,  107805);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  100034, 112,  107806);
@@ -930,12 +1098,21 @@ INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107812, 111,  107805);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107812, 112,  107806);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107812, 113,  107807);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107812, 114,  100036);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107840, 110,  107841);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107840, 111,  107805);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107840, 112,  107806);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107840, 113,  107807);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107840, 114,  100036);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (11,  107840, 115,  107842);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   100031, 1003, 100032);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   100031, 1004, 100033);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   100031, 1005, 100030);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107811, 1003, 100032);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107811, 1004, 100033);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107811, 1005, 100030);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107839, 1003, 100032);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107839, 1004, 100033);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (1,   107839, 1005, 100030);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   100001, 21,   100015);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   100001, 22,   100019);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   100001, 23,   100020);
@@ -948,6 +1125,10 @@ INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107789, 23,   107803);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107814, 21,   107826);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107814, 22,   107827);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107814, 23,   107828);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107843, 21,   107801);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107843, 22,   107802);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107843, 23,   107803);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107843, 24,   107844);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   100001, 2003, 100011);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   100001, 2004, 100012);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   100001, 2005, 100016);
@@ -960,6 +1141,9 @@ INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107789, 2005, 100016);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107814, 2003, 100011);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107814, 2004, 100012);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107814, 2005, 100016);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107843, 2003, 100011);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107843, 2004, 100012);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (2,   107843, 2005, 100016);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  100015, 210,  100009);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  100043, 210,  100046);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  107801, 210,  100046);
@@ -976,6 +1160,7 @@ INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  100015, 214,  100014);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  100043, 214,  100014);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  107801, 214,  100014);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  107826, 214,  100014);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (21,  107801, 215,  107810);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (22,  100019, 220,  110009);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (22,  100044, 220,  100047);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (22,  107802, 220,  100047);
@@ -1008,28 +1193,40 @@ INSERT INTO "HVS_LTAG2LTAG" VALUES (23,  100020, 234,  120014);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (23,  100045, 234,  120014);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (23,  107803, 234,  120014);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (23,  107828, 234,  120014);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (23,  107803, 235,  107808);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (23,  107803, 236,  107809);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (24,  107844, 240,  107845);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (24,  107844, 241,  107846);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (24,  107844, 242,  107847);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (24,  107844, 244,  107848);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (24,  107844, 245,  107849);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100002, 31,   100026);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100038, 31,   100037);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107783, 31,   107781);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107790, 31,   107791);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107815, 31,   107816);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107836, 31,   107791);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100002, 32,   100027);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100038, 32,   100041);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107790, 32,   107792);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107815, 32,   107817);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107836, 32,   107837);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100002, 3003, 100021);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100038, 3003, 100021);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107790, 3003, 100021);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107815, 3003, 100021);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107836, 3003, 100021);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100002, 3004, 100022);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100038, 3004, 100022);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107790, 3004, 100022);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107815, 3004, 100022);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107836, 3004, 100022);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100002, 3005, 100017);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   100038, 3005, 100017);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107781, 3005, 107785);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107790, 3005, 107787);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107815, 3005, 107829);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (3,   107836, 3005, 107787);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (31,  100026, 310,  106788);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (31,  100037, 310,  106790);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (31,  100081, 310,  107786);
@@ -1074,14 +1271,17 @@ INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  100027, 320,  100029);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  100041, 320,  100040);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107792, 320,  107794);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107817, 320,  107819);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107837, 320,  107838);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  100027, 321,  100004);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  100041, 321,  100004);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107792, 321,  100004);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107817, 321,  100004);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107837, 321,  100004);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  100027, 324,  100028);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  100041, 324,  100028);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107792, 324,  100028);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107817, 324,  100028);
+INSERT INTO "HVS_LTAG2LTAG" VALUES (32,  107837, 324,  100028);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (4,   100003, 41,   100056);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (4,   107795, 41,   107796);
 INSERT INTO "HVS_LTAG2LTAG" VALUES (4,   107820, 41,   107821);
@@ -1489,6 +1689,83 @@ INSERT INTO "HVS_TAGCACHE" VALUES ("FASER-TB00", "NeutrinoIdentifier", "Neutrino
 INSERT INTO "HVS_TAGCACHE" VALUES ("FASER-TB00", "ScintIdentifier",    "ScintIdentifier-TB00",    107832);
 INSERT INTO "HVS_TAGCACHE" VALUES ("FASER-TB00", "TrackerIdentifier",  "TrackerIdentifier-TB00",  107829);
 INSERT INTO "HVS_TAGCACHE" VALUES ("FASER-TB00", "CaloIdentifier",     "CaloIdentifier-TB00",     107831);
+--
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "FASER",              "FASERNU-03",              107835);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "FaserCommon",        "FaserCommon-00",          100013);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Cavern",             "Cavern-00",               107850);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Trench",             "Trench-00",               107851);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TrenchSwitches",     "TrenchSwitches-00",       107852);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Materials",          "Materials-00",            100005);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "StdMaterials",       "StdMaterials-00",         100006);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "StdMatComponents",   "StdMatComponents-00",     100007);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Elements",           "Elements-00",             100008);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Neutrino",           "Neutrino-01",             107839);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Emulsion",           "Emulsion-01",             107840);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EmulsionTopLevel",   "EmulsionTopLevel-01",     107841);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EmulsionGeneral",    "EmulsionGeneral-00",      107805);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EmulsionFilm",       "EmulsionFilm-00",         107806);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EmulsionPlates",     "EmulsionPlates-00",       107807);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EmulsionSwitches",   "EmulsionSwitches-00",     100036);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EmulsionSupport",    "EmulsionSupport-00",      107842);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Scintillator",       "Scintillator-03",         107843);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Tracker",            "Tracker-03",              107836);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SCT",                "SCT-02",                  107791);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctTopLevel",        "SCTTopLevel-02",          107793);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctBrlModule",       "SCTBrlModule-00",         107003);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctBrlSensor",       "SCTBrlSensor-00",         106730);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctFaserGeneral",    "SCTFaserGeneral-01",      106791);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctSwitches",        "SCTSwitches-00",          107782);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SCTMaterials",       "SCTMaterials-00",         107777);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SCTMatComponents",   "SCTMatComponents-00",     107778);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctConditions",      "SctConditions-00",        107779);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctFrame",           "SctFrame-00",             100053);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctFrameGeneral",    "SctFrameGeneral-00",      100054);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "SctFrameShape",      "SctFrameShape-00",        100055);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Dipole",             "Dipole-03",               107837);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "DipoleTopLevel",     "DipoleTopLevel-03",       107838);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "DipoleGeneral",      "DipoleGeneral-00",        100004);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "DipoleSwitches",     "DipoleSwitches-00",       100028);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Calorimeter",        "Calorimeter-02",          107795);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Ecal",               "Ecal-02",                 107796);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EcalTopLevel",       "EcalTopLevel-02",         107797);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EcalRowGeneral",     "EcalRowGeneral-00",       100059);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "EcalSwitches",       "EcalSwitches-00",         100057);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoTopLevel",       "VetoTopLevel-02",         107798);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoStationGeneral", "VetoStationGeneral-01",   100049);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoPlateGeneral",   "VetoPlateGeneral-01",     100050);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoRadiatorGeneral",   "VetoRadiatorGeneral-00", 107810);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoNuTopLevel",       "VetoNuTopLevel-00",        107845);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoNuStationGeneral", "VetoNuStationGeneral-00",  107846);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoNuPlateGeneral",   "VetoNuPlateGeneral-00",    107847);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoNuRadiatorGeneral","VetoNuRadiatorGeneral-00", 107849);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TriggerTopLevel",       "TriggerTopLevel-02",         107799);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TriggerStationGeneral", "TriggerStationGeneral-01",   100051);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TriggerPlateGeneral",   "TriggerPlateGeneral-00",     110025);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "PreshowerTopLevel",     "PreshowerTopLevel-02",         107800);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "PreshowerStationGeneral", "PreshowerStationGeneral-01",   100052);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "PreshowerPlateGeneral",   "PreshowerPlateGeneral-00",     120025);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "PreshowerRadiatorGeneral",   "PreshowerRadiatorGeneral-00",     107808);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "PreshowerAbsorberGeneral",   "PreshowerAbsorberGeneral-00",     107809);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "NeutrinoMaterials",     "NeutrinoMaterials-00",       100032);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "NeutrinoMatComponents", "NeutrinoMatComponents-00",   100033);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "ScintMaterials",     "ScintMaterials-00",       100011);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "ScintMatComponents", "ScintMatComponents-00",   100012);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TrackerMaterials",     "TrackerMaterials-00",     100021);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TrackerMatComponents", "TrackerMatComponents-00", 100022);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "CaloMaterials",     "CaloMaterials-00",         100023);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "CaloMatComponents", "CaloMatComponents-00",     100024);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoSwitches",       "VetoSwitches-00",         100014);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TriggerSwitches",    "TriggerSwitches-00",      110014);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "PreshowerSwitches",  "PreshowerSwitches-00",    120014);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoNuSwitches",     "VetoNuSwitches-00",       107848);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Veto",               "Veto-02",                 107801);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Trigger",            "Trigger-02",              107802);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "Preshower",          "Preshower-02",            107803);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "VetoNu",               "VetoNu-00",             107844);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "NeutrinoIdentifier", "NeutrinoIdentifier-00",   100030);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "ScintIdentifier",    "ScintIdentifier-00",      100016);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "TrackerIdentifier",  "TrackerIdentifier-02",     107787);
+INSERT INTO "HVS_TAGCACHE" VALUES ("FASERNU-03", "CaloIdentifier",     "CaloIdentifier-00",       100018);
 -- 
 --
 -- Part 2b: Content (Leaf node) data
@@ -2033,15 +2310,25 @@ INSERT INTO "ELEMENTS_DATA2TAG" VALUES (100008, 91);
 --
 INSERT INTO "EMULSIONTOPLEVEL_DATA" VALUES (0, 0.0, 0.0, -2475.72, 0.0, 0.0, 0.0, 321, "Emulsion");
 INSERT INTO "EMULSIONTOPLEVEL_DATA" VALUES (1, 0.0, 0.0,     0.0, 0.0, 0.0, 0.0, 321, "StationA");
+INSERT INTO "EMULSIONTOPLEVEL_DATA" VALUES (2, 10.0, -21.0, -2510.27, 0.0, 0.0, 0.0, 321, "Emulsion");
 INSERT INTO "EMULSIONTOPLEVEL_DATA2TAG" VALUES (100035, 0);
 INSERT INTO "EMULSIONTOPLEVEL_DATA2TAG" VALUES (100035, 1);
-INSERT INTO "EMULSIONTOPLEVEL_DATA2TAG" VALUES (107813, 0);
+INSERT INTO "EMULSIONTOPLEVEL_DATA2TAG" VALUES (107841, 2);
+INSERT INTO "EMULSIONTOPLEVEL_DATA2TAG" VALUES (107841, 1);
 --
 --
 INSERT INTO "EMULSIONGENERAL_DATA" VALUES (0, 35, 22, -524.275, 525.275);
 INSERT INTO "EMULSIONGENERAL_DATA2TAG" VALUES (107805, 0);
 --
 --
+INSERT INTO "EMULSIONSUPPORT_DATA" VALUES (0, "std::SSteel", 250.0, 300.0, 20.0, 0.0, 0.0, 535.45, "BoxBack");
+INSERT INTO "EMULSIONSUPPORT_DATA" VALUES (1, "std::SSteel", 250.0, 300.0, 30.0, 0.0, 0.0, -540.45, "Pusher");
+INSERT INTO "EMULSIONSUPPORT_DATA" VALUES (2, "std::SSteel", 250.0, 300.0, 20.0, 0.0, 0.0, -604.55, "BoxFront");
+INSERT INTO "EMULSIONSUPPORT_DATA2TAG" VALUES (107842, 0);
+INSERT INTO "EMULSIONSUPPORT_DATA2TAG" VALUES (107842, 1);
+INSERT INTO "EMULSIONSUPPORT_DATA2TAG" VALUES (107842, 2);
+--
+--
 INSERT INTO "EMULSIONFILM_DATA" VALUES (0, 250.0, 300.0, 0.210, "neutrino::Polystyrene", 250.0, 300.0, 0.070, "neutrino::Emulsion");
 INSERT INTO "EMULSIONFILM_DATA2TAG" VALUES (107806, 0);
 --
@@ -2122,6 +2409,19 @@ INSERT INTO "VETORADIATORGENERAL_DATA" VALUES (0, 400.0, 350.0, 100.0, "std::Lea
 INSERT INTO "VETORADIATORGENERAL_DATA2TAG" VALUES (107810, 0);
 --
 --
+INSERT INTO "VETONUTOPLEVEL_DATA" VALUES (0, 10.0, -21.0, -3112.0, 0.0, 0.0, 0.0, 321, "VetoNu");
+INSERT INTO "VETONUTOPLEVEL_DATA" VALUES (1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 321, "StationA");
+INSERT INTO "VETONUTOPLEVEL_DATA2TAG" VALUES (107845, 0);
+INSERT INTO "VETONUTOPLEVEL_DATA2TAG" VALUES (107845, 1);
+--
+INSERT INTO "VETONUSTATIONGENERAL_DATA" VALUES (0, 2, 21.0);
+INSERT INTO "VETONUSTATIONGENERAL_DATA2TAG" VALUES (107846, 0);
+--
+--
+INSERT INTO "VETONUPLATEGENERAL_DATA" VALUES (0, 1, 300.0, 350.0, 20.0, "scint::Scintillator");
+INSERT INTO "VETONUPLATEGENERAL_DATA2TAG" VALUES (107847, 0);
+--
+--
 INSERT INTO "TRIGGERTOPLEVEL_DATA" VALUES (0, 0.0, 0.0, 187.0, 0.0, 0.0, 0.0, 321, "Trigger");
 INSERT INTO "TRIGGERTOPLEVEL_DATA" VALUES (1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 321, "StationA");
 INSERT INTO "TRIGGERTOPLEVEL_DATA" VALUES (2, 0.0, -5.0, -28.9, 0.0, 0.0, 0.0, 321, "Trigger");
@@ -2227,6 +2527,10 @@ INSERT INTO "VETOSWITCHES_DATA" VALUES  (0, "Veto", 1, 0, "GEO", "Development",
 INSERT INTO "VETOSWITCHES_DATA2TAG" VALUES (100014, 0);
 --
 --
+INSERT INTO "VETONUSWITCHES_DATA" VALUES  (0, "VetoNu", 1, 0, "GEO", "Development", "Baseline geometry");
+INSERT INTO "VETONUSWITCHES_DATA2TAG" VALUES (107848, 0);
+--
+--
 INSERT INTO "TRIGGERSWITCHES_DATA" VALUES  (0, "Trigger", 1, 0, "GEO", "Development", "Baseline geometry");
 INSERT INTO "TRIGGERSWITCHES_DATA2TAG" VALUES (110014, 0);
 --
@@ -2239,6 +2543,10 @@ INSERT INTO "ECALSWITCHES_DATA" VALUES ( 0, "Ecal", 1, 0, "GEO", "Development",
 INSERT INTO "ECALSWITCHES_DATA2TAG" VALUES (100057, 0);
 --
 --
+INSERT INTO "TRENCHSWITCHES_DATA" VALUES ( 0, "Trench", 1, 0, "GEO", "Development", "Baseline geometry", "TrenchGeoModel/Trench.gdml");
+INSERT INTO "TRENCHSWITCHES_DATA2TAG" VALUES (107852, 0);
+--
+--
 INSERT INTO "NEUTRINOIDENTIFIER_DATA" VALUES (0, "Neutrino", "NeutrinoIdDictFiles/IdDictNeutrino.xml", "Baseline layout");
 INSERT INTO "NEUTRINOIDENTIFIER_DATA2TAG" VALUES (100030, 0);
 --
@@ -2443,6 +2751,7 @@ INSERT INTO "DIPOLETOPLEVEL_DATA" VALUES(7,0.0,0.0,-812.60,0.0,0.0,0.0,312,'Upst
 INSERT INTO "DIPOLETOPLEVEL_DATA" VALUES(8,0.0,0.0, 637.40,0.0,0.0,0.0,312,'CentralDipole');
 INSERT INTO "DIPOLETOPLEVEL_DATA" VALUES(9,0.0,0.0,1837.40,0.0,0.0,0.0,312,'DownstreamDipole');
 INSERT INTO "DIPOLETOPLEVEL_DATA" VALUES(10,0.0,0.0, 0.0,0.0,0.0,0.0,312,'Dipole');
+INSERT INTO "DIPOLETOPLEVEL_DATA" VALUES(11,0.0,0.0,-815.30,0.0,0.0,0.0,312,'UpstreamDipole');  -- shifted 2.7 mm upstream
 
 DROP TABLE IF EXISTS "DIPOLETOPLEVEL_DATA2TAG";
 CREATE TABLE "DIPOLETOPLEVEL_DATA2TAG" ( "DIPOLETOPLEVEL_TAG_ID" SLONGLONG ,"DIPOLETOPLEVEL_DATA_ID" SLONGLONG  );
@@ -2459,6 +2768,11 @@ INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107794,7);
 INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107794,8);
 INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107794,9);
 INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107819,10);
+INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107838,11);
+INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107838,3);
+INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107838,8);
+INSERT INTO "DIPOLETOPLEVEL_DATA2TAG" VALUES(107838,9);
+
 --
 --
 DROP TABLE IF EXISTS "DIPOLEGENERAL_DATA";
diff --git a/DetectorDescription/GeoModel/FaserGeoModel/python/FaserGeoModelConfig.py b/DetectorDescription/GeoModel/FaserGeoModel/python/FaserGeoModelConfig.py
index 72e1d79a4266d82191dfef3141587dea7ac460d3..d1390bf949c6322de99495b12ba0ade905fce47e 100644
--- a/DetectorDescription/GeoModel/FaserGeoModel/python/FaserGeoModelConfig.py
+++ b/DetectorDescription/GeoModel/FaserGeoModel/python/FaserGeoModelConfig.py
@@ -23,4 +23,7 @@ def FaserGeometryCfg (flags):
     from EcalGeoModel.EcalGeoModelConfig import EcalGeometryCfg
     acc.merge(EcalGeometryCfg( flags ))   
 
+    from FaserGeoModel.TrenchGMConfig import TrenchGeometryCfg
+    acc.merge(TrenchGeometryCfg( flags ))
+
     return acc
diff --git a/DetectorDescription/GeoModel/FaserGeoModel/python/GeoModelInit.py b/DetectorDescription/GeoModel/FaserGeoModel/python/GeoModelInit.py
index 0911c1db4ec3b16c8720abf755edf2c2568db721..af1ed16dcb630fd08948f6a648d9deb6d46756e2 100644
--- a/DetectorDescription/GeoModel/FaserGeoModel/python/GeoModelInit.py
+++ b/DetectorDescription/GeoModel/FaserGeoModel/python/GeoModelInit.py
@@ -47,6 +47,16 @@ def _setupGeoModel():
         emulsionDetectorTool = EmulsionDetectorTool(DetectorName = "Emulsion", Alignable = True, RDBAccessSvc = "RDBAccessSvc", GeometryDBSvc = "NeutrinoGeometryDBSvc", GeoDbTagSvc = "GeoDbTagSvc")
         geoModelSvc.DetectorTools += [ emulsionDetectorTool ]
 
+    if "FASERNU-03" in DDversion:
+        from VetoNuGeoModel.VetoNuGeoModelConf import VetoNuDetectorTool
+        vetoNuDetectorTool = VetoNuDetectorTool( DetectorName = "VetoNu",
+                                            Alignable = True,
+                                            RDBAccessSvc = "RDBAccessSvc",
+                                            GeometryDBSvc = "ScintGeometryDBSvc",
+                                            GeoDbTagSvc = "GeoDbTagSvc")
+
+        geoModelSvc.DetectorTools += [ vetoNuDetectorTool ]
+
     from VetoGeoModel.VetoGeoModelConf import VetoDetectorTool
     vetoDetectorTool = VetoDetectorTool( DetectorName = "Veto",
                                          Alignable = True,
@@ -112,6 +122,11 @@ def _setupGeoModel():
 
     geoModelSvc.DetectorTools += [ ecalDetectorTool ]
 
+    if "FASERNU-03" in DDversion:
+        from TrenchGeoModel.TrenchGeoModelConf import TrenchDetectorTool
+        trenchDetectorTool = TrenchDetectorTool( )
+
+        geoModelSvc.DetectorTools += [ trenchDetectorTool ]
 
 
     pass
diff --git a/DetectorDescription/GeoModel/FaserGeoModel/python/ScintGMConfig.py b/DetectorDescription/GeoModel/FaserGeoModel/python/ScintGMConfig.py
index 93c42c057852c8b24ac4760ea70a612941c04855..039b5155efd0d9b69278f79cd6a0cbc2fdf69ccc 100644
--- a/DetectorDescription/GeoModel/FaserGeoModel/python/ScintGMConfig.py
+++ b/DetectorDescription/GeoModel/FaserGeoModel/python/ScintGMConfig.py
@@ -6,6 +6,10 @@ from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
 
 def ScintGeometryCfg (flags):
     acc = ComponentAccumulator()
+
+    from VetoNuGeoModel.VetoNuGeoModelConfig import VetoNuGeometryCfg
+    acc.merge(VetoNuGeometryCfg( flags ))
+
     from VetoGeoModel.VetoGeoModelConfig import VetoGeometryCfg
     acc.merge(VetoGeometryCfg( flags ))
 
diff --git a/DetectorDescription/GeoModel/FaserGeoModel/python/TrenchGMConfig.py b/DetectorDescription/GeoModel/FaserGeoModel/python/TrenchGMConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..b53147bcddab5129489d520f75cc01a0ae6e5f74
--- /dev/null
+++ b/DetectorDescription/GeoModel/FaserGeoModel/python/TrenchGMConfig.py
@@ -0,0 +1,13 @@
+#
+#  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+#
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+def TrenchGeometryCfg( flags ):
+    from FaserGeoModel.GeoModelConfig import GeoModelCfg
+    acc = GeoModelCfg( flags )
+
+    if flags.Detector.GeometryTrench:
+        TrenchDetectorTool=CompFactory.TrenchDetectorTool
+        acc.getPrimary().DetectorTools += [ TrenchDetectorTool() ]
+    return acc
diff --git a/DetectorDescription/GeoModel/GeoModelFaserUtilities/src/DecodeFaserVersionKey.cxx b/DetectorDescription/GeoModel/GeoModelFaserUtilities/src/DecodeFaserVersionKey.cxx
index a913211431b395745ecb18e14b537351ec81a7d9..010fa49563a9b8d1bd83d135d132911bb7b9f826 100644
--- a/DetectorDescription/GeoModel/GeoModelFaserUtilities/src/DecodeFaserVersionKey.cxx
+++ b/DetectorDescription/GeoModel/GeoModelFaserUtilities/src/DecodeFaserVersionKey.cxx
@@ -49,6 +49,9 @@ void DecodeFaserVersionKey::defineTag(const T* svc, const std::string & node)
   } else if (node == "Veto") {
     scintOverrideTag = svc->scintVersionOverride();
     nodeOverrideTag = svc->vetoVersionOverride();
+  } else if (node == "VetoNu") {
+    scintOverrideTag = svc->scintVersionOverride();
+    nodeOverrideTag = svc->vetoNuVersionOverride();
   } else if (node == "Trigger") {
     scintOverrideTag = svc->scintVersionOverride();
     nodeOverrideTag = svc->triggerVersionOverride();
@@ -68,11 +71,12 @@ void DecodeFaserVersionKey::defineTag(const T* svc, const std::string & node)
   } else if (node == "Ecal") {
     caloOverrideTag = svc->caloVersionOverride();
     nodeOverrideTag = svc->ecalVersionOverride();
+  } else if (node == "Trench") {
+    nodeOverrideTag = svc->trenchVersionOverride();
   } else {
     std::cout << "DecodeFaserVersionKey passed an unknown node:" << node << std::endl; 
     nodeOverrideTag = "";
   }
-
   // Default to faser version
   m_tag = svc->faserVersion();
   m_node = "FASER";
@@ -137,6 +141,7 @@ void DecodeFaserVersionKey::defineTag(const T* svc, const std::string & node)
     m_tag  = outputTag;
     m_node = node;
   }
+
 }
 
 const std::string &
diff --git a/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoDbTagSvc.h b/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoDbTagSvc.h
index ecb285ff291c3c6b7584d137f07de5d38f970c3d..9f8e103c63901d01d42283a648ca9b8e862e5396 100644
--- a/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoDbTagSvc.h
+++ b/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoDbTagSvc.h
@@ -32,6 +32,7 @@ class IGeoDbTagSvc : virtual public IInterface {
   virtual const std::string & emulsionVersion()                 const =0;
   virtual const std::string & scintVersion()                    const =0;
   virtual const std::string & vetoVersion()                     const =0; 
+  virtual const std::string & vetoNuVersion()                   const =0; 
   virtual const std::string & triggerVersion()                  const =0; 
   virtual const std::string & preshowerVersion()                const =0; 
   virtual const std::string & trackerVersion()                  const =0;
@@ -40,12 +41,13 @@ class IGeoDbTagSvc : virtual public IInterface {
   virtual const std::string & caloVersion()                     const =0;
   virtual const std::string & ecalVersion()                     const =0;
   // virtual const std::string & magFieldVersion()                 const =0;
-  // virtual const std::string & cavernInfraVersion()              const =0;
+  virtual const std::string & trenchVersion()                   const =0;
   
   virtual const std::string & neutrinoVersionOverride()         const =0;
   virtual const std::string & emulsionVersionOverride()         const =0;
   virtual const std::string & scintVersionOverride()            const =0;
   virtual const std::string & vetoVersionOverride()             const =0;
+  virtual const std::string & vetoNuVersionOverride()           const =0;
   virtual const std::string & triggerVersionOverride()          const =0;
   virtual const std::string & preshowerVersionOverride()        const =0;
   virtual const std::string & trackerVersionOverride()          const =0;
@@ -54,7 +56,7 @@ class IGeoDbTagSvc : virtual public IInterface {
   virtual const std::string & caloVersionOverride()             const =0;
   virtual const std::string & ecalVersionOverride()             const =0;
   // virtual const std::string & magFieldVersionOverride()         const =0;
-  // virtual const std::string & cavernInfraVersionOverride()      const =0;
+  virtual const std::string & trenchVersionOverride()           const =0;
   
   virtual GeoModel::GeoConfig geoConfig() const = 0;
 };
diff --git a/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoModelSvc.h b/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoModelSvc.h
index 2268f87fb1f490b135f04aa8ed6c9d7ccb114763..b5c7d10665f1928f2acb22dd43e4f1236dd9995e 100644
--- a/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoModelSvc.h
+++ b/DetectorDescription/GeoModel/GeoModelInterfaces/GeoModelInterfaces/IGeoModelSvc.h
@@ -30,6 +30,7 @@ public:
     virtual const std::string & emulsionVersion()      const =0;
     virtual const std::string & scintVersion()         const =0;
     virtual const std::string & vetoVersion()          const =0;
+    virtual const std::string & vetoNuVersion()          const =0;
     virtual const std::string & triggerVersion()       const =0;
     virtual const std::string & preshowerVersion()     const =0;
     virtual const std::string & trackerVersion()       const =0;
@@ -38,12 +39,13 @@ public:
     virtual const std::string & caloVersion()          const =0;
     virtual const std::string & ecalVersion()          const =0;
     // virtual const std::string & magFieldVersion()      const =0;
-    // virtual const std::string & cavernInfraVersion()   const =0;
+    virtual const std::string & trenchVersion()        const =0;
 
     virtual const std::string & neutrinoVersionOverride()  const =0;
     virtual const std::string & emulsionVersionOverride()  const =0;
     virtual const std::string & scintVersionOverride()     const =0;
     virtual const std::string & vetoVersionOverride()      const =0;
+    virtual const std::string & vetoNuVersionOverride()      const =0;
     virtual const std::string & triggerVersionOverride()   const =0;
     virtual const std::string & preshowerVersionOverride() const =0;
     virtual const std::string & trackerVersionOverride()   const =0;
@@ -52,7 +54,7 @@ public:
     virtual const std::string & caloVersionOverride()      const =0;
     virtual const std::string & ecalVersionOverride()      const =0;
     // virtual const std::string & magFieldVersionOverride()    const =0;
-    // virtual const std::string & cavernInfraVersionOverride() const =0;
+    virtual const std::string & trenchVersionOverride() const =0;
     
     virtual GeoModel::GeoConfig geoConfig() const = 0;
 
diff --git a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.cxx b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.cxx
index f329c0f6d3b599c5a1971cc53ad1f52331c7aba2..7c64133060caaa8bf9036f6fbacb3fff0d978a37 100644
--- a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.cxx
+++ b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.cxx
@@ -84,6 +84,10 @@ StatusCode GeoDbTagSvc::setupTags()
 		    ? rdbAccessSvc->getChildTag("Veto", m_ScintVersion, "Scintillator", "FASERDD") 
 		    : m_VetoVersionOverride);
 
+  m_VetoNuVersion = (m_VetoNuVersionOverride.empty() 
+		    ? rdbAccessSvc->getChildTag("VetoNu", m_ScintVersion, "Scintillator", "FASERDD") 
+		    : m_VetoNuVersionOverride);
+
   m_TriggerVersion = (m_TriggerVersionOverride.empty() 
 		    ? rdbAccessSvc->getChildTag("Trigger", m_ScintVersion, "Scintillator", "FASERDD") 
 		    : m_TriggerVersionOverride);
@@ -116,9 +120,9 @@ StatusCode GeoDbTagSvc::setupTags()
 	// 	       ? rdbAccessSvc->getChildTag("MagneticField",m_AtlasVersion,"ATLAS")
 	// 	       : m_MagFieldVersionOverride);
 
-  // m_CavernInfraVersion = (m_CavernInfraVersionOverride.empty()
-	// 		  ? rdbAccessSvc->getChildTag("CavernInfra",m_AtlasVersion,"ATLAS")
-	// 		  : m_CavernInfraVersionOverride);
+  m_TrenchVersion = (m_TrenchVersionOverride.empty()
+			  ? rdbAccessSvc->getChildTag("Trench", m_FaserVersion,"FASERDD")
+			  : m_TrenchVersionOverride);
 
   // Retrieve geometry config information (RUN1, RUN2, etc...)
   IRDBRecordset_ptr faserCommonRec = rdbAccessSvc->getRecordsetPtr("FaserCommon", m_FaserVersion, "FASER", "FASERDD");
diff --git a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.h b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.h
index c7ed5637321b3e6a4124e39d40c72d8e3091a207..4fb1b0a29038b226a4955c9a9211c612defaa1bd 100644
--- a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.h
+++ b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoDbTagSvc.h
@@ -32,6 +32,7 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   void setEmulsionVersionOverride(const std::string& tag)          { m_EmulsionVersionOverride=tag; }
   void setScintVersionOverride(const std::string& tag)             { m_ScintVersionOverride=tag; }
   void setVetoVersionOverride(const std::string& tag)              { m_VetoVersionOverride=tag; }
+  void setVetoNuVersionOverride(const std::string& tag)            { m_VetoNuVersionOverride=tag; }
   void setTriggerVersionOverride(const std::string& tag)           { m_TriggerVersionOverride=tag; }
   void setPreshowerVersionOverride(const std::string& tag)         { m_PreshowerVersionOverride=tag; }
   void setTrackerVersionOverride(const std::string& tag)           { m_TrackerVersionOverride=tag; }
@@ -40,7 +41,7 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   void setCaloVersionOverride(const std::string& tag)              { m_CaloVersionOverride=tag; }
   void setEcalVersionOverride(const std::string& tag)              { m_EcalVersionOverride=tag; }
   // void setMagFieldVersionOverride(const std::string& tag)          { m_MagFieldVersionOverride=tag; }
-  // void setCavernInfraVersionOverride(const std::string& tag)       { m_CavernInfraVersionOverride=tag; }
+  void setTrenchVersionOverride(const std::string& tag)            { m_TrenchVersionOverride=tag; }
 
   StatusCode setupTags();
 
@@ -51,6 +52,7 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   const std::string & emulsionVersionOverride()          const { return m_EmulsionVersionOverride; }
   const std::string & scintVersionOverride()             const { return m_ScintVersionOverride; }
   const std::string & vetoVersionOverride()              const { return m_VetoVersionOverride; }
+  const std::string & vetoNuVersionOverride()            const { return m_VetoNuVersionOverride; }
   const std::string & triggerVersionOverride()           const { return m_TriggerVersionOverride; }
   const std::string & preshowerVersionOverride()         const { return m_PreshowerVersionOverride; }
   const std::string & trackerVersionOverride()           const { return m_TrackerVersionOverride; }
@@ -59,12 +61,13 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   const std::string & caloVersionOverride()              const { return m_CaloVersionOverride; }
   const std::string & ecalVersionOverride()              const { return m_EcalVersionOverride; }
   // const std::string & magFieldVersionOverride()          const { return m_MagFieldVersionOverride; }
-  // const std::string & cavernInfraVersionOverride()       const { return m_CavernInfraVersionOverride; }
+  const std::string & trenchVersionOverride()            const { return m_TrenchVersionOverride; }
 
   const std::string & neutrinoVersion()                  const { return m_NeutrinoVersion; }
   const std::string & emulsionVersion()                  const { return m_EmulsionVersion; }
   const std::string & scintVersion()                     const { return m_ScintVersion; }
   const std::string & vetoVersion()                      const { return m_VetoVersion; }
+  const std::string & vetoNuVersion()                    const { return m_VetoNuVersion; }
   const std::string & triggerVersion()                   const { return m_TriggerVersion; }
   const std::string & preshowerVersion()                 const { return m_PreshowerVersion; }
   const std::string & trackerVersion()                   const { return m_TrackerVersion; }
@@ -73,7 +76,7 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   const std::string & caloVersion()                      const { return m_CaloVersion; }
   const std::string & ecalVersion()                      const { return m_EcalVersion; }
   // const std::string & magFieldVersion()                  const { return m_MagFieldVersion; }
-  // const std::string & cavernInfraVersion()               const { return m_CavernInfraVersion; }
+  const std::string & trenchVersion()               const { return m_TrenchVersion; }
 
   GeoModel::GeoConfig geoConfig() const { return m_geoConfig; }
 
@@ -84,6 +87,7 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   std::string m_EmulsionVersion;
   std::string m_ScintVersion;
   std::string m_VetoVersion;
+  std::string m_VetoNuVersion;
   std::string m_TriggerVersion;
   std::string m_PreshowerVersion;
   std::string m_TrackerVersion;
@@ -92,12 +96,13 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   std::string m_CaloVersion;
   std::string m_EcalVersion;
   // std::string m_MagFieldVersion;
-  // std::string m_CavernInfraVersion;
+  std::string m_TrenchVersion;
 
   std::string m_NeutrinoVersionOverride;
   std::string m_EmulsionVersionOverride;
   std::string m_ScintVersionOverride;
   std::string m_VetoVersionOverride;
+  std::string m_VetoNuVersionOverride;
   std::string m_TriggerVersionOverride;
   std::string m_PreshowerVersionOverride;
   std::string m_TrackerVersionOverride;
@@ -106,7 +111,7 @@ class GeoDbTagSvc : public AthService, virtual public IGeoDbTagSvc
   std::string m_CaloVersionOverride;
   std::string m_EcalVersionOverride;
   // std::string m_MagFieldVersionOverride;
-  // std::string m_CavernInfraVersionOverride;
+  std::string m_TrenchVersionOverride;
 
   GeoModel::GeoConfig m_geoConfig;
 };
diff --git a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.cxx b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.cxx
index 4f728d36e0d6a1798d262c10cc4267b8f4d9089b..a1bf57b7f50e2ccf84714e96ae86b600c32ba46e 100644
--- a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.cxx
+++ b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.cxx
@@ -56,6 +56,7 @@ GeoModelSvc::GeoModelSvc(const std::string& name,ISvcLocator* svc)
   declareProperty( "EmulsionVersionOverride",     m_EmulsionVersionOverride);
   declareProperty( "ScintVersionOverride",        m_ScintVersionOverride);
   declareProperty( "VetoVersionOverride",         m_VetoVersionOverride);
+  declareProperty( "VetoNuVersionOverride",       m_VetoNuVersionOverride);
   declareProperty( "TriggerVersionOverride",      m_TriggerVersionOverride);
   declareProperty( "PreshowerVersionOverride",    m_PreshowerVersionOverride);
   declareProperty( "TrackerVersionOverride",      m_TrackerVersionOverride);
@@ -64,7 +65,7 @@ GeoModelSvc::GeoModelSvc(const std::string& name,ISvcLocator* svc)
   declareProperty( "CaloVersionOverride",         m_CaloVersionOverride);
   declareProperty( "EcalVersionOverride",         m_EcalVersionOverride);
   // declareProperty( "MagFieldVersionOverride",     m_MagFieldVersionOverride);
-  // declareProperty( "CavernInfraVersionOverride",  m_CavernInfraVersionOverride);
+  declareProperty( "TrenchVersionOverride",       m_TrenchVersionOverride);
   declareProperty( "AlignCallbacks",              m_callBackON);
   declareProperty( "IgnoreTagDifference",         m_ignoreTagDifference);
   declareProperty( "UseTagInfo",                  m_useTagInfo);
@@ -189,6 +190,7 @@ StatusCode GeoModelSvc::geoInit()
   ATH_MSG_DEBUG("* Emulsion  tag: " << m_EmulsionVersionOverride);
   ATH_MSG_DEBUG("* Scint     tag: " << m_ScintVersionOverride);
   ATH_MSG_DEBUG("* Veto      tag: " << m_VetoVersionOverride);
+  ATH_MSG_DEBUG("* VetoNu    tag: " << m_VetoNuVersionOverride);
   ATH_MSG_DEBUG("* Trigger   tag: " << m_TriggerVersionOverride);
   ATH_MSG_DEBUG("* Preshower tag: " << m_PreshowerVersionOverride);
   ATH_MSG_DEBUG("* Tracker   tag: " << m_TrackerVersionOverride);
@@ -197,7 +199,7 @@ StatusCode GeoModelSvc::geoInit()
   ATH_MSG_DEBUG("* Calo      tag: " << m_CaloVersionOverride);
   ATH_MSG_DEBUG("* Ecal      tag: " << m_EcalVersionOverride);
   // ATH_MSG_DEBUG("* MagField  tag: " << m_MagFieldVersionOverride);
-  // ATH_MSG_DEBUG("* CavernInfra  tag: " << m_CavernInfraVersionOverride);
+  ATH_MSG_DEBUG("* Trench    tag: " << m_TrenchVersionOverride);
   
   // GetRDBAccessSvc and open connection to DB
   ServiceHandle<IRDBAccessSvc> rdbAccess("RDBAccessSvc",name());
@@ -261,6 +263,7 @@ StatusCode GeoModelSvc::geoInit()
   dbTagSvc->setEmulsionVersionOverride(m_EmulsionVersionOverride);
   dbTagSvc->setScintVersionOverride(m_ScintVersionOverride);
   dbTagSvc->setVetoVersionOverride(m_VetoVersionOverride);
+  dbTagSvc->setVetoNuVersionOverride(m_VetoNuVersionOverride);
   dbTagSvc->setTriggerVersionOverride(m_TriggerVersionOverride);
   dbTagSvc->setPreshowerVersionOverride(m_PreshowerVersionOverride);
   dbTagSvc->setTrackerVersionOverride(m_TrackerVersionOverride);
@@ -269,7 +272,7 @@ StatusCode GeoModelSvc::geoInit()
   dbTagSvc->setCaloVersionOverride(m_CaloVersionOverride);
   dbTagSvc->setEcalVersionOverride(m_EcalVersionOverride);
   // dbTagSvc->setMagFieldVersionOverride(m_MagFieldVersionOverride);
-  // dbTagSvc->setCavernInfraVersionOverride(m_CavernInfraVersionOverride);
+  dbTagSvc->setTrenchVersionOverride(m_TrenchVersionOverride);
 
   if(dbTagSvc->setupTags().isFailure()) {
     ATH_MSG_FATAL("Failed to setup subsystem tags");
@@ -431,6 +434,8 @@ StatusCode GeoModelSvc::compareTags()
       tagsMatch = m_ScintVersionOverride == pair.second;
     else if(tagPairName=="GeoVeto")
       tagsMatch = m_VetoVersionOverride == pair.second;
+    else if(tagPairName=="GeoVetoNu")
+      tagsMatch = m_VetoNuVersionOverride == pair.second;
     else if(tagPairName=="GeoTrigger")
       tagsMatch = m_TriggerVersionOverride == pair.second;
     else if(tagPairName=="GeoPreshower")
@@ -445,6 +450,8 @@ StatusCode GeoModelSvc::compareTags()
       tagsMatch = m_CaloVersionOverride == pair.second;
     else if(tagPairName=="GeoEcal")
       tagsMatch = m_EcalVersionOverride == pair.second;
+    else if(tagPairName=="GeoTrench")
+      tagsMatch = m_TrenchVersionOverride == pair.second;
     
     if(!tagsMatch) break;
   }
@@ -458,6 +465,7 @@ StatusCode GeoModelSvc::compareTags()
     ATH_MSG_INFO("*   Emulsion  tag: " << m_EmulsionVersionOverride);
     ATH_MSG_INFO("* Scint       tag: " << m_ScintVersionOverride);
     ATH_MSG_INFO("*   Veto      tag: " << m_VetoVersionOverride);
+    ATH_MSG_INFO("*   VetoNu    tag: " << m_VetoNuVersionOverride);
     ATH_MSG_INFO("*   Trigger   tag: " << m_TriggerVersionOverride);
     ATH_MSG_INFO("*   Preshower tag: " << m_PreshowerVersionOverride);
     ATH_MSG_INFO("* Tracker     tag: " << m_TrackerVersionOverride);
@@ -466,7 +474,7 @@ StatusCode GeoModelSvc::compareTags()
     ATH_MSG_INFO("* Calo        tag: " << m_CaloVersionOverride);
     ATH_MSG_INFO("*   Ecal      tag: " << m_EcalVersionOverride);
     // ATH_MSG_INFO("* MagField  tag: " << m_MagFieldVersionOverride);
-    // ATH_MSG_INFO("* CavernInfra  tag: " << m_CavernInfraVersionOverride);
+    ATH_MSG_INFO("* Trench      tag: " << m_TrenchVersionOverride);
     ATH_MSG_INFO("** TAG INFO configuration: ");
     for (const auto& pair : pairs) {
       std::string tagPairName = pair.first;
@@ -480,6 +488,8 @@ StatusCode GeoModelSvc::compareTags()
         ATH_MSG_INFO("*Scint    tag: " << pair.second);
       else if(tagPairName=="GeoVeto")
         ATH_MSG_INFO("*Veto       tag: " << pair.second);
+      else if(tagPairName=="GeoVetoNu")
+        ATH_MSG_INFO("*VetoNu     tag: " << pair.second);
       else if(tagPairName=="GeoTrigger")
         ATH_MSG_INFO("*Trigger    tag: " << pair.second);
       else if(tagPairName=="GeoPreshower")
@@ -496,8 +506,8 @@ StatusCode GeoModelSvc::compareTags()
         ATH_MSG_INFO("*Ecal     tag: " << pair.second);
       // else if(tagPairName=="GeoMagField")
       //   ATH_MSG_INFO("*MagField  tag: " << pair.second);
-      // else if(tagPairName=="GeoCavernInfra")
-      //   ATH_MSG_INFO("*CavernInfra  tag: " << pair.second);
+      else if(tagPairName=="GeoTrench")
+        ATH_MSG_INFO("*Trench  tag: " << pair.second);
     }
     
     if(!m_ignoreTagDifference) {
@@ -554,6 +564,13 @@ StatusCode GeoModelSvc::fillTagInfo() const
     }
   }
 
+  if(m_VetoNuVersionOverride != "") {
+    if(m_tagInfoMgr->addTag("GeoVetoNu",m_VetoNuVersionOverride).isFailure()) {
+      ATH_MSG_ERROR("GeoModelSvc VetoNu tag: " << m_VetoNuVersionOverride << " not added to TagInfo " );
+      return StatusCode::FAILURE;
+    }
+  }
+
   if(m_TriggerVersionOverride != "") {
     if(m_tagInfoMgr->addTag("GeoTrigger",m_TriggerVersionOverride).isFailure()) {
       ATH_MSG_ERROR("GeoModelSvc Trigger tag: " << m_TriggerVersionOverride << " not added to TagInfo " );
@@ -610,12 +627,12 @@ StatusCode GeoModelSvc::fillTagInfo() const
   //   }
   // }
 
-  // if(m_CavernInfraVersionOverride != "") {
-  //   if(m_tagInfoMgr->addTag("GeoCavernInfra",m_CavernInfraVersionOverride).isFailure()) {
-  //     ATH_MSG_ERROR("GeoModelSvc CavernInfra tag: " << m_CavernInfraVersionOverride << " not added to TagInfo ");
-  //     return StatusCode::FAILURE; 
-  //   }
-  // }
+  if(m_TrenchVersionOverride != "") {
+    if(m_tagInfoMgr->addTag("GeoTrench",m_TrenchVersionOverride).isFailure()) {
+      ATH_MSG_ERROR("GeoModelSvc Trench tag: " << m_TrenchVersionOverride << " not added to TagInfo ");
+      return StatusCode::FAILURE; 
+    }
+  }
   return StatusCode::SUCCESS;
 }
 
diff --git a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.h b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.h
index e3880ced363400cb05ff949091346511d5c90c86..e40c06f9da7af23d6a20a14a95aab76f6faf5680 100644
--- a/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.h
+++ b/DetectorDescription/GeoModel/GeoModelSvc/src/GeoModelSvc.h
@@ -79,6 +79,7 @@ private:
     std::string           m_EmulsionVersionOverride;
     std::string           m_ScintVersionOverride;
     std::string           m_VetoVersionOverride;
+    std::string           m_VetoNuVersionOverride;
     std::string           m_TriggerVersionOverride;
     std::string           m_PreshowerVersionOverride;
     std::string           m_TrackerVersionOverride;
@@ -87,7 +88,7 @@ private:
     std::string           m_CaloVersionOverride;
     std::string           m_EcalVersionOverride;
     // std::string           m_MagFieldVersionOverride;
-    // std::string           m_CavernInfraVersionOverride;
+    std::string           m_TrenchVersionOverride;
 
     bool          m_printMaterials;               // Print the contents of the Material Manager at the end of geoInit
     bool          m_callBackON;                   // Register callback for Detector Tools
@@ -105,6 +106,7 @@ private:
     const std::string & emulsionVersionOverride()  const {return m_EmulsionVersionOverride; }
     const std::string & scintVersionOverride()     const {return m_ScintVersionOverride; }
     const std::string & vetoVersionOverride()      const {return m_VetoVersionOverride; }
+    const std::string & vetoNuVersionOverride()    const {return m_VetoNuVersionOverride; }
     const std::string & triggerVersionOverride()   const {return m_TriggerVersionOverride  ;}
     const std::string & preshowerVersionOverride() const {return m_PreshowerVersionOverride  ;}
     const std::string & trackerVersionOverride()   const {return m_TrackerVersionOverride  ;}
@@ -113,12 +115,13 @@ private:
     const std::string & caloVersionOverride()      const {return m_CaloVersionOverride  ;}
     const std::string & ecalVersionOverride()      const {return m_EcalVersionOverride  ;}
     // const std::string & magFieldVersionOverride()     const {return m_MagFieldVersionOverride  ;}
-    // const std::string & cavernInfraVersionOverride()  const {return m_CavernInfraVersionOverride  ;}
+    const std::string & trenchVersionOverride()    const {return m_TrenchVersionOverride  ;}
 
     const std::string & neutrinoVersion()      const {return m_geoDbTagSvc->neutrinoVersion(); }
     const std::string & emulsionVersion()      const {return m_geoDbTagSvc->emulsionVersion(); }
     const std::string & scintVersion()         const {return m_geoDbTagSvc->scintVersion(); }
     const std::string & vetoVersion()          const {return m_geoDbTagSvc->vetoVersion(); }
+    const std::string & vetoNuVersion()        const {return m_geoDbTagSvc->vetoNuVersion(); }
     const std::string & triggerVersion()       const {return m_geoDbTagSvc->triggerVersion(); }
     const std::string & preshowerVersion()     const {return m_geoDbTagSvc->preshowerVersion(); }
     const std::string & trackerVersion()       const {return m_geoDbTagSvc->trackerVersion(); }
@@ -127,7 +130,7 @@ private:
     const std::string & caloVersion()          const {return m_geoDbTagSvc->caloVersion(); }
     const std::string & ecalVersion()          const {return m_geoDbTagSvc->ecalVersion(); }
     // const std::string & magFieldVersion()      const {return m_geoDbTagSvc->magFieldVersion(); }
-    // const std::string & cavernInfraVersion()   const {return m_geoDbTagSvc->cavernInfraVersion(); }
+    const std::string & trenchVersion()        const {return m_geoDbTagSvc->trenchVersion(); }
 
     GeoModel::GeoConfig geoConfig() const {return m_geoDbTagSvc->geoConfig();}
 
diff --git a/DetectorDescription/GeoModel/GeoModelSvc/src/RDBMaterialManager.cxx b/DetectorDescription/GeoModel/GeoModelSvc/src/RDBMaterialManager.cxx
index dd440226fdff6abfc3e609d4f705e92884edd61e..988191de12501bf2105e9045baa3e468d234a0f5 100644
--- a/DetectorDescription/GeoModel/GeoModelSvc/src/RDBMaterialManager.cxx
+++ b/DetectorDescription/GeoModel/GeoModelSvc/src/RDBMaterialManager.cxx
@@ -215,35 +215,37 @@ StatusCode RDBMaterialManager::readMaterialsFromDB(ISvcLocator* pSvcLocator)
       log << MSG::WARNING << " Getting SCTMaterials with default tag" <<endmsg;
     m_trackermaterials = iAccessSvc->getRecordsetPtr("SCTMaterials","SCTMaterials-00", "", "FASERDD");
   }
+  // TrackerMatComponents and TrackerMaterials tables in DB are empty
   // --- Tracker materials
-  DecodeFaserVersionKey keyTracker(iGeoModel, "Tracker");
-  m_trackermatcomponents = iAccessSvc->getRecordsetPtr("TrackerMatComponents",keyTracker.tag(),keyTracker.node(),"FASERDD");
-  if(m_trackermatcomponents->size()==0) {
-    if(log.level()<=MSG::WARNING)
-      log << MSG::WARNING << " Getting TrackerMatComponents with default tag" <<endmsg;
-    m_trackermatcomponents = iAccessSvc->getRecordsetPtr("TrackerMatComponents","TrackerMatComponents-00", "", "FASERDD");
-  }
-  m_trackermaterials = iAccessSvc->getRecordsetPtr("TrackerMaterials",keyTracker.tag(),keyTracker.node(), "FASERDD");
-  if(m_trackermaterials->size()==0) {
-    if(log.level()<=MSG::WARNING)
-      log << MSG::WARNING << " Getting TrackerMaterials with default tag" <<endmsg;
-    m_trackermaterials = iAccessSvc->getRecordsetPtr("TrackerMaterials","TrackerMaterials-00", "", "FASERDD");
-  }
-
-  // --- Tracker materials
-  DecodeFaserVersionKey keyCalorimeter(iGeoModel, "Calorimeter");
-  m_calomatcomponents = iAccessSvc->getRecordsetPtr("CaloMatComponents",keyCalorimeter.tag(),keyCalorimeter.node(),"FASERDD");
-  if(m_calomatcomponents->size()==0) {
-    if(log.level()<=MSG::WARNING)
-      log << MSG::WARNING << " Getting CaloMatComponents with default tag" <<endmsg;
-    m_calomatcomponents = iAccessSvc->getRecordsetPtr("CaloMatComponents","CaloMatComponents-00", "", "FASERDD");
-  }
-  m_calomaterials = iAccessSvc->getRecordsetPtr("CaloMaterials",keyCalorimeter.tag(),keyCalorimeter.node(), "FASERDD");
-  if(m_calomaterials->size()==0) {
-    if(log.level()<=MSG::WARNING)
-      log << MSG::WARNING << " Getting CaloMaterials with default tag" <<endmsg;
-    m_calomaterials = iAccessSvc->getRecordsetPtr("CaloMaterials","CaloMaterials-00", "", "FASERDD");
-  }
+  // DecodeFaserVersionKey keyTracker(iGeoModel, "Tracker");
+  // m_trackermatcomponents = iAccessSvc->getRecordsetPtr("TrackerMatComponents",keyTracker.tag(),keyTracker.node(),"FASERDD");
+  // if(m_trackermatcomponents->size()==0) {
+  //   if(log.level()<=MSG::WARNING)
+  //     log << MSG::WARNING << " Getting TrackerMatComponents with default tag" <<endmsg;
+  //   m_trackermatcomponents = iAccessSvc->getRecordsetPtr("TrackerMatComponents","TrackerMatComponents-00", "", "FASERDD");
+  // }
+  // m_trackermaterials = iAccessSvc->getRecordsetPtr("TrackerMaterials",keyTracker.tag(),keyTracker.node(), "FASERDD");
+  // if(m_trackermaterials->size()==0) {
+  //   if(log.level()<=MSG::WARNING)
+  //     log << MSG::WARNING << " Getting TrackerMaterials with default tag" <<endmsg;
+  //   m_trackermaterials = iAccessSvc->getRecordsetPtr("TrackerMaterials","TrackerMaterials-00", "", "FASERDD");
+  // }
+
+  // CaloMatComponents and CaloMaterials tables are also empty
+  // // --- Calorimeter materials
+  // DecodeFaserVersionKey keyCalorimeter(iGeoModel, "Calorimeter");
+  // m_calomatcomponents = iAccessSvc->getRecordsetPtr("CaloMatComponents",keyCalorimeter.tag(),keyCalorimeter.node(),"FASERDD");
+  // if(m_calomatcomponents->size()==0) {
+  //   if(log.level()<=MSG::WARNING)
+  //     log << MSG::WARNING << " Getting CaloMatComponents with default tag" <<endmsg;
+  //   m_calomatcomponents = iAccessSvc->getRecordsetPtr("CaloMatComponents","CaloMatComponents-00", "", "FASERDD");
+  // }
+  // m_calomaterials = iAccessSvc->getRecordsetPtr("CaloMaterials",keyCalorimeter.tag(),keyCalorimeter.node(), "FASERDD");
+  // if(m_calomaterials->size()==0) {
+  //   if(log.level()<=MSG::WARNING)
+  //     log << MSG::WARNING << " Getting CaloMaterials with default tag" <<endmsg;
+  //   m_calomaterials = iAccessSvc->getRecordsetPtr("CaloMaterials","CaloMaterials-00", "", "FASERDD");
+  // }
      
   return StatusCode::SUCCESS;
 }
diff --git a/Event/FaserByteStreamCnvSvcBase/python/FaserByteStreamCnvSvcBaseConfig.py b/Event/FaserByteStreamCnvSvcBase/python/FaserByteStreamCnvSvcBaseConfig.py
index 3a50a75d55c0c2a1e06d6edfa6d900722d5a83c1..3a87cc4c75388ff53954f9a75be77b9c5d1dc9cd 100644
--- a/Event/FaserByteStreamCnvSvcBase/python/FaserByteStreamCnvSvcBaseConfig.py
+++ b/Event/FaserByteStreamCnvSvcBase/python/FaserByteStreamCnvSvcBaseConfig.py
@@ -15,6 +15,7 @@ def FaserByteStreamCnvSvcBaseCfg(flags, **kwargs):
     adxProvider.TypeNames += [
         "RawWaveformContainer/CaloWaveforms",
         "RawWaveformContainer/VetoWaveforms",
+        "RawWaveformContainer/VetoNuWaveforms",
         "RawWaveformContainer/TriggerWaveforms",
         "RawWaveformContainer/PreshowerWaveforms",
         "RawWaveformContainer/ClockWaveforms",
diff --git a/FaserGeometryCommon/TrenchGeoModel/CMakeLists.txt b/FaserGeometryCommon/TrenchGeoModel/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..041a00338e4f25ed8b0e27a1077571fbf23a342f
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/CMakeLists.txt
@@ -0,0 +1,35 @@
+################################################################################
+# Package: TrenchGeoModel
+################################################################################
+
+# Declare the package name:
+atlas_subdir( TrenchGeoModel )
+
+# External dependencies:
+find_package( Boost COMPONENTS filesystem thread system )
+find_package( Eigen )
+find_package( GeoModel COMPONENTS GeoModelKernel GeoModelDBManager GDMLtoGM )
+find_package( XercesC )
+
+# Component(s) in the package:
+atlas_add_component( TrenchGeoModel
+		             src/*.cxx
+                     src/components/*.cxx
+                     INCLUDE_DIRS ${Boost_INCLUDE_DIRS} ${GEOMODEL_INCLUDE_DIR} ${XERCESC_INCLUDE_DIRS}
+                     LINK_LIBRARIES ${Boost_LIBRARIES} ${GEOMODEL_LIBRARIES} ${XERCESC_LIBRARIES} AthenaKernel GeoModelFaserUtilities GaudiKernel 
+                                    StoreGateLib GeoModelInterfaces RDBAccessSvcLib PathResolver)
+
+add_custom_command (OUTPUT ${CMAKE_XML_OUTPUT_DIRECTORY}/TrenchGeoModel/Trench.gdml 
+                    COMMAND mkdir -p ${CMAKE_XML_OUTPUT_DIRECTORY}/TrenchGeoModel/   
+		       COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/data/Trench.gdml ${CMAKE_XML_OUTPUT_DIRECTORY}/TrenchGeoModel/Trench.gdml )
+add_custom_target (make_cavern_gdml ALL DEPENDS  ${CMAKE_XML_OUTPUT_DIRECTORY}/TrenchGeoModel/Trench.gdml)
+get_filename_component( _realpath ${CMAKE_CURRENT_SOURCE_DIR}/data/Trench.gdml REALPATH )
+
+install(FILES ${_realpath} DESTINATION ${CMAKE_INSTALL_PREFIX}/XML/TrenchGeoModel RENAME Trench.gdml)
+unset( _realpath )
+
+# Install files from the package:
+#atlas_install_python_modules( python/*.py )
+#atlas_install_scripts( test/*.py )
+# Not needed as done with symlink above
+#atlas_install_xmls( data/*.gdml )
diff --git a/FaserGeometryCommon/TrenchGeoModel/data/Trench.gdml b/FaserGeometryCommon/TrenchGeoModel/data/Trench.gdml
new file mode 100644
index 0000000000000000000000000000000000000000..476363b731343f73f515a5f207714252772571df
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/data/Trench.gdml
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gdml xsi:noNamespaceSchemaLocation="schema/gdml.xsd">
+    <define>
+        <position name="RampinWorldpos" unit="mm" x="576.9" y="-177.6" z="-598.8"/>
+        <rotation name="RampinWorldrot" unit="deg" z="0" y="-9.50625" x="0.711918"/>
+        <position name="Subpos" unit="mm" x="-668.4" y="245.1" z="498.3" />
+        <rotation name="Subrot" unit="deg" x="90.7119" y="-9.50625" z="0" />
+        <constant name="WorldSize" value="20000"/>
+        <constant name="RampWidth" value="7000"/>
+        <constant name="RampLengthZ" value="8422"/>
+        <constant name="RampMinThickness" value="2000"/>
+        <constant name="RampSlope" value="0.08533"/>
+    </define>
+    <materials>
+        <isotope N="1.400000000000000e+01" Z="7.000000000000000e+00" name="N140x8e46440">
+            <atom unit="g/mole" value="1.400310000000000e+01"/>
+        </isotope>
+        <isotope N="1.500000000000000e+01" Z="7.000000000000000e+00" name="N150x8e48da0">
+            <atom unit="g/mole" value="1.500010000000000e+01"/>
+        </isotope>
+        <element name="N0x8e48c30">
+            <fraction n="9.963200000000001e-01" ref="N140x8e46440"/>
+            <fraction n="3.680000000000000e-03" ref="N150x8e48da0"/>
+        </element>
+        <isotope N="1.600000000000000e+01" Z="8.000000000000000e+00" name="O160x1167b880">
+            <atom unit="g/mole" value="1.599490000000000e+01"/>
+        </isotope>
+        <isotope N="1.700000000000000e+01" Z="8.000000000000000e+00" name="O170x1167b7d0">
+            <atom unit="g/mole" value="1.699909999999999e+01"/>
+        </isotope>
+        <isotope N="1.800000000000000e+01" Z="8.000000000000000e+00" name="O180xfb79770">
+            <atom unit="g/mole" value="1.799920000000000e+01"/>
+        </isotope>
+        <element name="O0x1167b4f0">
+            <fraction n="9.975700000000000e-01" ref="O160x1167b880"/>
+            <fraction n="3.800000000000000e-04" ref="O170x1167b7d0"/>
+            <fraction n="2.050000000000000e-03" ref="O180xfb79770"/>
+        </element>
+        <isotope N="2.800000000000000e+01" Z="1.400000000000000e+01" name="Si280x8146230">
+            <atom unit="g/mole" value="2.797690000000000e+01"/>
+        </isotope>
+        <isotope N="2.900000000000000e+01" Z="1.400000000000000e+01" name="Si290x8146280">
+            <atom unit="g/mole" value="2.897650000000000e+01"/>
+        </isotope>
+        <isotope N="3.000000000000000e+01" Z="1.400000000000000e+01" name="Si300x8146e50">
+            <atom unit="g/mole" value="2.997380000000000e+01"/>
+        </isotope>
+        <element name="Si0x8147580">
+            <fraction n="9.222960777039223e-01" ref="Si280x8146230"/>
+            <fraction n="4.683195316804684e-02" ref="Si290x8146280"/>
+            <fraction n="3.087196912803088e-02" ref="Si300x8146e50"/>
+        </element>
+        <isotope N="4.000000000000000e+01" Z="2.000000000000000e+01" name="Ca400xee155f0">
+            <atom unit="g/mole" value="3.996260000000000e+01"/>
+        </isotope>
+        <isotope N="4.200000000000000e+01" Z="2.000000000000000e+01" name="Ca420xee15640">
+            <atom unit="g/mole" value="4.195860000000000e+01"/>
+        </isotope>
+        <isotope N="4.300000000000000e+01" Z="2.000000000000000e+01" name="Ca430xee15690">
+            <atom unit="g/mole" value="4.295880000000000e+01"/>
+        </isotope>
+        <isotope N="4.400000000000000e+01" Z="2.000000000000000e+01" name="Ca440xee156e0">
+            <atom unit="g/mole" value="4.395550000000001e+01"/>
+        </isotope>
+        <isotope N="4.600000000000000e+01" Z="2.000000000000000e+01" name="Ca460xee15730">
+            <atom unit="g/mole" value="4.595370000000000e+01"/>
+        </isotope>
+        <isotope N="4.800000000000000e+01" Z="2.000000000000000e+01" name="Ca480xee15780">
+            <atom unit="g/mole" value="4.795250000000000e+01"/>
+        </isotope>
+        <element name="Ca0xee154a0">
+            <fraction n="9.694100000000000e-01" ref="Ca400xee155f0"/>
+            <fraction n="6.470000000000000e-03" ref="Ca420xee15640"/>
+            <fraction n="1.350000000000000e-03" ref="Ca430xee15690"/>
+            <fraction n="2.086000000000000e-02" ref="Ca440xee156e0"/>
+            <fraction n="4.000000000000000e-05" ref="Ca460xee15730"/>
+            <fraction n="1.870000000000000e-03" ref="Ca480xee15780"/>
+        </element>
+        <isotope N="2.300000000000000e+01" Z="1.100000000000000e+01" name="Na230x9095660">
+            <atom unit="g/mole" value="2.298980000000000e+01"/>
+        </isotope>
+        <element name="Na0x90954f0">
+            <fraction n="1.000000000000000e+00" ref="Na230x9095660"/>
+        </element>
+        <isotope N="5.400000000000000e+01" Z="2.600000000000000e+01" name="Fe540x11685b60">
+            <atom unit="g/mole" value="5.393959999999999e+01"/>
+        </isotope>
+        <isotope N="5.600000000000000e+01" Z="2.600000000000000e+01" name="Fe560x11685bb0">
+            <atom unit="g/mole" value="5.593490000000000e+01"/>
+        </isotope>
+        <isotope N="5.700000000000000e+01" Z="2.600000000000000e+01" name="Fe570x11685c00">
+            <atom unit="g/mole" value="5.693540000000000e+01"/>
+        </isotope>
+        <isotope N="5.800000000000000e+01" Z="2.600000000000000e+01" name="Fe580x11685c50">
+            <atom unit="g/mole" value="5.793330000000000e+01"/>
+        </isotope>
+        <element name="Fe0x116859d0">
+            <fraction n="5.845000000000000e-02" ref="Fe540x11685b60"/>
+            <fraction n="9.175400000000000e-01" ref="Fe560x11685bb0"/>
+            <fraction n="2.119000000000000e-02" ref="Fe570x11685c00"/>
+            <fraction n="2.820000000000000e-03" ref="Fe580x11685c50"/>
+        </element>
+        <isotope N="2.700000000000000e+01" Z="1.300000000000000e+01" name="Al270x116850d0">
+            <atom unit="g/mole" value="2.698150000000000e+01"/>
+        </isotope>
+        <element name="Al0x11684f90">
+            <fraction n="1.000000000000000e+00" ref="Al270x116850d0"/>
+        </element>
+        <isotope N="1.200000000000000e+01" Z="6.000000000000000e+00" name="C120x8147220">
+          <atom unit="g/mole" value="1.200000000000000e+01"/>
+        </isotope>
+        <isotope N="1.300000000000000e+01" Z="6.000000000000000e+00" name="C130x8147270">
+          <atom unit="g/mole" value="1.300340000000000e+01"/>
+        </isotope>
+        <element name="C0x81470c0">
+        <fraction n="9.893000000000001e-01" ref="C120x8147220"/>
+        <fraction n="1.070000000000000e-02" ref="C130x8147270"/>
+        </element>
+        <isotope N="3.900000000000000e+01" Z="1.900000000000000e+01" name="K390x89fe1c0">
+            <atom unit="g/mole" value="3.896370000000000e+01"/>
+        </isotope>
+        <isotope N="4.000000000000000e+01" Z="1.900000000000000e+01" name="K400x89fe210">
+            <atom unit="g/mole" value="3.996400000000000e+01"/>
+        </isotope>
+        <isotope N="4.100000000000000e+01" Z="1.900000000000000e+01" name="K410x89fe260">
+            <atom unit="g/mole" value="4.096180000000000e+01"/>
+        </isotope>
+        <element name="K0x89fe020">
+            <fraction n="9.325810000000000e-01" ref="K390x89fe1c0"/>
+            <fraction n="1.170000000000000e-04" ref="K400x89fe210"/>
+            <fraction n="6.730200000000000e-02" ref="K410x89fe260"/>
+        </element>
+        <isotope N="1.000000000000000e+00" Z="1.000000000000000e+00" name="H10x8e31ab0">
+            <atom unit="g/mole" value="1.007825030813723e+00"/>
+        </isotope>
+        <isotope N="2.000000000000000e+00" Z="1.000000000000000e+00" name="H20x8e31b00">
+            <atom unit="g/mole" value="2.014101999666174e+00"/>
+        </isotope>
+        <element name="H0x8e31950">
+            <fraction n="9.998850000000000e-01" ref="H10x8e31ab0"/>
+            <fraction n="1.150000000000000e-04" ref="H20x8e31b00"/>
+        </element>
+        <isotope N="36" Z="18" name="Ar36Faser">
+            <atom unit="g/mole" value="35.967545105"/>
+        </isotope>
+        <isotope N="38" Z="18" name="Ar38Faser">
+            <atom unit="g/mole" value="37.96273210"/>
+        </isotope>
+        <isotope N="40" Z="18" name="Ar40Faser">
+            <atom unit="g/mole" value="39.9623831238"/>
+        </isotope>
+        <element name="ArgonFaser">
+            <fraction n="0.996035" ref="Ar40Faser"/>
+            <fraction n="0.000629" ref="Ar38Faser"/>
+            <fraction n="0.003336" ref="Ar36Faser"/>
+        </element>
+        <!--
+        <material name="Concrete" state="solid">
+            <MEE unit="eV" value="1.271741855079048e+02"/>
+            <D unit="g/cm3" value="2.499998684602534e+00"/>
+            <fraction n="5.306122448979591e-01" ref="O0x1167b4f0"/>
+            <fraction n="3.316326530612245e-01" ref="Si0x8147580"/>
+            <fraction n="6.122448979591836e-02" ref="Ca0xee154a0"/>
+            <fraction n="1.530612244897959e-02" ref="Na0x90954f0"/>
+            <fraction n="2.040816326530612e-02" ref="Fe0x116859d0"/>
+            <fraction n="4.081632653061224e-02" ref="Al0x11684f90"/>
+        </material>
+        -->
+        <!-- Concrete and Air composition taken from ATLAS; elements/isotopes taken from LHCb -->
+        <material name="Concrete" state="solid">
+            <MEE unit="eV" value="1.271741855079048e+02"/>
+            <D unit="g/cm3" value="2.4e+00"/>
+            <fraction n="0.5" ref="O0x1167b4f0"/>
+            <fraction n="0.2" ref="Si0x8147580"/>
+            <fraction n="0.2" ref="Ca0xee154a0"/>
+            <fraction n="0.01" ref="Na0x90954f0"/>
+            <fraction n="0.014" ref="Fe0x116859d0"/>
+            <fraction n="0.03" ref="Al0x11684f90"/>
+            <fraction n="0.03" ref="C0x81470c0"/>
+            <fraction n="0.01" ref="K0x89fe020"/>
+            <fraction n="0.006" ref="H0x8e31950"/>
+        </material>
+        <material name="Air" state="gas">
+            <T unit="K" value="2.930000000000000e+02"/>
+            <MEE unit="eV" value="8.570323231865051e+01"/>
+            <D unit="g/cm3" value="1.214e-03"/>
+            <fraction n="0.7494" ref="N0x8e48c30"/>
+            <fraction n="0.2369" ref="O0x1167b4f0"/>
+            <fraction n="0.0129" ref="ArgonFaser"/>
+            <fraction n="0.0008" ref="H0x8e31950"/>
+        </material>
+    </materials>
+    <solids>
+        <arb8 name="ramp" lunit="mm"
+                          v1x="-RampWidth/2" v1y="-RampMinThickness" v2x="RampWidth/2" v2y="-RampMinThickness" v3x="RampWidth/2" v3y="RampSlope*RampLengthZ" v4x="-RampWidth/2" v4y="RampSlope*RampLengthZ" 
+                          v5x="-RampWidth/2" v5y="-RampMinThickness" v6x="RampWidth/2" v6y="-RampMinThickness" v7x="RampWidth/2" v7y="0" v8x="-RampWidth/2" v8y="0"
+                          dz="RampLengthZ/2" />
+        <box name="world" lunit="mm" x="WorldSize" y="WorldSize" z="WorldSize" />
+        <xtru name="trenchOutline" lunit="mm">        
+            <twoDimVertex x="-249.75647241976003" y="-3162.7618497323056"/>
+            <twoDimVertex x="436.065806982358" y="-3162.7608114181166"/>
+            <twoDimVertex x="750.3626731862572" y="-1286.045959074479"/>
+            <twoDimVertex x="750.4059394616982" y="-604.5461685406792"/>
+            <twoDimVertex x="750.6632036024528" y="3447.692178833775"/>
+            <twoDimVertex x="598.5775838786217" y="3447.7018373451183"/>
+            <twoDimVertex x="-251.25127807309354" y="3447.755796000657"/>
+            <twoDimVertex x="-624.2972688145043" y="1219.150232226327"/>
+            <twoDimVertex x="-659.4916199548085" y="1008.9993568866641"/>
+            <twoDimVertex x="-659.5757293651513" y="-315.8326195671569"/>
+            <twoDimVertex x="-659.6885336897632" y="-2092.6465942800164"/>
+            <twoDimVertex x="-249.68853582392614" y="-2092.672626612777"/>
+            <section zOrder="0" zPosition="-375.0" xOffset="0" yOffset="0" scalingFactor="1" />
+            <section zOrder="1" zPosition="375.0" xOffset="0" yOffset="0" scalingFactor="1" />
+        </xtru>
+        <subtraction name="faserTrench">
+            <first ref="ramp"/>
+            <firstpositionref ref="RampinWorldpos"/>
+            <firstrotationref ref="RampinWorldrot"/>
+            <second ref="trenchOutline"/>
+            <!--
+            <rotationref ref="Subrot"/>
+            <positionref ref="Subpos"/>
+            -->
+            <rotation name="Xrotation" unit="deg" x="90"/>
+            <position name="Yshift" unit="mm" y="75"/>
+        </subtraction>
+    </solids>
+    <structure>
+        <volume name="Trench">
+            <solidref ref="faserTrench"/>
+            <materialref ref="Concrete"/>
+        </volume>
+        <volume name="World" >
+            <materialref ref="Air" />
+            <solidref ref="world" />
+            <physvol>
+                <volumeref ref="Trench" />
+                <!--
+                <rotationref ref="RampinWorldrot"/>
+                <positionref ref="RampinWorldpos"/>
+                -->
+            </physvol>
+        </volume>
+    </structure>
+    <setup name="Default" version="1.0" >
+        <world ref="World" />
+    </setup>
+</gdml>
\ No newline at end of file
diff --git a/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorFactory.cxx b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorFactory.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..da787acdfc58391b7cd3f68b00b7aae5f1055a91
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorFactory.cxx
@@ -0,0 +1,99 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "TrenchDetectorFactory.h"
+
+#include "GeoModelXMLParser/XercesParser.h"
+#include "PathResolver/PathResolver.h"
+
+
+#include "GeoModelKernel/GeoLogVol.h"  
+#include "GeoModelKernel/GeoNameTag.h"  
+#include "GeoModelKernel/GeoPhysVol.h"
+#include "StoreGate/StoreGateSvc.h"
+
+
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+#include "RDBAccessSvc/IRDBAccessSvc.h"
+
+#include "GaudiKernel/MsgStream.h"
+
+#include <string>
+#include <map>
+
+TrenchDetectorFactory::TrenchDetectorFactory(StoreGateSvc *detStore,
+				       IRDBAccessSvc *pAccess)
+  :m_detectorManager(NULL),
+   m_detectorStore(detStore),
+   m_access(pAccess)
+{
+}
+
+TrenchDetectorFactory::~TrenchDetectorFactory()
+{
+}
+
+void TrenchDetectorFactory::create(GeoPhysVol *world)
+{ 
+    m_detectorManager=new TrenchDetectorManager();
+
+    std::string gdmlFile;
+    IRDBRecordset_ptr switchSet{m_access->getRecordsetPtr("TrenchSwitches", m_versionTag, m_versionNode, "FASERDD")};
+    if (!switchSet || switchSet->size() == 0)
+    {
+        MsgStream gLog(Athena::getMessageSvc(), "TrenchDetectorFactory");
+        gLog << MSG::WARNING << "Unable to retrieve switches; Trench cannot be created" << endmsg;
+        return;
+    }
+    const IRDBRecord* switches{(*switchSet)[0]};
+    if (not switches->isFieldNull("GDMLFILE") && gdmlFile.empty())
+    {
+        gdmlFile = switches->getString("GDMLFILE");
+    } 
+    if (gdmlFile.empty())
+    {
+        MsgStream gLog(Athena::getMessageSvc(), "TrenchDetectorFactory");
+        gLog << MSG::WARNING << "GDML file name not found; Trench cannot be created"  << endmsg;
+        return;
+    }
+
+    std::string resolvedFile = PathResolver::find_file(gdmlFile, "XMLPATH", PathResolver::RecursiveSearch);
+
+    auto store = XMLHandlerStore::GetHandlerStore();
+    // for (auto p : *store)
+    // {
+    //   delete p.second;
+    // }
+    store->clear();
+
+    GDMLController controller {"TrenchGDMLController"};
+
+    // std::cout << "creating parser" << std::endl;
+    XercesParser xercesParser;
+
+    // std::cout << "parsing " << resolvedFile << std::endl;
+    xercesParser.ParseFileAndNavigate(resolvedFile);
+    // std::cout << "done parsing " << resolvedFile << std::endl;
+
+    const GeoLogVol* trenchLog  = controller.retrieveLogicalVolume("Trench").first;
+    GeoPhysVol*      trenchPhys = new GeoPhysVol(trenchLog);
+    GeoNameTag *tag = new GeoNameTag("Trench");
+    world->add(tag);
+    world->add(trenchPhys);
+    m_detectorManager->addTreeTop(trenchPhys);
+}
+
+const TrenchDetectorManager * TrenchDetectorFactory::getDetectorManager() const
+{
+  return m_detectorManager;
+}
+
+void TrenchDetectorFactory::setTagNode(const std::string& tag,
+                                       const std::string& node)
+{
+  m_versionTag = tag;
+  m_versionNode = node;
+}
+
diff --git a/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorFactory.h b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorFactory.h
new file mode 100644
index 0000000000000000000000000000000000000000..14cdbaa18bdc8cc941fb894c9085298ec4191f38
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorFactory.h
@@ -0,0 +1,59 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TrenchDetectorFactory_h
+#define TrenchDetectorFactory_h 1
+
+#include "GeoModelKernel/GeoVDetectorFactory.h"
+#include "TrenchDetectorManager.h"
+#include "RDBAccessSvc/IRDBAccessSvc.h"
+#include "GDMLInterface/GDMLController.h"
+
+#include <string>
+
+class StoreGateSvc;
+
+class TrenchDetectorFactory : public GeoVDetectorFactory  
+{
+ 
+ public:
+  
+  // Constructor:
+  TrenchDetectorFactory(StoreGateSvc *pDetStore,
+			  IRDBAccessSvc *pAccess);
+  
+  // Destructor:
+  ~TrenchDetectorFactory();
+  
+  // Creation of geometry:
+  virtual void create(GeoPhysVol *world);
+  
+  // Access to the results:
+  virtual const TrenchDetectorManager * getDetectorManager() const;
+  
+  // Set version Tag and Node
+  void setTagNode(const std::string& tag, const std::string& node);
+
+ 
+ private:  
+  // Illegal operations:
+  const TrenchDetectorFactory & operator=(const TrenchDetectorFactory &right);
+  TrenchDetectorFactory(const TrenchDetectorFactory &right);
+  
+  // The manager:
+  TrenchDetectorManager       *m_detectorManager;
+  
+  StoreGateSvc             *m_detectorStore;
+  IRDBAccessSvc            *m_access;
+  std::string              m_versionTag;
+  std::string              m_versionNode;
+
+//   GDMLController m_controller {"TrenchGDMLController"};
+
+};
+
+// Class TrenchDetectorFactory
+#endif
+
+
diff --git a/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorManager.cxx b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorManager.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f8df3b923f9a316af0d237285090ca34ba1107a1
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorManager.cxx
@@ -0,0 +1,38 @@
+/*
+  Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "TrenchDetectorManager.h"
+
+TrenchDetectorManager::TrenchDetectorManager()
+{
+  setName("Trench");
+}
+
+
+TrenchDetectorManager::~TrenchDetectorManager()
+{
+  for(unsigned int i=0; i<m_treeTops.size(); i++)
+    m_treeTops[i]->unref();
+}
+
+
+unsigned int TrenchDetectorManager::getNumTreeTops() const
+{
+  return m_treeTops.size();
+}
+
+PVConstLink TrenchDetectorManager::getTreeTop(unsigned int i) const
+{
+  if(i<m_treeTops.size())
+    return m_treeTops[i];
+  else
+    return 0;
+}
+
+void  TrenchDetectorManager::addTreeTop(PVLink link) 
+{
+  link->ref();
+  m_treeTops.push_back(link);
+}
+
diff --git a/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorManager.h b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..87ea3d0c258b7afa5d7255f8b4fa521c5f072883
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorManager.h
@@ -0,0 +1,44 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TrenchDetectorManager_h
+#define TrenchDetectorManager_h 1
+
+#include "GeoModelKernel/GeoVPhysVol.h"
+#include "GeoModelKernel/GeoVDetectorManager.h"
+#include <vector>
+
+class TrenchDetectorManager : public GeoVDetectorManager  
+{
+ public:
+
+  // Constructor
+  TrenchDetectorManager();
+
+  // Destructor
+  ~TrenchDetectorManager();
+
+  // Access to raw geometry:
+  virtual unsigned int getNumTreeTops() const;
+  virtual PVConstLink getTreeTop(unsigned int i) const;
+ 
+  void addTreeTop(PVLink);      // Add a Tree top:
+
+ private:  
+
+  const TrenchDetectorManager & operator=(const TrenchDetectorManager &right);
+  TrenchDetectorManager(const TrenchDetectorManager &right);
+  
+  // Tree Tops
+  std::vector<PVLink> m_treeTops;
+};
+
+#ifndef GAUDI_NEUTRAL
+#include "AthenaKernel/CLASS_DEF.h" 
+CLASS_DEF(TrenchDetectorManager, 124400828, 1)
+#endif
+
+#endif
+
+
diff --git a/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorTool.cxx b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..c036496ea42cef602db10a0190367d97ff25e3a3
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorTool.cxx
@@ -0,0 +1,104 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "TrenchDetectorTool.h"
+#include "TrenchDetectorFactory.h" 
+#include "TrenchDetectorManager.h" 
+
+#include "GeoModelInterfaces/IGeoDbTagSvc.h"
+#include "GeoModelFaserUtilities/GeoModelExperiment.h"
+#include "GeoModelFaserUtilities/DecodeFaserVersionKey.h"
+#include "RDBAccessSvc/IRDBAccessSvc.h"
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+
+#include "boost/algorithm/string/predicate.hpp"
+
+TrenchDetectorTool::TrenchDetectorTool( const std::string& type, 
+						  const std::string& name,
+						  const IInterface* parent )
+  : GeoModelTool( type, name, parent ),
+    m_manager(0)
+{
+}
+
+TrenchDetectorTool::~TrenchDetectorTool()
+{
+}
+  
+
+StatusCode TrenchDetectorTool::create()
+{ 
+  IGeoDbTagSvc *geoDbTag;
+  StatusCode sc = service ("GeoDbTagSvc",geoDbTag);
+  if(sc.isFailure()) {
+    msg(MSG::ERROR) << "Could not locate GeoDbTagSvc" << endmsg;
+    return sc;
+  }
+
+  IRDBAccessSvc* raccess = 0;
+  sc = service("RDBAccessSvc",raccess);
+  if(sc.isFailure()) {
+    msg(MSG::ERROR) << "Could not locate RDBAccessSvc" << endmsg;
+    return sc;
+  }
+
+  DecodeFaserVersionKey versionKey(geoDbTag,"Trench");
+  // IRDBRecordset_ptr switchSet
+  //   = raccess->getRecordsetPtr("TrenchSwitches", versionKey.tag(), versionKey.node(),"FASERDD");
+  // const IRDBRecord    *switches   = (*switchSet)[0];
+  // msg(MSG::DEBUG) << "Retrieved TrenchSwitches" << endmsg;
+
+  std::string trenchVersion = versionKey.tag();
+  msg(MSG::INFO) << "Building Trench geometry version " << trenchVersion << endmsg;
+  if(trenchVersion.empty()) {
+    msg(MSG::INFO) << "No Trench version for the given configuration. Skip building TrenchGeoModel" << endmsg;
+    return StatusCode::SUCCESS;
+  }
+
+  std::string versionNode = versionKey.node();
+
+  GeoModelExperiment* theExpt = nullptr;
+  if (StatusCode::SUCCESS != detStore()->retrieve(theExpt,"FASER")) { 
+    msg(MSG::ERROR) << "Could not find GeoModelExperiment FASER" << endmsg; 
+    return StatusCode::FAILURE; 
+  } 
+ 
+  GeoPhysVol *world=&*theExpt->getPhysVol();
+
+  if(!m_manager) {
+    // If geometry has not been built yet fall back to the default factory
+    TrenchDetectorFactory theTrenchFactory(detStore().operator->(),raccess);
+    theTrenchFactory.setTagNode(trenchVersion,versionNode);
+    theTrenchFactory.create(world);
+    m_manager = theTrenchFactory.getDetectorManager();
+  }
+
+  if(m_manager) {
+    theExpt->addManager(m_manager);
+    sc = detStore()->record(m_manager,
+			  m_manager->getName());
+    if(sc.isFailure()) {
+      msg(MSG::ERROR) << "Could not register Trench detector manager" << endmsg;
+      return sc;
+    }
+  }
+  else {
+    msg(MSG::ERROR) << "ERROR. Failed to build Trench Version " << trenchVersion << endmsg;
+    return StatusCode::FAILURE;
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode TrenchDetectorTool::clear()
+{
+  SG::DataProxy* proxy = detStore()->proxy(ClassID_traits<TrenchDetectorManager>::ID(),m_manager->getName());
+  if(proxy) {
+    proxy->reset();
+    m_manager = 0;
+  }
+  return StatusCode::SUCCESS;
+}
+
diff --git a/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorTool.h b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c5407662e5508608d8b189aa57c3d37d82a4eda
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/src/TrenchDetectorTool.h
@@ -0,0 +1,28 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRENCHDETECTORTOOL_H
+#define TRENCHDETECTORTOOL_H
+
+#include "GeoModelFaserUtilities/GeoModelTool.h"
+class TrenchDetectorManager;
+
+class TrenchDetectorTool final : public GeoModelTool 
+{
+ public:
+
+  // Standard Constructor
+  TrenchDetectorTool( const std::string& type, const std::string& name, const IInterface* parent );
+
+  // Standard Destructor
+  virtual ~TrenchDetectorTool() override final;
+
+  virtual StatusCode create() override final;
+  virtual StatusCode clear() override final;
+  
+ private:
+  const TrenchDetectorManager* m_manager;
+};
+
+#endif 
diff --git a/FaserGeometryCommon/TrenchGeoModel/src/components/TrenchGeoModel_entries.cxx b/FaserGeometryCommon/TrenchGeoModel/src/components/TrenchGeoModel_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..732ee6b6b059d51d29acadecabaab16b50562cd2
--- /dev/null
+++ b/FaserGeometryCommon/TrenchGeoModel/src/components/TrenchGeoModel_entries.cxx
@@ -0,0 +1,3 @@
+#include "../TrenchDetectorTool.h"
+
+DECLARE_COMPONENT( TrenchDetectorTool )
\ No newline at end of file
diff --git a/Generators/DIFGenerator/python/DIFSampler.py b/Generators/DIFGenerator/python/DIFSampler.py
index 24659c0178d8cbefc9843a24a1a10ef38bbcde21..8ec22459aba93a430093c4bd7b2a3f13e2d0f206 100644
--- a/Generators/DIFGenerator/python/DIFSampler.py
+++ b/Generators/DIFGenerator/python/DIFSampler.py
@@ -5,7 +5,7 @@
 import ParticleGun as PG
 from math import sqrt, sin, cos, acos
 from random import uniform
-from AthenaCommon.SystemOfUnits import TeV, MeV
+from AthenaCommon.SystemOfUnits import TeV, GeV, MeV
 from AthenaCommon.PhysicalConstants import pi
 from ROOT import TLorentzVector
 
@@ -90,8 +90,12 @@ class DIFSampler(PG.ParticleSampler):
             else:
                 self.mom = None
 
-    def __init__(self, daughter1_pid = 11, daughter2_pid = -11, mother_pid = None, mother_mom = PG.EThetaMPhiSampler(sqrt((1*TeV)**2 + (10*MeV)**2),0,10*MeV,0),mother_pos = CylinderSampler([0, 100**2],[0, 2*pi],[-1500, 0],0)):
-        self._mother_sampler = PG.ParticleSampler(pid = mother_pid, mom = mother_mom, pos = mother_pos)
+    # def __init__(self, daughter1_pid = 11, daughter2_pid = -11, mother_pid = None, mother_mom = PG.EThetaMPhiSampler(sqrt((1*TeV)**2 + (10*MeV)**2),0,10*MeV,0),mother_pos = CylinderSampler([0, 100**2],[0, 2*pi],[-1500, 0],0)):
+    def __init__(self, daughter1_pid=13, daughter2_pid=-13, mother_pid=None,
+                 mother_mom=PG.EThetaMPhiSampler(sqrt((1*TeV)**2 + (500*MeV)**2), [0, 0.0002], 500*MeV, [0, 2*pi]),
+                 my_z_position=-1500):
+                 # mother_pos=CylinderSampler([0, 100**2], [0, 2*pi], [-1500, 0], 0)):
+        self._mother_sampler = PG.ParticleSampler(pid = mother_pid, mom = mother_mom, pos=CylinderSampler([0, 100**2], [0, 2*pi], my_z_position, 0))
 
         self.daughter1 = self.particle(daughter1_pid)
         self.daughter2 = self.particle(daughter2_pid)
@@ -111,7 +115,7 @@ class DIFSampler(PG.ParticleSampler):
         return sqrt(m0**4 - (2*m0**2 * m1**2) + (m1**4) - (2*m0**2 * m2**2) - (2*m1**2 *m2**2) + (m2**4)) / (2*m0) 
 
     def lorentz_transformation(self):
-        self.mother_mom = self.mother.mom.shoot()
+        #self.mother_mom = self.mother.mom.shoot()
         Bx = self.mother_mom.Px() / self.mother_mom.E()
         By = self.mother_mom.Py() / self.mother_mom.E()
         Bz = self.mother_mom.Pz() / self.mother_mom.E()
@@ -132,6 +136,11 @@ class DIFSampler(PG.ParticleSampler):
 
         ## The magnitude of the momentum will be equal and opposite
         self.mother = self.mother_sampler
+        self.mother.mass_override = False
+        mother_part = self.mother.shoot()[0]
+        self.mother_mom = mother_part.mom
+        self.mother_pos = mother_part.pos
+        
         p = self.calculate_decay_p(self.mother.mom.mass(),self.daughter1.mass,self.daughter2.mass)
 
         self.daughter1.E = sqrt(self.daughter1.mass**2 + p**2)
@@ -412,4 +421,4 @@ if __name__ == "__main__":
     DIFS = DIFSampler() 
     DIFS.mother_sampler = SP()
 
-    print("DIFSampler: All unit tests passed")
\ No newline at end of file
+    print("DIFSampler: All unit tests passed")
diff --git a/Generators/FaserParticleGun/python/FaserParticleGunConfig.py b/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
index 28bb6147a3276b510f3b4c3de83ad281a2997af4..ac30494a4815f5bb06a5d67408b4c0551297a63b 100644
--- a/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
+++ b/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
@@ -44,10 +44,18 @@ def FaserParticleGunSingleParticleCfg(ConfigFlags, **kwargs) :
                                           theta = kwargs.setdefault("theta", [0, pi/20]), 
                                           phi = kwargs.setdefault("phi", [0, 2*pi]), 
                                           mass = kwargs.setdefault("mass", 0.0) )
-    pg.sampler.pos = PG.PosSampler(x = kwargs.setdefault("x", [-5, 5]), 
-                                   y = kwargs.setdefault("y", [-5, 5]), 
-                                   z = kwargs.setdefault("z", -3750.0), 
-                                   t = kwargs.setdefault("t", 0.0) )
+
+    if "radius" in kwargs:
+        pg.sampler.pos =  RadialPosSampler(x = kwargs.setdefault("x", 0.0), 
+                                           y = kwargs.setdefault("y", 0.0), 
+                                           z = kwargs.setdefault("z", -3750.0),
+                                           r = kwargs.setdefault("radius", 1.0),
+                                           t = kwargs.setdefault("t", 0.0) )
+    else:
+        pg.sampler.pos = PG.PosSampler(x = kwargs.setdefault("x", [-5, 5]), 
+                                       y = kwargs.setdefault("y", [-5, 5]), 
+                                       z = kwargs.setdefault("z", -3750.0), 
+                                       t = kwargs.setdefault("t", 0.0) )
 
     return cfg
 
@@ -140,8 +148,50 @@ def FaserParticleGunDecayInFlightCfg(ConfigFlags, **kwargs) :
 
     return cfg
 
+
+def FaserParticleGunForeseeCfg(ConfigFlags, **kwargs) :
+    # Supported keyword arguments:
+    # model_path    (detault: $PWD)
+    # model_name   (default: DarkPhoton)
+    # mother_mass   (default: 0.01 GeV)
+    # com_energy    (default: 14 TeV)
+    # daughter1_pid (default:  11)
+    # daughter2_pid (default: -11)
+    # mother_pid    (default: none)
+    # mother_pos    (default: CylinderSampler([0, 100**2],[0, 2*pi],[-1500, 0],0))
+    #
+    # Note that ALL of these can be samplers themselves - either the simple, "literal" variety or a sampler object configured by the caller
+    #
+
+    cfg = FaserParticleGunCommonCfg(ConfigFlags, **kwargs)
+
+    pg = cfg.getPrimary()
+
+    from ForeseeGenerator.ForeseeSampler import ForeseeNumpySampler
+    mother_part = ForeseeNumpySampler(
+        model_path = kwargs.get("model_path", "."),
+        model_name = kwargs.get("model_name", "DarkPhoton"),
+        com_energy = kwargs.get("com_energy", "14"),
+        mother_mass = kwargs.get("mother_mass", 0.01),
+        mother_pid = kwargs.get("mother_pid", None),
+        daughter1_pid = kwargs.get("daughter1_pid", 11),
+        daughter2_pid = kwargs.get("daughter2_pid", -11),
+        randomSeed = kwargs.get("randomSeed", None)        
+        )
+
+    from DIFGenerator import DIFSampler
+    pg.sampler = DIFSampler(
+        daughter1_pid = kwargs.get("daughter1_pid", 11),
+        daughter2_pid = kwargs.get("daughter2_pid", -11),
+        )
+        
+    pg.sampler.mother_sampler = mother_part
+
+    return cfg
+
 def FaserParticleGunCfg(ConfigFlags) :
-    generator = ConfigFlags.Sim.Gun.setdefault("Generator", "SingleParticle")
+    # generator = ConfigFlags.Sim.Gun.setdefault("Generator", "SingleParticle")
+    generator = ConfigFlags.Sim.Gun.setdefault("Generator", "DecayInFlight")
     kwargs = ConfigFlags.Sim.Gun
     if generator == "SingleEcalParticle" :
         return FaserParticleGunSingleEcalParticleCfg(ConfigFlags, **kwargs)
@@ -149,5 +199,7 @@ def FaserParticleGunCfg(ConfigFlags) :
         return FaserParticleGunCosmicsCfg(ConfigFlags, **kwargs)
     elif generator == "DecayInFlight" :
         return FaserParticleGunDecayInFlightCfg(ConfigFlags, **kwargs)
+    elif generator == "Foresee" :
+        return FaserParticleGunForeseeCfg(ConfigFlags, **kwargs)    
     else :
         return FaserParticleGunSingleParticleCfg(ConfigFlags, **kwargs )
diff --git a/Generators/FaserParticleGun/python/RadialPosSampler.py b/Generators/FaserParticleGun/python/RadialPosSampler.py
index 4a14987a142697816b22cbec202f56c8bf3183b5..6fcd6829b29ea474d5c7380526d3c46bab9c6871 100644
--- a/Generators/FaserParticleGun/python/RadialPosSampler.py
+++ b/Generators/FaserParticleGun/python/RadialPosSampler.py
@@ -34,12 +34,16 @@ class RadialPosSampler(Sampler):
     @property
     def r(self):
         "r position sampler"
+
         fwhm = 2*self.radius
         sig = fwhm/(2 * sqrt(2 * log(2)))
 
-        # return random.uniform(0, self.radius)
-        return random.gauss(0, self.radius)
-        # return random.gauss(0, sig)
+        if self.radius < 0:
+            return sqrt(random.uniform(0, abs(self.radius**2)))
+        else:
+            x = random.gauss(0, self.radius)
+            y = random.gauss(0, self.radius)
+            return sqrt(x**2 + y**2)
 
     @property 
     def phi(self):
@@ -57,7 +61,7 @@ class RadialPosSampler(Sampler):
         return ROOT.TLorentzVector(x, y, z, t)
 
 if __name__ == "__main__":
-# test when run from command line
+    # test when run from command line
 
     import numpy as np
     import matplotlib.pyplot as plt
@@ -65,8 +69,8 @@ if __name__ == "__main__":
     xlist = []
     ylist = []
 
-    r = RadialPosSampler(r = 1, x = 10, y = -10, z = 10)
-    for i in range (10000):
+    r = RadialPosSampler(r = -10, x = 0, y =  0, z = 10)
+    for i in range (100000):
         res = r.shoot()
         xlist.append(res.X())
         ylist.append(res.Y())
@@ -76,10 +80,13 @@ if __name__ == "__main__":
 
     plt.figure(figsize = (5,5))
     plt.subplot(2,2,1)
-    plt.scatter(xarr, yarr, marker = "*")
+    plt.hist2d(xarr, yarr)
     plt.subplot(2,2,2)
     plt.hist(yarr)
     plt.subplot(2,2,3)
     plt.hist(xarr)
+    plt.subplot(2,2,4)
+    plt.hist(np.sqrt(xarr**2 + yarr**2))    
     plt.tight_layout()
     plt.show()
+
diff --git a/Generators/FlukaReader/CMakeLists.txt b/Generators/FlukaReader/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..eebebd79e2b466203bb35ba1aa3d3d265d39920d
--- /dev/null
+++ b/Generators/FlukaReader/CMakeLists.txt
@@ -0,0 +1,11 @@
+################################################################################
+# Package: FlukaReader
+################################################################################
+
+# Declare the package name:
+atlas_subdir( FlukaReader )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
+#atlas_install_joboptions( share/*.py )
diff --git a/Generators/FlukaReader/python/FlukaReaderAlg.py b/Generators/FlukaReader/python/FlukaReaderAlg.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a8728e37229892c56f701f7b3ae5985823c8d76
--- /dev/null
+++ b/Generators/FlukaReader/python/FlukaReaderAlg.py
@@ -0,0 +1,456 @@
+from AthenaCommon.AppMgr import ServiceMgr as svcMgr
+from GeneratorModules.EvgenAlg import EvgenAlg
+from AthenaPython.PyAthena import StatusCode, EventInfo, EventID, EventType
+from AthenaCommon.SystemOfUnits import GeV, MeV, cm
+from AthenaCommon.Constants import DEBUG
+
+from FaserCosmicGenerator import Range
+
+import ROOT
+
+import numpy as np
+import math
+
+# TODO: correct angle for beam crossing angle: both in angel itself and position
+
+class FlukaReader(EvgenAlg):
+    def __init__(self, name="FlukaReader", MCEventKey="BeamTruthEvent", file_name = "", dist = 0, z = -1.5, randomSeed = None, nsamples = 1, test = False):
+        super(FlukaReader,self).__init__(name=name)
+        self.McEventKey = MCEventKey
+        self.file_name = file_name
+        self.dist = dist * 100 # cm
+        self.z = z * 1000 # mm
+        self.isample = 0
+        self.nsamples = nsamples
+        self.test = test
+
+        self.columns = ["run", "event", "type", "gen", "E", "w", "x", "y", "cosX", "cosY", "age", "_"]
+
+        if randomSeed is not None:
+            self.msg.info(f"Setting seed to {randomSeed}")
+            self.rng = np.random.default_rng(randomSeed)
+        else:
+            self.rng = np.random.default_rng()        
+
+        self.file = open(self.file_name)
+
+        if self.test:
+            self.before = dict(zip(self.columns, [[] for i in range(len(self.columns))]))
+            self.after = dict(zip(self.columns, [[] for i in range(len(self.columns))]))
+
+               
+        return
+
+    def genFinalize(self):
+
+        if self.test:
+            self.plot()
+        
+        self.file.close()
+        return StatusCode.Success
+    
+
+    def fillEvent(self, evt):
+        "This is called for every real event * the number of samplings"
+
+        # If the sample gets to the number requested, then reset to 0
+        if self.isample == self.nsamples:
+            self.msg.debug("Reseting samples")
+            self.isample = 0
+
+        # Only if the sample is 0 load the new fluka entry
+        if self.isample == 0:
+            self.msg.debug("Loading new fluka event")
+            try:
+                l = next(self.file)
+            except StopIteration:
+                return StatusCode.Success
+            
+            entry =  dict(zip(self.columns, l.strip("\n").split()))
+            for i,c in enumerate(self.columns):
+                if i < 4:
+                    entry[c] = int(entry[c])
+                else:
+                    entry[c] = float(entry[c])
+
+            self.entry = entry
+
+        # Call for each sample of each event
+        self.msg.debug(f"Processing sample {self.isample}")
+        self.process(self.entry, evt)
+        self.isample += 1
+
+        return StatusCode.Success
+
+    def angle(self, cosTheta):
+        "Convert cos(theta) wrt x or y axis to theta wrt z axis"
+        return np.pi/2 - np.arccos(cosTheta)
+
+    def pid(self, ftype):
+        "Convert fluka particle type to PID"
+        if ftype == 10:  # mu+
+            return -13
+        elif ftype == 11: # mu-
+            return 13
+        else:
+            return 0
+
+    def path_length(self, z, cosThetaX, cosThetaY):
+        "Get path length traversed in the material, taking into account incident angles"
+        
+        # Convert theta wrt x and y axis to wrt z axis
+        thetaX = self.angle(cosThetaX)
+        thetaY = self.angle(cosThetaY)        
+        
+        # Correct z for angle 
+        zcorr = z / np.abs(np.cos(thetaX)) / np.abs(np.cos(thetaY))
+        
+        return zcorr
+
+    def energy_after_loss_exact(self, e, zcorr):
+        "Calculate exact energy after loss in material"
+        return Range.muPropagate(e, zcorr/100.) # meters
+
+    def energy_after_loss(self, e, cosThetaX, cosThetaY, zcorr, a = 2e-3, b = 4e-6):
+        "Calculate approximate energy after loss in material"
+    
+        # Based on
+        # http://www.bartol.udel.edu/~stanev/lectures/apr17.pdf
+        # and PDG chapter 27 fig 27.1
+        # https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwiG9YjNtvr2AhUbiVwKHdQfD9sQFnoECAsQAQ&url=https%3A%2F%2Fpdg.lbl.gov%2F2005%2Freviews%2Fpassagerpp.pdf&usg=AOvVaw1HGA5PZtC2UiqA6B7_C5dz        
+                
+        eps = a/b
+        return (e + eps) * np.exp(-b * zcorr) - eps
+
+    def mean_scattering_angle(self, e, cosThetaX, cosThetaY, zcorr, X0 = 10.02, m = 105.66e-3, charge = 1, beta = 1):
+        "Calculate mean scattering angle over many scatters for given energy and length z"
+        
+        # Based on PDG chapter 27 eqns 27.10, 27.16, 27.17
+        # https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwiG9YjNtvr2AhUbiVwKHdQfD9sQFnoECAsQAQ&url=https%3A%2F%2Fpdg.lbl.gov%2F2005%2Freviews%2Fpassagerpp.pdf&usg=AOvVaw1HGA5PZtC2UiqA6B7_C5dz        
+        # and
+        # https://pdg.lbl.gov/2014/AtomicNuclearProperties/HTML/standard_rock.html
+        
+        # Convert E to momentum [GeV]
+        p = np.sqrt(e**2 - m**2)  
+        
+        # Mean angle [GeV and cm]
+        c = 1 # n.u
+        theta0 = 13.6e-3 / (p * c * beta) * charge *  np.sqrt(zcorr/X0) * (1 + 0.38 * math.log(zcorr, X0))
+        return theta0
+
+    def scattering_angle(self, cosTheta, theta0, rand1):
+        "Calculate actual scattering angle over many scatters for given start angle and mean angle"
+        
+        # Convert theta wrt x or y axis to wrt z axis
+        theta = self.angle(cosTheta)
+        
+        # Add random scattering angle
+        return theta + rand1 * theta0
+
+    def scattering_postition(self, x, cosThetaX, cosThetaY, zcorr, theta0, rand1, rand2):
+        "Calculate transverse scattering position over many scatters for given start angle and mean angle + length z"
+        
+        # Convert theta wrt x to wrt z axis
+        thetaX = self.angle(cosThetaX)
+        
+        xout = np.copy(x)
+        if xout.ndim == 0:
+            xout = float(xout)
+        
+        # Add displacement due to initial angle
+        #xang = z * np.tan(thetaX)
+        xang = zcorr * np.sin(thetaX)            
+        xout += xang
+        
+        # Add displacement due to multiple scattering
+        dx = rand1 * zcorr * theta0 / np.sqrt(12) + rand2 * zcorr * theta0/2
+        xout += dx
+    
+        return xout
+
+    def propagate(self, entry):
+        "Propagate the particle through a given distance of standard rock using the small-angle approxiumation"
+
+        if self.dist == 0:
+            return entry
+
+        # Random numbers
+        rand1 = self.rng.normal(0, 1)
+        rand2 = self.rng.normal(0, 1)        
+
+        # Get entry info
+        e = entry["E"]
+        x = entry["x"]
+        y = entry["y"]        
+        cosX = entry["cosX"]
+        cosY = entry["cosY"]
+
+        # Correct path length for angles
+        z = self.path_length(self.dist, cosX, cosY)
+
+        # Account for energy loss
+        #eout = self.energy_after_loss(e, cosX, cosY, z)        
+        eout = self.energy_after_loss_exact(e, z) 
+
+        # Account for scattering on angle and position
+        theta0 = self.mean_scattering_angle(e, cosX, cosY, z)
+        thetaXout = self.scattering_angle(cosX, theta0, rand1)
+        thetaYout = self.scattering_angle(cosY, theta0, rand1)
+        xout = self.scattering_postition(x, cosX, cosY, z, theta0, rand1, rand2)
+        yout = self.scattering_postition(y, cosY, cosX, z, theta0, rand1, rand2)        
+
+        # Update entry info using copy for cases when resample so don't change the original
+        newentry = entry.copy()
+        newentry["E"] = eout
+        newentry["x"] = xout
+        newentry["y"] = yout        
+        newentry["cosX"] =  np.cos(np.pi/2 + thetaXout)
+        newentry["cosY"] =  np.cos(np.pi/2 + thetaYout)
+        
+        return newentry
+
+
+    def process(self, entry, evt):
+
+        if self.test:
+            for k,v in entry.items():
+                self.before[k].append(float(v))
+
+        if self.msg.level > DEBUG: 
+            print("Original Entry", entry)
+
+        if self.dist != 0:
+            # Propoagate to FASER            
+            newentry = self.propagate(entry)
+        elif self.nsamples != 1:
+            # Else smear if sampling more than once, using copy to avoid changing original
+            newentry = entry.copy()
+            newentry["E"] *= self.rng.normal(1, 0.05)
+            newentry["x"] *= self.rng.normal(1, 0.05)
+            newentry["y"] *= self.rng.normal(1, 0.05)            
+            newentry["cosX"] = np.cos(np.arccos(entry["cosX"]) * self.rng.normal(1, 0.05))
+            newentry["cosY"] = np.cos(np.arccos(entry["cosY"]) * self.rng.normal(1, 0.05))
+        else:
+            # No propagation or smearing
+            newentry = entry
+
+        if self.msg.level > DEBUG: 
+            print("Propagated/Smeared Entry", newentry)
+
+            
+        if self.test:
+            for k,v in newentry.items():
+                self.after[k].append(float(v))
+        
+        try:
+          from AthenaPython.PyAthena import HepMC3  as HepMC
+        except ImportError:
+          from AthenaPython.PyAthena import HepMC   as HepMC
+
+        # Add weight, correcting for mutliple sampling
+        evt.weights().push_back(newentry["w"] / self.nsamples)
+
+
+        # Setup MC event
+        mcEventType = EventType()
+        mcEventType.add_type(EventType.IS_SIMULATION)
+
+        mcEventId = EventID(run_number = newentry["run"], event_number = newentry["event"])
+
+        mcEventInfo = EventInfo(id = mcEventId, type = mcEventType)
+
+        self.evtStore.record(mcEventInfo, "McEventInfo", True, False)
+
+        ROOT.SetOwnership(mcEventType, False)
+        ROOT.SetOwnership(mcEventId, False)
+        ROOT.SetOwnership(mcEventInfo, False)
+
+        # Create HepMC Vertex
+        pos = HepMC.FourVector(newentry["x"] * cm, newentry["y"] * cm, self.z, 0)
+        gv = HepMC.GenVertex(pos)
+
+        ROOT.SetOwnership(gv, False)
+        evt.add_vertex(gv)
+
+        # TODO: skip event if below a certain energy
+
+        # Create HepMC particle
+        gp = HepMC.GenParticle()
+
+        m = 105.66
+        e = newentry["E"] * 1000.  #MeV
+
+        # If the energy is less than mass then skip the event
+        if e < m:
+            self.setFilterPassed(False)
+            self.msg.debug("Event failed energy cut")
+            return False
+        else:
+            self.setFilterPassed(True)
+        
+        p = np.sqrt(e**2 - m**2)
+
+        thetaX = self.angle(newentry["cosX"])        
+        thetaY = self.angle(newentry["cosY"])
+
+        # theta: just above z axis as phi deals with negative 
+        theta = np.abs(thetaY)
+        # phi: 0 - 2pi
+        phi = np.arctan2(newentry["cosY"], newentry["cosX"])
+        #phi = np.arctan(newentry["cosY"] / newentry["cosX"])
+        if phi < 0: phi += 2*np.pi
+        if phi == 2*np.pi: phi = 0
+
+        #self.msg.debug(f"INPUT: {e}, {m}, {p}, {theta}, {phi}, {np.sin(theta)}, {np.cos(theta)}, {np.sin(phi)}, {np.cos(phi)}")
+                    
+        px = p * np.sin(theta) * np.cos(phi)
+        py = p * np.sin(theta) * np.sin(phi)        
+        pz = p * np.cos(theta) 
+
+        mom = HepMC.FourVector(px, py, pz, e)
+
+        gp.set_momentum(mom)
+        gp.set_generated_mass(m)
+        gp.set_pdg_id(self.pid(newentry["type"]))
+        gp.set_status(1)
+
+        #self.msg.debug(f"HEPMC:{px, py, pz, e}")
+        #gp.print()
+        
+        ROOT.SetOwnership(gp, False)
+        gv.add_particle_out(gp)
+
+        return True
+
+    def plot(self):
+        "Plot entries before and after propagation/smeating for tests"
+
+        if not self.test:
+            return
+
+        import matplotlib.pyplot as plt
+        
+        plt.figure()
+        ebins = np.linspace(0, 5000, 50)
+        plt.xlabel("Energy")
+        plt.hist(self.before["E"], bins=ebins, histtype='step', color = "g", fill = False, label = "before")
+        plt.hist(self.after["E"], bins = ebins, histtype='step', color = "r", fill = False, label = "after")
+        plt.gca().set_yscale('log')
+        plt.legend()
+        plt.savefig("energy.png")
+
+        plt.figure()
+        plt.xlabel("Angle to beam in X dir")        
+        thetaX =  np.pi/2. - np.arccos(np.array(self.before["cosX"]))
+        thetaXout =  np.pi/2. - np.arccos(np.array(self.after["cosX"]))        
+        tbins = np.linspace(-0.5, 0.5, 100)
+        plt.hist(thetaX, bins = tbins, histtype='step', color = "g", fill = False, label = "before")
+        plt.hist(thetaXout, bins = tbins, histtype='step', color = "r", fill = False, label = "after")
+        plt.gca().set_yscale('log')
+        plt.legend()
+        plt.savefig("thetaX.png")
+
+        plt.figure()
+        plt.xlabel("Angle to beam in Y dir")        
+        thetaY =  np.pi/2. - np.arccos(np.array(self.before["cosY"]))
+        thetaYout =  np.pi/2. - np.arccos(np.array(self.after["cosY"]))        
+        plt.hist(thetaY, bins = tbins, histtype='step', color = "g", fill = False, label = "before")
+        plt.hist(thetaYout, bins = tbins, histtype='step', color = "r", fill = False, label = "after")
+        plt.gca().set_yscale('log')
+        plt.legend()
+        plt.savefig("thetaY.png")
+
+        plt.figure()
+        plt.xlabel("Dispacement in X dir")        
+        xbins = np.linspace(-300, 300, 100)
+        plt.hist(self.before["x"], bins = xbins, histtype='step', color = "g", fill = False, label = "before")
+        plt.hist(self.after["x"], bins = xbins, histtype='step', color = "r", fill = False, label = "after")
+        plt.gca().set_yscale('log')
+        plt.legend()
+        plt.savefig("x.png")
+
+        plt.figure() 
+        plt.xlabel("Dispacement in Y dir")               
+        plt.hist(self.before["y"], bins = xbins, histtype='step', color = "g", fill = False, label = "before")
+        plt.hist(self.after["y"], bins = xbins, histtype='step', color = "r", fill = False, label = "after")        
+        plt.gca().set_yscale('log')
+        plt.legend()
+        plt.savefig("y.png")
+
+        return
+
+def getNEvents(fname, maxEvents):
+    "Work out how many events are in the file"
+
+    n = 0
+    with open(fname) as f:
+        n = sum(1 for _ in f)
+
+    if maxEvents != -1 and n > maxEvents:
+        n = maxEvents
+
+    print(">>> Setting number of real events to", n)
+
+    return n
+
+if __name__ == "__main__":
+
+#     from AthenaCommon.AlgSequence import AlgSequence
+#     job = AlgSequence()
+#     job += FlukaReader(file_name = "/user/gwilliam/unit30_Nm", dist = (480-409))
+# 
+#     from AthenaPoolCnvSvc.WriteAthenaPool import AthenaPoolOutputStream
+#     ostream = AthenaPoolOutputStream( "StreamEVGEN" , "evgen.pool.root", noTag=True )    
+#     ostream.ItemList.remove("EventInfo#*")
+#     ostream.ItemList += [ "EventInfo#McEventInfo", 
+#                            "McEventCollection#*" ]
+#                 
+#     theApp.EvtMax = 1000
+
+
+    import argparse, sys    
+    parser = argparse.ArgumentParser(description="Run Fluka reader")
+    parser.add_argument("file", help = "Path to fluka file")
+    parser.add_argument("--dist", "-d", default = 0, type = float, help = "depth of standard rock to propagate through [m]")
+    parser.add_argument("--pos", "-z", default = -3.75, type = float, help = "Position in z in FASER coordinate system [m]")    
+    parser.add_argument("--output", "-o",  default = "evgen.pool.root", help = "Name of output file")
+    parser.add_argument("--mcEventKey", "-k",  default = "BeamTruthEvent", help = "Name of MC collection")
+    parser.add_argument("--nevents", "-n", default = -1, type = int, help = "Number of events to process")
+    parser.add_argument("--randomSeed", "-r", default=12345, type=int, help = "Seed for random number generator")
+    parser.add_argument("--nsamples", "-s", default = 1, type = int, help = "Number of times to sample each event")
+    parser.add_argument("--test", "-t", action = "store_true", help = "Make test plots")    
+    args = parser.parse_args()
+
+
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG, INFO
+    
+    from AthenaCommon.Configurable import Configurable
+    Configurable.configurableRun3Behavior = 1
+
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Input.isMC = True
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"             # Always needed; must match FaserVersion
+    ConfigFlags.GeoModel.FaserVersion     = "FASER-01"           # Default FASER geometry
+    ConfigFlags.Detector.EnableFaserSCT = True
+    ConfigFlags.Output.EVNTFileName = args.output    
+    ConfigFlags.lock()
+
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+    
+    from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+    from AthenaConfiguration.ComponentFactory import CompFactory
+
+    acc = ComponentAccumulator()
+    reader = FlukaReader("FlukaReader", MCEventKey=args.mcEventKey, file_name = args.file, dist = args.dist, z = args.pos, randomSeed = args.randomSeed, nsamples = args.nsamples, test = args.test)
+    reader.OutputLevel = INFO
+    acc.addEventAlgo(reader)    
+    cfg.merge(acc)
+
+    itemList = [ "EventInfo#McEventInfo", "McEventCollection#*" ]
+    from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+    cfg.merge(OutputStreamCfg(ConfigFlags, "EVNT", itemList, disableEventTag = True))
+    cfg.getEventAlgo("OutputStreamEVNT").AcceptAlgs = ["FlukaReader"]
+    sc = cfg.run(maxEvents = getNEvents(args.file, args.nevents) * args.nsamples)
+    sys.exit(not sc.isSuccess())    
diff --git a/Generators/ForeseeGenerator/CMakeLists.txt b/Generators/ForeseeGenerator/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e2e5137b8c50f144c2cef2528f0b6be5c5548270
--- /dev/null
+++ b/Generators/ForeseeGenerator/CMakeLists.txt
@@ -0,0 +1,12 @@
+################################################################################
+# Package: ForeseeGenerator
+################################################################################
+
+# Declare the package name:
+atlas_subdir( ForeseeGenerator )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
+atlas_install_joboptions( share/*.py )
+
diff --git a/Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy b/Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy
new file mode 100644
index 0000000000000000000000000000000000000000..36320c3f1c9cced47c9307b48529b444a2384be0
Binary files /dev/null and b/Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy differ
diff --git a/Generators/ForeseeGenerator/python/ForeseeSampler.py b/Generators/ForeseeGenerator/python/ForeseeSampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f7d7241e560e552b8df3bcd9cb9b62e321f17ff
--- /dev/null
+++ b/Generators/ForeseeGenerator/python/ForeseeSampler.py
@@ -0,0 +1,463 @@
+import os, sys
+import random
+import numpy as np
+
+import ParticleGun as PG
+
+from DIFGenerator.DIFSampler import CylinderSampler
+
+class ForeseeNumpySampler(PG.ParticleSampler):
+    """ Sample from the output of Foresee generation in numpy format with columns E, theta, weight"""
+    def __init__(self, model_path = ".", model_name = "DarkPhoton",  com_energy = "14", mother_mass = 0.01,
+                 coupling = None, mother_pid = None, daughter1_pid = 11, daughter2_pid = -11, randomSeed = None):
+
+        self.path = os.path.expanduser(os.path.expandvars(model_path))
+        self.modelname = model_name
+        self.energy = com_energy
+        self.mass = mother_mass
+        self.coupling = coupling
+        self.pid = mother_pid
+        self.daughter1_pid = daughter1_pid
+        self.daughter2_pid = daughter2_pid
+        self.n = 1
+        self.distance = 480 # m
+        self.mass_override = False
+
+        if randomSeed is not None:
+            print(f"Setting seed to {randomSeed}")
+            self.rng = np.random.default_rng(randomSeed)
+        else:
+            self.rng = np.random.default_rng()        
+
+        self.xs = 0
+
+        self.read()
+
+    def read(self):
+        "Read data from numpy file in format E, theta, weight"
+
+        if self.path.endswith(".npy"):
+            filename = self.path
+        elif self.coupling is None:
+            filename = f"{self.path}/files/models/{self.modelname}/events/events_{self.energy}TeV_m{self.mass}GeV_to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+        else:
+            filename = f"{self.path}/files/models/{self.modelname}/events/events_{self.energy}TeV_m{self.mass}GeV_c{self.coupling}to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+
+        print(f"Reading data from file: {filename}")
+        self.data = np.load(filename)
+
+        # Create probablity for each mode as weight / sum(weights)
+        self.prob = self.data[2]/np.sum(self.data[2])
+        
+        return
+
+#     def mass(self):
+#         "Mass converted from GeV to MeV"
+#         return self._mass * 1000
+
+    def shoot(self):
+        "Choose a random item from the data, based on the probability"
+
+        energy, theta, weight = self.rng.choice(self.data, axis = 1, p = self.prob)
+
+        self.xs += weight
+
+        # Convert mass to MeV
+        mass = self.mass * 1000
+
+        # Sample phi
+        phi = self.rng.uniform(0, 2*np.pi)
+
+        # Sample z
+        z = self.rng.uniform(-1500, 0)        
+
+        # Create mother momentum, randomly sampling phi
+        self.mom = PG.EThetaMPhiSampler(energy, theta, mass, phi)
+
+        # Create mother pos, randomly sampling phi
+        r = (self.distance * 1000 + abs(z))  * np.tan(theta)
+        
+        self.pos = CylinderSampler(r**2, phi, z, 0)
+
+        # Create particle
+        p = PG.SampledParticle(self.pid)
+        p.mom = self.mom.shoot()
+        p.pos = self.pos.shoot()
+        p.mass = mass
+        #self.mom.mass = mass        
+
+        return [p]
+
+class ForeseeSampler(PG.MomSampler):
+    """Create events from foresee directly on the fly
+
+    Requires:
+        * foresee to be downloaded and in python path
+
+        cd <PATH>
+        git clone https://github.com/KlingFelix/FORESEE.git
+        export PYTHONPATH=$PYTHONPATH:<PATH>/FORESEE/src/        
+        
+        * scikit-hep installed
+
+        pip install scikit-hep --user
+        
+        * forsee files dir symlinked to the run dir
+
+        ln -s <PATH>/FORESEE/files .
+
+    """
+    
+    
+    def __init__(self, modelname, energy, mass, couplings, daughter1_pid, daughter2_pid, mother_pid = None):
+        self.modelname = modelname
+        self.model = Model(self.modelname)
+        self.energy = energy
+        self._mass = mass
+        self.couplings = [couplings] if isinstance(couplings, str) else couplings
+        self.mother_pid = mother_pid
+        self.daughter1_pid = daughter1_pid
+        self.daughter2_pid = daughter2_pid
+
+        self.rng = np.random.default_rng()
+        self.xs = 0
+
+        if not os.path.exists("files"):
+            os.symlink(os.path.expandvars("$Calypso_DIR/../calypso/Generators/foresee/files"), "files")
+
+        self.pid_map = { 
+            (11, 11) : "e_e",           
+            (13, 13) : "mu_mu",
+            (22, 22) : "gamma_gamma",
+            }
+
+        self.mode = self.pid_map.get((self.daughter1_pid, self.daughter2_pid), None)
+        if self.mode is None:
+            sys.exit(f"Undefined decay to {self.daughter1_pid} + {self.daughter2_pid} for {self.modelname}")
+
+        from foresee import Foresee, Model, Utility
+        self.foresee = Foresee()
+        self.foresee.set_detector(selection="np.sqrt(x.x**2 + x.y**2)< 0.1", channels=[self.mode], distance=480, length=1.5 , luminosity=150)        
+
+
+        if self.modelname == "DarkPhoton":
+            self.data = self.darkphoton()
+        elif self.modelname == "ALP-W":
+            self.data = self.alps()
+        else:
+            sys.exit(f"Unknown model {self.modelname}")
+
+        return
+
+
+    def mass(self):
+        return self._mass * 1000
+
+    def darkphoton(self):
+
+        # Production modes
+        self.model.add_production_2bodydecay(
+            pid0 =  "111",
+            pid1 = "22",
+            br = "2.*0.99 * coupling**2 * pow(1.-pow(mass/self.masses('111'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+    
+        self.model.add_production_2bodydecay(
+            pid0 = "221",
+            pid1 = "22",
+            br = "2.*0.39 * coupling**2 * pow(1.-pow(mass/self.masses('221'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+
+        # Handwavey 
+        self.model.add_production_mixing(
+            pid = "113",
+            mixing = "coupling * 0.3/5. * 0.77545**2/abs(mass**2-0.77545**2+0.77545*0.147*1j)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            )
+
+        # Question on validity as FASER gets larger
+        self.model.add_production_direct(
+            label = "Brem",
+            energy = self.energy,
+            condition = "p.pt<1",
+            coupling_ref=1,
+            )
+        
+        self.model.add_production_direct(
+            label = "DY",
+            energy = self.energy,
+            coupling_ref=1,
+            massrange=[1.5, 10.]
+            )
+
+        return self.decays()
+
+
+    def alps(self):
+
+        self.model.add_production_2bodydecay(
+            pid0 = "5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20, # Vary over out of phi and theta 
+            )
+    
+        
+        self.model.add_production_2bodydecay(
+            pid0 = "-5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20,
+            )
+        
+        self.model.add_production_2bodydecay(
+            pid0 = "130",
+            pid1 = "111",
+            br = "4.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        self.model.add_production_2bodydecay(
+            pid0 = "321",
+            pid1 = "211",
+            br = "10.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        return self.decays()
+
+
+    def decays(self):
+        # Decays
+        self.model.set_ctau_1d(
+            filename=f"files/models/{self.modelname}/ctau.txt", 
+            coupling_ref=1
+            )
+
+        # TODO take into account BR
+        self.model.set_br_1d(
+            modes = [self.mode],
+            filenames=[f"files/models/{self.modelname}/br/{self.mode}.txt"] 
+            )
+
+        # LLP spectrum
+        self.foresee.set_model(model=self.model)
+        
+        plt = self.foresee.get_llp_spectrum(self._mass, coupling=1, do_plot=True)  # This is just a reference coupling 
+        plt.savefig(f"{self.modelname}.png")
+
+        def flatten(l):
+            return [i for sublist in l for i in sublist]
+
+        coups, ctaus, nsigs, energies, weights, thetas = self.foresee.get_events(mass=self._mass, energy=self.energy, couplings=self.couplings)
+
+        return [flatten(thetas), flatten(energies), flatten(weights)] 
+
+    def shoot(self):
+        # Create probablity for each mode as weight / sum(weights)
+        prob = self.data[2]/np.sum(self.data[2])
+
+        # Choose a random item from the data, base on the probability
+        # TODO: what about reuse of events?
+        theta_mother, e_mother, w = self.rng.choice(self.data, axis = 1, p = prob)
+
+        self.xs += w
+
+        # Create other momentum
+        mother_mom = PG.EThetaMPhiSampler(e_mother*1000, theta_mother, self.mass(), [0,2*np.pi])
+
+        return mother_mom.shoot()
+
+if __name__ == "__main__":
+
+    # Testing ...
+
+    from math import sqrt, log10
+    import matplotlib.pyplot as plt
+    import matplotlib
+    from DIFGenerator import DIFSampler
+    
+    
+    path = os.path.expandvars("$Calypso_DIR/../calypso/Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy")
+    path = "files/models/DarkPhoton/events/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy"
+
+    modelname = "DarkPhoton"
+    mass = 0.1
+
+    theta = []
+    mom = []
+
+    d0theta = []
+    d0mom = []
+
+    d1theta = []
+    d1mom = []
+
+    # Accounting for rounding
+    epsilon = 6
+
+    # Create mother sampler reading data from foresee
+    mother_sampler = ForeseeNumpySampler(model_path = path, model_name = modelname, com_energy = "14", mother_mass = 0.1, coupling = 1e-5,  mother_pid = None, daughter1_pid = 11, daughter2_pid = -11)
+    
+    # Create decay-in-flight
+    d = DIFSampler(11, -11, None)
+    d.mother_sampler = mother_sampler
+    
+    # Loop over a range of events
+    for i in range(100000):        
+
+        # Shoot the decay in flight
+        daughters = d.shoot()
+
+        # Get mother and sum of daugthers and check these make sense.
+        mother_mom = d.mother_mom
+        s = daughters[0].mom+daughters[1].mom
+
+        try:            
+            assert mother_mom.E() - epsilon <= s.E() <= mother_mom.E() + epsilon
+            assert mother_mom.P() - epsilon <= s.P()<= mother_mom.P() + epsilon 
+            assert mother_mom.Px() - epsilon <= s.Px() <= mother_mom.Px() + epsilon
+            assert mother_mom.Py() - epsilon <= s.Py() <= mother_mom.Py() + epsilon
+            assert mother_mom.Pz() - epsilon <= s.Pz() <= mother_mom.Pz() + epsilon
+            assert daughters[0].pos.X() == daughters[1].pos.X() == d.mother_pos.X()
+            assert daughters[0].pos.Y() == daughters[1].pos.Y() == d.mother_pos.Y()
+            assert daughters[0].pos.Z() == daughters[1].pos.Z() == d.mother_pos.Z()
+        except AssertionError:
+            print("Error on run " + str(i))
+
+            print("mother particle:")
+            print(" E = "     + str(mother_mom.E()))
+            print(" M = "     + str(mother_mom.M()))
+            print(" P = "     + str(mother_mom.P()))
+            print(" Px = "    + str(mother_mom.Px()))
+            print(" Py = "    + str(mother_mom.Py()))
+            print(" Pz = "    + str(mother_mom.Pz()))
+            print(" theta = " + str(mother_mom.Theta()))
+            print(" phi = "   + str(mother_mom.Phi()))
+            print(" x = "     + str(d.mother_pos.X()))
+            print(" y = "     + str(d.mother_pos.Y()))
+            print(" z = "     + str(d.mother_pos.Z()))
+
+            print("daughter 0 particle:")
+            print(" E = "     + str(daughters[0].mom.E()))
+            print(" M = "     + str(daughters[0].mom.M()))
+            print(" P = "     + str(daughters[0].mom.P()))
+            print(" Px = "    + str(daughters[0].mom.Px()))
+            print(" Py = "    + str(daughters[0].mom.Py()))
+            print(" Pz = "    + str(daughters[0].mom.Pz()))
+            print(" theta = " + str(daughters[0].mom.Theta()))
+            print(" phi = "   + str(daughters[0].mom.Phi()))
+            print(" x = "     + str(daughters[0].pos.X()))
+            print(" y = "     + str(daughters[0].pos.Y()))
+            print(" z = "     + str(daughters[0].pos.Z()))
+
+            print("daughter 1 particle:")
+            print(" E = "     + str(daughters[1].mom.E()))
+            print(" M = "     + str(daughters[1].mom.M()))
+            print(" P = "     + str(daughters[1].mom.P()))
+            print(" Px = "    + str(daughters[1].mom.Px()))
+            print(" Py = "    + str(daughters[1].mom.Py()))
+            print(" Pz = "    + str(daughters[1].mom.Pz()))
+            print(" theta = " + str(daughters[1].mom.Theta()))
+            print(" phi = "   + str(daughters[1].mom.Phi()))
+            print(" x = "     + str(daughters[1].pos.X()))
+            print(" y = "     + str(daughters[1].pos.Y()))
+            print(" z = "     + str(daughters[1].pos.Z()))
+
+            raise
+
+        # Store mother info to plot
+        theta.append(log10(mother_mom.Theta()))
+        mom.append(log10(mother_mom.P()/1000.))
+
+        # Store mother info to plot
+        d0theta.append(log10(daughters[0].mom.Theta()))
+        d0mom.append(log10(daughters[0].mom.P()/1000.))
+        d1theta.append(log10(daughters[1].mom.Theta()))
+        d1mom.append(log10(daughters[1].mom.P()/1000.))
+        
+
+ 
+    # Plot mother from sampling events
+    prange=[[-6, 0, 120],[ 0, 5, 50]]
+    tmin, tmax, tnum = prange[0]
+    pmin, pmax, pnum = prange[1]
+    t_edges = np.logspace(tmin, tmax, num=tnum+1)
+    p_edges = np.logspace(pmin, pmax, num=pnum+1)   
+
+    ticks = np.array([[np.linspace(10**(j),10**(j+1),9)] for j in range(-7,6)]).flatten()
+    ticks = [np.log10(x) for x in ticks]
+    ticklabels = np.array([[r"$10^{"+str(j)+"}$","","","","","","","",""] for j in range(-7,6)]).flatten()
+    matplotlib.rcParams.update({'font.size': 15})
+
+    
+    fig = plt.figure(figsize=(8,5.5))
+    ax = plt.subplot(1,1,1)
+    h=ax.hist2d(x=theta,y=mom,
+                bins=[tnum,pnum],range=[[tmin,tmax],[pmin,pmax]],
+                norm=matplotlib.colors.LogNorm(), cmap="hsv",
+                )
+    fig.colorbar(h[3], ax=ax)
+    ax.set_xlabel(r"angle wrt. beam axis $\theta$ [rad]")
+    ax.set_ylabel(r"momentum $p$ [GeV]")
+    ax.set_xticks(ticks)
+    ax.set_xticklabels(ticklabels)
+    ax.set_yticks(ticks)
+    ax.set_yticklabels(ticklabels)
+    ax.set_xlim(tmin, tmax)
+    ax.set_ylim(pmin, pmax)
+    plt.savefig(f"{modelname}_PG_m{mass}.png")
+
+    fig = plt.figure(figsize=(8,5.5))
+    ax = plt.subplot(1,1,1)
+    h=ax.hist2d(x=d0theta,y=d0mom,
+                bins=[tnum,pnum],range=[[tmin,tmax],[pmin,pmax]],
+                norm=matplotlib.colors.LogNorm(), cmap="hsv",
+                )
+    fig.colorbar(h[3], ax=ax)
+    ax.set_xlabel(r"angle wrt. beam axis $\theta$ [rad]")
+    ax.set_ylabel(r"momentum $p$ [GeV]")
+    ax.set_xticks(ticks)
+    ax.set_xticklabels(ticklabels)
+    ax.set_yticks(ticks)
+    ax.set_yticklabels(ticklabels)
+    ax.set_xlim(tmin, tmax)
+    ax.set_ylim(pmin, pmax)
+    plt.savefig(f"{modelname}_PG_d0_m{mass}.png")
+
+    fig = plt.figure(figsize=(8,5.5))
+    ax = plt.subplot(1,1,1)
+    h=ax.hist2d(x=d1theta,y=d1mom,
+                bins=[tnum,pnum],range=[[tmin,tmax],[pmin,pmax]],
+                norm=matplotlib.colors.LogNorm(), cmap="hsv",
+                )
+    fig.colorbar(h[3], ax=ax)
+    ax.set_xlabel(r"angle wrt. beam axis $\theta$ [rad]")
+    ax.set_ylabel(r"momentum $p$ [GeV]")
+    ax.set_xticks(ticks)
+    ax.set_xticklabels(ticklabels)
+    ax.set_yticks(ticks)
+    ax.set_yticklabels(ticklabels)
+    ax.set_xlim(tmin, tmax)
+    ax.set_ylim(pmin, pmax)
+    plt.savefig(f"{modelname}_PG_d1_m{mass}.png")
+    
+
+    print (f"x-sect = {mother_sampler.xs} pb")
+    
+
+
+        
diff --git a/Generators/ForeseeGenerator/python/Validate.py b/Generators/ForeseeGenerator/python/Validate.py
new file mode 100644
index 0000000000000000000000000000000000000000..399d85ae788903d7f2829af6bd3239a69ddb0527
--- /dev/null
+++ b/Generators/ForeseeGenerator/python/Validate.py
@@ -0,0 +1,255 @@
+from AthenaPython.PyAthena import StatusCode, McEventCollection, HepMC, CLHEP
+from GeneratorModules.EvgenAnalysisAlg import EvgenAnalysisAlg
+
+import ROOT as R
+import numpy as np
+import os
+from math import sqrt
+
+def fix():
+    "Python Fixes for HepMC"
+    def add(self, other):
+        self.set(self.x() + other.x(), self.y() + other.y(),
+                 self.z() + other.z(), self.t() + other.t())
+        return self
+
+    HepMC.FourVector.__iadd__ = add
+    del add
+
+    return
+
+class HistSvc(object):
+    "Class to deal with histograms"
+
+    def __init__(self):
+        self.hists = {}
+
+    def add(self, name, nbinsX = None, loX = None, hiX = None, nbinsY = None, loY = None, hiY = None, title = None, arrayX = None, arrayY = None):
+        hname = os.path.basename(name)
+
+        if title is None:  title = hname
+
+        if nbinsY is not None:
+            self.hists[name] = R.TH2F(hname, title, nbinsX, loX, hiX, nbinsY, loY, hiY)
+        elif arrayX is not None and arrayY is not None:
+            self.hists[name] = R.TH2F(hname, title, len(arrayX) - 1, arrayX, len(arrayY) - 1, arrayY)
+        elif arrayX is not None and arrayY is None and nbinsY is not None:
+            self.hists[name] = R.TH2F(hname, title, len(arrayX) - 1, arrayX, nbinsY, loY, hiY)
+        elif arrayX is None and arrayY is not None:
+            self.hists[name] = R.TH2F(hname, title, nbinsX, loX, hiX, len(arrayY) - 1, arrayY)            
+        elif arrayX is not None:
+            self.hists[name] = R.TH1F(hname, title, len(arrayX) - 1, arrayX)
+        else:
+            self.hists[name] = R.TH1F(hname, title, nbinsX, loX, hiX)                
+
+    def __getitem__(self, name):
+        return self.hists[name]
+
+    def write(self, name):
+
+        f = R.TFile.Open(name, "RECREATE")
+    
+        for n, h in self.hists.items():
+            path = os.path.dirname(n)
+            if path and not f.GetDirectory(path):
+                f.mkdir(path)
+            
+            f.cd(path)
+            h.Write()
+
+        f.Close()
+
+        return
+
+class EvgenValidation(EvgenAnalysisAlg):
+    "Gen-level validation"
+
+    def __init__(self, name = "EvgenValidation", ndaughters = 2, outname = "validation.root", mother_stored = False):
+        super(EvgenValidation, self).__init__(name=name)
+        self.hists = HistSvc()
+        self.mother_stored = mother_stored
+        self.ndaughters = ndaughters
+        self.outname = outname
+
+    def binning(self):
+        "binning for theta vs phi plot"
+        tmin, tmax, tnum = [-6, 0, 24]
+        pmin, pmax, pnum = [ 0, 5, 10]
+        t_edges = np.logspace(tmin, tmax, num=tnum+1)
+        p_edges = np.logspace(pmin, pmax, num=pnum+1)
+        return t_edges, p_edges
+
+    def initialize(self):
+
+        # All daughters
+        self.hists.add("PIDs", 600, -300, 300)
+
+        # Daughter i
+        tbins, pbins = self.binning()
+        for i in range(self.ndaughters):
+            self.hists.add(f"E_d{i}", 100, 0, 10000)                        
+            self.hists.add(f"P_d{i}", 100, 0, 10000)
+            self.hists.add(f"Pz_d{i}", 100, 0, 10000)
+            self.hists.add(f"Pt_d{i}", 100, 0, 1)
+            self.hists.add(f"Theta_d{i}", 20, 0, 0.001)
+            self.hists.add(f"Phi_d{i}", 16, -3.2, 3.2)
+            self.hists.add(f"ThetaVsP_d{i}", arrayX = tbins, arrayY = pbins)
+            self.hists.add(f"Mass_d{i}", 5000, 0, 1)            
+
+        # Mother
+        self.hists.add("E_M", 1000, 0, 10000)
+        self.hists.add("P_M", 1000, 0, 10000)
+        self.hists.add("Pz_M", 1000, 0, 10000)         
+        self.hists.add("Pt_M", 1000, 0, 1)       
+        self.hists.add("Theta_M", 200, 0, 0.02)
+        self.hists.add("Phi_M", 16, -3.2, 3.2)
+        self.hists.add("Mass_M", 200, 0, 2)
+        self.hists.add("ThetaVsP_M", arrayX = tbins, arrayY = pbins)
+
+        # Vertex
+        self.hists.add("Vtx_X_LLP", 50, -100, 100)
+        self.hists.add("Vtx_Y_LLP", 50, -100, 100)
+        self.hists.add("Vtx_Z_LLP", 500, -5000, 0)
+        self.hists.add("Vtx_R_LLP", 20, 0, 200)        
+        self.hists.add("Vtx_XY_LLP", 50, -100, 100, 50, -100, 100)
+
+        self.hists.add("Vtx_X_All", 50, -100, 100)
+        self.hists.add("Vtx_Y_All", 50, -100, 100)
+        self.hists.add("Vtx_Z_All", 500, -5000, 0)
+        self.hists.add("Vtx_R_All", 20, 0, 200)        
+        self.hists.add("Vtx_XY_All", 50, -100, 100, 50, -100, 100)      
+        
+        return StatusCode.Success
+
+
+    def fillKin(self, label, p, mass = True, twoD = True):
+
+        self.hists[f"E_{label}"].Fill(p.t()/1000, self.weight)        
+        self.hists[f"P_{label}"].Fill(p.rho()/1000, self.weight)
+        self.hists[f"Pz_{label}"].Fill(p.pz()/1000, self.weight)                
+        self.hists[f"Pt_{label}"].Fill(p.perp()/1000, self.weight)
+        self.hists[f"Theta_{label}"].Fill(p.theta(), self.weight)
+        self.hists[f"Phi_{label}"].Fill(p.phi(), self.weight)
+
+        if mass:
+            self.hists[f"Mass_{label}"].Fill(p.m()/1000, self.weight)
+
+        if twoD:
+            self.hists[f"ThetaVsP_{label}"].Fill(p.theta(), p.rho()/1000, self.weight)
+
+        return
+
+    def fillDaughter(self, p):
+        self.hists["PIDs"].Fill(p.pdg_id(), self.weight)
+        return
+
+    def fillVertex(self, label, v):
+        self.hists[f"Vtx_X_{label}"].Fill(v.x(), self.weight)
+        self.hists[f"Vtx_Y_{label}"].Fill(v.y(), self.weight)
+        self.hists[f"Vtx_Z_{label}"].Fill(v.z(), self.weight)
+        self.hists[f"Vtx_XY_{label}"].Fill(v.x(), v.y(), self.weight)
+        self.hists[f"Vtx_R_{label}"].Fill(sqrt(v.x()**2 + v.y()**2), self.weight)                
+        return
+    
+
+    def execute(self):
+        evt = self.events()[0]
+        self.weight = evt.weights()[0] if evt.weights() else 1
+
+        # Loop over all particles in events 
+        momenta = []
+        vertices = []
+        mother = HepMC.FourVector(0,0,0,0)
+        llp_vtx = None
+        
+        for i, p in enumerate(evt.particles):
+            print("--- ", i)
+            p.print()
+            self.fillDaughter(p)
+
+            if self.mother_stored:
+                if i == 0:
+                    mother = p.momentum()
+                else:
+                    momenta.append(p.momentum()) 
+            else:
+                momenta.append(p.momentum())    
+                mother += p.momentum()
+
+            if i == 0 and p.production_vertex():
+                #p.production_vertex().print()
+                llp_vtx = p.production_vertex().point3d()
+
+            if p.production_vertex():
+                vertices.append(p.production_vertex().point3d())
+
+        # Fill daughter plots
+        for i in range(self.ndaughters):
+            if i >= len(momenta): continue
+            self.fillKin(f"d{i}", momenta[i])
+
+        # Fill mother plots
+        self.fillKin("M", mother, mass = True)
+
+        # Fill all vertices
+        for v in vertices:
+            self.fillVertex("All", v)
+
+        # Fill LLP vertex plots
+        if llp_vtx:
+            self.fillVertex("LLP", llp_vtx)
+            
+        return StatusCode.Success
+
+    def finalize(self):
+        self.hists.write(self.outname)
+        return StatusCode.Success
+
+        
+if __name__ == "__main__":
+
+    import argparse, sys
+    parser = argparse.ArgumentParser(description="Run gen-level validation")
+    parser.add_argument("file", nargs="+", help = "full path to imput file")
+    parser.add_argument("--ndaughters", "-d", default = 2, type = int, help = "Number of daugthers to plot")
+    parser.add_argument("--mother_stored", "-m", default = False, action = "store_true", help = "Is mother stored in input?")    
+    parser.add_argument("--output", "-o",  default = "validation.root", help = "Name of output file")
+    parser.add_argument("--mcEventKey", "-k",  default = "BeamTruthEvent", help = "Name of MC collection")
+    parser.add_argument("--nevents", "-n", default = -1, type = int, help = "Number of events to process")    
+    args = parser.parse_args()    
+
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG
+    log.setLevel(DEBUG)
+    
+    from AthenaCommon.Configurable import Configurable
+    Configurable.configurableRun3Behavior = 1
+
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Input.isMC = True
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"             # Always needed; must match FaserVersion
+    ConfigFlags.GeoModel.FaserVersion     = "FASER-01"           # Default FASER geometry
+    ConfigFlags.Detector.EnableFaserSCT = True
+    ConfigFlags.Input.Files = args.file
+    ConfigFlags.lock()
+
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+    
+    from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+    cfg.merge(PoolReadCfg(ConfigFlags))
+
+    from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+    from AthenaConfiguration.ComponentFactory import CompFactory
+
+    import McParticleEvent.Pythonizations
+    fix()
+    
+    acc = ComponentAccumulator()
+    valid = EvgenValidation("EvgenValidation", ndaughters =  args.ndaughters, outname = args.output, mother_stored = args.mother_stored)
+    valid.McEventKey = args.mcEventKey
+    acc.addEventAlgo(valid)    
+    cfg.merge(acc)
+
+    sc = cfg.run(maxEvents = args.nevents)
+    sys.exit(not sc.isSuccess())
diff --git a/Generators/ForeseeGenerator/share/convert_hepmc_to_evnt.py b/Generators/ForeseeGenerator/share/convert_hepmc_to_evnt.py
new file mode 100644
index 0000000000000000000000000000000000000000..382ad439e5ec1a7d31699c482fd0f17758f18f9c
--- /dev/null
+++ b/Generators/ForeseeGenerator/share/convert_hepmc_to_evnt.py
@@ -0,0 +1,62 @@
+def getNEvents(fname, maxEvents):
+    "Work out how many events are in the file"
+
+    n = 0
+    with open(fname) as f:
+        n = sum(1 for l in f if l.startswith("E "))
+
+    if maxEvents != -1 and n > maxEvents:
+        n = maxEvents
+
+    print ("Setting Maximum events to", n)
+    return n
+
+
+if __name__ == "__main__":
+
+    import sys
+    
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG
+    log.setLevel(DEBUG)
+    
+    from AthenaCommon.Configurable import Configurable
+    Configurable.configurableRun3Behavior = 1
+
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Input.RunNumber = [12345]
+    ConfigFlags.Input.OverrideRunNumber = True
+    ConfigFlags.Input.LumiBlockNumber = [1]
+
+    ConfigFlags.Input.isMC = True
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"             # Always needed; must match FaserVersion
+    ConfigFlags.GeoModel.FaserVersion     = "FASER-01"           # Default FASER geometry
+    ConfigFlags.Detector.EnableFaserSCT = True
+
+    ConfigFlags.Output.EVNTFileName = "myEVNT.pool.root"
+
+    ConfigFlags.Exec.MaxEvents= -1
+
+    import sys
+    ConfigFlags.fillFromArgs(sys.argv[1:])
+
+
+    ConfigFlags.Exec.MaxEvents = getNEvents(ConfigFlags.Input.Files[0], ConfigFlags.Exec.MaxEvents)    
+
+    ConfigFlags.lock()
+
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+
+    from HEPMCReader.HepMCReaderConfig import HepMCReaderCfg
+    cfg.merge(HepMCReaderCfg(ConfigFlags))
+
+    from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+    cfg.merge(McEventSelectorCfg(ConfigFlags))    
+
+    itemList = [ "EventInfo#McEventInfo", "McEventCollection#*" ]
+    from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+    cfg.merge(OutputStreamCfg(ConfigFlags, "EVNT", itemList, disableEventTag = True))
+
+    sc = cfg.run()
+    sys.exit(not sc.isSuccess())
diff --git a/Generators/ForeseeGenerator/share/generate_forsee_events.py b/Generators/ForeseeGenerator/share/generate_forsee_events.py
new file mode 100644
index 0000000000000000000000000000000000000000..37372b7bd5d574e084dad7842ad43a54eb31f1f9
--- /dev/null
+++ b/Generators/ForeseeGenerator/share/generate_forsee_events.py
@@ -0,0 +1,393 @@
+import os
+
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib
+
+class ForeseeGenerator(object):
+    """
+    Generate LLP particles within FASER acceptance from FORESEE
+    """
+    
+    def __init__(self, modelname, energy, mass, couplings, daughter1_pid, daughter2_pid, outdir = None, path = '.', randomSeed = 12345):
+
+        self.modelname = modelname
+        self.energy = energy
+        self.mass = mass
+        self.couplings = [couplings] if isinstance(couplings, (str, int, float)) else couplings
+        self.daughter1_pid = daughter1_pid
+        self.daughter2_pid = daughter2_pid
+        self.outdir = outdir
+        self.path = path
+        self.version = 1  # Forsee "version": 2 is in testing
+        self.seed = randomSeed
+
+        # Set decay mode ...
+        
+        self.pid_map = { 
+            (-11, 11) : "e_e",
+            (11, -11) : "e_e",                       
+            (-13, 13) : "mu_mu",
+            (13, -13) : "mu_mu",            
+            (22, 22) : "gamma_gamma",
+            }
+
+        self.mode = self.pid_map.get((self.daughter1_pid, self.daughter2_pid), None)
+        if self.mode is None:
+            sys.exit(f"Undefined decay to {self.daughter1_pid} + {self.daughter2_pid} for {self.modelname}")
+
+        # Set detector ...
+        if self.version == 1:
+            self.foresee = Foresee()
+        else:
+            self.foresee = Foresee(path = self.path)
+
+        # TODO: relax this a bit as daughters may enter even if mother doesn't
+        self.foresee.set_detector(selection="np.sqrt(x.x**2 + x.y**2)< 0.1",  
+                                  channels=[self.mode], distance=480, length=1.5 ,
+                                  luminosity=1/1000.) # 1 pb-1        
+
+        # Set model ...
+        if self.version == 1:
+            self.model = Model(self.modelname)
+        else:
+            self.model = Model(self.modelname, path = f"{self.path}/Models/{self.modelname}/")
+        
+        if self.modelname == "DarkPhoton":
+            self.data = self.darkphoton()
+        elif self.modelname == "ALP-W":
+            self.data = self.alp_W()
+        else:
+            sys.exit(f"Unknown model {self.modelname}")
+
+        return
+
+    def darkphoton(self):
+
+        # Production modes
+        self.model.add_production_2bodydecay(
+            pid0 =  "111",
+            pid1 = "22",
+            br = "2.*0.99 * coupling**2 * pow(1.-pow(mass/self.masses('111'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+    
+        self.model.add_production_2bodydecay(
+            pid0 = "221",
+            pid1 = "22",
+            br = "2.*0.39 * coupling**2 * pow(1.-pow(mass/self.masses('221'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+
+        self.model.add_production_mixing(
+            pid = "113",
+            mixing = "coupling * 0.3/5. * 0.77545**2/abs(mass**2-0.77545**2+0.77545*0.147*1j)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            )
+
+        if self.version == 1:
+            self.model.add_production_direct(
+                label = "Brem",
+                energy = self.energy,
+                condition = "p.pt<1",
+                coupling_ref=1,
+                )
+            
+            self.model.add_production_direct(
+                label = "DY",
+                energy = self.energy,
+                coupling_ref=1,
+                massrange=[1.5, 10.]
+                )
+        else:
+            masses_brem = [ 
+                0.01  ,  0.0126,  0.0158,  0.02  ,  0.0251,  0.0316,  0.0398,
+                0.0501,  0.0631,  0.0794,  0.1   ,  0.1122,  0.1259,  0.1413,
+                0.1585,  0.1778,  0.1995,  0.2239,  0.2512,  0.2818,  0.3162,
+                0.3548,  0.3981,  0.4467,  0.5012,  0.5623,  0.6026,  0.631 ,
+                0.6457,  0.6607,  0.6761,  0.6918,  0.7079,  0.7244,  0.7413,
+                0.7586,  0.7762,  0.7943,  0.8128,  0.8318,  0.8511,  0.871 ,
+                0.8913,  0.912 ,  0.9333,  0.955 ,  0.9772,  1.    ,  1.122 ,
+                1.2589,  1.4125,  1.5849,  1.7783,  1.9953,  2.2387,  2.5119,
+                2.8184,  3.1623,  3.9811,  5.0119,  6.3096,  7.9433, 10.    
+                ]
+            
+            self.model.add_production_direct(
+                label = "Brem",
+                energy = self.energy,
+                condition = "p.pt<1",
+                coupling_ref=1,
+                masses = masses_brem,
+                )
+            
+            masses_dy = [ 
+                1.5849,  1.7783,  1.9953,  2.2387,  2.5119, 2.8184,  3.1623,  3.9811,  5.0119,  6.3096,  7.9433, 10.    
+                ]
+            
+            self.model.add_production_direct(
+                label = "DY",
+                energy = self.energy,
+                coupling_ref=1,
+                masses = masses_dy,
+                )
+
+        return self.decays()
+
+
+    def alp_W(self):
+
+        self.model.add_production_2bodydecay(
+            pid0 = "5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20, # Vary over phi and theta 
+            )
+            
+        self.model.add_production_2bodydecay(
+            pid0 = "-5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20,
+            )
+        
+        self.model.add_production_2bodydecay(
+            pid0 = "130",
+            pid1 = "111",
+            br = "4.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        self.model.add_production_2bodydecay(
+            pid0 = "321",
+            pid1 = "211",
+            br = "10.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        return self.decays()
+
+
+    def decays(self):
+        # Set up liftime and BRs
+
+        if self.version == 1:
+            self.model.set_ctau_1d(
+                filename=f"files/models/{self.modelname}/ctau.txt", 
+                coupling_ref=1
+                )
+            
+            self.model.set_br_1d(
+                modes = [self.mode],
+                filenames=[f"files/models/{self.modelname}/br/{self.mode}.txt"] 
+                )
+        else:
+            self.model.set_ctau_1d(
+                filename=f"model/ctau.txt", 
+                coupling_ref=1
+                )
+            
+            self.model.set_br_1d(
+                modes = [self.mode],
+                finalstates = [[self.daughter1_pid, self.daughter2_pid]],
+                filenames=[f"model/br/{self.mode}.txt"] 
+                )            
+
+        # Get LLP spectrum
+        self.foresee.set_model(model=self.model)
+        # This is just a reference coupling 
+        plt = self.foresee.get_llp_spectrum(self.mass, coupling=1, do_plot=True)  
+        plt.savefig(f"{self.modelname}_m{self.mass}.png")
+
+        def flatten(l):
+            return [i for sublist in l for i in sublist]
+
+        # Get list of events within detector
+        output = self.foresee.get_events(mass=self.mass, energy=self.energy, couplings=self.couplings)        
+        coups, ctaus, nsigs, energies, weights, thetas = output
+
+        self.plot(flatten(thetas), flatten(energies), flatten(weights))
+
+        # Return energy (converting to MeV), theta and weights
+        return [[e*1000 for e in flatten(energies)], flatten(thetas), flatten(weights)] 
+
+    def plot(self, thetas, energies, weights):
+        # Plot the results in Forsee format
+
+        t = np.array(thetas)
+        p = np.sqrt(np.array(energies)**2 - self.mass**2)
+
+        prange=[[-6, 0, 120],[ 0, 5, 50]]
+        tmin, tmax, tnum = prange[0]
+        pmin, pmax, pnum = prange[1]
+        t_edges = np.logspace(tmin, tmax, num=tnum+1)
+        p_edges = np.logspace(pmin, pmax, num=pnum+1)  
+
+        ticks = np.array([[np.linspace(10**(j),10**(j+1),9)] for j in range(-7,6)]).flatten()
+        ticks = [np.log10(x) for x in ticks]
+        ticklabels = np.array([[r"$10^{"+str(j)+"}$","","","","","","","",""] for j in range(-7,6)]).flatten()
+        matplotlib.rcParams.update({'font.size': 15})
+
+        fig = plt.figure(figsize=(8,5.5))
+        ax = plt.subplot(1,1,1)
+        h=ax.hist2d(x=np.log10(t),y=np.log10(p),weights=weights,
+                    bins=[tnum,pnum],range=[[tmin,tmax],[pmin,pmax]],
+                    norm=matplotlib.colors.LogNorm(), cmap="hsv",
+                    )
+        
+        fig.colorbar(h[3], ax=ax)
+        ax.set_xlabel(r"angle wrt. beam axis $\theta$ [rad]")
+        ax.set_ylabel(r"momentum $p$ [GeV]")
+        ax.set_xticks(ticks)
+        ax.set_xticklabels(ticklabels)
+        ax.set_yticks(ticks)
+        ax.set_yticklabels(ticklabels)
+        ax.set_xlim(tmin, tmax)
+        ax.set_ylim(pmin, pmax)
+        plt.savefig(f"{self.modelname}_m{self.mass}_acc.png")
+
+    def write(self):
+        # Write LLP results to a file
+        
+        energies, thetas, weights = self.data
+
+        if self.outdir is None:
+            if self.version == 1:
+                self.outdir = f"files/models/{self.modelname}/events"
+            else:
+                self.outdir = f"{self.foresee.dirpath}/Models/{self.modelname}/model/events"
+
+        if not os.path.exists(self.outdir):
+            os.mkdir(self.outdir)
+
+        if len(self.couplings) == 1:
+            filename = f"{self.outdir}/events_{self.energy}TeV_m{self.mass}GeV_c{self.couplings[0]}to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+        else:
+            filename = f"{self.outdir}/events_{self.energy}TeV_m{self.mass}GeV_to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+
+        print(f"Generated {len(thetas)} events")
+        print(f"save data to file: {filename}")
+        np.save(filename,[energies,thetas, weights])
+
+        cfgname = filename.replace(".npy", ".cfg")
+        print(f"save config to file: {cfgname}")
+        with open(cfgname, "w") as f:
+            f.write(" ".join(sys.argv))
+
+        return
+
+    def write_hepmc(self, nevents):
+
+        if self.outdir is None:
+            self.outdir = "model/events/"
+        elif not os.path.exists(self.outdir):
+            os.mkdir(self.outdir)
+
+        filename =  f"{self.outdir}/events_{self.energy}TeV_m{self.mass}GeV_c{self.couplings[0]}to_{self.daughter1_pid}_{self.daughter2_pid}.hepmc"
+
+        self.foresee.write_events(self.mass, self.couplings[0], self.energy, filename, nevents, zfront = -1500, seed = self.seed)
+
+def setup_foresee(path):
+
+    if path is None:
+        return
+
+    # Add foresee to python path
+    path = os.path.expandvars(os.path.expanduser(path))
+    os.sys.path.append(f"{path}/FORESEE/src")
+
+    # Symlink foresee files/Models dirs to current dir
+    #if not os.path.exists("files"):
+    #    os.symlink(os.path.expandvars(f"{path}/FORESEE/files"), "files")
+    #if not os.path.exists("Models"):
+    #    os.symlink(os.path.expandvars(f"{path}/FORESEE/Models"), "files")        
+
+    # Install scikit-hep if needed.
+
+    try:
+        from skhep.math.vectors import LorentzVector, Vector3D
+    except ModuleNotFoundError:
+        os.system("pip install scikit-hep --user")
+        try:
+            from skhep.math.vectors import LorentzVector, Vector3D
+        except ModuleNotFoundError:
+            raise ModuleNotFoundError("Unable to find skhep.  Please install the scikit-hep package")
+        
+    return
+
+def add_to_python_path(path):
+    if path in sys.path: return
+    path = os.path.expandvars(os.path.expanduser(path))
+    os.sys.path.append(path)
+    return
+
+def parse_couplings(data, write_hepMC = False):    
+
+    if write_hepMC:
+        try:
+            couplings = float(couplings)
+        except ValueError:
+            sus.exit("Only a single coupling allowed when writing HEPMC events")
+
+    try:
+        couplings = [float(d) for d in data]
+    except ValueError:
+        try:
+            couplings = np.logspace(*eval(data[0]))
+        except:
+            sys.exit("Unable to parse couplings")
+
+    return couplings
+
+if __name__ == "__main__":
+
+    import argparse, sys
+    
+    parser = argparse.ArgumentParser(description="Run FORSEE generation")
+    parser.add_argument("model", help = "Name of foresee model")
+    parser.add_argument("--mass", "-m", required = True, type = float, help = "Mass of mother [GeV]")
+    parser.add_argument("--couplings", "-c", required = True, nargs = "+", help = "Couplings of mother (either single/mulitple values or tuple to pass to np.logspace)")
+    parser.add_argument("--pid1", required = True, type = int, help = "PID of daughter 1")
+    parser.add_argument("--pid2", default = None, type = int, help = "PID of daughter 2 (if not set then will be -PID1)")
+    parser.add_argument("--Ecom", default = "14", help = "Center of mass energy [TeV]")
+    parser.add_argument("--outdir", "-o", default = None, help = "Output path")    
+    parser.add_argument("--path", default = ".", help = "Path to foresee installation")
+    parser.add_argument("--hepmc", action = "store_true", help = "Write HepMC events")
+    parser.add_argument("--nevents", "-n", default = 10, type = int, help = "Number of HepMC events ")
+    parser.add_argument("--randomSeed", "-s", default = 1234, type = int, help = "Random seed for HepMC generation")  
+    args = parser.parse_args()
+
+    add_to_python_path(f"{args.path}/src")
+
+    from foresee import Foresee, Model, Utility
+
+    # Create PIDs
+    if args.pid2 is None:
+        args.pid2 = -args.pid1
+    
+    couplings = parse_couplings(args.couplings)
+
+    print(f"Generating {args.model} events at Ecom = {args.Ecom}") 
+    print(f"   mother mass = {args.mass} GeV")
+    print(f"   decay = {args.pid1} {args.pid2}")
+    print(f"   couplings = {couplings}")    
+
+    f = ForeseeGenerator(args.model, args.Ecom, args.mass, couplings, args.pid1, args.pid2, outdir = args.outdir, path = args.path, randomSeed = args.randomSeed)
+
+    if args.hepmc:
+        f.write_hepmc(args.nevents)
+    else:
+        f.write()
+    
+        
+
diff --git a/Generators/ForeseeGenerator/share/plot_validation.py b/Generators/ForeseeGenerator/share/plot_validation.py
new file mode 100644
index 0000000000000000000000000000000000000000..8429978bc6a091abd853456d1a10fa3d33ab200d
--- /dev/null
+++ b/Generators/ForeseeGenerator/share/plot_validation.py
@@ -0,0 +1,151 @@
+import ROOT as R
+from collections import namedtuple
+
+Hist = namedtuple("Hist", "name, xtitle, ytitle, xlo, xhi, ylo, yhi, r, d, logx, logy, ndiv",
+                  defaults = [None, None, None, None, None, None, 1, "hist", False, False, None])
+
+def plot(f, name, xtitle, ytitle, xlo = None, xhi = None, ylo = None, yhi = None,
+         r = 1, d = "hist", logx = False, logy = False, ndiv = None):
+
+    h = f.Get(name)
+
+    if xlo is not None and xhi is not None:
+        h.SetAxisRange(xlo, xhi)
+
+    if ylo is not None and yhi is not None:
+        h.SetAxisRange(ylo, yhi, "Y")
+    elif not logy:
+        h.SetMinimum(0)
+
+    if isinstance(r, tuple):
+        h.Rebin2D(r[0], r[1])        
+    elif r != 1:
+        h.Rebin(r)
+
+    if xtitle is not None:
+        h.GetXaxis().SetTitle(xtitle)
+
+    if ytitle is not None:
+        h.GetYaxis().SetTitle(ytitle)
+
+    if logx:
+        R.gPad.SetLogx()
+
+    if logy:
+        R.gPad.SetLogy()
+
+    if ndiv is not None:
+        h.SetNdivisions(ndiv)
+
+    h.SetLabelSize(0.05, "X")
+    h.SetTitleSize(0.05, "X")
+    h.SetLabelSize(0.05, "Y")
+    h.SetTitleSize(0.05, "Y")
+
+    h.GetXaxis().SetTitleOffset(1.2)
+
+    R.gPad.SetBottomMargin(0.15)
+    R.gPad.SetLeftMargin(0.12)
+    R.gPad.SetRightMargin(0.2)        
+
+    h.Draw(d)
+    return h
+
+def plotn(f, args, configs, x, y, outname):
+
+    c = R.TCanvas()
+    c.Divide(x, y)
+    c._objs = []
+
+    if isinstance(configs, tuple):
+        configs = [configs]
+        
+    for i, cfg in enumerate(configs):
+        c.cd(i+1)
+        c._objs.append(plot(f, *cfg))
+        
+    c.Print(f"{args.output}/{outname}.eps")
+
+    return
+
+if __name__ == "__main__":
+
+    
+    R.gROOT.SetBatch(True)
+    R.gStyle.SetOptStat(0)
+
+    import argparse, sys, os
+    parser = argparse.ArgumentParser(description="Run gen-level validation plotting")
+    parser.add_argument("file", help = "full path to imput file")
+    parser.add_argument("--output", "-o",  default = "valplot", help = "Name of output directory")
+    parser.add_argument("--ndaughters", "-d", default = 2, type = int, help = "Number of daugthers to plot")
+    args = parser.parse_args()
+
+    if not os.path.exists(args.output):
+        os.mkdir(args.output)
+
+    print (args.file)
+    f = R.TFile.Open(args.file)
+
+    for i in range(args.ndaughters):
+        config = [Hist(f"P_d{i}", logy = True, xtitle = "p^{0} [GeV]", ndiv = 5, r = 5),
+                  Hist(f"Theta_d{i}", xtitle = "#theta [rad]", ndiv = -4),
+                  Hist(f"Mass_d{i}", xtitle = "m^{0} [GeV]", xlo = 0, xhi = 0.1, ndiv = 4),               
+                  Hist(f"Pt_d{i}", logy = True, xtitle = "p_{T}^{0} [GeV]", ndiv = 10, r = 5),
+                  Hist(f"Phi_d{i}", xtitle = "#phi [rad]"),
+                  Hist(f"ThetaVsP_d{i}", ytitle = "p^{0} [GeV]", xtitle = "#theta [rad]", logx = True, logy = True, d = "colz")
+                  ]
+
+        plotn(f, args, config, 3, 2, f"daug{i}")
+        
+    config = [Hist("P_M", logy = True, xtitle = "p^{0} [GeV]", ndiv = 5, xlo = 0, xhi = 10000, r=10),
+              Hist("Theta_M", xtitle = "#theta [rad]", ndiv = -4, r = 10),
+              Hist("Mass_M", xtitle = "m^{0} [GeV]", xlo = 0, xhi = 1., ndiv = 5),               
+              Hist("Pt_M", logy = True, xtitle = "p_{T}^{0} [GeV]", ndiv = 10, r = 50),
+              Hist("Phi_M", xtitle = "#phi [rad]", r = 2),
+              Hist("ThetaVsP_M", ytitle = "p^{0} [GeV]", xtitle = "#theta [rad]", logx = True, logy = True, d = "colz")
+              ]
+
+    plotn(f, args, config, 3, 2, "mother")
+
+    plotn(f, args, Hist("PIDs", xtitle="PDG Id"), 1, 1, "pid") 
+
+    
+#     config = [Hist("ThetaVsP_M", ytitle = "p^{0} [GeV]", xtitle = "#theta [rad]", logx = True, logy = True, d = "colz"),
+#               Hist("ThetaVsP_d0", ytitle = "p^{0} [GeV]", xtitle = "#theta [rad]", logx = True, logy = True, d = "colz"),
+#               Hist("ThetaVsP_d1", ytitle = "p^{0} [GeV]", xtitle = "#theta [rad]", logx = True, logy = True, d = "colz")
+#               ]                   
+# 
+#     plotn(f, args, config, 2, 2, "twod")
+
+    config = [Hist("Vtx_X_LLP", xtitle = "x [mm]", r = 5),
+              Hist("Vtx_Y_LLP", xtitle = "y [mm]", r = 5),
+              Hist("Vtx_Z_LLP", xtitle = "z [mm]", r = 5, ndiv = 5),
+              Hist("Vtx_XY_LLP", xtitle = "x [mm]", ytitle = "y [mm]", d = "colz", r = (5,5)),
+              Hist("Vtx_R_LLP", xtitle = "r [mm]", r = 5, ndiv = 5)
+              ]
+
+    plotn(f, args, config, 3, 2, "vtx_llp")
+
+
+    config = [Hist("Vtx_X_All", xtitle = "x [mm]", r = 5),
+              Hist("Vtx_Y_All", xtitle = "y [mm]", r = 5),
+              Hist("Vtx_Z_All", xtitle = "z [mm]", r = 5, ndiv = 5),
+              Hist("Vtx_XY_All", xtitle = "x [mm]", ytitle = "y [mm]", d = "colz", r = (5,5)),
+              Hist("Vtx_R_All", xtitle = "r [mm]", r = 5, ndiv = 5)
+              ]
+
+    plotn(f, args, config, 3, 2, "vtx_all")
+
+
+#     config = [Hist("Vtx_X", xtitle = "x [mm]", r = 5),
+#               Hist("Vtx_Y", xtitle = "y [mm]", r = 5),
+#               Hist("Vtx_Z", xtitle = "z [mm]", r = 5, ndiv = 5),
+#               Hist("Vtx_XY", xtitle = "x [mm]", ytitle = "y [mm]", d = "colz", r = (5,5)),
+#               Hist("Vtx_R", xtitle = "r [mm]", r = 5, ndiv = 5)
+#               ]
+# 
+#     plotn(f, args, config, 3, 2, "vtx")
+
+
+    
diff --git a/Generators/GeneratorUtils/CMakeLists.txt b/Generators/GeneratorUtils/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d471fbe183ae3da80c2a0ce1a49785ca0b907387
--- /dev/null
+++ b/Generators/GeneratorUtils/CMakeLists.txt
@@ -0,0 +1,10 @@
+################################################################################
+# Package: GeneratorUtils
+################################################################################
+
+# Declare the package name:
+atlas_subdir( GeneratorUtils )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
diff --git a/Generators/GeneratorUtils/python/ShiftLOS.py b/Generators/GeneratorUtils/python/ShiftLOS.py
new file mode 100644
index 0000000000000000000000000000000000000000..44a94c7a2572bdef501c4f8954a79c3fa8e2a544
--- /dev/null
+++ b/Generators/GeneratorUtils/python/ShiftLOS.py
@@ -0,0 +1,178 @@
+
+from AthenaCommon.AppMgr import ServiceMgr as svcMgr
+from AthenaPython import PyAthena
+from AthenaPython.PyAthena import StatusCode, McEventCollection, CLHEP
+from AthenaCommon.SystemOfUnits import m
+import ROOT
+
+try:
+    from AthenaPython.PyAthena import HepMC3  as HepMC
+except ImportError:
+    from AthenaPython.PyAthena import HepMC   as HepMC
+
+class ShiftLOS(PyAthena.Alg):
+    def __init__(self, name="ShiftLOS", InputMCEventKey="BeamTruthEvent", OutputMCEventKey="BeamTruthEventShifted", xcross = 0, ycross = 0, xshift = 0, yshift = 0):
+        super(ShiftLOS,self).__init__(name=name)
+        self.InputMCEventKey = InputMCEventKey
+        self.OutputMCEventKey = OutputMCEventKey
+        self.xcross = xcross 
+        self.ycross = ycross 
+        self.xshift = xshift 
+        self.yshift = yshift 
+        self.distance = 480 * m # Assumes 480m is 0 of FASER coordinate system
+        return
+
+    def shift_vertices(self, evt):
+
+        # Don't need to shift if at IP unless request explicit shift
+        if not self.distance and not self.xshift and not self.yshift:
+            return evt
+
+        # Loop over all vertices
+        for v in evt.vertices:
+            # Get position
+            pos = v.position()
+            x = pos.x()
+            y = pos.y()
+            z = pos.z()            
+            dz = self.distance + z
+
+            # Shift x or y by appropriate crossing angle
+            if self.xcross:                
+                x += dz * self.xcross 
+                self.msg.debug(f"Shifting x by {self.xcross*1E6} urad over {dz/1E3:.2} m: {pos.x()} -> {x} mm ")
+            elif self.ycross:
+                y += dz * self.ycross
+                self.msg.debug(f"Shifting y by {self.ycross*1E6} urad over {dz/1E3:.2} m: {pos.y()} -> {y} mm ")
+
+            if self.xshift:
+                x += self.xshift
+                self.msg.debug(f"Shifting x by {self.xshift} mm: {pos.x()} -> {x} mm ")                
+            elif self.yshift:
+                y += self.yshift
+                self.msg.debug(f"Shifting y by {self.yshift} mm: {pos.y()} -> {y} mm ")                                
+                            
+            v.set_position(HepMC.FourVector(x, y, z, pos.t()))
+
+        return evt
+
+        
+    def boost_particles(self, evt):
+
+        if self.xcross == self.ycross == 0:
+            return evt
+
+        pxsum, pysum = 0,0
+        pxsum_orig, pysum_orig = 0,0
+
+        # Loop over all particles
+        for p in evt.particles:
+            # Get momentum
+            mom = p.momentum()
+
+            pxsum_orig += mom.x()
+            pysum_orig += mom.y()            
+
+            # Boost in x or y using CLHEP
+            boost = CLHEP.Hep3Vector(self.xcross, self.ycross, 0.0)
+            tmp = CLHEP.HepLorentzVector(mom.px(), mom.py(), mom.pz(), mom.e())
+            tmp.boost(boost)
+            
+            pxsum += tmp.x() - mom.x()
+            pysum += tmp.y() - mom.y()            
+
+            # Convert back to HepMC
+            p.set_momentum(HepMC.FourVector(tmp.px(), tmp.py(), tmp.pz(), tmp.e()))
+            
+            self.msg.debug(f"Change in total px = {pxsum:.1f} MeV ({pxsum/pxsum_orig * 100: .3f} %), change in total py = {pysum:.1f} MeV ({pysum/pysum_orig * 100: .3f} %)")
+
+        return evt
+
+    def execute(self):
+        self.msg.debug(f"Exectuing {self.getName()}")
+
+        if not self.xcross and not self.ycross and not self.xshift and not self.yshift:
+            return StatusCode.Success
+
+        self.msg.debug(f"Reading {self.InputMCEventKey}")
+        inevt = self.evtStore[self.InputMCEventKey][0]
+
+        self.msg.debug("Creating output event and collection")
+        outcoll = McEventCollection()
+        ROOT.SetOwnership(outcoll, False)        
+
+        # Clone input event
+        outevt = HepMC.GenEvent(inevt.__follow__()) # go from ElementProxy to element itself
+
+        # Modify
+        outevt = self.shift_vertices(outevt)
+        outevt = self.boost_particles(outevt)        
+
+        # Write output
+        outcoll.push_back(outevt)
+        ROOT.SetOwnership(outevt, False)                
+        
+        self.msg.debug(f"Recording {self.OutputMCEventKey}")
+        self.evtStore.record(outcoll, self.OutputMCEventKey, True, False)
+
+        return StatusCode.Success
+
+if __name__ == "__main__":
+    import argparse, sys    
+    parser = argparse.ArgumentParser(description="Run ShiftLOS")
+    parser.add_argument("infile", help = "Path to input EVNT file")
+    parser.add_argument("outfile", help = "Path to output EVNT file")    
+    parser.add_argument("--InputMCEventKey", "-i",  default = "BeamTruthEvent", help = "Name of Input MC collection")
+    parser.add_argument("--OutputMCEventKey", "-o",  default = "BeamTruthEventShifted", help = "Name of Output MC collection")    
+    parser.add_argument("--xcross", "-x", default = 0, type = float, help = "Crossing angle of LHC beam in x [urad]")
+    parser.add_argument("--ycross", "-y", default = 0, type = float, help = "Crossing angle of LHC beam in y [urad]")
+    parser.add_argument("--xshift",  default = 0, type = float, help = "Shift of LHC beam in x [mm]")
+    parser.add_argument("--yshift",  default = 0, type = float, help = "Shift of LHC beam in y [mm]")    
+    parser.add_argument("--nevents", "-n", default = -1, type = int, help = "Number of events to process")    
+    args = parser.parse_args()
+
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG, INFO
+    
+    from AthenaCommon.Configurable import Configurable
+    Configurable.configurableRun3Behavior = 1
+
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Input.isMC = True
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"             # Always needed; must match FaserVersion
+    ConfigFlags.GeoModel.FaserVersion     = "FASER-01"           # Default FASER geometry
+    ConfigFlags.Detector.EnableFaserSCT = True
+    ConfigFlags.Input.Files = [ args.infile ]    
+    ConfigFlags.Output.EVNTFileName = args.outfile
+    ConfigFlags.lock()
+
+    # Configure components
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+
+    from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+    from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+    
+    cfg = MainServicesCfg(ConfigFlags)
+    cfg.merge(PoolReadCfg(ConfigFlags))
+
+    import McParticleEvent.Pythonizations
+    
+    from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+    from AthenaConfiguration.ComponentFactory import CompFactory
+    
+    acc = ComponentAccumulator()
+    alg = ShiftLOS("ShiftLOS", InputMCEventKey=args.InputMCEventKey, OutputMCEventKey=args.OutputMCEventKey,
+                   xcross = args.xcross * 1e-6, ycross = args.ycross * 1e-6, xshift = args.xshift, yshift = args.yshift)
+    alg.OutputLevel = INFO
+    acc.addEventAlgo(alg)    
+    cfg.merge(acc)
+
+    itemList = [ "EventInfo#McEventInfo", "McEventCollection#*" ]
+    from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+    cfg.merge(OutputStreamCfg(ConfigFlags, "EVNT", itemList, disableEventTag = True))
+
+    sc = cfg.run(maxEvents = args.nevents)
+    sys.exit(not sc.isSuccess())
+
+    
diff --git a/Generators/GeneratorUtils/python/ShiftLOSConfig.py b/Generators/GeneratorUtils/python/ShiftLOSConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..ffc5c79b926b1674ac43d9e8947bfbbe1750da16
--- /dev/null
+++ b/Generators/GeneratorUtils/python/ShiftLOSConfig.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+# import sys
+from AthenaConfiguration.MainServicesConfig import AthSequencer
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator, ConfigurationError
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+from GeneratorUtils.ShiftLOS import ShiftLOS
+
+
+def ShiftLOSCfg(ConfigFlags, **kwargs) :
+    import McParticleEvent.Pythonizations
+
+    cfg = ComponentAccumulator()
+
+    shift = ShiftLOS(name = kwargs.setdefault("name", "ShiftLOS"))
+    shift.InputMCEventKey =  kwargs.setdefault("InputMCEventKey", "BeamTruthEvent_ATLASCoord")
+    shift.OutputMCEventKey =  kwargs.setdefault("OutputMCEventKey", "BeamTruthEvent")    
+    shift.xcross = kwargs.setdefault("xcross", 0)
+    shift.ycross = kwargs.setdefault("ycross", 0)
+    shift.xshift = kwargs.setdefault("xshift", 0)
+    shift.yshift = kwargs.setdefault("yshift", 0)    
+
+    try:
+        # Run for PG
+        cfg.addEventAlgo(shift, sequenceName = "AthBeginSeq", primary = True) # to run *before* G4
+    except ConfigurationError:
+        # Run for pool or hepmc
+        cfg.addEventAlgo(shift, sequenceName = "AthAlgSeq", primary = True) # to run *before* G4        
+        
+    return cfg
diff --git a/Generators/HEPMCReader/CMakeLists.txt b/Generators/HEPMCReader/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3076e7abc887d5cc1fabaedddc42d5db36286270
--- /dev/null
+++ b/Generators/HEPMCReader/CMakeLists.txt
@@ -0,0 +1,9 @@
+################################################################################
+# Package: HEPMCGenie
+################################################################################
+
+# Declare the package name:
+atlas_subdir( HEPMCReader )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
diff --git a/Generators/HEPMCReader/python/HepMCReaderConfig.py b/Generators/HEPMCReader/python/HepMCReaderConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..510b63c5ea51b6d69d2ae36c3d477c0123c69495
--- /dev/null
+++ b/Generators/HEPMCReader/python/HepMCReaderConfig.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+import sys, tempfile, pathlib
+from AthenaConfiguration.MainServicesConfig import AthSequencer
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+from TruthIO.TruthIOConf import HepMCReadFromFile
+
+def get_file_skip_events(ConfigFlags):
+    "Create a file with events from ConfigFlags.Exec.SkipEvents to ConfigFlags.Exec.SkipEvents + ConfigFlags.Exec.MaxEvents"
+
+    usetemp = True
+    #usetemp = False
+
+    skip = ConfigFlags.Exec.SkipEvents
+    fname = ConfigFlags.Input.Files[0]
+    evtMax = ConfigFlags.Exec.MaxEvents
+
+    if skip == 0:
+        return fname
+
+    print(f"get_file_skip_events skipping {skip} events with max {evtMax}")
+
+    if usetemp:
+        fout = tempfile.NamedTemporaryFile("w", delete = False)
+        foutname = fout.name
+    else:
+        infile = pathlib.Path(fname)
+        # Put this in current working directory
+        if evtMax > 0:
+            end = skip+evtMax
+        else:
+            end = 'all'
+
+        foutname = f"{infile.stem}=evts{skip}-{end}.{infile.suffix}"
+        #foutname, fext = ".".join(fname.split('.')[:-1]), fname.split('.')[-1]
+        #foutname = f"{foutname}-evts{skip}-{skip+evtMax}{fext}"        
+        fout = open(foutname, "w")
+
+    fout.write("HepMC::Version 2.06.09\nHepMC::IO_GenEvent-START_EVENT_LISTING\n")
+    
+    ievt = 0
+    with open(fname) as fin:
+        for l in fin:
+            if l.startswith("E "):
+                ievt += 1
+
+            if evtMax > 0 and ievt > skip + evtMax:
+                break
+                
+            if ievt > skip:
+                #print(f"Writing event {ievt}")
+                fout.write(l)
+            # else:
+                # print(f"Skipping event {ievt}")
+
+    fout.write("HepMC::IO_GenEvent-END_EVENT_LISTING\n")
+    fout.close()
+
+    #print(f"Wrote to file {foutname}")
+
+    return foutname
+                
+    
+def HepMCReaderCfg(ConfigFlags, **kwargs) :
+    cfg = ComponentAccumulator(AthSequencer("AthBeginSeq", Sequential = True))
+    from TruthIO.TruthIOConf import HepMCReadFromFile
+    hepmc = CompFactory.HepMCReadFromFile(name = kwargs.setdefault("name", "FASERHepMCReader"))
+    hepmc.InputFile = get_file_skip_events(ConfigFlags) # ConfigFlags.Input.Files[0] 
+    hepmc.McEventKey = kwargs.setdefault("McEventKey", "BeamTruthEvent")
+    
+    cfg.addEventAlgo(hepmc, sequenceName = "AthBeginSeq", primary = True) # to run *before* G4
+
+    return cfg
diff --git a/Generators/ParticleGun/CMakeLists.txt b/Generators/ParticleGun/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1794305ed0291e77478aae72b1216db87fce0810
--- /dev/null
+++ b/Generators/ParticleGun/CMakeLists.txt
@@ -0,0 +1,14 @@
+################################################################################
+# Package: ParticleGun
+################################################################################
+
+# Declare the package name:
+atlas_subdir( ParticleGun )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
+# Install files from the package:
+atlas_install_joboptions( share/common/*.py
+                          share/example/*.py )
+
diff --git a/Generators/ParticleGun/README b/Generators/ParticleGun/README
new file mode 100644
index 0000000000000000000000000000000000000000..6cdb698eda4f549c076ac3a3ce732ad65225c11d
--- /dev/null
+++ b/Generators/ParticleGun/README
@@ -0,0 +1,5 @@
+ParticleGun documentation
+-------------------------
+See https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/ParticleGunForAtlas
+for some coherent documentation that should be kept up to date.
+
diff --git a/Generators/ParticleGun/python/__init__.py b/Generators/ParticleGun/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e88fc56c49679a210ad57b62055a6a7c0867ddd
--- /dev/null
+++ b/Generators/ParticleGun/python/__init__.py
@@ -0,0 +1,129 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+from AthenaCommon.AppMgr import ServiceMgr as svcMgr
+from GeneratorModules.EvgenAlg import EvgenAlg
+from ParticleGun.samplers import ParticleSampler
+from ParticleGun.samplers import * # noqa: F401, F403 (import into our namespace)
+# commenting out the HepMC import for now
+#try:
+#          from AthenaPython.PyAthena import HepMC3  as HepMC
+#except ImportError:
+#          from AthenaPython.PyAthena import HepMC   as HepMC  
+
+from AthenaPython.PyAthena import StatusCode
+import ROOT,random
+
+__author__ = "Andy Buckley <andy.buckley@cern.ch>"
+
+class ParticleGun(EvgenAlg):
+    """
+    A simple but flexible algorithm for generating events from simple distributions.
+    """
+
+    def __init__(self, name="ParticleGun", randomSvcName="AtRndmGenSvc", randomStream="ParticleGun", randomSeed=None):
+        super(ParticleGun, self).__init__(name=name)
+        self.samplers = [ParticleSampler()]
+        self.randomStream = randomStream
+        self.randomSvcName = randomSvcName
+        self.randomSeed = randomSeed
+
+    @property
+    def sampler(self):
+        "Get the first (and presumed only) sampler"
+        return self.samplers[0] if self.samplers else None
+    @sampler.setter
+    def sampler(self, s):
+        "Set the samplers list to include only a single sampler, s"
+        self.samplers = [s]
+
+
+    def initialize(self):
+        """
+        Pass the AtRndmGenSvc seed to Python's random module, or use a fixed value set via pg.randomSeed.
+        """
+        seed = None
+        ## Use self.randomSeed directly, or if it's None find a seed string from the ATLAS random number service
+        if self.randomSeed is not None:
+            seed = self.randomSeed
+        else:
+            randomSvc = getattr(svcMgr, self.randomSvcName, None)
+            if randomSvc is not None:
+                for seedstr in randomSvc.Seeds:
+                    if seedstr.startswith(self.randomStream):
+                        seed = seedstr
+                        self.msg.info("ParticleGun: Using random seed '%s' ", seed)
+                        break
+                if seed is None:
+                    self.msg.warning("ParticleGun: Failed to find a seed for the random stream named '%s' ", self.randomStream)
+            else:
+                self.msg.warning("ParticleGun: Failed to find random number service called '%s' ", self.randomSvcName)
+        ## Apply the seed
+        if seed is not None:
+            random.seed(seed)
+            return StatusCode.Success
+        else:
+            self.msg.error("ParticleGun: randomSeed property not set, and no %s random number service found", self.randomSvcName)
+            return StatusCode.Failure
+
+
+    def fillEvent(self, evt):
+        """
+        Sample a list of particle properties, which are then used to create a new GenEvent in StoreGate.
+        """
+        ## Set event weight(s)
+        # TODO: allow weighted sampling?
+        try:
+          from AthenaPython.PyAthena import HepMC3  as HepMC
+        except ImportError:
+          from AthenaPython.PyAthena import HepMC   as HepMC   
+        evt.weights().push_back(1.0)
+
+        ## Make and fill particles
+        for s in self.samplers:
+            particles = s.shoot()
+            for p in particles:
+                ## Debug printout of particle properties
+                #print DEBUG0, p.pid, p.mom.E(), p.mom.Pt(), p.mom.M()
+                #print "DEBUG1 (px,py,pz,E) = (%0.2e, %0.2e, %0.2e, %0.2e)" % (p.mom.Px(), p.mom.Py(), p.mom.Pz(), p.mom.E())
+                #print "DEBUG2 (eta,phi,pt,m) = (%0.2e, %0.2e, %0.2e, %0.2e)" % (p.mom.Eta(), p.mom.Phi(), p.mom.Pt(), p.mom.M())
+                #print "DEBUG3 (x,y,z,t) = (%0.2e, %0.2e, %0.2e, %0.2e)" % (p.pos.X(), p.pos.Y(), p.pos.Z(), p.pos.T())
+
+                ## Make particle-creation vertex
+                # TODO: do something cleverer than one vertex per particle?
+                pos = HepMC.FourVector(p.pos.X(), p.pos.Y(), p.pos.Z(), p.pos.T())
+                gv = HepMC.GenVertex(pos)
+                ROOT.SetOwnership(gv, False)
+                evt.add_vertex(gv)
+
+                ## Make particle with status == 1
+                mom = HepMC.FourVector(p.mom.Px(), p.mom.Py(), p.mom.Pz(), p.mom.E())
+                gp = HepMC.GenParticle()
+                gp.set_status(1)
+                gp.set_pdg_id(p.pid)
+                gp.set_momentum(mom)
+                if p.mass is not None:
+                    gp.set_generated_mass(p.mass)
+                ROOT.SetOwnership(gp, False)
+                gv.add_particle_out(gp)
+
+        return StatusCode.Success
+
+
+## PyAthena HepMC notes
+#
+## evt.print() isn't valid syntax in Python2 due to reserved word
+# TODO: Add a Pythonisation, e.g. evt.py_print()?
+#getattr(evt, 'print')()
+#
+## How to check that the StoreGate key exists and is an McEventCollection
+# if self.sg.contains(McEventCollection, self.sgkey):
+#     print self.sgkey + " found!"
+#
+## Modifying an event other than that supplied as an arg
+# mcevts = self.sg[self.sgkey]
+# for vtx in mcevts[0].vertices: # only way to get the first vtx?!
+#     gp2 = HepMC.GenParticle()
+#     gp2.set_momentum(HepMC.FourVector(1,2,3,4))
+#     gp2.set_status(1)
+#     vtx.add_particle_out(gp2)
+#     break
diff --git a/Generators/ParticleGun/python/histsampling.py b/Generators/ParticleGun/python/histsampling.py
new file mode 100644
index 0000000000000000000000000000000000000000..c64112cc84e23963b63ea381817d1457f1c2a122
--- /dev/null
+++ b/Generators/ParticleGun/python/histsampling.py
@@ -0,0 +1,132 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+"""
+Tools for histogram sampling, in particular inverse transform sampling which is
+missing from ROOT's TH2 classes.
+"""
+
+__author__ = "Andy Buckley <andy.buckley@cern.ch>"
+
+import random, ROOT
+
+
+def load_hist(*args):
+    """
+    Load a histogram from a filename/TFile and histo name. If a single arg is
+    provided, it has to be a histo object and will be cloned before return.
+    """
+    h = None
+    if len(args) == 1 and issubclass(type(args[0]), ROOT.TH1):
+        h = args[0].Clone()
+    elif len(args) == 2:
+        if isinstance(args[0], str) and isinstance(args[1], str) :
+            f = ROOT.TFile.Open(args[0])
+            h = f.Get(args[1]).Clone()
+            #f.Close()
+        elif type(args[0]) is ROOT.TFile and type(args[1]) is str:
+            h = args[0].Get(args[1]).Clone()
+    if h is None:
+        raise Exception("Error in histogram loading from " + args)
+    return h
+
+
+def get_sampling_vars(h):
+    """
+    Get the following from a histogram h, since the ROOT API sucks:
+    * list of global bin IDs (not even contiguous for 2D, gee thanks ROOT)
+    * dict mapping global bin IDs to a tuple of axis bin IDs
+    * list of nbins+1 cumulative bin values, in the same order as globalbins
+    """
+    globalbin_to_axisbin = {} # for reverse axis bin lookup to get edges
+    globalbins = [] # because they aren't easily predicted, nor contiguous
+    cheights = [0] # cumulative "histogram" from which to uniformly sample
+    if issubclass(type(h), ROOT.TH1):
+        for ix in range(1, h.GetNbinsX()+1):
+            iglobal = h.GetBin(ix)
+            globalbins.append(iglobal)
+            globalbin_to_axisbin[iglobal] = (ix,)
+            cheights.append(cheights[-1] + h.GetBinContent(iglobal))
+    elif issubclass(type(h), ROOT.TH2):
+        for ix in range(1, h.GetNbinsX()+1):
+            for iy in range(1, h.GetNbinsY()+1):
+                iglobal = h.GetBin(ix, iy)
+                globalbins.append(iglobal)
+                globalbin_to_axisbin[iglobal] = (ix, iy)
+                cheights.append(cheights[-1] + h.GetBinContent(iglobal))
+    return globalbins, globalbin_to_axisbin, cheights
+
+
+def get_random_bin(globalbins, cheights):
+    """
+    Choose a random bin from the cumulative distribution list of nbins+1 entries.
+
+    TODO: Search more efficiently (lin and log guesses, then lin search or
+    binary split depending on vector size).
+    """
+    assert len(cheights) == len(globalbins)+1
+    randomheight = random.uniform(0, cheights[-1])
+    for i, iglobal in enumerate(globalbins):
+        if randomheight >= cheights[i] and randomheight < cheights[i+1]:
+            return iglobal
+    raise Exception("Sample fell outside range of cumulative distribution?!?!")
+
+
+def get_random_x(h, globalbins, cheights, globalbin_to_axisbin):
+    """
+    Choose a random bin via get_random_bin, then pick a uniform random x
+    point in that bin (without any attempt at estimating the in-bin distribution).
+    """
+    irand = get_random_bin(globalbins, cheights)
+    axisids = globalbin_to_axisbin.get(irand)
+    assert axisids is not None
+    xrand = random.uniform(h.GetXaxis().GetBinLowEdge(axisids[0]), h.GetXaxis().GetBinUpEdge(axisids[0]))
+    return xrand
+
+
+def get_random_xy(h2, globalbins, cheights, globalbin_to_axisbin):
+    """
+    Choose a random bin via get_random_bin, then pick a uniform random x,y
+    point in that bin (without any attempt at estimating the in-bin distribution).
+    """
+    irand = get_random_bin(globalbins, cheights)
+    axisids = globalbin_to_axisbin.get(irand)
+    assert axisids is not None
+    xrand = random.uniform(h2.GetXaxis().GetBinLowEdge(axisids[0]), h2.GetXaxis().GetBinUpEdge(axisids[0]))
+    yrand = random.uniform(h2.GetYaxis().GetBinLowEdge(axisids[1]), h2.GetYaxis().GetBinUpEdge(axisids[1]))
+    return xrand, yrand
+
+
+class TH1(object):
+    "Minimal wrapper for ROOT TH1, for sampling consistency and easy loading"
+
+    def __init__(self, *args):
+        self.th1 = load_hist(*args)
+        self.globalbins, self.globalbin_to_axisbin, self.cheights = None, None, None
+
+    def GetRandom(self):
+        "A GetRandom that works for TH1s and uses Python random numbers"
+        if self.globalbins is None or self.globalbin_to_axisbin is None or self.cheights is None:
+            self.globalbins, self.globalbin_to_axisbin, self.cheights = get_sampling_vars(self.th1)
+        return get_random_x(self.th1, self.globalbins, self.cheights, self.globalbin_to_axisbin)
+
+    def __getattr__(self, attr):
+        "Forward all attributes to the contained TH1"
+        return getattr(self.th1, attr)
+
+
+class TH2(object):
+    "Minimal wrapper for ROOT TH2, for easy loading and to allow 2D sampling"
+
+    def __init__(self, *args):
+        self.th2 = load_hist(*args)
+        self.globalbins, self.globalbin_to_axisbin, self.cheights = None, None, None
+
+    def GetRandom(self):
+        "A GetRandom that works for TH2s"
+        if self.globalbins is None or self.globalbin_to_axisbin is None or self.cheights is None:
+            self.globalbins, self.globalbin_to_axisbin, self.cheights = get_sampling_vars(self.th2)
+        return get_random_xy(self.th2, self.globalbins, self.cheights, self.globalbin_to_axisbin)
+
+    def __getattr__(self, attr):
+        "Forward other attributes to the contained TH2"
+        return getattr(self.th2, attr)
diff --git a/Generators/ParticleGun/python/samplers.py b/Generators/ParticleGun/python/samplers.py
new file mode 100644
index 0000000000000000000000000000000000000000..90e9676a5ce20dc1b52e48c79f0b36a7da49f639
--- /dev/null
+++ b/Generators/ParticleGun/python/samplers.py
@@ -0,0 +1,912 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+import ROOT, math, random
+from ParticleGun.histsampling import TH1
+
+## For convenience
+PI = math.pi
+TWOPI = 2*math.pi
+
+
+class Sampler(object):
+    "Base class for all samplers"
+
+    def shoot(self):
+        return RuntimeError("Can't sample from an abstract sampler object.")
+
+    def __call__(self):
+        """This is the call method that will actually be used (so that normal
+        functions can also be passed in as samplers)."""
+        return self.shoot()
+
+    # TODO: add a sampling weight?
+
+
+class ConstSampler(Sampler):
+    "A special-case sampler which just returns one value rather than sampling."
+
+    def __init__(self, val):
+        self.val = val
+
+    def shoot(self):
+        return self.val
+
+    def __repr__(self):
+        return "ConstSampler[%s]" % str(self.val)
+
+
+## Continuous distribution samplers
+
+class ContinuousSampler(Sampler):
+    "Base class for samplers from continuous distributions."
+    pass
+
+
+class UniformSampler(ContinuousSampler):
+    "Uniformly sample in the range [low,high)."
+
+    def __init__(self, low, high):
+        assert(low <= high)
+        self.low = float(low)
+        self.high = float(high)
+
+    def shoot(self):
+        return random.uniform(self.low, self.high)
+
+
+class ModUniformSampler(ContinuousSampler):
+    "Uniformly sample in the modulus range (-high,low]+[low,high)."
+
+    def __init__(self, low, high):
+        assert(low == abs(low) and high == abs(high))
+        assert(low <= high)
+        self.low = float(low)
+        self.high = float(high)
+
+    def shoot(self):
+        val = random.uniform(self.low, self.high)
+        if random.random() > 0.5:
+            val *= -1
+        return val
+
+
+class DisjointUniformSampler(ContinuousSampler):
+    "Uniformly sample from a set of disjoint intervals."
+
+    def __init__(self, ranges):
+        """
+        The ranges variable can either be a list of increasing numbers or a
+        list of pairs of numbers.
+
+        The former case will be treated as
+        defining alternating on/off ranges for sampling, starting with an active
+        one (i.e. it's a list of bin edges). The latter way specifically lists
+        the 'on' regions only, with their start and end values in the pairs.
+
+        The behaviour is undefined if the numbers are not ordered or overlap --
+        i.e. it might work but hasn't been designed that way and might change in
+        future. Don't rely on this behaviour!
+        """
+        if not ranges:
+            raise Exception("You must supply at least one non-null sampling range")
+        if hasattr(ranges[0], "__len__"):
+            assert all(len(x) == 2 for x in ranges)
+            self.ranges = ranges
+        else:
+            assert len(ranges) > 1
+            lows = [x for x in ranges[:-1]]
+            highs = [x for x in ranges[1:]]
+            myranges = []
+            for i, pair in enumerate(zip(lows, highs)):
+                if i % 2 == 0:
+                    myranges.append(pair)
+            assert len(myranges) == len(ranges) // 2
+            self.ranges = myranges
+
+    def _getRanges(self):
+        return self._ranges
+
+    def _setRanges(self, ranges):
+        # TODO: Check that ranges don't overlap
+        self._ranges = ranges
+        self._totalwidth = sum(r[1] - r[0] for r in ranges)
+
+        runningwidth = 0.0
+        self._divisions = [0.0]
+        for r in ranges:
+            assert(r[1] >= r[0])
+            runningwidth += float(r[1] - r[0])
+            self._divisions.append(runningwidth)
+        self._totalwidth = runningwidth
+        for i in range(len(self._divisions)):
+            self._divisions[i] = float(self._divisions[i]) / float(self._totalwidth)
+
+    ranges = property(_getRanges, _setRanges)
+
+    def _map_unit_to_val(self, x):
+        assert(x >= 0 and x <= 1)
+        idx = None
+        rem = None
+        for i in range(len(self._divisions) - 1):
+            if x >= self._divisions[i] and x < self._divisions[i+1]:
+                idx = i
+                rem = x - self._divisions[i]
+                break
+        if idx is None:
+            raise ValueError("No matching division found in unit interval! How?")
+        val = self.ranges[idx][0] + self._totalwidth * rem
+        return val
+
+    def shoot(self):
+        rand = random.random()
+        val = self._map_unit_to_val(rand)
+        return val
+
+
+class LogSampler(ContinuousSampler):
+    "Randomly sample from an exponential distribution (i.e. uniformly on a log scale)."
+
+    def __init__(self, low, high):
+        self.low = float(low)
+        self.high = float(high)
+
+    def shoot(self):
+        rand = random.random()
+        logval = rand * math.log(self.high) + (1 - rand) * math.log(self.low)
+        val = math.exp(logval)
+        return val
+
+
+class GaussianSampler(ContinuousSampler):
+    "Randomly sample from a 1D Gaussian distribution."
+
+    def __init__(self, mean, sigma, oneside = False):
+        self.mean = float(mean)
+        self.sigma = float(sigma)
+        self.oneside = bool(oneside)
+
+    def shoot(self):
+        if self.oneside:
+            return abs(random.gauss(self.mean, self.sigma))
+        else:
+            return random.gauss(self.mean, self.sigma)
+
+class InvSampler(ContinuousSampler):
+    "Randomly sample from a 1/x distribution."
+
+    def __init__(self, low, high):
+        self.low = float(low)
+        self.high = float(high)
+
+    def shoot(self):
+        invx = random.uniform(1/self.high, 1/self.low) #< limit inversion not actually necessary
+        return 1./invx
+
+
+########################################
+
+
+class TH1Sampler(ContinuousSampler):
+    "Randomly sample from a 1D ROOT histogram."
+
+    def __init__(self, *args):
+        self.hist = TH1(*args)
+        if self.hist.GetEntries() < 1:
+            raise Exception("Histogram %s is EMPTY! Cannot sample" % self.hist.GetName())
+
+    def shoot(self):
+        return self.hist.GetRandom()
+
+
+########################################
+
+
+## Discrete sequence samplers
+
+class DiscreteSampler(Sampler):
+    "Base class for samplers from lists of discrete values"
+    pass
+
+
+class RandomSeqSampler(DiscreteSampler):
+    "Uniformly random sample from a list of values."
+
+    def __init__(self, *args):
+        if len(args) == 1:
+            self.sequence = args[0]
+        else:
+            self.sequence = args
+
+    def shoot(self):
+        return random.choice(self.sequence)
+# Alias:
+RndmSeq = RandomSeqSampler
+
+
+class CyclicSeqSampler(DiscreteSampler):
+    "Sequentially sample from a list of values, returning to the beginning once exhausted."
+
+    def __init__(self, *args):
+        if len(args) == 1:
+            self.sequence = args[0]
+        else:
+            self.sequence = args
+        self.index = 0
+
+    def shoot(self):
+        self.index = (self.index + 1) % len(self.sequence)
+        return self.sequence[self.index]
+## Alias:
+Sequence = CyclicSeqSampler
+
+
+########################################
+
+
+## Convenience function for sampler-making from Python literals
+
+def mksampler(x):
+    """
+    Automatically cast the provided object to a sampler type. This is used
+    extensively inside the particle and position samplers, so that the user
+    can pass in a primitive type like a number or list and it will be
+    treated as if the more verbose sampler constructors had been called.
+
+    Behaviour:
+     - if x can be called, i.e. x() is valid, we just return x;
+     - a Python list (square brackets) will be converted to a continuous
+       UniformSampler or DisjointUniformSampler;
+     - a Python tuple (round brackets/parentheses) will be treated
+       as a discrete CyclicSeqSampler;
+     - a Python set (curly brackets/braces) will be treated
+       as a discrete RandomSeqSampler;
+     - otherwise a ConstSampler will be created from x, so that x is
+       returned when the sampler is called.
+    """
+    if hasattr(x, "__call__"):
+        return x
+    elif type(x) is list:
+        # NB: disjoint ranges can be given as nested lists, e.g. [(1,2), (4,5)]
+        if len(x) == 2 and type(x[0]) in (int,float) and type(x[1]) in (int,float):
+            #print "MKSAMPLER: Casting %s to UniformSampler" % str(x)
+            return UniformSampler(*x)
+        elif len(x) > 2 or (len(x) > 0 and type(x[0]) not in (int,float)):
+            #print "MKSAMPLER: Casting %s to DisjointUniformSampler" % str(x)
+            return DisjointUniformSampler(x)
+        if len(x) < 2:
+            raise Exception("Supplied list could not be converted to a continuous sampler")
+    elif type(x) is tuple:
+        #print "MKSAMPLER: Casting %s to CyclicSeqSampler" % str(x)
+        return CyclicSeqSampler(*x)
+    elif type(x) is set:
+        #print "MKSAMPLER: Casting %s to RandomSeqSampler" % str(x)
+        return RandomSeqSampler(*x)
+    else:
+        #print "MKSAMPLER: Casting %s to ConstSampler" % str(x)
+        return ConstSampler(x)
+
+
+########################################
+
+
+## Beam-spot (origin vertex) sampling
+
+class PosSampler(Sampler):
+    """
+    Sampler of position 3-vectors, for modelling a beamspot.
+    """
+
+    def __init__(self, x, y, z, t=0):
+        self.x = x
+        self.y = y
+        self.z = z
+        self.t = t
+
+    @property
+    def x(self):
+        "x position sampler"
+        return self._x
+    @x.setter
+    def x(self, x):
+        self._x = mksampler(x)
+
+    @property
+    def y(self):
+        "y position sampler"
+        return self._y
+    @y.setter
+    def y(self, y):
+        self._y = mksampler(y)
+
+    @property
+    def z(self):
+        "z position sampler"
+        return self._z
+    @z.setter
+    def z(self, z):
+        self._z = mksampler(z)
+
+    @property
+    def t(self):
+        "Time sampler"
+        return self._t
+    @t.setter
+    def t(self, t):
+        self._t = mksampler(t)
+
+    def shoot(self):
+        x = self.x()
+        y = self.y()
+        z = self.z()
+        t = self.t()
+        #print "POS =", x, y, z, t
+        return ROOT.TLorentzVector(x, y, z, t)
+
+
+# TODO: Make a 3-Gaussian BeamspotSampler
+
+
+## Momentum sampling
+
+class MomSampler(Sampler):
+    """
+    Base class for four-momentum sampling.
+
+    There are many ways to unambiguously specify four-momenta. Not all are sensible/useful,
+    though. The following are implemented here:
+     * M,px,py,pz
+     * E,M,phi,eta
+     * E,M,phi,y
+     * E,M,phi,theta
+     * pT,M,phi,eta
+     * pT,M,phi,y
+     * pT,M,phi,theta
+
+    Possibly the following (not yet implemented) could be useful: let us know if you
+    need one of these:
+     * E,px,py,pz
+     * pT,E,M,phi
+    """
+    pass
+
+
+class NullMomSampler(MomSampler):
+    "A momentum sampler which just returns a null vector with the given mass."
+
+    def __init__(self, mass=0.0):
+        self.mass = mass
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    def shoot(self):
+        v4 = ROOT.TLorentzVector(0, 0, 0, self.mass)
+        return v4
+
+
+class MXYZSampler(MomSampler):
+    "Create a 4-momentum vector from mass, px, py, pz distributions/samplers."
+
+    def __init__(self, px, py, pz, mass=0.0):
+        self.mass = mass
+        self.px = px
+        self.py = py
+        self.pz = pz
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def px(self):
+        "px sampler"
+        return self._px
+    @px.setter
+    def px(self, x):
+        self._px = mksampler(x)
+
+    @property
+    def py(self):
+        "py sampler"
+        return self._py
+    @py.setter
+    def py(self, x):
+        self._py = mksampler(x)
+
+    @property
+    def pz(self):
+        "pz sampler"
+        return self._pz
+    @pz.setter
+    def pz(self, x):
+        self._pz = mksampler(x)
+
+    def shoot(self):
+        m = self.mass()
+        px = self.px()
+        py = self.py()
+        pz = self.pz()
+        e = math.sqrt(px**2 + py**2 + pz**2 + m**2)
+        v4 = ROOT.TLorentzVector(px, py, pz, e)
+        return v4
+
+
+class EEtaMPhiSampler(MomSampler):
+    "Create a 4-momentum vector from E, eta, m and phi distributions/samplers."
+
+    # TODO: ensure that E >= m!
+
+    def __init__(self, energy, eta, mass=0.0, phi=[0, TWOPI]):
+        self.energy = energy
+        self.eta = eta
+        self.mass = mass
+        self.phi = phi
+
+    @property
+    def energy(self):
+        "Energy sampler"
+        return self._e
+    @energy.setter
+    def energy(self, x):
+        self._e = mksampler(x)
+
+    @property
+    def eta(self):
+        "Pseudorapidity sampler"
+        return self._eta
+    @eta.setter
+    def eta(self, x):
+        self._eta = mksampler(x)
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def phi(self):
+        "Azimuthal angle sampler"
+        return self._phi
+    @phi.setter
+    def phi(self, x):
+        self._phi = mksampler(x)
+
+    def shoot(self):
+        """
+        eta = - ln(tan(theta/2)) / 2
+        => theta = 2 atan( exp(-eta) )
+        """
+        eta = self.eta()
+        theta = 2 * math.atan(math.exp(-eta))
+        e = self.energy()
+        m = self.mass()
+        p = math.sqrt( e**2 - m**2 )
+        pz = p * math.cos(theta)
+        pt = p * math.sin(theta)
+        phi = self.phi()
+        px = pt * math.cos(phi)
+        py = pt * math.sin(phi)
+        v4 = ROOT.TLorentzVector(px, py, pz, e)
+        return v4
+
+
+class ERapMPhiSampler(MomSampler):
+    "Create a 4-momentum vector from E, y, m and phi distributions."
+
+    # TODO: ensure that E >= m!
+
+    def __init__(self, energy, rap, mass=0.0, phi=[0, TWOPI]):
+        self.energy = energy
+        self.rap = rap
+        self.mass = mass
+        self.phi = phi
+
+    @property
+    def energy(self):
+        "Energy sampler"
+        return self._e
+    @energy.setter
+    def energy(self, x):
+        self._e = mksampler(x)
+
+    @property
+    def rap(self):
+        "Rapidity sampler"
+        return self._rap
+    @rap.setter
+    def rap(self, x):
+        self._rap = mksampler(x)
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def phi(self):
+        "Azimuthal angle sampler"
+        return self._phi
+    @phi.setter
+    def phi(self, x):
+        self._phi = mksampler(x)
+
+    def shoot(self):
+        """
+        y = 0.5 * ln((E+pz)/(E-pz))
+        -> (E^2 - pz^2) exp(2y) = (E+pz)^2
+         & (E^2 - pz^2) exp(-2y) = (E-pz)^2
+        -> E = sqrt(pt^2 + m^2) cosh(y)
+        -> pz = sqrt(pt^2 + m^2) sinh(y)
+        -> sqrt(pt^2 + m^2) = E / cosh(y)
+        """
+        e = self.energy()
+        y = self.rap()
+        sqrt_pt2_m2 = e / math.cosh(y)
+        pz = sqrt_pt2_m2 * math.sinh(y)
+        m = self.mass()
+        pt = math.sqrt( sqrt_pt2_m2**2 - m**2 )
+        phi = self.phi()
+        px = pt * math.cos(phi)
+        py = pt * math.sin(phi)
+        v4 = ROOT.TLorentzVector(px, py, pz, e)
+        return v4
+
+
+class EThetaMPhiSampler(MomSampler):
+    "Create a 4-momentum vector from E, theta, m and phi distributions/samplers."
+
+    # TODO: ensure that E >= m!
+
+    def __init__(self, energy, theta, mass=0.0, phi=[0, TWOPI]):
+        self.energy = energy
+        self.theta = theta
+        self.mass = mass
+        self.phi = phi
+
+    @property
+    def energy(self):
+        "Energy sampler"
+        return self._e
+    @energy.setter
+    def energy(self, x):
+        self._e = mksampler(x)
+
+    @property
+    def theta(self):
+        "Polar angle sampler"
+        return self._theta
+    @theta.setter
+    def theta(self, x):
+        self._theta = mksampler(x)
+
+    @property
+    def mass(self):
+        "Mass sampler" 
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def phi(self):
+        "Azimuthal angle sampler"
+        return self._phi
+    @phi.setter
+    def phi(self, x):
+        self._phi = mksampler(x)
+
+    def shoot(self):
+        """
+        p = sqrt(e^2 - m^2)
+        pz = p cos(theta)
+        pt = p sin(theta)
+        """
+        e = self.energy()
+        m = self.mass()
+        p = math.sqrt( e**2 - m**2 )
+        theta = self.theta()
+        pz = p * math.cos(theta)
+        pt = p * math.sin(theta)
+        phi = self.phi()
+        px = pt * math.cos(phi)
+        py = pt * math.sin(phi)
+        v4 = ROOT.TLorentzVector(px, py, pz, e)
+        return v4
+
+
+class PtEtaMPhiSampler(MomSampler):
+    "Create a 4-momentum vector from pt, eta, m and phi distributions/samplers."
+
+    def __init__(self, pt, eta, mass=0.0, phi=[0, TWOPI]):
+        self.pt = pt
+        self.eta = eta
+        self.mass = mass
+        self.phi = phi
+
+    @property
+    def pt(self):
+        "Transverse momentum sampler"
+        return self._pt
+    @pt.setter
+    def pt(self, x):
+        self._pt = mksampler(x)
+
+    @property
+    def eta(self):
+        "Pseudorapidity sampler"
+        return self._eta
+    @eta.setter
+    def eta(self, x):
+        self._eta = mksampler(x)
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def phi(self):
+        "Azimuthal angle sampler"
+        return self._phi
+    @phi.setter
+    def phi(self, x):
+        self._phi = mksampler(x)
+
+    def shoot(self):
+        """
+        eta = - ln(tan(theta/2)) / 2
+        => theta = 2 atan( exp(-eta) )
+        """
+        eta = self.eta()
+        theta = 2 * math.atan(math.exp(-eta))
+        pt = self.pt()
+        p = pt / math.sin(theta)
+        phi = self.phi()
+        px = pt * math.cos(phi)
+        py = pt * math.sin(phi)
+        pz = p * math.cos(theta)
+        m = self.mass()
+        e = math.sqrt( p**2 + m**2 )
+        v4 = ROOT.TLorentzVector(px, py, pz, e)
+        return v4
+
+
+class PtRapMPhiSampler(MomSampler):
+    "Create a 4-momentum vector from pt, y, m and phi distributions/samplers."
+
+    def __init__(self, pt, rap, mass=0.0, phi=[0, TWOPI]):
+        self.pt = pt
+        self.rap = rap
+        self.mass = mass
+        self.phi = phi
+
+    @property
+    def pt(self):
+        "Transverse momentum sampler"
+        return self._pt
+    @pt.setter
+    def pt(self, x):
+        self._pt = mksampler(x)
+
+    @property
+    def rap(self):
+        "Rapidity sampler"
+        return self._rap
+    @rap.setter
+    def rap(self, x):
+        self._rap = mksampler(x)
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def phi(self):
+        "Azimuthal angle sampler"
+        return self._phi
+    @phi.setter
+    def phi(self, x):
+        self._phi = mksampler(x)
+
+    def shoot(self):
+        """
+        y = 0.5 * ln((E+pz)/(E-pz))
+        -> (E^2 - pz^2) exp(2y) = (E+pz)^2
+         & (E^2 - pz^2) exp(-2y) = (E-pz)^2
+        -> E = sqrt(pt^2 + m^2) cosh(y)
+        -> pz = sqrt(pt^2 + m^2) sinh(y)
+        -> sqrt(pt^2 + m^2) = E / cosh(y)
+        """
+        pt = self.pt()
+        assert pt >= 0
+        m = self.mass()
+        assert m >= 0
+        sqrt_pt2_m2 = math.sqrt( pt**2 + m**2 )
+        y = self.rap()
+        e = sqrt_pt2_m2 * math.cosh(y)
+        pz = sqrt_pt2_m2 * math.sinh(y)
+        phi = self.phi()
+        px = pt * math.cos(phi)
+        py = pt * math.sin(phi)
+        v4 = ROOT.TLorentzVector(px, py, pz, e)
+        return v4
+
+
+class PtThetaMPhiSampler(MomSampler):
+    "Create a 4-momentum vector from pt, theta, m and phi distributions/samplers."
+
+    def __init__(self, pt, theta, mass=0.0, phi=[0, TWOPI]):
+        self.pt = pt
+        self.theta = theta
+        self.mass = mass
+        self.phi = phi
+
+    @property
+    def pt(self):
+        "Transverse momentum sampler"
+        return self._pt
+    @pt.setter
+    def pt(self, x):
+        self._pt = mksampler(x)
+
+    @property
+    def theta(self):
+        "Polar angle sampler"
+        return self._theta
+    @theta.setter
+    def theta(self, x):
+        self._theta = mksampler(x)
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def phi(self):
+        "Azimuthal angle sampler"
+        return self._phi
+    @phi.setter
+    def phi(self, x):
+        self._phi = mksampler(x)
+
+    def shoot(self):
+        """
+        p = pt / math.sin(theta)
+        pz = p cos(theta)
+        pt = p sin(theta)
+        E = sqrt(p^2 + m^2)
+        """
+        theta = self.theta()
+        pt = self.pt()
+        p = pt / math.sin(theta)
+        phi = self.phi()
+        px = pt * math.cos(phi)
+        py = pt * math.sin(phi)
+        pz = p * math.cos(theta)
+        m = self.mass()
+        e = math.sqrt( p**2 + m**2 )
+        v4 = ROOT.TLorentzVector(px, py, pz, e)
+        return v4
+
+
+# TODO: add the missing ways to specify/sample 4-momenta
+
+
+###########################################################
+
+
+## Combined samplers returning a particle configuration
+
+
+## A default dictionary of particle masses (in MeV)
+MASSES = { 22   :    0.0, # photon
+           11   :    0.5, # electron
+           12   :    0.0, # nu_e
+           13   :  105.7, # muon
+           14   :    0.0, # nu_mu
+           15   : 1777.8, # tau
+           16   :    0.0, # nu_tau
+           2212 :  938.0, # proton
+           2112 :  940.0, # neutron
+           111  :  135.0, # pi0
+           211  :  140.0, # pi+-
+           221  :  547.0, # eta
+           321  :  494.0, # K+-
+           311  :  598.0  # K0
+           }
+
+
+class SampledParticle(object):
+    """
+    A particle object for use as a return value from the particle samplers.
+    """
+    def __init__(self, pid=None, mom=ROOT.TLorentzVector(0,0,0,0), pos=ROOT.TLorentzVector(0,0,0,0)):
+        """
+        Constructor/initializer: PID is the (int) PDG particle ID code
+        of this particle, mom is its momentum 4-vector, and pos is
+        the vertex 4-position (both as ROOT.TLorentzVector, in MeV).
+        """
+        self.pid = pid
+        self.mom = mom
+        self.pos = pos
+        self.mass = None
+
+
+class ParticleSampler(Sampler):
+    """
+    A simple N-independent-particle sampler.
+    """
+
+    def __init__(self, pid=999,
+                 mom=NullMomSampler(),
+                 n=1,
+                 pos=PosSampler(0, 0, 0)):
+        self.pid = pid
+        self.mom = mom
+        self.n = n
+        self.pos = pos
+        self.massdict = MASSES
+        self.mass_override = True
+
+    @property
+    def pid(self):
+        "Particle ID code sampler"
+        return self._pid
+    @pid.setter
+    def pid(self, x):
+        self._pid = mksampler(x)
+
+    @property
+    def n(self):
+        "Particle number sampler"
+        return self._n
+    @n.setter
+    def n(self, x):
+        self._n = mksampler(x)
+
+    def shoot(self):
+        "Return a vector of sampled particles"
+        numparticles = self.n()
+        rtn = []
+        for i in range(numparticles):
+            ## Sample the particle ID and create a particle
+            pid = self.pid()
+            p = SampledParticle(pid)
+            ## Pass mass info to the v4 sampler and set same generated mass
+            if self.mass_override and abs(pid) in self.massdict:
+                m = self.massdict[abs(pid)]
+                self.mom.mass = m
+                p.mass = m
+            # TODO: Should the particle generated_mass be set from the sampler by default?
+            ## Sample momentum and vertex positions into the particle
+            p.mom = self.mom()
+            p.pos = self.pos()
+            ## Add particle to output list
+            rtn.append(p)
+        return rtn
diff --git a/Generators/ParticleGun/share/common/ParticleGun_Common.py b/Generators/ParticleGun/share/common/ParticleGun_Common.py
new file mode 100644
index 0000000000000000000000000000000000000000..3fab6fb0b7c4093fe92bd0e34e57bc7093b0281a
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_Common.py
@@ -0,0 +1,4 @@
+## Common setup for ParticleGun
+import ParticleGun as PG
+genSeq += PG.ParticleGun()
+evgenConfig.generators += ["ParticleGun"]
diff --git a/Generators/ParticleGun/share/common/ParticleGun_EoverP_Config.py b/Generators/ParticleGun/share/common/ParticleGun_EoverP_Config.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b78a953f31c253a9ead42158bf4d8b0dab77ce0
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_EoverP_Config.py
@@ -0,0 +1,66 @@
+#! -*- python -*-
+evgenConfig.description = "Single particle gun for E/p event generation"
+evgenConfig.keywords = ["singleParticle",]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["zach.marshall@cern.ch"]
+
+import ParticleGun as PG
+import ROOT
+from ParticleGun.samplers import *
+class PEtaSampler(PG.MomSampler):
+    "Create a 4-momentum vector from pt, eta, m and phi distributions/samplers."
+
+    def __init__(self, momentum, eta, pid=211, phi=[0, math.pi*2.]):
+        self.momentum = momentum
+        self.eta = eta
+        pdg_table = ROOT.TDatabasePDG.Instance()
+        mass = pdg_table.GetParticle(pid).Mass()
+        self.mass = mass
+        self.phi = phi
+
+    @property
+    def momentum(self):
+        "Momentum sampler"
+        return self._momentum
+    @momentum.setter
+    def momentum(self, x):
+        self._momentum = mksampler(x)
+
+    @property
+    def eta(self):
+        "Pseudorapidity sampler"
+        return self._eta
+    @eta.setter
+    def eta(self, x):
+        self._eta = mksampler(x)
+
+    @property
+    def mass(self):
+        "Mass sampler"
+        return self._m
+    @mass.setter
+    def mass(self, x):
+        self._m = mksampler(x)
+
+    @property
+    def phi(self):
+        "Azimuthal angle sampler"
+        return self._phi
+    @phi.setter
+    def phi(self, x):
+        self._phi = mksampler(x)
+
+    def shoot(self):
+        v4 = ROOT.TLorentzVector()
+        pt = p / math.cosh(self.eta())
+        v4.SetPtEtaPhiM(pt, self.eta(), self.phi(), self.mass())
+        return v4
+
+a_particle = int(jofile.split('_')[-1].split('.py')[0].replace('m','-'))
+
+pg = PG.ParticleGun()
+pg.sampler.pid = int(a_particle) #PID
+pg.sampler.mom = PEtaSampler(momentum=(500,800,1000,1200,1500,2000,3000,4000,5000,6000,7000,8000,9000,10000,11000,13000,15000,17000,20000,\
+                                       25000,35000,50000,75000,100000,200000,350000,500000), eta=[-0.3,0.3], pid=int(a_particle))
+genSeq += pg
+
diff --git a/Generators/ParticleGun/share/common/ParticleGun_FastCalo_ChargeFlip_Config.py b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_ChargeFlip_Config.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5399a64019b935aa729244f88c4b5c0ebe5b35f
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_ChargeFlip_Config.py
@@ -0,0 +1,78 @@
+#! -*- python -*-
+evgenConfig.description = "Single particle gun for FastCaloSim event generation"
+evgenConfig.keywords = ["singleParticle",]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["david.sosa@cern.ch"]
+
+import ParticleGun as PG
+import ROOT
+
+class MyParticleSampler(PG.ParticleSampler):
+    def __init__(self,energy,eta,pid,shift_z=0):
+        self.pid = pid
+        self.shift_z = shift_z
+        pdg_table = ROOT.TDatabasePDG.Instance()
+        mass = pdg_table.GetParticle(self.pid()).Mass()
+        self.mom1 = PG.EEtaMPhiSampler(energy=energy,eta=eta,mass=mass)
+
+    def shoot(self):
+        pid = self.pid()
+        
+        shift_z = self.shift_z
+
+        mom = self.mom1.shoot()
+        pos_temp = mom.Vect().Unit()
+        
+        # Would it hit the barrel, or the endcap?
+        if abs(pos_temp.Z())/3550.<pos_temp.Perp()/1148.: # Hit the barrel!
+            pos_temp *= 1148./pos_temp.Perp()
+        else: # Hit the endcap!
+            pos_temp *= 3550./abs(pos_temp.Z())
+
+        # Shift position of vector in the Z direction
+        pos_temp_2 = ROOT.TVector3()
+        pos_temp_2.SetXYZ(pos_temp.X(), pos_temp.Y(), pos_temp.Z()+shift_z)
+        pos_temp_2 *= 1. / pos_temp_2.Mag(); # reduce magnitude of vector
+
+        # recalculate; Would it hit the barrel, or the endcap?
+        if abs(pos_temp_2.Z())/3550.<pos_temp_2.Perp()/1148.:
+            pos_temp_2 *= 1148./pos_temp_2.Perp()
+        else:
+            pos_temp_2 *= 3550./abs(pos_temp_2.Z())
+
+        pos = ROOT.TLorentzVector(pos_temp_2.X(),pos_temp_2.Y(),pos_temp_2.Z(), pos_temp_2.Mag())
+        
+        #print "pid ",pid
+        
+        return [ PG.SampledParticle( pid , mom , pos ) ]
+
+myE = float(jofile.split('_E')[1].split('_')[0])
+myZV = float(jofile.split('_')[-1].split('.py')[0].replace("m","-"))
+
+myPDGID = jofile.split('_pid')[1].split('_')[0].replace('n','-')
+myPDGID = int(float(myPDGID.replace('p','')))
+
+eta_li = []
+
+if "disj" in jofile:
+    myLowEta1  = 0.01*float(jofile.split('eta_')[1].split('_')[0].replace('m','-'))
+    myLowEta2  = 0.01*float(jofile.split('eta_')[1].split('_')[1].replace('m','-'))
+    myHighEta1 = 0.01*float(jofile.split('eta_')[1].split('_')[2].replace('m','-'))
+    myHighEta2 = 0.01*float(jofile.split('eta_')[1].split('_')[3].replace('m','-'))
+    eta_li.extend([myLowEta1,myLowEta2,myHighEta1,myHighEta2])
+
+else:
+    myLowEta  = 0.01*float(jofile.split('eta')[1].split('_')[0].replace('m','-'))
+    myHighEta = 0.01*float(jofile.split('eta')[1].split('_')[1].replace('m','-'))
+    eta_li.extend([myLowEta,myHighEta])
+
+
+print "================ SETTTINGS ================="
+print ("energy = ", myE)
+print ("eta = ", eta_li)
+print ("pid = ", myPDGID)
+print ("shift_z = ", myZV)
+print "============================================"
+
+genSeq += PG.ParticleGun()
+genSeq.ParticleGun.sampler = MyParticleSampler(energy=myE,eta=eta_li,pid=(myPDGID,myPDGID),shift_z=myZV) #unmixed
diff --git a/Generators/ParticleGun/share/common/ParticleGun_FastCalo_Config.py b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_Config.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b2e9a68bc5e1c1612bf2e294c58dcc472f700fb
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_Config.py
@@ -0,0 +1,101 @@
+#! -*- python -*-
+evgenConfig.description = "Single particle gun for FastCaloSim event generation"
+evgenConfig.keywords = ["singleParticle",]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["david.sosa@cern.ch"]
+
+import ParticleGun as PG
+import ROOT
+
+class MyParticleSampler(PG.ParticleSampler):
+    def __init__(self,energy,eta,pid,shift_z=0):
+        self.pid = pid
+        self.shift_z = shift_z
+        pdg_table = ROOT.TDatabasePDG.Instance()
+        mass = pdg_table.GetParticle(self.pid()).Mass()
+        self.mom1 = PG.EEtaMPhiSampler(energy=energy,eta=eta,mass=mass)
+
+    def shoot(self):
+        pid = self.pid()
+
+        shift_z = self.shift_z
+
+        mom = self.mom1.shoot()
+        pos_temp = mom.Vect().Unit()
+
+        # Define geometry
+        barrelR1 = 1148.0
+        barrelR2 = 120.0
+        barrelR3 = 41.0
+        endcapZ1 = 3550.0
+        endcapZ2 = 4587.0
+
+        # Would it hit the barrel, or the endcap?
+        tanTheta = pos_temp.Perp() / abs( pos_temp.Z() );
+        if   tanTheta > barrelR1 / endcapZ1:
+            pos_temp *= barrelR1 / pos_temp.Perp()
+        elif tanTheta > barrelR2 / endcapZ1:
+            pos_temp *= endcapZ1 / abs( pos_temp.Z() )
+        elif tanTheta > barrelR2 / endcapZ2:
+            pos_temp *= barrelR2 / pos_temp.Perp()
+        elif tanTheta > barrelR3 / endcapZ2:
+            pos_temp *= endcapZ2 / abs( pos_temp.Z() )
+        else:
+            pos_temp *= barrelR3 / pos_temp.Perp()
+
+        # Shift position of vector in the Z direction
+        pos_temp_2 = ROOT.TVector3()
+        pos_temp_2.SetXYZ(pos_temp.X(), pos_temp.Y(), pos_temp.Z()+shift_z)
+        pos_temp_2 *= 1. / pos_temp_2.Mag(); # reduce magnitude of vector
+
+        # recalculate; Would it hit the barrel, or the endcap?
+        tanTheta_2 = pos_temp_2.Perp() / abs( pos_temp_2.Z() );
+        if   tanTheta_2 > barrelR1 / endcapZ1:
+            pos_temp_2 *= barrelR1 / pos_temp_2.Perp()
+        elif tanTheta_2 > barrelR2 / endcapZ1:
+            pos_temp_2 *= endcapZ1 / abs( pos_temp_2.Z() )
+        elif tanTheta_2 > barrelR2 / endcapZ2:
+            pos_temp_2 *= barrelR2 / pos_temp_2.Perp()
+        elif tanTheta_2 > barrelR3 / endcapZ2:
+            pos_temp_2 *= endcapZ2 / abs( pos_temp_2.Z() )
+        else:
+            pos_temp_2 *= barrelR3 / pos_temp_2.Perp()
+
+        pos = ROOT.TLorentzVector(pos_temp_2.X(),pos_temp_2.Y(),pos_temp_2.Z(), pos_temp_2.Mag())
+
+        #print "pid ",pid
+
+        return [ PG.SampledParticle( pid , mom , pos ) ]
+
+myE = float(jofile.split('_E')[1].split('_')[0])
+myZV = float(jofile.split('_')[-1].split('.py')[0].replace("m","-"))
+myPDGID = int(float(jofile.split('_pid')[1].split('_')[0].replace('m','-')))
+
+eta_li = []
+
+if "disj" in jofile:
+    myLowEta1  = 0.01*float(jofile.split('eta_')[1].split('_')[0].replace('m','-'))
+    myLowEta2  = 0.01*float(jofile.split('eta_')[1].split('_')[1].replace('m','-'))
+    myHighEta1 = 0.01*float(jofile.split('eta_')[1].split('_')[2].replace('m','-'))
+    myHighEta2 = 0.01*float(jofile.split('eta_')[1].split('_')[3].replace('m','-'))
+    eta_li.extend([myLowEta1,myLowEta2,myHighEta1,myHighEta2])
+
+else:
+    myLowEta  = 0.01*float(jofile.split('eta')[1].split('_')[0].replace('m','-'))
+    myHighEta = 0.01*float(jofile.split('eta')[1].split('_')[1].replace('m','-'))
+    eta_li.extend([myLowEta,myHighEta])
+
+
+print "================ SETTTINGS ================="
+print ("energy = ", myE)
+print ("eta = ", eta_li)
+print ("pid = ", myPDGID)
+print ("shift_z = ", myZV)
+print "============================================"
+
+genSeq += PG.ParticleGun()
+if myPDGID != 22:
+    genSeq.ParticleGun.sampler = MyParticleSampler(energy=myE,eta=eta_li,pid=(-myPDGID,myPDGID),shift_z=myZV)
+else:
+    genSeq.ParticleGun.sampler = MyParticleSampler(energy=myE,eta=eta_li,pid=myPDGID,shift_z=myZV)
+
diff --git a/Generators/ParticleGun/share/common/ParticleGun_FastCalo_Config_Erange.py b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_Config_Erange.py
new file mode 100644
index 0000000000000000000000000000000000000000..75ebc0621e7ba4e6803ae25c20a6d7438fc45466
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_Config_Erange.py
@@ -0,0 +1,103 @@
+#! -*- python -*-
+evgenConfig.description = "Single particle gun for FastCaloSim event generation"
+evgenConfig.keywords = ["singleParticle",]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["david.sosa@cern.ch"]
+
+import ParticleGun as PG
+import ROOT
+
+class MyParticleSampler(PG.ParticleSampler):
+    def __init__(self,energy,eta,pid,shift_z=0):
+        self.pid = pid
+        self.shift_z = shift_z
+        pdg_table = ROOT.TDatabasePDG.Instance()
+        mass = pdg_table.GetParticle(self.pid()).Mass()
+        self.mom1 = PG.EEtaMPhiSampler(energy=energy,eta=eta,mass=mass)
+
+    def shoot(self):
+        pid = self.pid()
+
+        shift_z = self.shift_z
+
+        mom = self.mom1.shoot()
+        pos_temp = mom.Vect().Unit()
+
+        # Define geometry
+        barrelR1 = 1148.0
+        barrelR2 = 120.0
+        barrelR3 = 41.0
+        endcapZ1 = 3550.0
+        endcapZ2 = 4587.0
+
+        # Would it hit the barrel, or the endcap?
+        tanTheta = pos_temp.Perp() / abs( pos_temp.Z() );
+        if   tanTheta > barrelR1 / endcapZ1:
+            pos_temp *= barrelR1 / pos_temp.Perp()
+        elif tanTheta > barrelR2 / endcapZ1:
+            pos_temp *= endcapZ1 / abs( pos_temp.Z() )
+        elif tanTheta > barrelR2 / endcapZ2:
+            pos_temp *= barrelR2 / pos_temp.Perp()
+        elif tanTheta > barrelR3 / endcapZ2:
+            pos_temp *= endcapZ2 / abs( pos_temp.Z() )
+        else:
+            pos_temp *= barrelR3 / pos_temp.Perp()
+
+        # Shift position of vector in the Z direction
+        pos_temp_2 = ROOT.TVector3()
+        pos_temp_2.SetXYZ(pos_temp.X(), pos_temp.Y(), pos_temp.Z()+shift_z)
+        pos_temp_2 *= 1. / pos_temp_2.Mag(); # reduce magnitude of vector
+
+        # recalculate; Would it hit the barrel, or the endcap?
+        tanTheta_2 = pos_temp_2.Perp() / abs( pos_temp_2.Z() );
+        if   tanTheta_2 > barrelR1 / endcapZ1:
+            pos_temp_2 *= barrelR1 / pos_temp_2.Perp()
+        elif tanTheta_2 > barrelR2 / endcapZ1:
+            pos_temp_2 *= endcapZ1 / abs( pos_temp_2.Z() )
+        elif tanTheta_2 > barrelR2 / endcapZ2:
+            pos_temp_2 *= barrelR2 / pos_temp_2.Perp()
+        elif tanTheta_2 > barrelR3 / endcapZ2:
+            pos_temp_2 *= endcapZ2 / abs( pos_temp_2.Z() )
+        else:
+            pos_temp_2 *= barrelR3 / pos_temp_2.Perp()
+
+        pos = ROOT.TLorentzVector(pos_temp_2.X(),pos_temp_2.Y(),pos_temp_2.Z(), pos_temp_2.Mag())
+
+        #print "pid ",pid
+
+        return [ PG.SampledParticle( pid , mom , pos ) ]
+
+E_li = []
+myLowE = float(jofile.split('_E')[1].split('_')[0])
+myHighE = float(jofile.split('_E')[1].split('_')[1])
+E_li.extend([myLowE,myHighE])
+
+myZV = float(jofile.split('_')[-1].split('.py')[0].replace("m","-"))
+myPDGID = int(float(jofile.split('_pid')[1].split('_')[0].replace('m','-')))
+
+eta_li = []
+
+if "disj" in jofile:
+    myLowEta1  = 0.01*float(jofile.split('eta_')[1].split('_')[0].replace('m','-'))
+    myLowEta2  = 0.01*float(jofile.split('eta_')[1].split('_')[1].replace('m','-'))
+    myHighEta1 = 0.01*float(jofile.split('eta_')[1].split('_')[2].replace('m','-'))
+    myHighEta2 = 0.01*float(jofile.split('eta_')[1].split('_')[3].replace('m','-'))
+    eta_li.extend([myLowEta1,myLowEta2,myHighEta1,myHighEta2])
+
+else:
+    myLowEta  = 0.01*float(jofile.split('eta')[1].split('_')[0].replace('m','-'))
+    myHighEta = 0.01*float(jofile.split('eta')[1].split('_')[1].replace('m','-'))
+    eta_li.extend([myLowEta,myHighEta])
+
+
+print "================ SETTTINGS ================="
+print ("energy = ", E_li)
+print ("eta = ", eta_li)
+print ("pid = ", myPDGID)
+print ("shift_z = ", myZV)
+print "============================================"
+
+genSeq += PG.ParticleGun()
+print "E_li = ", E_li, ", eta_li = ", eta_li, ", pid = ", myPDGID, ", myZV = ", myZV
+genSeq.ParticleGun.sampler = MyParticleSampler(energy=E_li,eta=eta_li,pid=myPDGID,shift_z=myZV)
+
diff --git a/Generators/ParticleGun/share/common/ParticleGun_FastCalo_NoChargeFlip_Config.py b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_NoChargeFlip_Config.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ba60ef2bc9d3a9195ada72e9a2232864f173567
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_FastCalo_NoChargeFlip_Config.py
@@ -0,0 +1,78 @@
+#! -*- python -*-
+evgenConfig.description = "Single particle gun for FastCaloSim event generation"
+evgenConfig.keywords = ["singleParticle",]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["david.sosa@cern.ch"]
+
+import ParticleGun as PG
+import ROOT
+
+class MyParticleSampler(PG.ParticleSampler):
+    def __init__(self,energy,eta,pid,shift_z=0):
+        self.pid = pid
+        self.shift_z = shift_z
+        pdg_table = ROOT.TDatabasePDG.Instance()
+        mass = pdg_table.GetParticle(self.pid()).Mass()
+        self.mom1 = PG.EEtaMPhiSampler(energy=energy,eta=eta,mass=mass)
+
+    def shoot(self):
+        pid = self.pid()
+        
+        shift_z = self.shift_z
+
+        mom = self.mom1.shoot()
+        pos_temp = mom.Vect().Unit()
+        
+        # Would it hit the barrel, or the endcap?
+        if abs(pos_temp.Z())/3550.<pos_temp.Perp()/1148.: # Hit the barrel!
+            pos_temp *= 1148./pos_temp.Perp()
+        else: # Hit the endcap!
+            pos_temp *= 3550./abs(pos_temp.Z())
+
+        # Shift position of vector in the Z direction
+        pos_temp_2 = ROOT.TVector3()
+        pos_temp_2.SetXYZ(pos_temp.X(), pos_temp.Y(), pos_temp.Z()+shift_z)
+        pos_temp_2 *= 1. / pos_temp_2.Mag(); # reduce magnitude of vector
+
+        # recalculate; Would it hit the barrel, or the endcap?
+        if abs(pos_temp_2.Z())/3550.<pos_temp_2.Perp()/1148.:
+            pos_temp_2 *= 1148./pos_temp_2.Perp()
+        else:
+            pos_temp_2 *= 3550./abs(pos_temp_2.Z())
+
+        pos = ROOT.TLorentzVector(pos_temp_2.X(),pos_temp_2.Y(),pos_temp_2.Z(), pos_temp_2.Mag())
+        
+        #print "pid ",pid
+        
+        return [ PG.SampledParticle( pid , mom , pos ) ]
+
+myE = float(jofile.split('_E')[1].split('_')[0])
+myZV = float(jofile.split('_')[-1].split('.py')[0].replace("m","-"))
+
+myPDGID = jofile.split('_pid')[1].split('_')[0].replace('n','-')
+myPDGID = int(float(myPDGID.split('_pid')[1].split('_')[0].replace('p','')))
+
+eta_li = []
+
+if "disj" in jofile:
+    myLowEta1  = 0.01*float(jofile.split('eta_')[1].split('_')[0].replace('m','-'))
+    myLowEta2  = 0.01*float(jofile.split('eta_')[1].split('_')[1].replace('m','-'))
+    myHighEta1 = 0.01*float(jofile.split('eta_')[1].split('_')[2].replace('m','-'))
+    myHighEta2 = 0.01*float(jofile.split('eta_')[1].split('_')[3].replace('m','-'))
+    eta_li.extend([myLowEta1,myLowEta2,myHighEta1,myHighEta2])
+
+else:
+    myLowEta  = 0.01*float(jofile.split('eta')[1].split('_')[0].replace('m','-'))
+    myHighEta = 0.01*float(jofile.split('eta')[1].split('_')[1].replace('m','-'))
+    eta_li.extend([myLowEta,myHighEta])
+
+
+print "================ SETTTINGS ================="
+print ("energy = ", myE)
+print ("eta = ", eta_li)
+print ("pid = ", myPDGID)
+print ("shift_z = ", myZV)
+print "============================================"
+
+genSeq += PG.ParticleGun()
+genSeq.ParticleGun.sampler = MyParticleSampler(energy=myE,eta=eta_li,pid=(myPDGID,myPDGID),shift_z=myZV) #unmixed
diff --git a/Generators/ParticleGun/share/common/ParticleGun_SamplingFraction.py b/Generators/ParticleGun/share/common/ParticleGun_SamplingFraction.py
new file mode 100644
index 0000000000000000000000000000000000000000..54557f0d5d2afacedb09999acac22e02ad8576ac
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_SamplingFraction.py
@@ -0,0 +1,97 @@
+#! -*- python -*-
+evgenConfig.description = "Single particle gun for Sampling Fraction event generation"
+evgenConfig.keywords = ["singleParticle",]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["michael.duehrssen@cern.ch"]
+
+import ParticleGun as PG
+import ROOT, math, random
+
+class MyParticleSampler(PG.ParticleSampler):
+    """
+    Projective showers starting at entrance of calorimeter, flat in eta, constant energy
+    """
+
+    def __init__(self,pid=11,momentum=50000.,eta1=0.,eta2=1.4,bec=0,radius=1500.,z=3740.5):
+        self.pid = pid
+        self.momentum = momentum
+        self.eta1 = eta1
+        self.eta2 = eta2
+        pdg_table = ROOT.TDatabasePDG.Instance()
+        self.mass = pdg_table.GetParticle(self.pid()).Mass()
+        self.bec=bec
+        self.radius=radius
+        self.z=z
+
+    def shoot(self):
+       rtn=[]
+       eta = random.uniform(self.eta1, self.eta2) 
+       phi = random.uniform(0, math.tau)  # tau = 2 * pi
+       v4 = ROOT.TLorentzVector()
+       pt = self.momentum / math.cosh(eta)
+       v4.SetPtEtaPhiM(pt, eta, phi, self.mass)
+       if self.bec==0:
+           radius= self.radius
+           x=radius*math.cos(phi)
+           y=radius*math.sin(phi)
+           z=radius*math.sinh(eta)
+       else:
+           z=self.z
+           radius=z/math.sinh(eta)
+           x=radius*math.cos(phi)
+           y=radius*math.sin(phi)
+       t=math.sqrt(x*x+y*y+z*z)
+       vp = ROOT.TLorentzVector(x,y,z,t)
+       p = PG.SampledParticle(pid=self.pid(),mom=v4,pos=vp)
+       #print "E,eta,phi,mass ",e,eta,phi,self.mass,"  position ",x,y,z," pid=",p.pid
+       rtn.append(p)
+       return rtn
+
+##MC15 style with Generate_tf.py
+#args=jofile.split('.py')[0]
+
+##MC16 style with Gen_tf.py 
+FIRST_DIR = (os.environ['JOBOPTSEARCHPATH']).split(":")[0]
+jofiles = [f for f in os.listdir(FIRST_DIR) if (f.startswith('mc') and f.endswith('.py'))]
+
+print "================ SETTTINGS ================="
+print ("jofiles     = ", jofiles)
+
+### parse options from MC job-options filename
+args = jofiles[0].split('.py')[0]
+print ("args     = ", args)
+
+myMomentum = float(args.split('_Mom')[1].split('_')[0])
+print ("Momentum = ", myMomentum,"MeV")
+
+myPDGID = int(float(args.split('_pid')[1].split('_')[0].replace('m','-')))
+print ("pid      = ", myPDGID)
+
+myLowEta  = 0.01*float(args.split('eta_')[1].split('_')[0].replace('m','-'))
+print ("etalow   = ", myLowEta)
+
+myHighEta = 0.01*float(args.split('eta_')[1].split('_')[1].replace('m','-'))
+print ("etahigh   = ", myHighEta)
+
+if "_Radius" in args:
+  myRadius = 0.001*float(args.split('_Radius')[1].split('_')[0]) #Argument needs to by in mum, since a "." in the filename is not allowed
+else: 
+  myRadius = 1500.
+print ("radius   = ", myRadius,"mm")
+
+if "_Z" in args:
+  myZ = 0.001*float(args.split('_Z')[1].split('_')[0]) #Argument needs to by in mum, since a "." in the filename is not allowed
+else:  
+  myZ = 3740.5
+print ("Z        = ", myZ,"mm")
+  
+if "bec" in args:
+  bec=1
+else:
+  bec=0
+print ("bec      = ", bec)
+print "============================================"
+
+genSeq += PG.ParticleGun()
+genSeq.ParticleGun.sampler = MyParticleSampler(momentum=myMomentum,eta1=myLowEta,eta2=myHighEta,pid=myPDGID,bec=bec,radius=myRadius,z=myZ)
+
diff --git a/Generators/ParticleGun/share/common/ParticleGun_SingleHECO.py b/Generators/ParticleGun/share/common/ParticleGun_SingleHECO.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ffb36dd28235948f0ec1d298c121902267f27d1
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_SingleHECO.py
@@ -0,0 +1,74 @@
+
+PDG = 10000000 + int(float(charge)*100.0)
+loE = (float(mass) + 10.)*1000.
+hiE  = (float(mass) + 6000.)*1000.	
+MeVmass=float(mass)*1000.
+#--------------------------------------------------------------
+# Configuration for EvgenJobTransforms
+#--------------------------------------------------------------
+evgenConfig.description = "Single HECO generation for Mass=%s, Charge=%s in MC15" % (mass,charge)
+evgenConfig.keywords = ["exotic", "singleParticle","highElectricChargeObject"]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["anlionti@cern.ch"]
+
+evgenConfig.specialConfig = 'MASS=%s;CHARGE=%s;preInclude=SimulationJobOptions/preInclude.Qball.py' % (mass,charge)
+
+
+
+#--------------------------------------------------------------
+# Configuration for ParticleGun
+#--------------------------------------------------------------
+include("ParticleGun/ParticleGun_Common.py")
+
+import ParticleGun as PG
+PG.MASSES[PDG] = float(MeVmass)
+genSeq.ParticleGun.sampler.pid = (-PDG, PDG)
+genSeq.ParticleGun.sampler.mom = PG.EEtaMPhiSampler(energy=[loE,hiE], eta=[-2,2])
+
+
+#--------------------------------------------------------------
+# Edit PDGTABLE.MeV with monopole mass
+#--------------------------------------------------------------
+ALINE1="M %s                          %s.E+03       +0.0E+00 -0.0E+00 Monopole        0" % (PDG,mass)
+ALINE2="W %s                          0.E+00         +0.0E+00 -0.0E+00 Monopole        0" % (PDG)
+
+import os
+import sys
+
+pdgmod = os.path.isfile('PDGTABLE.MeV')
+if pdgmod is True:
+    os.remove('PDGTABLE.MeV')
+os.system('get_files -data PDGTABLE.MeV')
+f=open('PDGTABLE.MeV','a')
+f.writelines(str(ALINE1))
+f.writelines('\n')
+f.writelines(str(ALINE2))
+f.writelines('\n')
+f.close()
+
+del ALINE1
+del ALINE2
+
+#--------------------------------------------------------------
+# Edit G4particle_whitelist.txt with monopole
+#--------------------------------------------------------------
+
+ALINE1="%s   qb  %s.E+03 (Mev/c) lepton %s" % (PDG,mass,charge)
+ALINE2="-%s  qbbar  %s.E+03 (Mev/c) lepton -%s" % (PDG,mass,charge)
+
+import os
+import sys
+
+pdgmod = os.path.isfile('G4particle_whitelist.txt')
+if pdgmod is True:
+    os.remove('G4particle_whitelist.txt')
+os.system('get_files -data G4particle_whitelist.txt')
+f=open('G4particle_whitelist.txt','a')
+f.writelines(str(ALINE1))
+f.writelines('\n')
+f.writelines(str(ALINE2))
+f.writelines('\n')
+f.close()
+
+del ALINE1
+del ALINE2
diff --git a/Generators/ParticleGun/share/common/ParticleGun_SingleMonopole.py b/Generators/ParticleGun/share/common/ParticleGun_SingleMonopole.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a6bf0574561af0f4cab4a20e68248dcc3111bae
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_SingleMonopole.py
@@ -0,0 +1,74 @@
+
+PDG = 4110000
+loE = (float(monmass) + 10.)*1000.
+hiE  = (float(monmass) + 6000.)*1000.
+MeVmass=float(monmass)*1000.
+#--------------------------------------------------------------
+# Configuration for EvgenJobTransforms
+#--------------------------------------------------------------
+evgenConfig.description = "Single magnetic monopole generation for Mass=%s, Gcharge=%s in MC15" % (monmass,gcharge)
+evgenConfig.keywords = ["exotic", "magneticMonopole", "singleParticle"]
+evgenConfig.generators = ["ParticleGun"]
+evgenConfig.contact = ["anlionti@cern.ch"]
+
+evgenConfig.specialConfig = 'MASS=%s;GCHARGE=%s;preInclude=SimulationJobOptions/preInclude.Monopole.py' % (monmass,gcharge)
+
+
+
+#--------------------------------------------------------------
+# Configuration for ParticleGun
+#--------------------------------------------------------------
+include("ParticleGun/ParticleGun_Common.py")
+
+import ParticleGun as PG
+PG.MASSES[4110000] = float(MeVmass)
+genSeq.ParticleGun.sampler.pid = (-PDG, PDG)
+genSeq.ParticleGun.sampler.mom = PG.EEtaMPhiSampler(energy=[loE,hiE], eta=[-2,2])
+
+
+#--------------------------------------------------------------
+# Edit PDGTABLE.MeV with monopole mass
+#--------------------------------------------------------------
+ALINE1="M 4110000                          %s.E+03       +0.0E+00 -0.0E+00 Monopole        0" % (monmass)
+ALINE2="W 4110000                          0.E+00         +0.0E+00 -0.0E+00 Monopole        0"
+
+import os
+import sys
+
+pdgmod = os.path.isfile('PDGTABLE.MeV')
+if pdgmod is True:
+    os.remove('PDGTABLE.MeV')
+os.system('get_files -data PDGTABLE.MeV')
+f=open('PDGTABLE.MeV','a')
+f.writelines(str(ALINE1))
+f.writelines('\n')
+f.writelines(str(ALINE2))
+f.writelines('\n')
+f.close()
+
+del ALINE1
+del ALINE2
+
+#--------------------------------------------------------------
+# Edit G4particle_whitelist.txt with monopole
+#--------------------------------------------------------------
+
+ALINE1="4110000   mm  %s.E+03 (Mev/c) lepton %s" % (monmass,gcharge)
+ALINE2="-4110000  mmbar  %s.E+03 (Mev/c) lepton -%s" % (monmass,gcharge)
+
+import os
+import sys
+
+pdgmod = os.path.isfile('G4particle_whitelist.txt')
+if pdgmod is True:
+    os.remove('G4particle_whitelist.txt')
+os.system('get_files -data G4particle_whitelist.txt')
+f=open('G4particle_whitelist.txt','a')
+f.writelines(str(ALINE1))
+f.writelines('\n')
+f.writelines(str(ALINE2))
+f.writelines('\n')
+f.close()
+
+del ALINE1
+del ALINE2
diff --git a/Generators/ParticleGun/share/common/ParticleGun_egammaET.py b/Generators/ParticleGun/share/common/ParticleGun_egammaET.py
new file mode 100644
index 0000000000000000000000000000000000000000..04ea9b92a3e209afc279d743307dff393f1d8839
--- /dev/null
+++ b/Generators/ParticleGun/share/common/ParticleGun_egammaET.py
@@ -0,0 +1,51 @@
+__doc__ = "Holds a 4-momentum sampler according to the egamma Et spectrum"
+
+import ParticleGun as PG
+from GaudiKernel.SystemOfUnits import GeV
+
+def dbnFermiDirac(x,mu,kT):
+   import math
+   arg = (x-mu)/kT
+   if arg < -20 :	# avoid numerical underflows
+      result = 1
+   elif arg > 20 :	# avoid numerical overflows
+      result = 0
+   else :
+      div = math.exp(arg)+1
+      result = 1/div
+   return result
+
+class egammaETSampler(PG.PtEtaMPhiSampler):
+  "4-momentum sampler according to the egamma Et spectrum."
+  def __init__(self, pid, eta=[-2.5, 2.5], phi=[0, PG.TWOPI],
+    mu1 = 0.5, kT1 = 0.1, mu2 = 200, kT2 = 20, y0 = 0.005, PtMin = 0 , PtMax = 3e3, nBins=None):
+    """ 
+    Parameters for the MVA-shaped spectrum : higher density in the < 100 GeV range
+    PtMin = 0 # minimum Pt
+    PtMax = 3000 # maximum Pt (3 TeV)
+    nBins # number of bins (one every 100 MeV by default)
+    mu1 = 0.5		# mu1,kT1 : smooth but steep ramp-up from 0 to 1 GeV (requested by TauCP)
+    kT1 = 0.1
+    mu2 = 200		# mu2,kT2 : smooth, slow ramp-down in the 100-300 GeV range
+    kT2 = 20
+    y0 = 0.005		# y0 : baseline for low-density at high ET up to PtMax
+    """
+    self.m = PG.MASSES[abs(pid)]
+    self.eta = eta
+    self.phi = phi
+    
+    # Create and fill a very fine-grained histogram 
+    from ROOT import TH1D
+    etSpectrumFullRange = TH1D("ETSpectrumFullRange", 
+                               "Reference ET spectrum for egamma MVA calib",
+                               int(nBins or (PtMax - PtMin)*10), PtMin , PtMax)
+    for i in xrange(etSpectrumFullRange.GetNbinsX()):
+         x = etSpectrumFullRange.GetBinCenter(i+1)
+         y1 = dbnFermiDirac(x,mu1,kT1)
+         y2 = dbnFermiDirac(x,mu2,kT2)
+         y = y0 - y1 + y2
+         etSpectrumFullRange.SetBinContent(i+1,y)
+    self.hist = PG.TH1(etSpectrumFullRange) #< wrap *after* populating
+  
+  def pt(self):
+    return self.hist.GetRandom() * GeV
diff --git a/Generators/ParticleGun/share/examples/jobOption.ParticleGun_constenergy_flateta.py b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_constenergy_flateta.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c3cb0321b37e7f3d5ec3fcfc2602b2f1cba2cb0
--- /dev/null
+++ b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_constenergy_flateta.py
@@ -0,0 +1,17 @@
+#! -*- python -*-
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+include("GeneratorUtils/StdEvgenSetup.py")
+theApp.EvtMax = 100
+
+import ParticleGun as PG
+pg = PG.ParticleGun()
+pg.randomSeed = 123456
+pg.sampler.pid = {11,-11,211,111}
+pg.sampler.mom = PG.EEtaMPhiSampler(energy=10000, eta=[-2,2])
+topSeq += pg
+
+include("GeneratorUtils/postJO.CopyWeights.py")
+include("GeneratorUtils/postJO.PoolOutput.py")
+include("GeneratorUtils/postJO.DumpMC.py")
diff --git a/Generators/ParticleGun/share/examples/jobOption.ParticleGun_correlated.py b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_correlated.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb0a9437388d3b21fffd744c67cfe590dbddaf4e
--- /dev/null
+++ b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_correlated.py
@@ -0,0 +1,34 @@
+#! -*- python -*-
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+include("GeneratorUtils/StdEvgenSetup.py")
+theApp.EvtMax = 100
+
+import ParticleGun as PG
+
+class MyParticleSampler(PG.ParticleSampler):
+    "A special sampler with two _correlated_ particles."
+
+    def __init__(self):
+        self.mom1 = PG.PtEtaMPhiSampler(pt=25000, eta=[-2,2])
+
+    def shoot(self):
+        "Return a vector of sampled particles"
+        p1 = PG.SampledParticle(11, self.mom1.shoot())
+        eta1 = p1.mom.Eta()
+        phi1 = p1.mom.Phi()
+        # TODO: will phi be properly wrapped into range?
+        mom2 = PG.PtEtaMPhiSampler(pt=25000,
+                                   eta=[eta1-0.5, eta1+0.5],
+                                   phi=[phi1-0.5, phi1+0.5])
+        p2 = PG.SampledParticle(11, mom2.shoot())
+        return [p1, p2]
+
+topSeq += PG.ParticleGun()
+topSeq.ParticleGun.randomSeed = 123456
+topSeq.ParticleGun.sampler = MyParticleSampler()
+
+include("GeneratorUtils/postJO.CopyWeights.py")
+include("GeneratorUtils/postJO.PoolOutput.py")
+include("GeneratorUtils/postJO.DumpMC.py")
diff --git a/Generators/ParticleGun/share/examples/jobOption.ParticleGun_corrhist.py b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_corrhist.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c0cd41b88e35f0290b137e5eebe80296a90b3ca
--- /dev/null
+++ b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_corrhist.py
@@ -0,0 +1,41 @@
+#! -*- python -*-
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+## ROOT 2D histogram sampling alg (in ParticleGun.histsampling) by Andy Buckley
+## Thanks to Alejandro Alonso for the initial Athena example on which this is based.
+
+include("GeneratorUtils/StdEvgenSetup.py")
+theApp.EvtMax = 100
+
+import ParticleGun as PG
+
+class PtEtaHistParticleSampler(PG.ParticleSampler):
+    "Particle sampler with correlated pT and eta from a 2D histogram."
+
+    def __init__(self, pid, histfile, num=100):
+        self.pid = PG.mksampler(pid)
+        self.hist = PG.TH2(histfile, "h_pt_eta")
+        self.numparticles = num
+
+    def shoot(self):
+        "Return a vector of sampled particles from the provided pT--eta histogram"
+        particles = []
+        for i in xrange(self.numparticles):
+            ptrand, etarand = self.hist.GetRandom()
+            ptrand *= 1000 # NB. This _particular_ histogram is in GeV, but Athena needs MeV!
+            # TODO: Provide 4-mom construction functions to avoid building this one-time sampler
+            pid = self.pid()
+            mom = PG.PtEtaMPhiSampler(pt=ptrand, eta=etarand, mass=PG.MASSES[abs(pid)])
+            p = PG.SampledParticle(pid, mom())
+            #print p.mom.Pt(), "\t", p.mom.Eta(), "\t", p.mom.Phi(), "\t", p.mom.M()
+            particles.append(p)
+        return particles
+
+topSeq += PG.ParticleGun()
+topSeq.ParticleGun.randomSeed = 123456
+topSeq.ParticleGun.sampler = PtEtaHistParticleSampler(11, "data_histos_el_1470pt.root")
+
+include("GeneratorUtils/postJO.CopyWeights.py")
+include("GeneratorUtils/postJO.PoolOutput.py")
+include("GeneratorUtils/postJO.DumpMC.py")
diff --git a/Generators/ParticleGun/share/examples/jobOption.ParticleGun_flatcurvature_flatip.py b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_flatcurvature_flatip.py
new file mode 100644
index 0000000000000000000000000000000000000000..38233563de9673b1d9ebd1ae434b57f6051ae70e
--- /dev/null
+++ b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_flatcurvature_flatip.py
@@ -0,0 +1,41 @@
+#! -*- python -*-
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+include("GeneratorUtils/StdEvgenSetup.py")
+theApp.EvtMax = 100
+
+import ParticleGun as PG
+
+class MyParticleSampler(PG.ParticleSampler):
+    """
+    A special sampler to generate single particles flat in 1/pT and in
+    impact parameter to the beam, with flat z0.
+    """
+
+    def __init__(self):
+        psamp = PG.PtEtaMPhiSampler(pt=PG.InvSampler(4000, 400000), eta=[0.1,0.3], phi=[0.3, 0.5])
+        xsamp = PG.PosSampler(0, 0, [-150,150], 0)
+        PG.ParticleSampler.__init__(self, pid={13,-13}, mom=psamp, pos=xsamp)
+        self.ip = PG.mksampler([-2,2])
+
+    def shoot(self):
+        "Return a vector of sampled particles"
+        ps = PG.ParticleSampler.shoot(self)
+        assert len(ps) == 1
+        p = ps[0]
+        from math import sqrt
+        m = -p.mom.X() / p.mom.Y() #< gradient of azimuthal IP sampling line, perp to mom
+        x = self.ip() / sqrt(1 + m**2) #< just decomposing sampled IP into x component...
+        y = m*x #< ... and y-component
+        p.pos.SetX(x)
+        p.pos.SetY(m*x)
+        return [p]
+
+topSeq += PG.ParticleGun()
+topSeq.ParticleGun.randomSeed = 123456
+topSeq.ParticleGun.sampler = MyParticleSampler()
+
+include("GeneratorUtils/postJO.CopyWeights.py")
+include("GeneratorUtils/postJO.PoolOutput.py")
+include("GeneratorUtils/postJO.DumpMC.py")
diff --git a/Generators/ParticleGun/share/examples/jobOption.ParticleGun_flatpt_2particle.py b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_flatpt_2particle.py
new file mode 100644
index 0000000000000000000000000000000000000000..97ed64f8857e82dbcaeb06085fc84d84eb246c0b
--- /dev/null
+++ b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_flatpt_2particle.py
@@ -0,0 +1,20 @@
+#! -*- python -*-
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+include("GeneratorUtils/StdEvgenSetup.py")
+theApp.EvtMax = 100
+
+import ParticleGun as PG
+pg = PG.ParticleGun()
+pg.randomSeed = 123456
+pg.samplers.append(PG.ParticleSampler()) # add a second sampler
+pg.samplers[0].pid = (-13, 13) # cycle mu+-
+pg.samplers[0].mom = PG.PtEtaMPhiSampler(pt=[4000, 100000], eta=[1.0, 3.2]) # flat in pt and +ve eta
+pg.samplers[1].pid = (13, -13) # cycle mu-+
+pg.samplers[1].mom = PG.PtEtaMPhiSampler(pt=[4000, 100000], eta=[-3.2, -1.0]) # flat in pt and -ve eta
+topSeq += pg
+
+include("GeneratorUtils/postJO.CopyWeights.py")
+include("GeneratorUtils/postJO.PoolOutput.py")
+include("GeneratorUtils/postJO.DumpMC.py")
diff --git a/Generators/ParticleGun/share/examples/jobOption.ParticleGun_fwd_sequence.py b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_fwd_sequence.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1d4746fb7b7002fb8e096058c1b2b5d1c4eb348
--- /dev/null
+++ b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_fwd_sequence.py
@@ -0,0 +1,19 @@
+#! -*- python -*-
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+include("GeneratorUtils/StdEvgenSetup.py")
+theApp.EvtMax = 100
+
+import ParticleGun as PG
+pg = PG.ParticleGun()
+pg.randomSeed = 123456
+pg.sampler.pid = (2112, 22, 2112, 22)
+pg.sampler.mom = PG.EThetaMPhiSampler(energy=(1360000, 500000, 1360000, 500000),
+                                      theta=(0, 0, PG.PI, PG.PI))
+pg.sampler.pos = PG.PosSampler(x=[-120,-100], y=[-10,10], z=203950)
+topSeq += pg
+
+include("GeneratorUtils/postJO.CopyWeights.py")
+include("GeneratorUtils/postJO.PoolOutput.py")
+include("GeneratorUtils/postJO.DumpMC.py")
diff --git a/Generators/ParticleGun/share/examples/jobOption.ParticleGun_vtx.py b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_vtx.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdacb7ff5a678cb95894a39a14f0ead507c41a4b
--- /dev/null
+++ b/Generators/ParticleGun/share/examples/jobOption.ParticleGun_vtx.py
@@ -0,0 +1,18 @@
+#! -*- python -*-
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+include("GeneratorUtils/StdEvgenSetup.py")
+theApp.EvtMax = 100
+
+import ParticleGun as PG
+pg = PG.ParticleGun()
+pg.randomSeed = 123456
+pg.sampler.pid = 13
+pg.sampler.pos = PG.PosSampler(x=3140.0, y=[-154.134,154.134], z=[4938.76,5121.29], t=5929.7)
+pg.sampler.mom = PG.EEtaMPhiSampler(energy=100000, eta=1.25, phi=0.0)
+topSeq += pg
+
+include("GeneratorUtils/postJO.CopyWeights.py")
+include("GeneratorUtils/postJO.PoolOutput.py")
+include("GeneratorUtils/postJO.DumpMC.py")
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.cxx b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.cxx
index eae5fd1e07a8e70ec9bcec697d3deeb4da41d4d0..4f3a48084e7c3e7e2357844508fed2c17ac8de46 100644
--- a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.cxx
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.cxx
@@ -80,6 +80,8 @@ EmulsionDataBase::EmulsionDataBase(const EmulsionGeoModelAthenaComps * athenaCom
   m_emulsionPlates = rdbSvc->getRecordsetPtr("EmulsionPlates", versionTag, versionNode, "FASERDD");
   msg(MSG::DEBUG) << "Table EmulsionPlates Fetched" << endmsg;
 
+  m_emulsionSupport = rdbSvc->getRecordsetPtr("EmulsionSupport", versionTag, versionNode, "FASERDD");
+  msg(MSG::DEBUG) << "Table EmulsionSupport Fetched" << endmsg;
 }
 
 const EmulsionGeoModelAthenaComps* EmulsionDataBase::athenaComps() const { return m_athenaComps; }
@@ -91,6 +93,8 @@ IRDBRecordset_ptr EmulsionDataBase::scalingTable() const {return m_scalingTable;
 // //const IRDBRecord* EmulsionDataBase::atls() const {return *m_atls)[0];}  
 IRDBRecordset_ptr EmulsionDataBase::topLevelTable() const {return m_topLevel;}
 
+IRDBRecordset_ptr EmulsionDataBase::emulsionSupportTable() const {return m_emulsionSupport;}
+
 // IRDBRecordset_ptr EmulsionDataBase::conditionsTable() const {return m_conditions;}
 // const IRDBRecord* EmulsionDataBase::conditions() const {return (*m_conditions)[0];}
 
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.h b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.h
index 35bf120c7a04cfe5ecaa88effd524785642271a2..879a5e01012fb257d283c568c0688eb4557fbe19 100644
--- a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.h
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDataBase.h
@@ -25,6 +25,7 @@ public:
   IRDBRecordset_ptr weightTable() const;
   IRDBRecordset_ptr scalingTable() const;
   IRDBRecordset_ptr topLevelTable() const;
+  IRDBRecordset_ptr emulsionSupportTable() const;
 
   const IRDBRecord* emulsionGeneral() const;
   const IRDBRecord* emulsionFilm() const;
@@ -54,6 +55,7 @@ private:
   IRDBRecordset_ptr m_emulsionGeneral;
   IRDBRecordset_ptr m_emulsionFilm;
   IRDBRecordset_ptr m_emulsionPlates;
+  IRDBRecordset_ptr m_emulsionSupport;
 
 
 };
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDetectorFactory.cxx b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDetectorFactory.cxx
index 25fc89125f7974ef00f89ae2899a67a7b68d2551..30cb89edb936f0d61397ac6bc6522d0a7636d07d 100644
--- a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDetectorFactory.cxx
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionDetectorFactory.cxx
@@ -14,6 +14,7 @@
 #include "EmulsionGeometryManager.h" 
 #include "EmulsionMaterialManager.h"
 #include "EmulsionGeneralParameters.h"
+#include "EmulsionSupportParameters.h"
 #include "NeutrinoReadoutGeometry/Version.h" 
 #include "NeutrinoReadoutGeometry/NeutrinoCommonItems.h" 
 #include "NeutrinoReadoutGeometry/NeutrinoDD_Defs.h"
@@ -143,13 +144,16 @@ void EmulsionDetectorFactory::create(GeoPhysVol *world)
   // The tree tops get added to world. We name it "neutrino" though.
   GeoPhysVol *neutrino = world;
 
+  // const EmulsionSupportParameters* emulsionSupport = m_geometryManager->supportParameters();
+  // msg(MSG::ALWAYS) << "Found " << emulsionSupport->supportElements().size() << " emulsion support elements" << endmsg;
+
   const EmulsionGeneralParameters * emulsionGeneral = m_geometryManager->generalParameters();
 
   GeoTrf::Transform3D emulsionTransform = emulsionGeneral->partTransform("Emulsion");
 
-    std::string stationA_Label = "StationA";
+  std::string stationA_Label = "StationA";
 
-    bool stationA_Present = emulsionGeneral->partPresent(stationA_Label);
+  bool stationA_Present = emulsionGeneral->partPresent(stationA_Label);
 
   //
   //  Plate is the same for all stations
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.cxx b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.cxx
index 679678fba793dddd06cb3b2e13169f57a08a1b0d..dca5f2d2ad0b16f977f58026d9e86854dcc0ec4c 100644
--- a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.cxx
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.cxx
@@ -11,6 +11,7 @@
 #include "EmulsionPlatesParameters.h"
 #include "EmulsionDataBase.h"
 #include "EmulsionGeneralParameters.h"
+#include "EmulsionSupportParameters.h"
 #include "EmulsionGeoModel/EmulsionGeoModelAthenaComps.h"
 
 EmulsionGeometryManager::EmulsionGeometryManager(EmulsionDataBase* rdb)
@@ -23,6 +24,7 @@ EmulsionGeometryManager::EmulsionGeometryManager(EmulsionDataBase* rdb)
   m_filmParameters = std::make_unique<EmulsionFilmParameters>(m_rdb);
   m_platesParameters = std::make_unique<EmulsionPlatesParameters>(m_rdb);
   m_generalParameters = std::make_unique<EmulsionGeneralParameters>(m_rdb);
+  m_supportParameters = std::make_unique<EmulsionSupportParameters>(m_rdb);
   m_distortedMatManager = std::make_unique<NeutrinoDD::DistortedMaterialManager>();
 }
 
@@ -79,6 +81,12 @@ EmulsionGeometryManager::generalParameters() const
   return m_generalParameters.get();
 }
 
+const EmulsionSupportParameters *
+EmulsionGeometryManager::supportParameters() const
+{
+  return m_supportParameters.get();
+}
+
 const NeutrinoDD::DistortedMaterialManager * 
 EmulsionGeometryManager::distortedMatManager() const
 {    
@@ -95,6 +103,7 @@ EmulsionGeometryManager::operator=(const EmulsionGeometryManager& right) {
     m_filmParameters.reset(new EmulsionFilmParameters(m_rdb));
     m_platesParameters.reset(new EmulsionPlatesParameters(m_rdb));
     m_generalParameters.reset(new EmulsionGeneralParameters(m_rdb));
+    m_supportParameters.reset(new EmulsionSupportParameters(m_rdb));
     m_distortedMatManager.reset(new NeutrinoDD::DistortedMaterialManager());
   }
   return *this;
@@ -108,5 +117,6 @@ EmulsionGeometryManager::EmulsionGeometryManager(const EmulsionGeometryManager&
   m_filmParameters.reset(new EmulsionFilmParameters(m_rdb));
   m_platesParameters.reset(new EmulsionPlatesParameters(m_rdb));
   m_generalParameters.reset(new EmulsionGeneralParameters(m_rdb));
+  m_supportParameters.reset(new EmulsionSupportParameters(m_rdb));
   m_distortedMatManager.reset(new NeutrinoDD::DistortedMaterialManager());
 }
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.h b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.h
index a068602e658925ba5a363bd571d0486fa08a04ec..989520e43084b873c843cebc5f0e01546f375a8d 100644
--- a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.h
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionGeometryManager.h
@@ -18,6 +18,7 @@ class EmulsionFilmParameters;
 class EmulsionPlatesParameters;
 class EmulsionDataBase;
 class EmulsionGeneralParameters;
+class EmulsionSupportParameters;
 class EmulsionGeoModelAthenaComps;
 
 class EmulsionGeometryManager {
@@ -43,6 +44,7 @@ public:
   const EmulsionFilmParameters*               filmParameters() const;
   const EmulsionPlatesParameters*             platesParameters() const;
   const EmulsionGeneralParameters*            generalParameters() const;
+  const EmulsionSupportParameters*            supportParameters() const;
   const NeutrinoDD::DistortedMaterialManager* distortedMatManager() const;
 
   EmulsionGeometryManager& operator=(const EmulsionGeometryManager& right);
@@ -58,6 +60,7 @@ private:
   std::unique_ptr<EmulsionFilmParameters> m_filmParameters;
   std::unique_ptr<EmulsionPlatesParameters> m_platesParameters;
   std::unique_ptr<EmulsionGeneralParameters> m_generalParameters;
+  std::unique_ptr<EmulsionSupportParameters> m_supportParameters;
   std::unique_ptr<NeutrinoDD::DistortedMaterialManager> m_distortedMatManager;
 
 };
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.cxx b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.cxx
index 7424987e17929d371002aa7f778e7a2a0ae08b06..5b97b1a1ca65476d0c61d285c549b675a44e24bf 100644
--- a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.cxx
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.cxx
@@ -40,23 +40,26 @@ EmulsionPlates::EmulsionPlates(const std::string & name,
   m_logVolume = preBuild();
 }
 
-
 void
 EmulsionPlates::getParameters()
 {
   const EmulsionGeneralParameters * generalParameters = m_geometryManager->generalParameters();
   const EmulsionPlatesParameters *   platesParameters = m_geometryManager->platesParameters();
+  const EmulsionSupportParameters*  supportParameters = m_geometryManager->supportParameters();
 
-  m_width = platesParameters->platesWidth();
-  m_height = platesParameters->platesHeight();
-  m_thickness = platesParameters->platesThickness();
-  m_material = m_materials->getMaterial(platesParameters->platesMaterial());
+  m_tungstenWidth = platesParameters->platesWidth();
+  m_tungstenHeight = platesParameters->platesHeight();
+  m_tungstenThickness = platesParameters->platesThickness();
+  m_absorberMaterial = m_materials->getMaterial(platesParameters->platesMaterial());
+  m_airMaterial = m_materials->gasMaterial();
 
   m_nBasesPerModule = generalParameters->nBasesPerModule();
   m_nModules       = generalParameters->nModules();
   m_firstBaseZ     = generalParameters->firstBaseZ();
   m_lastBaseZ      = generalParameters->lastBaseZ();
 
+  m_supportElements = supportParameters->supportElements();
+
   m_detectorManager->numerology().setNumBasesPerModule(m_nBasesPerModule);
 }
 
@@ -66,12 +69,36 @@ EmulsionPlates::preBuild()
   // create child element
   m_base = new EmulsionBase("Base", m_detectorManager, m_geometryManager, m_materials);
 
+  double width = m_tungstenWidth/2;
+  double height = m_tungstenHeight/2;
+  // double thickness = m_tungstenThickness/2;
+  double minZ = -m_tungstenThickness/2;
+  double maxZ = m_tungstenThickness/2;
+
+  for (auto e : m_supportElements)
+  {
+    width = std::max(width, std::max(std::abs(e.x + e.dx/2),std::abs(e.x - e.dx/2)));
+    height = std::max(height, std::max(std::abs(e.y + e.dy/2),std::abs(e.y - e.dy/2)));
+    minZ = std::min(minZ, e.z - e.dz/2);
+    maxZ = std::max(maxZ, e.z + e.dz/2);
+    const GeoBox* supportShape = new GeoBox(e.dx/2, e.dy/2, e.dz/2);
+    GeoLogVol* supportLog = new GeoLogVol(e.label, supportShape, m_materials->getMaterial(e.mat));
+    m_supportVolumes.push_back(supportLog);
+  }
+
+  m_width = 2 * width;
+  m_height = 2 * height;
+  m_thickness = maxZ - minZ;
+  m_zShift = -(maxZ + minZ)/2;
 
   // Build a box to hold everything
   const GeoBox* platesShape = new GeoBox(0.5*m_width, 0.5*m_height, 0.5*m_thickness);
 
   // GeoLogVol * platesLog = new GeoLogVol(getName(), platesShape, m_materials->gasMaterial());
-  GeoLogVol * platesLog = new GeoLogVol(getName(), platesShape, m_material);
+  GeoLogVol * platesLog = new GeoLogVol(getName(), platesShape, m_airMaterial);
+
+  const GeoBox* absorberShape = new GeoBox(m_tungstenWidth/2, m_tungstenHeight/2, m_tungstenThickness/2);
+  m_absorberVolume = new GeoLogVol(getName()+"_absorber", absorberShape, m_absorberMaterial);
 
   // m_baseboardPos = new GeoTrf::Translate3D(0.0, 0.0, 0.0);
   // m_frontPos     = new GeoTrf::Translate3D(0.0, 0.0, -(m_baseThickness + m_filmThickness)/2);
@@ -83,7 +110,12 @@ EmulsionPlates::preBuild()
 GeoVPhysVol * 
 EmulsionPlates::build(EmulsionIdentifier id)
 {
-  GeoFullPhysVol * plates = new GeoFullPhysVol(m_logVolume); 
+  GeoFullPhysVol * plates = new GeoFullPhysVol(m_logVolume);
+  GeoPhysVol * physAbsorber = new GeoPhysVol(m_absorberVolume);
+  GeoAlignableTransform* absorberTransform = new GeoAlignableTransform( GeoTrf::Translate3D { 0.0, 0.0, m_zShift} );
+  plates->add(absorberTransform);
+  plates->add(new GeoNameTag("Absorber"));
+  plates->add(physAbsorber);
   
   int nBases = 0;
   int nBasesTotal = m_nModules * m_nBasesPerModule;
@@ -97,16 +129,33 @@ EmulsionPlates::build(EmulsionIdentifier id)
     for (int base = 0; base < m_nBasesPerModule; base++)
     {
       id.setBase(base);
+      // GeoAlignableTransform* theTransform = new GeoAlignableTransform( GeoTrf::Translate3D {0.0, 0.0, m_zShift + m_firstBaseZ + ((m_lastBaseZ - m_firstBaseZ)/(nBasesTotal-1))*nBases++} );
+      // plates->add(theTransform);
+      // plates->add(new GeoNameTag("Base#"+intToString(module*100 + base)));
+      // plates->add(new GeoIdentifierTag(module*100 + base));
+      // GeoVPhysVol* physBase = m_base->build(id);
+      // plates->add(physBase);
       GeoAlignableTransform* theTransform = new GeoAlignableTransform( GeoTrf::Translate3D {0.0, 0.0, m_firstBaseZ + ((m_lastBaseZ - m_firstBaseZ)/(nBasesTotal-1))*nBases++} );
-      plates->add(theTransform);
-      plates->add(new GeoNameTag("Base#"+intToString(module*100 + base)));
-      plates->add(new GeoIdentifierTag(module*100 + base));
+      physAbsorber->add(theTransform);
+      physAbsorber->add(new GeoNameTag("Base#"+intToString(module*100 + base)));
+      physAbsorber->add(new GeoIdentifierTag(module*100 + base));
       GeoVPhysVol* physBase = m_base->build(id);
-      plates->add(physBase);
+      physAbsorber->add(physBase);
       m_detectorManager->addAlignableTransform(1, id.getFilmId(), theTransform, physBase);
     }
     m_detectorManager->numerology().useModule(module);
   }
 
+  auto iv = m_supportVolumes.begin();
+  for (auto e : m_supportElements)
+  {
+    GeoVPhysVol* pv = new GeoPhysVol(*iv);
+    GeoTransform* theTransform = new GeoTransform( GeoTrf::Translate3D {e.x, e.y, e.z + m_zShift});
+    plates->add(theTransform);
+    plates->add(new GeoNameTag(e.label));
+    plates->add(pv);
+    iv++;
+  }
+
   return plates;
 }
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.h b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.h
index b827265b9f0753fd834f24d0d051eb02ba2a5675..7af04358b41879a5f23155d0367a5c6d4f137b09 100644
--- a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.h
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionPlates.h
@@ -6,6 +6,7 @@
 #define EMULSIONGEOMODEL_EMULSIONPLATES_H
 
 #include "EmulsionComponentFactory.h"
+#include "EmulsionSupportParameters.h"
 #include "EmulsionBase.h"
 #include "GeoPrimitives/GeoPrimitives.h"
 #include "GeoModelKernel/GeoDefinitions.h"
@@ -28,6 +29,7 @@ public:
   double thickness() const {return m_thickness;}
   double width()     const {return m_width;}
   double height()    const {return m_height;}
+  double shift()     const {return m_zShift;}
 
   virtual GeoVPhysVol * build(EmulsionIdentifier id);
   
@@ -38,14 +40,23 @@ private:
   double m_thickness;
   double m_width;
   double m_height;
-  const GeoMaterial * m_material;
+  double m_tungstenThickness;
+  double m_tungstenWidth;
+  double m_tungstenHeight;
+  double m_zShift;
+  const GeoMaterial * m_absorberMaterial;
+  const GeoMaterial * m_airMaterial;
 
   int m_nModules;
   int m_nBasesPerModule;
   double m_firstBaseZ;
   double m_lastBaseZ;
 
+  std::vector<EmulsionSupportParameters::SupportElement> m_supportElements;
+
   EmulsionBase* m_base;
+  GeoLogVol* m_absorberVolume;
+  std::vector<GeoLogVol*> m_supportVolumes;
 
 };
 
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionSupportParameters.cxx b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionSupportParameters.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..7ed41a0050ee15c99c6dc4f840cce3e572ee663e
--- /dev/null
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionSupportParameters.cxx
@@ -0,0 +1,45 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "EmulsionSupportParameters.h"
+#include "EmulsionGeometryManager.h"
+
+#include "EmulsionDataBase.h"
+
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+#include <cmath>
+
+
+EmulsionSupportParameters::EmulsionSupportParameters(EmulsionDataBase* rdb)
+{
+  m_rdb = rdb;
+}
+
+std::vector<EmulsionSupportParameters::SupportElement>
+EmulsionSupportParameters::supportElements() const
+{
+  std::vector<SupportElement> result;
+  IRDBRecordset_ptr table = m_rdb->emulsionSupportTable();
+  if (table.get() == nullptr) return result;
+
+  size_t numElements = table->size();
+  for (size_t i = 0; i < numElements; i++)
+  {
+    const IRDBRecord* element = (*table)[i];
+    result.push_back(SupportElement(element->getDouble("DX"),
+                                    element->getDouble("DY"),
+                                    element->getDouble("DZ"),
+                                    element->getDouble("X"),
+                                    element->getDouble("Y"),
+                                    element->getDouble("Z"),
+                                    element->getString("MATERIAL"),
+                                    element->getString("LABEL")));
+  }
+
+  return result;
+
+}
\ No newline at end of file
diff --git a/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionSupportParameters.h b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionSupportParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..b357bfcb02fcbf06b90db67d2e26a7b16254287f
--- /dev/null
+++ b/Neutrino/NeutrinoDetDescr/EmulsionGeoModel/src/EmulsionSupportParameters.h
@@ -0,0 +1,46 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef EmulsionGeoModel_EmulsionSupportParameters_H
+#define EmulsionGeoModel_EmulsionSupportParameters_H
+
+#include <string>
+#include <vector>
+
+class EmulsionDataBase;
+
+class EmulsionSupportParameters {
+
+public:
+
+  // Constructor 
+  EmulsionSupportParameters(EmulsionDataBase* rdb);
+
+
+  class SupportElement
+  {
+    public:
+      SupportElement(double width, double height, double thickness, double xPos, double yPos, double zPos, std::string material, std::string name)
+      : dx{width}, dy{height}, dz{thickness}, x{xPos}, y{yPos}, z{zPos}, mat{material}, label{name} {}
+      double dx;
+      double dy;
+      double dz;
+      double x;
+      double y;
+      double z;
+      std::string mat;
+      std::string label;
+  };
+
+  // General
+
+  std::vector<SupportElement> supportElements() const;  
+
+ private:
+  EmulsionDataBase * m_rdb;
+
+};
+
+
+#endif // EmulsionGeoModel_EmulsionSupportParameters_H
diff --git a/README.md b/README.md
index 0b334b19690dd56d1a0bae23fd5c0849c1cd83dd..2d7e57abb75b58112939efb7b2726607bcfb6ec3 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ source ./setup.sh
 
 Don't omit the dot in the `source ./setup.sh` command! 
 
-It can be convenient to alias the "asetup --input=calypso/asetup.faser" to something like "fsetup"
+It can be convenient to alias the command "asetup --input=calypso/asetup.faser" to something like "fsetup"
 
 **Lxplus/afs setup**
 
@@ -44,12 +44,12 @@ When compiling, CERN IT recommends using condor to submit batch jobs. The basics
 
 * It is now essential to use the tags `ConfigFlags.GeoModel.FaserVersion` and `ConfigFlags.IOVDb.GlobalTag` in a consistent way.  If nothing is specified the first option (baseline) should be chosen by default.
 
-** `ConfigFlags.GeoModel.FaserVersion = "FASER-01"` and `ConfigFlags.IOVDb.GlobalTag = OFLCOND-FASER-01` enables the baseline TI-12 detector
+** `ConfigFlags.GeoModel.FaserVersion = "FASER-01"` and `ConfigFlags.IOVDb.GlobalTag = OFLCOND-FASER-01` enables the baseline TI-12 detector (obsolete and no longer supported)
 
-** `ConfigFlags.GeoModel.FaserVersion = "FASER-02"` and `ConfigFlags.IOVDb.GlobalTag = OFLCOND-FASER-02` enables the interface tracker and repositioned Veto
+** `ConfigFlags.GeoModel.FaserVersion = "FASER-02"` and `ConfigFlags.IOVDb.GlobalTag = OFLCOND-FASER-02` enables the interface tracker and repositioned Veto (obsolete and no longer supported)
 
-** `ConfigFlags.GeoModel.FaserVersion = "FASERNU-02"` and `ConfigFlags.IOVDb.GlobalTag = OFLCOND-FASER-02` enables the full FaserNu (IFT + emulsion) setup
+** `ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"` and `ConfigFlags.IOVDb.GlobalTag = OFLCOND-FASER-02` enables the full FaserNu (IFT + emulsion) setup
 
 ** `ConfigFlags.GeoModel.FaserVersion = "FASER-TB00"` and `ConfigFlags.IOVDb.GlobalTag = OFLCOND-FASER-TB00` enables the 2021 Test-beam setup.
 
-* The command `lsetup "lcgenv -p LCG_98python3_ATLAS_8 x86_64-centos7-gcc8-opt sqlite"` may be necessary to avoid errors when generating a database
+* The command `source /cvmfs/sft.cern.ch/lcg/releases/LCG_101_ATLAS_6/sqlite/3320300/x86_64-centos7-gcc11-opt/sqlite-env.sh` may be necessary to avoid errors when generating a database
diff --git a/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator.xml b/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator.xml
index 13d77e21372822bd19cbdb4c5b7ae3046d27693f..54552a212ae62ae33650c186ea6529c383524518 100644
--- a/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator.xml
+++ b/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator.xml
@@ -4,6 +4,7 @@
     <label name="Veto" value="1" />
     <label name="Trigger" value="2" />
     <label name="Preshower" value="3" />
+    <label name="VetoNu" value="4" />
   </field>
 
   <region>
@@ -26,4 +27,12 @@
     <range field="plate" minvalue="0" maxvalue="1" />
     <range field="pmt" minvalue="0" maxvalue="0" />
   </region>
-</IdDictionary>
\ No newline at end of file
+
+  <region>
+    <range field="part" value="VetoNu" />
+    <range field="station" minvalue="0" maxvalue="0" />
+    <range field="plate" minvalue="0" maxvalue="1" />
+    <range field="pmt" minvalue="0" maxvalue="0" />
+  </region>
+
+</IdDictionary>
diff --git a/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator_TB00.xml b/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator_TB00.xml
index a4b3f0c98f58f58bf64ef490bf6d6f446bb0256b..71fd8298645674dae3bd0d54c411d099742a6416 100644
--- a/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator_TB00.xml
+++ b/Scintillator/ScintDetDescr/ScintIdDictFiles/data/IdDictScintillator_TB00.xml
@@ -4,8 +4,8 @@
     <label name="Veto" value="1" />
     <label name="Trigger" value="2" />
     <label name="Preshower" value="3" />
+    <label name="VetoNu" value="4" />
   </field>
-
   <region>
     <range field="part" value="Veto" />
     <range field="station" minvalue="0" maxvalue="0" />
@@ -24,4 +24,10 @@
     <range field="plate" minvalue="0" maxvalue="1" />
     <range field="pmt" minvalue="0" maxvalue="0" />
   </region>
-</IdDictionary>
\ No newline at end of file
+  <region>
+    <range field="part" value="VetoNu" />
+    <range field="station" minvalue="0" maxvalue="0" />
+    <range field="plate" minvalue="0" maxvalue="1" />
+    <range field="pmt" minvalue="0" maxvalue="0" />
+  </region>
+</IdDictionary>
diff --git a/Scintillator/ScintDetDescr/ScintIdentifier/CMakeLists.txt b/Scintillator/ScintDetDescr/ScintIdentifier/CMakeLists.txt
index 6dc9408f8ffb04b6a32144d7d03dc338ab9ad8bb..5874ae0de68035f16d71d47887a95cdfb8f857a6 100644
--- a/Scintillator/ScintDetDescr/ScintIdentifier/CMakeLists.txt
+++ b/Scintillator/ScintDetDescr/ScintIdentifier/CMakeLists.txt
@@ -11,6 +11,7 @@ find_package( ROOT COMPONENTS Core Tree MathCore Hist RIO pthread )
 # Component(s) in the package:
 atlas_add_library( ScintIdentifier
                    src/VetoID.cxx
+		   src/VetoNuID.cxx
                    src/TriggerID.cxx
                    src/PreshowerID.cxx
 #                   src/ScintillatorID.cxx
diff --git a/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/ScintIdentifierDict.h b/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/ScintIdentifierDict.h
index 9f3832c16e73859facfe81aa17f4864cf63a9b19..0968f0b4136d48216bcea7305e4933dac7337e2d 100644
--- a/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/ScintIdentifierDict.h
+++ b/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/ScintIdentifierDict.h
@@ -16,5 +16,6 @@
 #include "ScintIdentifier/PreshowerID.h"
 #include "ScintIdentifier/TriggerID.h"
 #include "ScintIdentifier/VetoID.h"
+#include "ScintIdentifier/VetoNuID.h"
 
 #endif // SCINTIDENTIFIER_SCINTIDENTIFIERDICT_H 
diff --git a/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/VetoNuID.h b/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/VetoNuID.h
new file mode 100644
index 0000000000000000000000000000000000000000..064f9a132378b5cc5740103b402e88d32c48d84d
--- /dev/null
+++ b/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/VetoNuID.h
@@ -0,0 +1,541 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef SCINTIDENTIFIER_VETONUID_H
+#define SCINTIDENTIFIER_VETONUID_H
+/**
+ * @file VetoNuID.h
+ *
+ * @brief This is an Identifier helper class for the VetoNu
+ *  subdetector. This class is a factory for creating compact
+ *  Identifier objects and IdentifierHash or hash ids. And it also
+ *  allows decoding of these ids.
+ *
+ */
+
+//<<<<<< INCLUDES                                                       >>>>>>
+
+#include "FaserDetDescr/FaserDetectorID.h"
+#include "Identifier/Identifier.h"
+#include "Identifier/IdentifierHash.h"
+#include "Identifier/Range.h"
+#include "Identifier/IdHelper.h"
+#include "IdDict/IdDictFieldImplementation.h"
+#include "AthenaKernel/CLASS_DEF.h"
+
+#include <string>
+#include <assert.h>
+#include <algorithm>
+
+//<<<<<< PUBLIC DEFINES                                                 >>>>>>
+//<<<<<< PUBLIC CONSTANTS                                               >>>>>>
+//<<<<<< PUBLIC TYPES                                                   >>>>>>
+
+class IdDictDictionary;
+
+//<<<<<< PUBLIC VARIABLES                                               >>>>>>
+//<<<<<< PUBLIC FUNCTIONS                                               >>>>>>
+//<<<<<< CLASS DECLARATIONS                                             >>>>>>
+
+/**
+ **  @class VetoNuID
+ **  
+ **  @brief This is an Identifier helper class for the VetoNu
+ **  subdetector. This class is a factory for creating compact
+ **  Identifier objects and IdentifierHash or hash ids. And it also
+ **  allows decoding of these ids.
+ **
+ **  Definition and the range of values for the levels of the
+ **  identifier are:
+ **
+ ** @verbatim
+ **    element           range              meaning
+ **    -------           -----              -------
+ **
+ **    station          0 to 1              longitudinal location
+ **    plate            0 to 1              two plates per station
+ **    pmt              0                   single pmt per plate
+ **
+ ** @endverbatim
+ **
+ */
+class VetoNuID : public FaserDetectorID
+{
+public:
+        
+    /// @name public typedefs
+    //@{
+    typedef Identifier::size_type                       size_type; 
+    typedef std::vector<Identifier>::const_iterator     const_id_iterator;
+    typedef MultiRange::const_identifier_factory        const_expanded_id_iterator;
+    //@}
+
+    /// @name strutors
+    //@{
+    VetoNuID(void);
+    virtual ~VetoNuID(void) = default;
+    //@}
+        
+    /// @name Creators for plate ids and pmt ids
+    //@{
+    /// For a single station
+    Identifier  station_id ( int station ) const;
+    Identifier  station_id ( int station,
+                             bool checks) const;
+
+    /// For a station from a plate id
+    Identifier  station_id ( const Identifier& plate_id ) const;
+
+    /// For a single plate
+    Identifier  plate_id ( int station, 
+                           int plate ) const;
+    Identifier  plate_id ( int station, 
+                           int plate,
+                           bool checks) const;
+
+    /// For a single plate from a pmt id
+    Identifier  plate_id ( const Identifier& pmt_id ) const;
+
+    /// From hash - optimized
+    Identifier  plate_id ( IdentifierHash plate_hash ) const;
+
+    /// For an individual pmt
+    Identifier  pmt_id ( int station, 
+                         int plate, 
+                         int pmt ) const; 
+
+    Identifier  pmt_id ( int station, 
+                         int plate, 
+                         int pmt,
+                         bool check ) const; 
+
+    Identifier  pmt_id ( const Identifier& plate_id, 
+                         int pmt ) const;
+
+    //@}
+
+
+    /// @name Hash table maximum sizes
+    //@{
+    size_type   plate_hash_max          (void) const;
+    size_type   pmt_hash_max            (void) const;
+    //@}
+
+    /// @name Access to all ids
+    //@{
+    /// Iterators over full set of ids. Plate iterator is sorted
+    const_id_iterator   plate_begin                     (void) const;
+    const_id_iterator   plate_end                       (void) const;
+    /// For pmt ids, only expanded id iterators are available. Use
+    /// following "pmt_id" method to obtain a compact identifier
+    const_expanded_id_iterator  pmt_begin             (void) const;  
+    const_expanded_id_iterator  pmt_end               (void) const;
+    //@}
+    
+
+    /// @name Optimized accessors  - ASSUMES id IS a vetonu id, i.e. NOT other
+    //@{
+    /// wafer hash from id - optimized
+    IdentifierHash      plate_hash      (Identifier plate_id) const;
+
+    /// Values of different levels (failure returns 0)
+    int         station       (const Identifier& id) const;  
+    int         plate         (const Identifier& id) const; 
+    int         pmt           (const Identifier& id) const; 
+
+    /// Max/Min values for each field (-999 == failure)
+    int         station_max  (const Identifier& id) const;
+    int         plate_max    (const Identifier& id) const;
+    int         pmt_max      (const Identifier& id) const;
+    //@}
+
+    /// @name module navigation
+    //@{
+        /// Previous plate in z
+        int get_prev_in_z(const IdentifierHash& id, IdentifierHash& prev) const;
+        /// Next plate in z
+        int get_next_in_z(const IdentifierHash& id, IdentifierHash& next) const;
+    // /// Previous wafer hash in phi (return == 0 for neighbor found)
+    // int         get_prev_in_phi (const IdentifierHash& id, IdentifierHash& prev) const;
+    // /// Next wafer hash in phi (return == 0 for neighbor found)
+    // int         get_next_in_phi (const IdentifierHash& id, IdentifierHash& next) const;
+    // /// Previous wafer hash in eta (return == 0 for neighbor found)
+    // int         get_prev_in_eta (const IdentifierHash& id, IdentifierHash& prev) const;
+    // /// Next wafer hash in eta (return == 0 for neighbor found)
+    // int         get_next_in_eta (const IdentifierHash& id, IdentifierHash& next) const;
+    // /// Wafer hash on other side
+    // int         get_other_side  (const IdentifierHash& id, IdentifierHash& other) const;
+    
+    // // To check for when phi wrap around may be needed, use
+    // bool        is_phi_module_max(const Identifier& id) const;
+    // /// For the barrel
+    // bool        is_eta_module_min(const Identifier& id) const;
+    // /// For the barrel
+    // bool        is_eta_module_max(const Identifier& id) const;
+    //@}
+
+    /// @name contexts to distinguish plate id from pixel id
+    //@{
+    IdContext   plate_context           (void) const;
+    IdContext   pmt_context             (void) const;
+    //@}
+
+    /// @name methods from abstract interface - slower than opt version
+    //@{
+    /// Create compact id from hash id (return == 0 for OK)
+    virtual int         get_id          (const IdentifierHash& hash_id,
+                                         Identifier& id,
+                                         const IdContext* context = 0) const;
+    
+    /// Create hash id from compact id (return == 0 for OK)
+    virtual int         get_hash        (const Identifier& id, 
+                                         IdentifierHash& hash_id,
+                                         const IdContext* context = 0) const;
+    //@}
+
+    /// Return the lowest bit position used in the channel id
+    int                 base_bit        (void) const;
+
+    /// Calculate a channel offset between the two identifiers.
+    Identifier::diff_type calc_offset(const Identifier& base,
+                                      const Identifier& target) const;
+
+    /// Create an identifier with a given base and channel offset
+    Identifier pmt_id_offset(const Identifier& base,
+                             Identifier::diff_type offset) const;
+
+    /// @name interaction with id dictionary
+    //@{
+    /// Create strip Identifier from expanded id, which is returned by the
+    /// id_iterators
+    Identifier          pmt_id        (const ExpandedIdentifier& pmt_id) const;
+
+    /// Create expanded id from compact id (return == 0 for OK)
+    void                get_expanded_id (const Identifier& id,
+                                         ExpandedIdentifier& exp_id,
+                                         const IdContext* context = 0) const;
+
+    /// Initialization from the identifier dictionary
+    virtual int         initialize_from_dictionary(const IdDictMgr& dict_mgr);
+
+    /// Tests of packing
+    void        test_plate_packing      (void) const;
+    //@}
+    
+private:
+        
+    enum {NOT_VALID_HASH        = 64000};
+
+    typedef std::vector<Identifier>     id_vec;
+    typedef id_vec::const_iterator      id_vec_it;
+    typedef std::vector<unsigned short> hash_vec;
+    typedef hash_vec::const_iterator    hash_vec_it;
+
+    void plate_id_checks ( int station, 
+                           int plate ) const; 
+
+    void pmt_id_checks ( int station, 
+                         int plate, 
+                         int pmt ) const;
+
+
+    int         initLevelsFromDict(void);
+
+    int         init_hashes(void);
+
+    int         init_neighbors(void);
+
+    // Temporary method for adapting an identifier for the MultiRange
+    // check - MR is missing the InnerDetector level
+    // Identifier  idForCheck      (const Identifier& id) const;
+
+    size_type                   m_vetonu_region_index;
+    size_type                   m_SCINT_INDEX;
+    size_type                   m_VETONU_INDEX;
+    size_type                   m_STATION_INDEX;
+    size_type                   m_PLATE_INDEX;
+    size_type                   m_PMT_INDEX;
+
+    const IdDictDictionary*     m_dict;
+    MultiRange                  m_full_plate_range;
+    MultiRange                  m_full_pmt_range;
+    size_type                   m_plate_hash_max;
+    size_type                   m_pmt_hash_max;
+    // Range::field                m_barrel_field;
+    id_vec                      m_plate_vec;
+    hash_vec                    m_prev_z_plate_vec;
+    hash_vec                    m_next_z_plate_vec;
+    // hash_vec                    m_prev_phi_wafer_vec;
+    // hash_vec                    m_next_phi_wafer_vec;
+    // hash_vec                    m_prev_eta_wafer_vec;
+    // hash_vec                    m_next_eta_wafer_vec;   
+    // bool 			m_hasRows	;
+
+    IdDictFieldImplementation   m_scint_impl	;
+    IdDictFieldImplementation   m_vetonu_impl	;
+    IdDictFieldImplementation   m_station_impl	;
+    IdDictFieldImplementation   m_plate_impl	;
+    IdDictFieldImplementation   m_pmt_impl	;
+};
+    
+
+//<<<<<< INLINE PUBLIC FUNCTIONS                                        >>>>>>
+
+/////////////////////////////////////////////////////////////////////////////
+//<<<<<< INLINE MEMBER FUNCTIONS                                        >>>>>>
+/////////////////////////////////////////////////////////////////////////////
+
+//using the macros below we can assign an identifier (and a version)
+//This is required and checked at compile time when you try to record/retrieve
+CLASS_DEF(VetoNuID, 247779284, 1)
+
+//----------------------------------------------------------------------------
+inline Identifier  
+VetoNuID::station_id ( int station, 
+                     bool checks) const
+{
+    
+    // Build identifier
+    Identifier result((Identifier::value_type)0);
+
+    // Pack fields independently
+    m_scint_impl.pack       (scint_field_value(), result);
+    m_vetonu_impl.pack      (vetonu_field_value(),  result);
+    m_station_impl.pack     (station,             result);
+    // Do checks
+    if(checks) 
+    {
+        plate_id_checks ( station, 0 );
+    }
+
+    return result;
+}
+
+inline Identifier  
+VetoNuID::station_id ( int station ) const 
+{
+  return station_id (station, do_checks());
+}
+
+//----------------------------------------------------------------------------
+inline Identifier  
+VetoNuID::station_id ( const Identifier& plate_id ) const
+{
+    Identifier result(plate_id);
+    //  Reset the plate and pmt fields
+    m_plate_impl.reset(result);
+    m_pmt_impl.reset(result);
+    return (result);
+}
+
+//----------------------------------------------------------------------------
+inline Identifier
+VetoNuID::plate_id ( int station,  
+                   int plate, 
+                   bool checks) const
+{
+    // Build identifier
+    Identifier result((Identifier::value_type)0);
+
+    // Pack fields independently
+    m_scint_impl.pack    (scint_field_value(), result);
+    m_vetonu_impl.pack   (vetonu_field_value(),  result);
+    m_station_impl.pack  (station,             result);
+    m_plate_impl.pack    (plate,               result);
+
+    // Do checks
+    if(checks) 
+    {
+        plate_id_checks ( station, plate );
+    }
+    return result;
+}
+
+inline Identifier
+VetoNuID::plate_id ( int station,  
+                   int plate ) const
+{
+  return plate_id (station, plate, do_checks());
+}
+
+//----------------------------------------------------------------------------
+inline Identifier
+VetoNuID::plate_id ( const Identifier& pmt_id ) const
+{
+    Identifier result(pmt_id);
+    // reset the pmt field
+    m_pmt_impl.reset(result);
+    return (result);
+}
+
+//----------------------------------------------------------------------------
+inline Identifier  VetoNuID::plate_id ( IdentifierHash plate_hash ) const
+{
+    return (m_plate_vec[plate_hash]);
+}
+
+//----------------------------------------------------------------------------
+inline IdentifierHash      VetoNuID::plate_hash      (Identifier plate_id) const 
+{
+    // MsgStream log(m_msgSvc, "VetoNuID");
+    // log << MSG::VERBOSE << "m_plate_vec size: " << m_plate_vec.size() << endmsg;
+    // log << MSG::VERBOSE << "input id = " << plate_id << endmsg;
+    // for (size_t i = 0; i < m_plate_vec.size(); i++)
+    // {
+    //     log << MSG::VERBOSE << "Hash = " <<  i << " : ID = " << m_plate_vec[i] << endmsg;
+    // }
+    id_vec_it it = std::lower_bound(m_plate_vec.begin(), 
+                                    m_plate_vec.end(), 
+                                    plate_id);
+    // Require that plate_id matches the one in vector
+    if (it != m_plate_vec.end() && plate_id == (*it)) {
+        return (it - m_plate_vec.begin());
+    }
+    IdentifierHash result;
+    return (result); // return hash in invalid state
+}
+
+//----------------------------------------------------------------------------
+inline Identifier
+VetoNuID::pmt_id ( int station,  
+                 int plate, 
+                 int pmt,
+                 bool checks) const
+{
+    // Build identifier
+    Identifier result((Identifier::value_type)0);
+
+    // Pack fields independently
+    m_scint_impl.pack    (scint_field_value(), result);
+    m_vetonu_impl.pack   (vetonu_field_value(),result);
+    m_station_impl.pack  (station,             result);
+    m_plate_impl.pack    (plate,               result);
+    m_pmt_impl.pack      (pmt,                 result);
+
+    // Do checks
+    if(checks) {
+        pmt_id_checks ( station, plate, pmt );
+    }
+    return result;
+}
+
+inline Identifier
+VetoNuID::pmt_id ( int station,  
+                 int plate, 
+                 int pmt ) const
+{
+  return pmt_id (station, plate, pmt, do_checks());
+}
+
+//----------------------------------------------------------------------------
+inline Identifier               
+VetoNuID::pmt_id        (const ExpandedIdentifier& id) const
+{
+    // Build identifier
+    Identifier result((Identifier::value_type)0);
+
+    // Pack fields independently
+    m_scint_impl.pack    (scint_field_value(),    result);
+    m_vetonu_impl.pack   (vetonu_field_value(),   result);
+    m_station_impl.pack  (id[m_STATION_INDEX],    result);
+    m_plate_impl.pack    (id[m_PLATE_INDEX],      result);
+    m_pmt_impl.pack      (id[m_PMT_INDEX],        result);
+
+    // Do checks
+    if(m_do_checks) 
+    {
+       	pmt_id_checks ( id[m_STATION_INDEX],  
+                          id[m_PLATE_INDEX], 
+                       	  id[m_PMT_INDEX]);    
+    }
+    return result;
+}
+
+//----------------------------------------------------------------------------
+inline Identifier  
+VetoNuID::pmt_id ( const Identifier& plate_id, int pmt ) const
+{
+	// Build identifier
+    Identifier result(plate_id);
+  
+    // Reset strip and then add in value
+    m_pmt_impl.reset   (result);
+ 	m_pmt_impl.pack    (pmt, result);
+  
+    if(m_do_checks)
+    {          
+	    pmt_id_checks ( station(result), 
+		           		plate(result), 
+                        pmt );
+	}
+	return result;
+}
+
+//----------------------------------------------------------------------------
+inline Identifier::diff_type
+VetoNuID::calc_offset(const Identifier& base, const Identifier& target) const
+{
+  Identifier::diff_type tval = static_cast<Identifier::diff_type>(target.get_compact() >> base_bit());
+  Identifier::diff_type bval = static_cast<Identifier::diff_type>(base.get_compact() >> base_bit());
+  return (tval - bval);
+}
+
+//----------------------------------------------------------------------------
+inline Identifier
+VetoNuID::pmt_id_offset(const Identifier& base,
+                        Identifier::diff_type offset) const
+{
+  Identifier::value_type bval = base.get_compact() >> base_bit();
+  return Identifier((bval + offset) << base_bit());
+}
+
+//----------------------------------------------------------------------------
+inline int
+VetoNuID::base_bit ( void ) const
+{
+  int base = static_cast<int>(m_pmt_impl.shift()); // lowest field base
+  return (base > 32) ? 32 : base;
+  // max base is 32 so we can still read old strip id's and differences
+  // from non-SLHC releases.
+}
+
+//----------------------------------------------------------------------------
+inline IdContext        
+VetoNuID::plate_context           (void) const
+{
+    ExpandedIdentifier id;
+    return (IdContext(id, 0, m_PLATE_INDEX));
+}
+
+//----------------------------------------------------------------------------
+inline IdContext        
+VetoNuID::pmt_context   (void) const
+{
+    ExpandedIdentifier id;
+    return (IdContext(id, 0, m_PMT_INDEX));
+}
+
+//----------------------------------------------------------------------------
+inline int 
+VetoNuID::station       (const Identifier& id) const
+{
+    return (m_station_impl.unpack(id));
+}
+
+//----------------------------------------------------------------------------
+inline int 
+VetoNuID::plate     (const Identifier& id) const
+{
+    return (m_plate_impl.unpack(id));
+}
+
+//----------------------------------------------------------------------------
+inline int 
+VetoNuID::pmt           (const Identifier& id) const
+{
+    return (m_pmt_impl.unpack(id));
+}
+
+
+#endif // SCINTIDENTIFIER_VETONUID_H
diff --git a/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/selection.xml b/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/selection.xml
index 543856daa22bebedab8c75cff790f07b7f7468f5..bebeff9ec46d1ef9554a176ba64038033469d21b 100644
--- a/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/selection.xml
+++ b/Scintillator/ScintDetDescr/ScintIdentifier/ScintIdentifier/selection.xml
@@ -3,6 +3,7 @@
   <class name="ScintillatorID" />
 -->
   <class name="VetoID" />
+  <class name="VetoNuID" />
   <class name="TriggerID" />
   <class name="PreshowerID" />
 </lcgdict>
diff --git a/Scintillator/ScintDetDescr/ScintIdentifier/src/VetoNuID.cxx b/Scintillator/ScintDetDescr/ScintIdentifier/src/VetoNuID.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..837b3c892da1ec7a338bd1e6a7c0da18d9eeeeca
--- /dev/null
+++ b/Scintillator/ScintDetDescr/ScintIdentifier/src/VetoNuID.cxx
@@ -0,0 +1,1030 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+/***************************************************************************
+ Scintillator identifier package
+ -------------------------------------------
+***************************************************************************/
+
+//<<<<<< INCLUDES                                                       >>>>>>
+#include "GaudiKernel/MsgStream.h"
+
+#include "ScintIdentifier/VetoNuID.h"
+#include "Identifier/IdentifierHash.h"
+#include "IdDict/IdDictDefs.h"  
+#include <set>
+#include <algorithm>
+#include <iostream>
+
+//<<<<<< PRIVATE DEFINES                                                >>>>>>
+//<<<<<< PRIVATE CONSTANTS                                              >>>>>>
+//<<<<<< PRIVATE TYPES                                                  >>>>>>
+//<<<<<< PRIVATE VARIABLE DEFINITIONS                                   >>>>>>
+//<<<<<< PUBLIC VARIABLE DEFINITIONS                                    >>>>>>
+//<<<<<< CLASS STRUCTURE INITIALIZATION                                 >>>>>>
+//<<<<<< PRIVATE FUNCTION DEFINITIONS                                   >>>>>>
+//<<<<<< PUBLIC FUNCTION DEFINITIONS                                    >>>>>>
+//<<<<<< MEMBER FUNCTION DEFINITIONS                                    >>>>>>
+
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+
+VetoNuID::VetoNuID(void)
+        :
+        m_vetonu_region_index(0),
+        m_SCINT_INDEX(0),
+        m_VETONU_INDEX(1),
+        m_STATION_INDEX(2),
+        m_PLATE_INDEX(3),
+        m_PMT_INDEX(4),
+        m_dict(0),
+        m_plate_hash_max(0),
+        m_pmt_hash_max(0)
+{
+}
+
+void
+VetoNuID::plate_id_checks ( int station,  
+                          int plate ) const
+{
+
+    // Check that id is within allowed range
+
+    // Fill expanded id
+    ExpandedIdentifier id;
+    id << scint_field_value() << vetonu_field_value()
+       << station << plate;
+
+    if (!m_full_plate_range.match(id)) {  // module range check is sufficient
+        MsgStream log(m_msgSvc, "VetoNuID");
+        log << MSG::ERROR << " VetoNuID::plate_id result is NOT ok. ID, range "
+            << (std::string)id <<  " " << (std::string)m_full_plate_range << endmsg;
+    }
+}
+
+void
+VetoNuID::pmt_id_checks ( int station,  
+                        int plate, 
+                        int pmt) const
+{
+
+    // Check that id is within allowed range
+
+    // Fill expanded id
+    ExpandedIdentifier id;
+    id << scint_field_value() << vetonu_field_value()
+       << station << plate << pmt;
+
+    if (!m_full_pmt_range.match(id)) {  
+        MsgStream log(m_msgSvc, "VetoNuID");
+        log << MSG::ERROR << " VetoNuID::pmt_id result is NOT ok. ID, range "
+            << (std::string)id << " " << (std::string)m_full_pmt_range << std::endl;
+    }
+}
+
+int 
+VetoNuID::station_max(const Identifier& id) const
+{
+    // get max from dictionary
+    ExpandedIdentifier expId;
+    IdContext plate_context1 = plate_context();
+    get_expanded_id(id, expId, &plate_context1);
+    for (unsigned int i = 0; i < m_full_plate_range.size(); ++i) {
+        const Range& range = m_full_plate_range[i];
+        if (range.match(expId)) {
+            const Range::field& station_field = range[m_STATION_INDEX];
+            if (station_field.has_maximum()) {
+                return (station_field.get_maximum());
+            }
+        }
+    }
+    return (-999);  // default
+}
+
+int     
+VetoNuID::pmt_max       (const Identifier& id) const
+{
+    ExpandedIdentifier expId;
+    IdContext station_context(expId, 0, m_STATION_INDEX);
+    get_expanded_id(id, expId, &station_context);
+    int result = -999;
+    for (unsigned int i = 0; i < m_full_pmt_range.size(); ++i) {
+        const Range& range = m_full_pmt_range[i];
+        if (range.match(expId)) {
+            const Range::field& pmt_field = range[m_PMT_INDEX];
+            if (pmt_field.has_maximum()) {
+                int pmt = pmt_field.get_maximum();
+                if (result < pmt) result = pmt;
+            }
+        }
+    }
+    return (result);
+}
+
+int 
+VetoNuID::plate_max(const Identifier& id) const
+{
+    // get max from dictionary
+    ExpandedIdentifier expId;
+    IdContext plate_context1 = plate_context();
+    get_expanded_id(id, expId, &plate_context1);
+    for (unsigned int i = 0; i < m_full_plate_range.size(); ++i) {
+        const Range& range = m_full_plate_range[i];
+        if (range.match(expId)) {
+            const Range::field& plate_field = range[m_PLATE_INDEX];
+            if (plate_field.has_maximum()) {
+                return (plate_field.get_maximum());
+            }
+        }
+    }
+    return -1;
+}
+
+int
+VetoNuID::initialize_from_dictionary(const IdDictMgr& dict_mgr)
+{
+    MsgStream log(m_msgSvc, "VetoNuID");
+    log << MSG::INFO << "Initialize from dictionary" << endmsg;
+  
+    // Check whether this helper should be reinitialized
+    if (!reinitialize(dict_mgr)) {
+        log << MSG::INFO << "Request to reinitialize not satisfied - tags have not changed" << endmsg;
+        return (0);
+    }
+    else {
+        if (m_msgSvc) {
+            log << MSG::DEBUG << "(Re)initialize" << endmsg;
+        }
+        else {
+            std::cout  << " DEBUG (Re)initialize" << std::endl;
+        }
+    }
+
+    // init base object
+    if(FaserDetectorID::initialize_from_dictionary(dict_mgr)) return (1);
+
+    // Register version of InnerDetector dictionary 
+    if (register_dict_tag(dict_mgr, "Scintillator")) return(1);
+
+    m_dict = dict_mgr.find_dictionary ("Scintillator"); 
+    if(!m_dict) {
+        log << MSG::ERROR << " VetoNuID::initialize_from_dict - cannot access Scintillator dictionary " << endmsg;
+        return 1;
+    }
+
+    // Initialize the field indices
+    if(initLevelsFromDict()) return (1);
+
+    //
+    // Build multirange for the valid set of identifiers
+    //
+
+
+    // Find value for the field Scintillator
+    const IdDictDictionary* faserDict = dict_mgr.find_dictionary ("FASER"); 
+    int scintField   = -1;
+    if (faserDict->get_label_value("subdet", "Scintillator", scintField)) {
+        log << MSG::ERROR << "Could not get value for label 'Scintillator' of field 'subdet' in dictionary " 
+            << faserDict->m_name
+            << endmsg;
+        return (1);
+    }
+
+    // Find value for the field VetoNu
+    int vetonuField   = -1;
+    if (m_dict->get_label_value("part", "VetoNu", vetonuField)) {
+        log << MSG::ERROR << "Could not get value for label 'VetoNu' of field 'part' in dictionary " 
+            << m_dict->m_name
+            << endmsg;
+        return (1);
+    }
+    if (m_msgSvc) {
+        log << MSG::DEBUG << " VetoNuID::initialize_from_dict " 
+            << "Found field values: VetoNu "  
+            << vetonuField
+            << std::endl;
+    }
+    else {
+        std::cout << " DEBUG VetoNuID::initialize_from_dict " 
+                  << "Found field values: VetoNu "  
+                  << vetonuField
+                  << std::endl;
+    }
+    
+    // Set up id for region and range prefix
+    ExpandedIdentifier region_id;
+    region_id.add(scintField);
+    region_id.add(vetonuField);
+    Range prefix;
+    m_full_plate_range = m_dict->build_multirange(region_id, prefix, "plate");
+    m_full_pmt_range = m_dict->build_multirange(region_id, prefix);
+
+    // Setup the hash tables
+    if(init_hashes()) return (1);
+
+    // Setup hash tables for finding neighbors
+    if(init_neighbors()) return (1);
+    
+    if (m_msgSvc) {
+        log << MSG::INFO << " VetoNuID::initialize_from_dict "  << endmsg;
+        log << MSG::DEBUG  
+            << "Plate range -> " << (std::string)m_full_plate_range
+            <<   endmsg;
+        log << MSG::DEBUG
+            << "Pmt range -> " << (std::string)m_full_pmt_range
+            << endmsg;
+    }
+    else {
+        std::cout << " INFO VetoNuID::initialize_from_dict "  << std::endl;
+        std::cout << " DEBUG  Plate range -> " << (std::string)m_full_plate_range
+                  <<   std::endl;
+        std::cout << " DEBUG Pmt range -> " << (std::string)m_full_pmt_range
+                  << std::endl;
+    }
+    
+    return 0;
+}
+
+int
+VetoNuID::init_hashes(void)
+{
+
+    //
+    // create a vector(s) to retrieve the hashes for compact ids. For
+    // the moment, we implement a hash for plates but NOT for pmts
+    //
+    MsgStream log(m_msgSvc, "VetoNuID");
+    // plate hash
+    m_plate_hash_max = m_full_plate_range.cardinality();
+    m_plate_vec.resize(m_plate_hash_max);
+    unsigned int nids = 0;
+    std::set<Identifier> ids;
+    for (unsigned int i = 0; i < m_full_plate_range.size(); ++i) {
+        const Range& range = m_full_plate_range[i];
+        Range::const_identifier_factory first = range.factory_begin();
+        Range::const_identifier_factory last  = range.factory_end();
+        for (; first != last; ++first) {
+            const ExpandedIdentifier& exp_id = (*first);
+            Identifier id = plate_id(exp_id[m_STATION_INDEX],
+                                     exp_id[m_PLATE_INDEX]); 
+            if(!(ids.insert(id)).second) {
+                log << MSG::ERROR << " VetoNuID::init_hashes "
+                    << " Error: duplicated id for plate id. nid " << nids
+                    << " compact id " << id.getString()
+                    << " id " << (std::string)exp_id << endmsg;
+                return (1);
+            }
+            nids++;
+        }
+    }
+    if(ids.size() != m_plate_hash_max) {
+        log << MSG::ERROR << " VetoNuID::init_hashes "
+            << " Error: set size NOT EQUAL to hash max. size " << ids.size()
+            << " hash max " << m_plate_hash_max 
+            << endmsg;
+        return (1);
+    }
+
+    nids = 0;
+    std::set<Identifier>::const_iterator first = ids.begin();
+    std::set<Identifier>::const_iterator last  = ids.end();
+    for (; first != last && nids < m_plate_vec.size(); ++first) {
+        m_plate_vec[nids] = (*first);
+        nids++;
+    }
+
+    // pmt hash - we do not keep a vec for the pmts
+    m_pmt_hash_max = m_full_pmt_range.cardinality();
+
+    return (0);
+}
+
+    int
+    VetoNuID::get_prev_in_z(const IdentifierHash& id, IdentifierHash& prev) const
+    {
+        unsigned short index = id;
+        if (index < m_prev_z_plate_vec.size())
+        {
+            if (m_prev_z_plate_vec[index] == NOT_VALID_HASH) return (1);
+            prev = m_prev_z_plate_vec[index];
+            return (0);
+        }
+        return (1);
+    }
+
+    int
+    VetoNuID::get_next_in_z(const IdentifierHash& id, IdentifierHash& next) const
+    {
+        unsigned short index = id;
+        if (index < m_next_z_plate_vec.size())
+        {
+            if (m_next_z_plate_vec[index] == NOT_VALID_HASH) return (1);
+            next = m_next_z_plate_vec[index];
+            return (0);
+        }
+        return (1);
+    }
+
+// int             
+// VetoNuID::get_prev_in_phi(const IdentifierHash& id, IdentifierHash& prev) const
+// {
+//     unsigned short index = id;
+//     if (index < m_prev_phi_wafer_vec.size()) {
+//         if (m_prev_phi_wafer_vec[index] == NOT_VALID_HASH) return (1);
+//         prev =  m_prev_phi_wafer_vec[index];
+//         return (0);
+//     }
+//     return (1);
+// }
+
+// int             
+// VetoNuID::get_next_in_phi(const IdentifierHash& id, IdentifierHash& next) const
+// {
+//     unsigned short index = id;
+//     if (index < m_next_phi_wafer_vec.size()) {
+//         if (m_next_phi_wafer_vec[index] == NOT_VALID_HASH) return (1);
+//         next =  m_next_phi_wafer_vec[index];
+//         return (0);
+//     }
+//     return (1);
+// }
+
+// int             
+// VetoNuID::get_prev_in_eta(const IdentifierHash& id, IdentifierHash& prev) const
+// {
+//     unsigned short index = id;
+//     if (index < m_prev_eta_wafer_vec.size()) {
+//         if (m_prev_eta_wafer_vec[index] == NOT_VALID_HASH) return (1);
+//         prev =  m_prev_eta_wafer_vec[index];
+//         return (0);
+//     }
+//     return (1);
+// }
+
+// int
+// VetoNuID::get_next_in_eta(const IdentifierHash& id, IdentifierHash& next) const
+// {
+//     unsigned short index = id;
+//     if (index < m_next_eta_wafer_vec.size()) {
+//         if (m_next_eta_wafer_vec[index] == NOT_VALID_HASH) return (1);
+//         next =  m_next_eta_wafer_vec[index];
+//         return (0);
+//     }
+//     return (1);
+// }
+
+// int
+// VetoNuID::get_other_side  (const IdentifierHash& hashId, IdentifierHash& other) const
+// {
+//     if (m_dict) {
+//         // get max from dictionary
+//         Identifier id;
+//         IdContext wafer_context1 = wafer_context();
+//         if(!get_id(hashId, id, &wafer_context1)) {
+//             other = side(id) ? hashId - 1 : hashId + 1;
+//             return (0);
+//         }
+//     }
+//     return (1);
+// }
+
+int
+VetoNuID::init_neighbors(void)
+{
+    //
+    // create a vector(s) to retrieve the hashes for compact ids for
+    // plate neighbors.
+    //
+    MsgStream log(m_msgSvc, "VetoNuID");
+
+    m_prev_z_plate_vec.resize(m_plate_hash_max, NOT_VALID_HASH);
+    m_next_z_plate_vec.resize(m_plate_hash_max, NOT_VALID_HASH);
+    for (unsigned int i = 0; i < m_full_plate_range.size(); i++)
+    {
+        const Range& range = m_full_plate_range[i];
+        const Range::field& station_field = range[m_STATION_INDEX];
+        const Range::field& plate_field   = range[m_PLATE_INDEX];
+        Range::const_identifier_factory first = range.factory_begin();
+        Range::const_identifier_factory last  = range.factory_end();
+        for (; first != last; ++first)
+        {
+            const ExpandedIdentifier& exp_id = (*first);
+            ExpandedIdentifier::element_type previous_plate;
+            ExpandedIdentifier::element_type next_plate;
+            ExpandedIdentifier::element_type previous_station;
+            ExpandedIdentifier::element_type next_station;
+            bool pplate = plate_field.get_previous(exp_id[m_PLATE_INDEX], previous_plate);
+            bool nplate = plate_field.get_next    (exp_id[m_PLATE_INDEX], next_plate);
+            bool pstation = station_field.get_previous(exp_id[m_STATION_INDEX], previous_station);
+            bool nstation = station_field.get_next    (exp_id[m_STATION_INDEX], next_station);
+
+            IdContext  pcontext = plate_context();
+
+            IdentifierHash hash_id;
+            Identifier originalId = plate_id(exp_id[m_STATION_INDEX],
+                                             exp_id[m_PLATE_INDEX]);
+
+            if (get_hash(originalId, hash_id, &pcontext)) 
+            {
+                log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get hash, exp/compact "
+                    << show_to_string(originalId, &pcontext)
+                    << " " << (std::string)m_full_plate_range << endmsg;
+                return (1);
+            }
+
+            // index for the subsequent arrays
+            unsigned short index = hash_id;
+            assert (hash_id < m_prev_z_plate_vec.size());
+            assert (hash_id < m_next_z_plate_vec.size());
+            
+            if (pplate) {
+                // Get previous plate hash id
+                ExpandedIdentifier expId = exp_id;
+                expId[m_PLATE_INDEX] = previous_plate;
+                Identifier id = plate_id(expId[m_STATION_INDEX],
+                                         expId[m_PLATE_INDEX]);
+
+                if (get_hash(id, hash_id, &pcontext)) {
+                    log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get previous plate hash, exp/compact " << id.getString() << " " 
+                        << endmsg;
+                    return (1);
+                }
+                m_prev_z_plate_vec[index] = hash_id;
+            }
+            else if (pstation)
+            {
+                ExpandedIdentifier expId = exp_id;
+                expId[m_STATION_INDEX] = previous_station;
+                ExpandedIdentifier stationId;
+                stationId.add(expId[m_SCINT_INDEX]);
+                stationId.add(expId[m_VETONU_INDEX]);
+                stationId.add(previous_station);
+                Range prefix;
+                MultiRange stationPlateRange = m_dict->build_multirange(stationId, prefix, "plate");
+                const Range::field& upstream_plate_field = range[m_PLATE_INDEX];
+                if (upstream_plate_field.has_maximum())
+                {
+                    expId[m_PLATE_INDEX] = upstream_plate_field.get_maximum();
+                    Identifier id = plate_id(expId[m_STATION_INDEX],
+                                             expId[m_PLATE_INDEX]);
+                    if (get_hash(id, hash_id, &pcontext)) {
+                        log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get last plate hash from previous station, exp/compact " << id.getString() << " " 
+                            << endmsg;
+                        return (1);
+                    }
+                    m_prev_z_plate_vec[index] = hash_id;
+                }
+                else
+                {
+                    log << MSG::ERROR << "VetoNuID::init_neighbors - unable to get plate_max for previous station, exp/compact " << originalId.getString() << " "
+                    << endmsg;
+                    return (1);
+                }
+            }
+
+            if (nplate) {
+                // Get next plate hash id
+                ExpandedIdentifier expId = exp_id;
+                expId[m_PLATE_INDEX] = next_plate;
+                Identifier id = plate_id(expId[m_STATION_INDEX],
+                                         expId[m_PLATE_INDEX]);
+
+                if (get_hash(id, hash_id, &pcontext)) {
+                    log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get next plate hash, exp/compact " << id.getString() << " " 
+                        << endmsg;
+                    return (1);
+                }
+                m_next_z_plate_vec[index] = hash_id;
+            }
+            else if (nstation)
+            {
+                ExpandedIdentifier expId = exp_id;
+                expId[m_STATION_INDEX] = next_station;
+                ExpandedIdentifier stationId;
+                stationId.add(expId[m_SCINT_INDEX]);
+                stationId.add(expId[m_VETONU_INDEX]);
+                stationId.add(next_station);
+                Range prefix;
+                MultiRange stationPlateRange = m_dict->build_multirange(stationId, prefix, "plate");
+                const Range::field& downstream_plate_field = range[m_PLATE_INDEX];
+                if (downstream_plate_field.has_minimum())
+                {
+                    expId[m_PLATE_INDEX] = downstream_plate_field.get_minimum();
+                    Identifier id = plate_id(expId[m_STATION_INDEX],
+                                             expId[m_PLATE_INDEX]);
+                    if (get_hash(id, hash_id, &pcontext)) {
+                        log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get previous plate hash from next station, exp/compact " << id.getString() << " " 
+                            << endmsg;
+                        return (1);
+                    }
+                    m_next_z_plate_vec[index] = hash_id;
+                }
+                else
+                {
+                    log << MSG::ERROR << "VetoNuID::init_neighbors - unable to get plate_min for next station, exp/compact " << originalId.getString() << " "
+                    << endmsg;
+                    return (1);
+                }
+            }
+
+        }
+    }
+
+    // m_prev_phi_wafer_vec.resize(m_wafer_hash_max, NOT_VALID_HASH);
+    // m_next_phi_wafer_vec.resize(m_wafer_hash_max, NOT_VALID_HASH);
+    // m_prev_eta_wafer_vec.resize(m_wafer_hash_max, NOT_VALID_HASH);
+    // m_next_eta_wafer_vec.resize(m_wafer_hash_max, NOT_VALID_HASH);
+
+    // for (unsigned int i = 0; i < m_full_wafer_range.size(); ++i) {
+    //     const Range& range = m_full_wafer_range[i];
+    //     const Range::field& phi_field = range[m_PHI_MODULE_INDEX];
+    //     const Range::field& eta_field = range[m_ETA_MODULE_INDEX];
+    //     Range::const_identifier_factory first = range.factory_begin();
+    //     Range::const_identifier_factory last  = range.factory_end();
+    //     for (; first != last; ++first) {
+    //         const ExpandedIdentifier& exp_id = (*first);
+    //         ExpandedIdentifier::element_type previous_phi;
+    //         ExpandedIdentifier::element_type next_phi;
+    //         ExpandedIdentifier::element_type previous_eta;
+    //         ExpandedIdentifier::element_type next_eta;
+    //         bool pphi = phi_field.get_previous(exp_id[m_PHI_MODULE_INDEX], previous_phi);
+    //         bool nphi = phi_field.get_next    (exp_id[m_PHI_MODULE_INDEX], next_phi);
+    //         bool peta = eta_field.get_previous(exp_id[m_ETA_MODULE_INDEX], previous_eta);
+    //         bool neta = eta_field.get_next    (exp_id[m_ETA_MODULE_INDEX], next_eta);
+
+    //         IdContext      wcontext = wafer_context();
+            
+    //         // First get primary hash id
+    //         IdentifierHash hash_id;
+    //         Identifier id = wafer_id(exp_id[m_BARREL_EC_INDEX],
+    //                                  exp_id[m_LAYER_DISK_INDEX], 
+    //                                  exp_id[m_PHI_MODULE_INDEX],
+    //                                  exp_id[m_ETA_MODULE_INDEX],
+    //                                  exp_id[m_SIDE_INDEX]);
+    //         if (get_hash(id, hash_id, &wcontext)) {
+    //             log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get hash, exp/compact "
+    //                 << show_to_string(id, &wcontext)
+    //                 << " " << (std::string)m_full_wafer_range << endmsg;
+    //             return (1);
+    //         }
+
+    //         // index for the subsequent arrays
+    //         unsigned short index = hash_id;
+    //         assert (hash_id < m_prev_phi_wafer_vec.size());
+    //         assert (hash_id < m_next_phi_wafer_vec.size());
+    //         assert (hash_id < m_prev_eta_wafer_vec.size());
+    //         assert (hash_id < m_next_eta_wafer_vec.size());
+            
+    //         if (pphi) {
+    //             // Get previous phi hash id
+    //             ExpandedIdentifier expId = exp_id;
+    //             expId[m_PHI_MODULE_INDEX] = previous_phi;
+    //             Identifier id = wafer_id(expId[m_BARREL_EC_INDEX],
+    //                                      expId[m_LAYER_DISK_INDEX], 
+    //                                      expId[m_PHI_MODULE_INDEX],
+    //                                      expId[m_ETA_MODULE_INDEX],
+    //                                      expId[m_SIDE_INDEX]);
+    //             if (get_hash(id, hash_id, &wcontext)) {
+    //                 log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get previous phi hash, exp/compact " << id.getString() << " " 
+    //                     << endmsg;
+    //                 return (1);
+    //             }
+    //             m_prev_phi_wafer_vec[index] = hash_id;
+    //         }
+            
+    //         if (nphi) {
+    //             // Get next phi hash id
+    //             ExpandedIdentifier expId = exp_id;
+    //             expId[m_PHI_MODULE_INDEX] = next_phi;
+    //             Identifier id = wafer_id(expId[m_BARREL_EC_INDEX],
+    //                                      expId[m_LAYER_DISK_INDEX], 
+    //                                      expId[m_PHI_MODULE_INDEX],
+    //                                      expId[m_ETA_MODULE_INDEX],
+    //                                      expId[m_SIDE_INDEX]);
+    //             if (get_hash(id, hash_id, &wcontext)) {
+    //                 log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get next phi hash, exp/compact " << id.getString() << 
+    //                     " " << MSG::hex << id.getString() << MSG::dec << endmsg;
+    //                 return (1);
+    //             }
+    //             m_next_phi_wafer_vec[index] = hash_id;
+    //         }
+            
+    //         if (peta) {
+    //             // Get previous eta hash id
+    //             ExpandedIdentifier expId = exp_id;
+    //             expId[m_ETA_MODULE_INDEX] = previous_eta;
+    //             Identifier id = wafer_id(expId[m_BARREL_EC_INDEX],
+    //                                      expId[m_LAYER_DISK_INDEX], 
+    //                                      expId[m_PHI_MODULE_INDEX],
+    //                                      expId[m_ETA_MODULE_INDEX],
+    //                                      expId[m_SIDE_INDEX]);
+    //             if (get_hash(id, hash_id, &wcontext)) {
+    //                 log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get previous eta hash, exp/compact " << id.getString() 
+    //                     << " " << std::endl;
+    //                 return (1);
+    //             }
+    //             m_prev_eta_wafer_vec[index] = hash_id;
+    //         }
+            
+    //         if (neta) {
+    //             // Get next eta hash id
+    //             ExpandedIdentifier expId = exp_id;
+    //             expId[m_ETA_MODULE_INDEX] = next_eta;
+    //             Identifier id = wafer_id(expId[m_BARREL_EC_INDEX],
+    //                                      expId[m_LAYER_DISK_INDEX], 
+    //                                      expId[m_PHI_MODULE_INDEX],
+    //                                      expId[m_ETA_MODULE_INDEX],
+    //                                      expId[m_SIDE_INDEX]);
+    //             if (get_hash(id, hash_id, &wcontext)) {
+    //                 log << MSG::ERROR << " VetoNuID::init_neighbors - unable to get next eta hash, exp/compact " << id.getString() 
+    //                     << " " << endmsg;
+    //                 return (1);
+    //             }
+    //             m_next_eta_wafer_vec[index] = hash_id;
+    //         }
+            
+
+//          std::cout << " VetoNuID::init_neighbors "
+//                    << " phi, previous, next " << id[m_PHI_MODULE_INDEX]
+//                    << " " << pphi
+//                    << " " << previous_phi
+//                    << " " << nphi
+//                    << " " << next_phi
+//                    << " eta, previous, next " << id[m_ETA_MODULE_INDEX]
+//                    << " " << peta
+//                    << " " << previous_eta
+//                    << " " << neta
+//                    << " " << next_eta
+//                    << " id " << (std::string)(*first) 
+//                    << std::endl;
+    //     }
+    // }
+    return (0);
+}
+
+
+
+int     
+VetoNuID::initLevelsFromDict()
+{
+
+
+    MsgStream log(m_msgSvc, "VetoNuID");
+    if(!m_dict) {
+        log << MSG::ERROR << " VetoNuID::initLevelsFromDict - dictionary NOT initialized " << endmsg;
+        return (1);
+    }
+    
+    // Find out which identifier field corresponds to each level. Use
+    // names to find each field/leve.
+
+    m_SCINT_INDEX               = 999;
+    m_VETONU_INDEX                = 999;
+    m_STATION_INDEX             = 999;
+    m_PLATE_INDEX               = 999;
+    m_PMT_INDEX                 = 999;    
+
+    // Save index to a VetoNu region for unpacking
+    ExpandedIdentifier id; 
+    id << scint_field_value() << vetonu_field_value();
+    if (m_dict->find_region(id, m_vetonu_region_index)) {
+        log << MSG::ERROR << "VetoNuID::initLevelsFromDict - unable to find vetonu region index: id, reg "  
+            << (std::string)id << " " << m_vetonu_region_index
+            << endmsg;
+        return (1);
+    }
+
+    // Find a VetoNu region
+    IdDictField* field = m_dict->find_field("subdet");
+    if (field) {
+        m_SCINT_INDEX = field->m_index;
+    }
+    else {
+        log << MSG::ERROR << "VetoNuID::initLevelsFromDict - unable to find 'subdet' field "  << endmsg;
+        return (1);
+    }
+    field = m_dict->find_field("part");
+    if (field) {
+        m_VETONU_INDEX = field->m_index;
+    }
+    else {
+        log << MSG::ERROR << "VetoNuID::initLevelsFromDict - unable to find 'part' field "  << endmsg;
+        return (1);
+    }
+    field = m_dict->find_field("station");
+    if (field) {
+        m_STATION_INDEX = field->m_index;
+    }
+    else {
+        log << MSG::ERROR << "VetoNuID::initLevelsFromDict - unable to find 'station' field "   << endmsg;
+        return (1);
+    }
+    field = m_dict->find_field("plate");
+    if (field) {
+        m_PLATE_INDEX = field->m_index;
+    }
+    else {
+        log << MSG::ERROR<< "VetoNuID::initLevelsFromDict - unable to find 'plate' field "  << endmsg;
+        return (1);
+    }
+    field = m_dict->find_field("pmt");
+    if (field) {
+        m_PMT_INDEX = field->m_index;
+    }
+    else {
+        log << MSG::ERROR << "VetoNuID::initLevelsFromDict - unable to find 'pmt' field " << endmsg;    
+        return (1);
+    }
+    
+    // Set the field implementations
+
+    const IdDictRegion& region = *m_dict->m_regions[m_vetonu_region_index];
+
+    m_scint_impl      = region.m_implementation[m_SCINT_INDEX]; 
+    m_vetonu_impl       = region.m_implementation[m_VETONU_INDEX]; 
+    m_station_impl    = region.m_implementation[m_STATION_INDEX]; 
+    m_plate_impl      = region.m_implementation[m_PLATE_INDEX];
+    m_pmt_impl        = region.m_implementation[m_PMT_INDEX]; 
+
+    if (m_msgSvc) {
+        log << MSG::DEBUG << "decode index and bit fields for each level: " << endmsg;
+        log << MSG::DEBUG << "scint    "  << m_scint_impl.show_to_string() << endmsg;
+        log << MSG::DEBUG << "vetonu     "  << m_vetonu_impl.show_to_string() << endmsg; 
+        log << MSG::DEBUG << "station  "  << m_station_impl.show_to_string() << endmsg; 
+        log << MSG::DEBUG << "plate    "  << m_plate_impl.show_to_string() << endmsg; 
+        log << MSG::DEBUG << "pmt      "  << m_pmt_impl.show_to_string() << endmsg; 
+    }
+    else {
+        std::cout << " DEBUG decode index and bit fields for each level: " << std::endl;
+        std::cout << " DEBUG scint    "  << m_scint_impl.show_to_string() << std::endl;
+        std::cout << " DEBUG vetonu     "  << m_vetonu_impl.show_to_string() << std::endl; 
+        std::cout << " DEBUG station  "  << m_station_impl.show_to_string() << std::endl; 
+        std::cout << " DEBUG plate    "  << m_plate_impl.show_to_string() << std::endl;
+        std::cout << " DEBUG pmt      "  << m_pmt_impl.show_to_string() << std::endl; 
+    }
+    
+    std::cout << "scint "  << m_scint_impl.decode_index() << " " 
+              <<   (std::string)m_scint_impl.ored_field() << " " 
+              << std::hex    << m_scint_impl.mask() << " " 
+              << m_scint_impl.zeroing_mask() << " " 
+              << std::dec    << m_scint_impl.shift() << " "
+              << m_scint_impl.bits() << " "
+              << m_scint_impl.bits_offset()
+              << std::endl;
+    std::cout << "vetonu"     << m_vetonu_impl.decode_index() << " " 
+              <<   (std::string)m_vetonu_impl.ored_field() << " " 
+              << std::hex    << m_vetonu_impl.mask() << " " 
+              << m_vetonu_impl.zeroing_mask() << " " 
+              << std::dec    << m_vetonu_impl.shift() << " "
+              << m_vetonu_impl.bits() << " "
+              << m_vetonu_impl.bits_offset()
+              << std::endl;
+    std::cout << "station"<< m_station_impl.decode_index() << " " 
+              <<   (std::string)m_station_impl.ored_field() << " " 
+              << std::hex    << m_station_impl.mask() << " " 
+              << m_station_impl.zeroing_mask() << " " 
+              << std::dec    << m_station_impl.shift() << " "
+              << m_station_impl.bits() << " "
+              << m_station_impl.bits_offset()
+              << std::endl;
+    std::cout << "plate"    << m_plate_impl.decode_index() << " " 
+              <<   (std::string)m_plate_impl.ored_field() << " " 
+              << std::hex    << m_plate_impl.mask() << " " 
+              << m_plate_impl.zeroing_mask() << " " 
+              << std::dec    << m_plate_impl.shift() << " "
+              << m_plate_impl.bits() << " "
+              << m_plate_impl.bits_offset()
+              << std::endl;
+    std::cout << "pmt"   << m_pmt_impl.decode_index() << " " 
+              <<   (std::string)m_pmt_impl.ored_field() << " " 
+              << std::hex    << m_pmt_impl.mask() << " " 
+              << m_pmt_impl.zeroing_mask() << " " 
+              << std::dec    << m_pmt_impl.shift() << " "
+              << m_pmt_impl.bits() << " "
+              << m_pmt_impl.bits_offset()
+              << std::endl;
+
+    return (0);
+}
+
+VetoNuID::size_type       
+VetoNuID::plate_hash_max (void) const
+{
+    return m_plate_hash_max;
+}
+
+VetoNuID::size_type       
+VetoNuID::pmt_hash_max (void) const
+{
+    return m_pmt_hash_max;
+}
+
+VetoNuID::const_id_iterator       VetoNuID::plate_begin             (void) const
+{
+    return (m_plate_vec.begin());
+}
+
+VetoNuID::const_id_iterator       VetoNuID::plate_end               (void) const
+{
+    return (m_plate_vec.end());
+}
+
+VetoNuID::const_expanded_id_iterator      VetoNuID::pmt_begin     (void) const
+{
+    return (m_full_pmt_range.factory_begin());
+}
+
+VetoNuID::const_expanded_id_iterator      VetoNuID::pmt_end       (void) const
+{
+    return (m_full_pmt_range.factory_end());
+}
+
+// From hash get Identifier
+int     
+VetoNuID::get_id          (const IdentifierHash& hash_id,
+                         Identifier& id,
+                         const IdContext* context) const
+{
+
+    int result = 1;
+    id.clear();
+
+    size_t begin = (context) ? context->begin_index(): 0;
+    // cannot get hash if end is 0:
+    size_t end   = (context) ? context->end_index()  : 0; 
+    if (0 == begin) { 
+        // No hashes yet for ids with prefixes
+        if (m_PLATE_INDEX == end) {
+            if (hash_id < (unsigned int)(m_plate_vec.end() - m_plate_vec.begin())) {
+                id = m_plate_vec[hash_id];
+                result = 0;
+            }
+        }
+        else if (m_PMT_INDEX == end) {
+            // Do not know how to calculate strip id from hash yet!!
+            std::cout << "Do not know how to calculate pmt id from hash yet!!" << std::endl;
+        }
+    }
+    return (result);
+}
+
+void
+VetoNuID::get_expanded_id (const Identifier& id,
+                         ExpandedIdentifier& exp_id,
+                         const IdContext* context) const
+{
+    exp_id.clear();
+    exp_id << scint_field_value()
+           << vetonu_field_value()
+           << station(id)
+           << plate(id);
+    if(!context || context->end_index() == m_PMT_INDEX) 
+    {
+       	exp_id << pmt(id);
+    }
+}
+
+int     
+VetoNuID::get_hash        (const Identifier& id, 
+                         IdentifierHash& hash_id,
+                         const IdContext* context) const
+{
+
+    // Get the hash code from either a vec (for plate) or calculate
+    // it (pmts). For the former, we convert to compact and call
+    // get_hash again. For the latter, we calculate the hash from the
+    // Identifier.
+
+    int result = 1;
+    hash_id = 0;
+    size_t begin = (context) ? context->begin_index(): 0;
+    size_t end   = (context) ? context->end_index()  : 0; 
+    if (0 == begin) {
+        // No hashes yet for ids with prefixes
+        if (m_PLATE_INDEX  == end) {
+            hash_id = plate_hash(id);
+            if (hash_id.is_valid()) result = 0;
+        }
+        else if (context && context->end_index() == m_PMT_INDEX) {
+            // Must calculate for strip hash
+            ExpandedIdentifier new_id;
+            get_expanded_id(id, new_id);
+            hash_id =  m_full_pmt_range.cardinalityUpTo(new_id);
+            result = 0;
+        }
+    }
+    return (result);
+}
+
+
+void    
+VetoNuID::test_plate_packing      (void) const
+{
+    MsgStream log(m_msgSvc, "VetoNuID");
+
+    if (m_dict) {
+        
+        int nids = 0;
+        int nerr = 0;
+        IdContext context = plate_context();
+        const_id_iterator first = m_plate_vec.begin();
+        const_id_iterator last  = m_plate_vec.end();
+        for (; first != last; ++first, ++nids) {
+            Identifier id = (*first);
+            ExpandedIdentifier exp_id;
+            get_expanded_id(id, exp_id, &context);
+            Identifier new_id = plate_id(exp_id[m_STATION_INDEX],
+                                         exp_id[m_PLATE_INDEX]);
+            if (id != new_id) {
+                log << MSG::ERROR << "VetoNuID::test_plate_packing: new and old compacts not equal. New/old/expanded ids " 
+                    << MSG::hex << show_to_string(id) << " " << show_to_string(new_id) << " " << MSG::dec 
+                    << (std::string)exp_id << endmsg;
+                nerr++;
+                continue;
+            }
+            // check station id
+            if (!exp_id[m_PLATE_INDEX]) {
+                
+                Identifier new_id1 = station_id(exp_id[m_STATION_INDEX]);
+                if (id != new_id1) {
+                    log << MSG::ERROR << "VetoNuID::test_plate_packing: new and old station ids not equal. New/old/expanded ids " 
+                        << MSG::hex << show_to_string(id) << " " << show_to_string(new_id1) << " " << MSG::dec 
+                        << (std::string)exp_id << endmsg;
+                    nerr++;
+                    continue;
+                }
+            }
+        }
+
+        if (m_msgSvc) { 
+            log << MSG::DEBUG << "VetoNuID::test_plate_packing: tested plate and station ids. nids, errors " 
+                << nids << " " << nerr << endmsg;
+        }
+        else {
+            std::cout << " DEBUG VetoNuID::test_plate_packing: tested plate and station ids. nids, errors " 
+                      << nids << " " << nerr << std::endl;
+        }
+        
+        nids = 0;
+        context = pmt_context();
+        const_expanded_id_iterator      first_vetonu = pmt_begin();  
+        const_expanded_id_iterator      last_vetonu  = pmt_end();
+        for (; first_vetonu != last_vetonu; ++first_vetonu, ++nids) {
+            // if (nids%10000 != 1) continue;
+            const ExpandedIdentifier& exp_id = *first_vetonu;
+            ExpandedIdentifier new_exp_id;
+
+            Identifier id = plate_id(exp_id[m_STATION_INDEX],
+                                     exp_id[m_PLATE_INDEX]);
+            get_expanded_id(id, new_exp_id, &context);
+            if (exp_id[0] != new_exp_id[0] ||
+                exp_id[1] != new_exp_id[1] ||
+                exp_id[2] != new_exp_id[2] ||
+                exp_id[3] != new_exp_id[3])
+            {
+                log << MSG::ERROR << "VetoNuID::test_plate_packing: new and old ids not equal. New/old/compact ids "
+                    << (std::string)new_exp_id << " " << (std::string)exp_id
+                    << " " << show_to_string(id) << endmsg;
+                continue;
+            }
+
+            Identifier pmtid	;
+	        Identifier pmtid1	;
+           	pmtid = pmt_id ( 
+                       exp_id[m_STATION_INDEX],
+					   exp_id[m_PLATE_INDEX],
+					   exp_id[m_PMT_INDEX]);
+
+    	   	pmtid1 = pmt_id (	    
+                        station(pmtid),
+                        plate(pmtid),
+                        pmt(pmtid));
+
+            if (pmtid != pmtid1) {
+                log << MSG::ERROR << "VetoNuID::test_plate_packing: new and old pixel ids not equal. New/old ids "
+                    << " " << show_to_string(pmtid1) << " " 
+                    << show_to_string(pmtid) << endmsg;
+            }
+        }
+
+        if (m_msgSvc) {
+            log << MSG::DEBUG << "VetoNuID::test_plate_packing: Successful tested " 
+                << nids << " ids. " 
+                << endmsg;
+        }
+        else {
+            std::cout << " DEBUG VetoNuID::test_plate_packing: Successful tested " 
+                      << nids << " ids. " 
+                      << std::endl;
+        }
+    }
+    else {
+        log << MSG::ERROR << "VetoNuID::test_plate_packing: Unable to test plate packing - no dictionary has been defined. " 
+            << endmsg;
+    }
+}
+
diff --git a/Scintillator/ScintDetDescr/ScintReadoutGeometry/ScintReadoutGeometry/ScintDetectorElement.h b/Scintillator/ScintDetDescr/ScintReadoutGeometry/ScintReadoutGeometry/ScintDetectorElement.h
index 2f1e7c0a95be335fcc8e349159d9f6b4b4efe213..6711c085aff1e6b7386f3680b04bb06b9dc65659 100644
--- a/Scintillator/ScintDetDescr/ScintReadoutGeometry/ScintReadoutGeometry/ScintDetectorElement.h
+++ b/Scintillator/ScintDetDescr/ScintReadoutGeometry/ScintReadoutGeometry/ScintDetectorElement.h
@@ -156,6 +156,7 @@ namespace ScintDD {
       bool isVeto() const;
       bool isTrigger() const;
       bool isPreshower() const;
+      bool isVetoNu() const;
 
       // Identifier <-> pmt 
     
@@ -458,6 +459,7 @@ namespace ScintDD {
       bool m_isVeto;
       bool m_isTrigger;
       bool m_isPreshower;
+      bool m_isVetoNu;
       
       //
       // Cached values.
diff --git a/Scintillator/ScintDetDescr/ScintReadoutGeometry/ScintReadoutGeometry/VetoNuDetectorManager.h b/Scintillator/ScintDetDescr/ScintReadoutGeometry/ScintReadoutGeometry/VetoNuDetectorManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..86d34b84c94bc6f7ef4087a57696afa0be7ce3b3
--- /dev/null
+++ b/Scintillator/ScintDetDescr/ScintReadoutGeometry/ScintReadoutGeometry/VetoNuDetectorManager.h
@@ -0,0 +1,163 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+///////////////////////////////////////////////////////////////////
+// VetoNuDetectorManager.h
+///////////////////////////////////////////////////////////////////
+// (c) ATLAS Detector software
+///////////////////////////////////////////////////////////////////
+
+#ifndef SCINTREADOUTGEOMETRY_VETONUDETECTORMANAGER_H
+#define SCINTREADOUTGEOMETRY_VETONUDETECTORMANAGER_H
+
+#include "GeoPrimitives/GeoPrimitives.h"
+
+#include "GeoModelKernel/GeoVPhysVol.h"
+
+#include "ScintReadoutGeometry/ScintDetectorManager.h"
+#include "ScintReadoutGeometry/ScintDetectorElementCollection.h"
+#include "ScintReadoutGeometry/ScintDD_Defs.h"
+
+#include "ScintIdentifier/VetoNuID.h"
+  
+class StoreGateSvc;
+class Identifier; 
+class IdentifierHash;
+class GeoAlignableTransform;
+class GeoVFullPhysVol;
+class GeoVPhysVol;
+class GeoVAlignmentStore;
+class CondAttrListCollection;
+
+namespace ScintDD {
+
+  class ScintDetectorElement;
+  class ExtendedAlignableTransform;
+  class VetoNuDetectorDesign;
+
+  /** @class VetoNuDetectorManager
+  
+      Dedicated detector manager extending the functionality of the ScintDetectorManager
+      with dedicated VetoNu information, access.
+      
+      @author: Grant Gorfine
+      - modified and maintained by Nick Styles & Andreas Salzburger
+      - modified for FASER by D. Casper
+      */
+      
+  class VetoNuDetectorManager : public ScintDetectorManager  {
+
+    public:
+    
+      // Constructor
+      VetoNuDetectorManager( StoreGateSvc* detStore );
+     
+      // Destructor
+      virtual ~VetoNuDetectorManager();
+     
+      /** Access Raw Geometry */
+      virtual unsigned int getNumTreeTops()           const override;
+      virtual PVConstLink  getTreeTop(unsigned int i) const override;
+      /** Add tree top */
+      void addTreeTop(PVLink); 
+    
+    
+      //
+      // Access Readout Elements
+      //
+    
+      /** access to individual elements via Identifier */
+      virtual ScintDetectorElement * getDetectorElement(const Identifier &id) const override;
+
+      /** access to individual elements via IdentifierHash */
+      virtual ScintDetectorElement * getDetectorElement(const IdentifierHash &idHash) const override;
+      
+      /** access to individual elements via module numbering schema */
+      ScintDetectorElement * getDetectorElement(int station, int plate) const;
+    
+      /** access to whole collectiom via iterators */
+      virtual const ScintDetectorElementCollection * getDetectorElementCollection() const override;
+      virtual ScintDetectorElementCollection::const_iterator getDetectorElementBegin() const override;
+      virtual ScintDetectorElementCollection::const_iterator getDetectorElementEnd() const override;
+    
+      /** Add elememts during construction */
+      virtual void addDetectorElement(ScintDetectorElement * element) override;
+    
+      /** Add alignable transforms. No access to these, they will be changed by manager: */
+      virtual void addAlignableTransform (int level,
+    				                      const Identifier &id, 
+    				                      GeoAlignableTransform *xf,
+    				                      const GeoVFullPhysVol * child);
+      
+      /**  As above but does a dynamic_cast to GeoVFullPhysVol */
+      virtual void addAlignableTransform (int level,
+    				                      const Identifier &id, 
+    				                      GeoAlignableTransform *xf,
+    				                      const GeoVPhysVol * child); 
+    
+      /** Initialize the neighbours. This can only be done when all elements are built. */
+      virtual void initNeighbours() override;
+
+      /** Check identifier is for this detector */
+      virtual bool identifierBelongs(const Identifier & id) const override;
+    
+      /** Access to module design, casts to VetoNuDetectorDesign */
+      const ScintDetectorDesign * getVetoNuDesign() const;
+
+      /** Process new global DB folders for L1 and L2 **/
+      virtual
+      bool processGlobalAlignment(const std::string &, int level, FrameType frame,
+                                  const CondAttrListCollection* obj,
+                                  GeoVAlignmentStore* alignStore) const override;
+
+      // comply with ScintDetectorManager interface
+      bool processSpecialAlignment(const std::string & key,
+                                   ScintDD::AlignFolderType alignfolder) const override;
+
+      bool processSpecialAlignment(const std::string& key,
+                                   const CondAttrListCollection* obj=nullptr,
+                                   GeoVAlignmentStore* alignStore=nullptr) const override;
+
+
+    private:
+      /** implements the main alignment update for delta transforms in different frames,
+          it translates into the LocalDelta or GlobalDelta function of ScintDetectorManager
+      */
+      virtual bool setAlignableTransformDelta(int level, 
+                                              const Identifier & id, 
+                                              const Amg::Transform3D & delta,
+                                              FrameType frame,
+                                              GeoVAlignmentStore* alignStore) const override;
+      
+      /** Prevent copy and assignment */
+      const VetoNuDetectorManager & operator=(const VetoNuDetectorManager &right);
+      VetoNuDetectorManager(const VetoNuDetectorManager &right); 
+    
+      virtual const VetoNuID* getIdHelper() const override;
+       
+      // Private member data
+      std::vector<PVLink>                                           m_volume;  
+      ScintDetectorElementCollection                                m_elementCollection;
+      typedef std::map<Identifier, ExtendedAlignableTransform *>    AlignableTransformMap;
+      std::vector< AlignableTransformMap >                          m_higherAlignableTransforms;
+      std::vector< ExtendedAlignableTransform *>                    m_alignableTransforms; 
+      const VetoNuID*                                               m_idHelper;
+      
+      /** This variable switches the how the local alignment corrections are applied
+          If true they will be calcualted on top  of all of other corrections but in the default reference frame
+          If false they will be calcualted  on top  of all of other corrections but in the globally aligned reference frame    
+      */
+      bool                                                          m_isLogical;      
+      
+      
+    };
+
+} // namespace ScintDD
+
+#ifndef GAUDI_NEUTRAL
+#include "AthenaKernel/CLASS_DEF.h" 
+CLASS_DEF(ScintDD::VetoNuDetectorManager, 238247911, 1) 
+#endif
+
+#endif // SCINTREADOUTGEOMETRY_VETONIDETECTORMANAGER_H
diff --git a/Scintillator/ScintDetDescr/ScintReadoutGeometry/src/ScintDetectorElement.cxx b/Scintillator/ScintDetDescr/ScintReadoutGeometry/src/ScintDetectorElement.cxx
index 3b4a0c4812177549e28af5e39cae2a4ed2d92e91..e06fa94894bf323189fad6f786d9e1daf6b57da1 100644
--- a/Scintillator/ScintDetDescr/ScintReadoutGeometry/src/ScintDetectorElement.cxx
+++ b/Scintillator/ScintDetDescr/ScintReadoutGeometry/src/ScintDetectorElement.cxx
@@ -14,6 +14,7 @@
 #include "ScintIdentifier/VetoID.h"
 #include "ScintIdentifier/TriggerID.h"
 #include "ScintIdentifier/PreshowerID.h"
+#include "ScintIdentifier/VetoNuID.h"
 
 #include "GeoModelKernel/GeoVFullPhysVol.h"
 #include "GeoModelFaserUtilities/GeoAlignmentStore.h"
@@ -93,13 +94,14 @@ ScintDetectorElement::commonConstructor()
   m_isVeto = getIdHelper()->is_veto(m_id);
   m_isTrigger = getIdHelper()->is_trigger(m_id);
   m_isPreshower = getIdHelper()->is_preshower(m_id);
-  if (!m_isVeto && !m_isTrigger && !m_isPreshower)
+  m_isVetoNu = getIdHelper()->is_vetonu(m_id);
+  if (!m_isVeto && !m_isTrigger && !m_isPreshower && !m_isVetoNu)
   {
-    if (msgLvl(MSG::WARNING)) msg(MSG::WARNING) << "Element id is not for veto, trigger or preshower" << endmsg;
+    if (msgLvl(MSG::WARNING)) msg(MSG::WARNING) << "Element id is not for veto, trigger, preshower or vetonu" << endmsg;
   }
-  else if ((m_isVeto && m_isTrigger) || (m_isVeto && m_isPreshower) || (m_isTrigger && m_isPreshower))
+  else if ((m_isVeto && m_isTrigger) || (m_isVeto && m_isPreshower) || (m_isTrigger && m_isPreshower) || (m_isVetoNu && (m_isVeto || m_isTrigger || m_isPreshower)))
   {
-    if (msgLvl(MSG::WARNING)) msg(MSG::WARNING) << "Element id belongs to more than one of veto, trigger or preshower" << endmsg;
+    if (msgLvl(MSG::WARNING)) msg(MSG::WARNING) << "Element id belongs to more than one of veto, trigger, preshower or vetonu" << endmsg;
   }
   
   // Set IdHash.
@@ -118,6 +120,11 @@ ScintDetectorElement::commonConstructor()
       const PreshowerID* preshowerId = dynamic_cast<const PreshowerID* >(getIdHelper());
       m_idHash = preshowerId->plate_hash(m_id);
   }
+  else if (isVetoNu())
+  {
+      const VetoNuID* vetoNuId = dynamic_cast<const VetoNuID* >(getIdHelper());
+      m_idHash = vetoNuId->plate_hash(m_id);
+  }
   
   if (!m_idHash.is_valid()) throw std::runtime_error("ScintDetectorElement: Unable to set IdentifierHash");
 
@@ -599,6 +606,11 @@ bool ScintDetectorElement::isPreshower() const
   return m_isPreshower;
 }
 
+bool ScintDetectorElement::isVetoNu() const
+{
+  return m_isVetoNu;
+}
+
 Trk::Surface & 
 ScintDetectorElement::surface()
 {
diff --git a/Scintillator/ScintDetDescr/ScintReadoutGeometry/src/VetoNuDetectorManager.cxx b/Scintillator/ScintDetDescr/ScintReadoutGeometry/src/VetoNuDetectorManager.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..b7990ebc33a4b0af1f4e24dcdc5acd6bfc21d611
--- /dev/null
+++ b/Scintillator/ScintDetDescr/ScintReadoutGeometry/src/VetoNuDetectorManager.cxx
@@ -0,0 +1,348 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h"
+
+#include "AthenaBaseComps/AthMsgStreamMacros.h"
+#include "AthenaPoolUtilities/CondAttrListCollection.h"
+#include "GeoPrimitives/CLHEPtoEigenConverter.h"
+#include "Identifier/Identifier.h"
+#include "Identifier/IdentifierHash.h"
+#include "ScintIdentifier/VetoNuID.h"
+#include "ScintReadoutGeometry/ScintDetectorElementCollection.h"
+#include "ScintReadoutGeometry/ScintDetectorElement.h"
+#include "ScintReadoutGeometry/ExtendedAlignableTransform.h"
+#include "ScintReadoutGeometry/ScintDetectorDesign.h"
+#include "StoreGate/StoreGateSvc.h"
+
+#include <iostream>
+
+namespace ScintDD {
+
+  const int FIRST_HIGHER_LEVEL = 1;
+
+  VetoNuDetectorManager::VetoNuDetectorManager( StoreGateSvc* detStore )
+    : ScintDetectorManager(detStore, "VetoNu"),
+      m_idHelper(0),
+      m_isLogical(false) // Change to true to change the definition of local module corrections
+  {
+    //  
+    // Initialized the Identifier helper.
+    //
+    StatusCode sc = detStore->retrieve(m_idHelper, "VetoNuID");  
+    if (sc.isFailure()) {
+      ATH_MSG_ERROR("Could not retrieve VetoNu id helper");
+    }
+    // Initialize the collections.
+    if (m_idHelper) {
+      m_elementCollection.resize(m_idHelper->plate_hash_max());
+      m_alignableTransforms.resize(m_idHelper->plate_hash_max());
+      // m_moduleAlignableTransforms.resize(m_idHelper->plate_hash_max());
+    } 
+  }
+
+
+
+  VetoNuDetectorManager::~VetoNuDetectorManager()
+  {
+    // Clean up
+    for (size_t i=0; i < m_volume.size(); i++) {
+      m_volume[i]->unref();
+    }
+
+    for (size_t j=0; j < m_higherAlignableTransforms.size(); j++){
+      AlignableTransformMap::iterator iterMap;  
+      for (iterMap = m_higherAlignableTransforms[j].begin(); 
+           iterMap != m_higherAlignableTransforms[j].end();
+           ++iterMap) {
+        delete iterMap->second;
+      }
+    }
+
+    for (size_t k=0; k < m_alignableTransforms.size(); k++){
+      delete m_alignableTransforms[k];
+    }
+
+    // for (size_t l=0; l < m_moduleAlignableTransforms.size(); l++){
+    //   delete m_moduleAlignableTransforms[l];
+    // }
+  }
+
+  unsigned int VetoNuDetectorManager::getNumTreeTops() const
+  {
+    return m_volume.size(); 
+  }
+
+  PVConstLink VetoNuDetectorManager::getTreeTop(unsigned int i) const
+  {
+    return m_volume[i];
+  }
+
+  void VetoNuDetectorManager::addTreeTop(PVLink vol){
+    vol->ref();
+    m_volume.push_back(vol);
+  }
+
+
+  ScintDetectorElement* VetoNuDetectorManager::getDetectorElement(const Identifier & id) const
+  {  
+    // NB the id helpers implementation for getting a hash is not optimal.
+    // Essentially does a binary search.
+    // Make sure it is a wafer Id
+    Identifier waferId =  m_idHelper->plate_id(id);
+    IdentifierHash idHash = m_idHelper->plate_hash(waferId);
+    if (idHash.is_valid()) {
+      return m_elementCollection[idHash];
+    } else {
+      return 0;
+    }
+  }
+
+  ScintDetectorElement* VetoNuDetectorManager::getDetectorElement(const IdentifierHash & idHash) const
+  {
+    return m_elementCollection[idHash];
+  }
+
+  ScintDetectorElement* VetoNuDetectorManager::getDetectorElement(int station, int plate) const
+  {
+    return getDetectorElement(m_idHelper->plate_id(station, plate));
+  }
+
+  const ScintDetectorElementCollection* VetoNuDetectorManager::getDetectorElementCollection() const
+  { 
+    return &m_elementCollection;
+  }
+
+  ScintDetectorElementCollection::const_iterator VetoNuDetectorManager::getDetectorElementBegin() const
+  {
+    return m_elementCollection.begin();
+  }
+
+  ScintDetectorElementCollection::const_iterator VetoNuDetectorManager::getDetectorElementEnd() const
+  {
+    return m_elementCollection.end();
+  }
+
+
+  void VetoNuDetectorManager::addDetectorElement(ScintDetectorElement * element)
+  {
+    IdentifierHash idHash = element->identifyHash();
+    if (idHash >=  m_elementCollection.size())
+      throw std::runtime_error("VetoNuDetectorManager: Error adding detector element.");
+    m_elementCollection[idHash] = element;
+  }
+
+  void VetoNuDetectorManager::initNeighbours()
+  {
+    ScintDetectorElementCollection::iterator iter;
+
+    // Loop over all elements and set the neighbours
+    for (iter = m_elementCollection.begin(); iter != m_elementCollection.end(); ++iter){
+
+      ScintDetectorElement * element = *iter;
+      if (element) {
+
+        IdentifierHash idHash = element->identifyHash();
+        IdentifierHash idHashOther;
+
+        int result;
+        // If no neighbour, result != 0 in which case we leave neighbour as null
+        result = m_idHelper->get_next_in_z(idHash, idHashOther);
+        if (result==0) element->setNextInZ(m_elementCollection[idHashOther]);
+
+        result = m_idHelper->get_prev_in_z(idHash, idHashOther);
+        if (result==0) element->setPrevInZ(m_elementCollection[idHashOther]);
+      }
+    }
+  }
+
+  const VetoNuID* VetoNuDetectorManager::getIdHelper() const
+  {
+    return m_idHelper;
+  }
+
+
+  bool VetoNuDetectorManager::setAlignableTransformDelta(int level, 
+                                                       const Identifier & id, 
+                                                       const Amg::Transform3D & delta,
+                                                       FrameType frame,
+                                                       GeoVAlignmentStore* alignStore) const
+  {
+
+    if (level == 0) { // 0 - At the element level
+
+      // We retrieve it via a hashId.
+      IdentifierHash idHash = m_idHelper->plate_hash(id);
+      if (!idHash.is_valid()) return false;
+
+      if (frame == ScintDD::global) { // global shift
+        // Its a global transform
+        return setAlignableTransformGlobalDelta(m_alignableTransforms[idHash], delta, alignStore);
+
+      } else if (frame == ScintDD::local) { // local shift
+
+        ScintDetectorElement * element =  m_elementCollection[idHash];
+        if (!element) return false;
+
+
+        // Its a local transform
+        //See header file for definition of m_isLogical          
+        if( m_isLogical ){
+          //Ensure cache is up to date and use the alignment corrected local to global transform
+          element->setCache();
+          return setAlignableTransformLocalDelta(m_alignableTransforms[idHash], element->transform(), delta, alignStore);
+        } else 
+          //Use default local to global transform
+          return setAlignableTransformLocalDelta(m_alignableTransforms[idHash], element->defTransform(), delta, alignStore);
+
+      } else {   
+        // other not supported
+        ATH_MSG_WARNING("Frames other than global or local are not supported.");
+        return false;
+      }
+    } else { // higher level
+      if (frame != ScintDD::global) {
+        ATH_MSG_WARNING("Non global shift at higher levels is not supported.");
+        return false;
+      }
+
+      int index = level - FIRST_HIGHER_LEVEL; // level 0 is treated separately.
+      if (index  >=  static_cast<int>(m_higherAlignableTransforms.size())) return false;
+
+      // We retrieve it from a map. 
+      AlignableTransformMap::const_iterator iter;    
+      iter = m_higherAlignableTransforms[index].find(id);
+      if (iter == m_higherAlignableTransforms[index].end()) return false;      
+
+      // Its a global transform
+      return setAlignableTransformGlobalDelta(iter->second, delta, alignStore);
+    }
+  }
+
+  void VetoNuDetectorManager::addAlignableTransform (int level, 
+                                                   const Identifier & id, 
+                                                   GeoAlignableTransform *transform,
+                                                   const GeoVPhysVol * child)
+  {
+    if (m_idHelper) {
+
+      const GeoVFullPhysVol * childFPV = dynamic_cast<const GeoVFullPhysVol *>(child);
+      if (!childFPV) { 
+        ATH_MSG_ERROR("Child of alignable transform is not a full physical volume");
+      } else {
+        addAlignableTransform (level, id, transform, childFPV);
+      }
+    }
+  }
+
+  void VetoNuDetectorManager::addAlignableTransform (int level, 
+                                                   const Identifier & id, 
+                                                   GeoAlignableTransform *transform,
+                                                   const GeoVFullPhysVol * child)
+  { 
+    if (m_idHelper) {
+      if (level == 0) { 
+        // Element
+        IdentifierHash idHash = m_idHelper->plate_hash(id);
+        if (idHash.is_valid()) {
+          m_alignableTransforms[idHash]= new ExtendedAlignableTransform(transform, child);
+        } 
+      } else {
+        // Higher levels are saved in a map. NB level=0 is treated above.   
+        int index = level - FIRST_HIGHER_LEVEL; // level 0 is treated separately.
+        if (index >= static_cast<int>(m_higherAlignableTransforms.size())) m_higherAlignableTransforms.resize(index+1); 
+        m_higherAlignableTransforms[index][id] = new ExtendedAlignableTransform(transform, child);
+      }  
+    }
+  }
+
+  bool
+  VetoNuDetectorManager::identifierBelongs(const Identifier & id) const
+  {
+    return getIdHelper()->is_vetonu(id);
+  }
+
+
+  const ScintDetectorDesign* VetoNuDetectorManager::getVetoNuDesign() const
+  {
+    return dynamic_cast<const ScintDetectorDesign *>(getDesign());
+  }
+
+  // New global alignment folders
+  bool VetoNuDetectorManager::processGlobalAlignment(const std::string & key, int level, FrameType frame,
+                                                   const CondAttrListCollection* obj, GeoVAlignmentStore* alignStore) const
+  {
+    ATH_MSG_INFO("Processing new global alignment containers with key " << key << " in the " << frame << " frame at level ");
+
+    const CondAttrListCollection* atrlistcol=obj;
+    if(atrlistcol==nullptr and m_detStore->retrieve(atrlistcol,key)!=StatusCode::SUCCESS) {
+      ATH_MSG_INFO("Cannot find new global align Container for key "
+                   << key << " - no new global alignment ");
+      return false;
+    }
+
+    bool alignmentChange = false;
+    Identifier ident=Identifier();
+
+    // loop over objects in collection
+    for (CondAttrListCollection::const_iterator citr=atrlistcol->begin(); citr!=atrlistcol->end();++citr) {
+      const coral::AttributeList& atrlist=citr->second;
+      // SCT manager, therefore ignore all that is not a SCT Identifier
+      //   if (atrlist["det"].data<int>()!=2) continue;
+      ATH_MSG_FATAL("Using invalid alignment data for VetoNu detector (correct treatment not yet implemented");
+      ident = getIdHelper()->plate_id(atrlist["station"].data<int>(),
+                                      atrlist["plate"].data<int>()); // The last is the module side which is at this ident-level always the 0-side
+
+      // construct new transform
+      // Order of rotations is defined as around z, then y, then x.
+      Amg::Translation3D  newtranslation(atrlist["Tx"].data<float>(),atrlist["Ty"].data<float>(),atrlist["Tz"].data<float>());
+      Amg::Transform3D newtrans = newtranslation * Amg::RotationMatrix3D::Identity();
+      newtrans *= Amg::AngleAxis3D(atrlist["Rz"].data<float>()*CLHEP::mrad, Amg::Vector3D(0.,0.,1.));
+      newtrans *= Amg::AngleAxis3D(atrlist["Ry"].data<float>()*CLHEP::mrad, Amg::Vector3D(0.,1.,0.));
+      newtrans *= Amg::AngleAxis3D(atrlist["Rx"].data<float>()*CLHEP::mrad, Amg::Vector3D(1.,0.,0.));
+
+      ATH_MSG_DEBUG("New global DB -- channel: " << citr->first
+                    << " ,det: "    << atrlist["det"].data<int>()
+                    << " ,bec: "    << atrlist["bec"].data<int>()
+                    << " ,layer: "  << atrlist["layer"].data<int>()
+                    << " ,ring: "   << atrlist["ring"].data<int>()
+                    << " ,sector: " << atrlist["sector"].data<int>()
+                    << " ,Tx: "     << atrlist["Tx"].data<float>()
+                    << " ,Ty: "     << atrlist["Ty"].data<float>()
+                    << " ,Tz: "     << atrlist["Tz"].data<float>()
+                    << " ,Rx: "     << atrlist["Rx"].data<float>()
+                    << " ,Ry: "     << atrlist["Ry"].data<float>()
+                    << " ,Rz: "     << atrlist["Rz"].data<float>());
+
+      // Set the new transform; Will replace existing one with updated transform
+      bool status = setAlignableTransformDelta(level,
+                                               ident,
+                                               newtrans,
+                                               frame,
+                                               alignStore);
+
+      if (!status) {
+        ATH_MSG_DEBUG("Cannot set AlignableTransform for identifier."
+                      << getIdHelper()->show_to_string(ident)
+                      << " at level " << level << " for new global DB ");
+      }
+
+      alignmentChange = (alignmentChange || status);
+    }
+    return alignmentChange;
+  }
+
+bool VetoNuDetectorManager::processSpecialAlignment(
+    const std::string &, ScintDD::AlignFolderType) const {
+  return false;
+}
+
+bool VetoNuDetectorManager::processSpecialAlignment(const std::string& /*key*/,
+                                                  const CondAttrListCollection* /*obj*/,
+                                                  GeoVAlignmentStore* /*alignStore*/) const {
+  return false;
+
+}
+
+} // namespace ScintDD
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/CMakeLists.txt b/Scintillator/ScintDetDescr/VetoNuGeoModel/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..404a64a14c4deecf4fd9f51908c5800e0129cd28
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/CMakeLists.txt
@@ -0,0 +1,29 @@
+################################################################################
+# Package: VetoNuGeoModel
+################################################################################
+
+# Declare the package name:
+atlas_subdir( VetoNuGeoModel )
+
+# External dependencies:
+find_package( Boost COMPONENTS filesystem thread system )
+find_package( CORAL COMPONENTS CoralBase CoralKernel RelationalAccess )
+find_package( Eigen )
+find_package( GeoModel )
+
+# Component(s) in the package:
+atlas_add_component( VetoNuGeoModel
+        		     src/*.cxx
+                     src/components/*.cxx
+                     INCLUDE_DIRS ${Boost_INCLUDE_DIRS} ${CORAL_INCLUDE_DIRS} 
+                     LINK_LIBRARIES ${Boost_LIBRARIES} ${CORAL_LIBRARIES} ${GEOMODEL_LIBRARIES} AthenaKernel GeoModelFaserUtilities GaudiKernel SGTools StoreGateLib SGtests AthenaPoolUtilities DetDescrConditions FaserDetDescr ScintGeoModelUtils ScintReadoutGeometry ScintIdentifier Identifier )
+
+atlas_add_test( VetoNuGMConfig_test
+                SCRIPT python ${CMAKE_CURRENT_SOURCE_DIR}/test/VetoNuGMConfig_test.py
+                PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+                PROPERTIES TIMEOUT 300 )
+
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py )
+atlas_install_scripts( test/*.py )
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/VetoNuGeoModel/VetoNuDetectorTool.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/VetoNuGeoModel/VetoNuDetectorTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..60e4ee6ab63659996fd6b2b942e6dc64ceb03fbb
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/VetoNuGeoModel/VetoNuDetectorTool.h
@@ -0,0 +1,61 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUGEOMODEL_VETONUDETECTORTOOL_H
+#define VETONUGEOMODEL_VETONUDETECTORTOOL_H
+
+#include "GeoModelFaserUtilities/GeoModelTool.h"
+#include "VetoNuGeoModel/VetoNuGeoModelAthenaComps.h" 
+
+#include "GeometryDBSvc/IGeometryDBSvc.h"
+#include "GeoModelInterfaces/IGeoDbTagSvc.h"
+#include "RDBAccessSvc/IRDBAccessSvc.h"
+
+#include "GaudiKernel/ServiceHandle.h"
+
+#include <string>
+
+namespace ScintDD {
+  class VetoNuDetectorManager;
+}
+
+class VetoNuID;
+// class FaserDetectorID;
+
+class VetoNuDetectorTool : public GeoModelTool {
+
+public:
+  // Standard Constructor
+  VetoNuDetectorTool(const std::string& type, const std::string& name, const IInterface* parent);
+
+  virtual StatusCode create() override final;
+  virtual StatusCode clear() override final;
+
+  // Register callback function on ConDB object
+  virtual StatusCode registerCallback ATLAS_NOT_THREAD_SAFE () override final;
+
+  // Callback function itself
+  virtual StatusCode align(IOVSVC_CALLBACK_ARGS) override;
+
+private:
+  StringProperty m_detectorName{this, "DetectorName", "VetoNu"};
+  BooleanProperty m_alignable{this, "Alignable", true};
+  BooleanProperty m_useDynamicAlignFolders{this, "useDynamicAlignFolders", false};
+  bool m_cosmic;
+
+  const ScintDD::VetoNuDetectorManager* m_manager;
+  
+  VetoNuGeoModelAthenaComps m_athenaComps;
+
+  ServiceHandle< IGeoDbTagSvc > m_geoDbTagSvc;
+  ServiceHandle< IRDBAccessSvc > m_rdbAccessSvc;
+  ServiceHandle< IGeometryDBSvc > m_geometryDBSvc;
+
+  StringProperty m_run1Folder{this,   "Run1Folder",   "/Scint/Align"};
+  StringProperty m_run2L1Folder{this, "Run2L1Folder", "/Scint/AlignL1/ID"};
+  StringProperty m_run2L2Folder{this, "Run2L2Folder", "/Scint/AlignL2/SCT"};
+  StringProperty m_run2L3Folder{this, "Run2L3Folder", "/Scint/AlignL3"};
+};
+
+#endif // VETOGEOMODEL_VETODETECTORTOOL_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/VetoNuGeoModel/VetoNuGeoModelAthenaComps.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/VetoNuGeoModel/VetoNuGeoModelAthenaComps.h
new file mode 100644
index 0000000000000000000000000000000000000000..c873da9365eade697769bbcc116d05bfd1b9ee45
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/VetoNuGeoModel/VetoNuGeoModelAthenaComps.h
@@ -0,0 +1,29 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuGeoModelAthenaComps_H
+#define VetoNuGeoModel_VetoNuGeoModelAthenaComps_H 1
+
+#include "ScintGeoModelUtils/ScintDDAthenaComps.h"
+
+class VetoNuID;
+
+/// Class to hold various Athena components
+// template <class ID_HELPER>
+class VetoNuGeoModelAthenaComps : public ScintDD::AthenaComps {
+
+public:
+
+  VetoNuGeoModelAthenaComps();
+
+  void setIdHelper(const VetoNuID* idHelper);
+
+  const VetoNuID* getIdHelper() const;
+
+private:
+  const VetoNuID* m_idHelper;
+
+};
+
+#endif // VetoNuGeoModel_VetoNuGeoModelAthenaComps_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/python/VetoNuGeoModelConfig.py b/Scintillator/ScintDetDescr/VetoNuGeoModel/python/VetoNuGeoModelConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f55fbad90243aa8ba56ba4044a99ca085a1a811
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/python/VetoNuGeoModelConfig.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+# from IOVDbSvc.IOVDbSvcConfig import addFoldersSplitOnline
+
+def VetoNuGeometryCfg( flags ):
+    from FaserGeoModel.GeoModelConfig import GeoModelCfg
+    acc = GeoModelCfg( flags )
+    geoModelSvc = acc.getPrimary()
+
+    GeometryDBSvc=CompFactory.GeometryDBSvc
+    acc.addService(GeometryDBSvc("ScintGeometryDBSvc"))
+
+    VetoNuDetectorTool = CompFactory.VetoNuDetectorTool
+    vetoNuDetectorTool = VetoNuDetectorTool()
+
+    vetoNuDetectorTool.useDynamicAlignFolders = flags.GeoModel.Align.Dynamic
+    geoModelSvc.DetectorTools += [ vetoNuDetectorTool ]
+
+    return acc
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuComponentFactory.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuComponentFactory.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..711a8dfd097f349dddc59d33a00dc955090bcd2c
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuComponentFactory.cxx
@@ -0,0 +1,40 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuComponentFactory.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+#include <sstream>
+#include <string>
+
+using ScintDD::VetoNuDetectorManager;
+
+const double VetoNuComponentFactory::s_epsilon = 1.0e-6 * Gaudi::Units::mm;
+
+VetoNuComponentFactory::VetoNuComponentFactory(const std::string & name,
+                                           ScintDD::VetoNuDetectorManager* detectorManager,
+                                           const VetoNuGeometryManager* geometryManager,
+                                           VetoNuMaterialManager* materials)
+  : m_detectorManager(detectorManager), 
+    m_geometryManager(geometryManager),
+    m_materials(materials),
+    m_name(name)
+{}
+
+VetoNuComponentFactory::~VetoNuComponentFactory() 
+{}
+
+std::string 
+VetoNuComponentFactory::intToString(int i) const
+{
+  std::ostringstream str;
+  str << i;
+  return str.str();
+}
+
+double
+VetoNuComponentFactory::epsilon() const
+{
+  return s_epsilon;
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuComponentFactory.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuComponentFactory.h
new file mode 100644
index 0000000000000000000000000000000000000000..340f896f9b111f7b620194b7ded5943c72523899
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuComponentFactory.h
@@ -0,0 +1,89 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUGEOMODEL_VETONUCOMPONENTFACTORY_H
+#define VETONUGEOMODEL_VETONUCOMPONENTFACTORY_H
+
+#include "VetoNuIdentifier.h"
+#include <string>
+
+namespace ScintDD{class VetoNuDetectorManager;}
+class VetoNuGeometryManager;
+class VetoNuMaterialManager;
+
+class GeoLogVol;
+class GeoVPhysVol;
+
+
+class VetoNuComponentFactory
+{
+
+public:
+  VetoNuComponentFactory(const std::string & name,
+                       ScintDD::VetoNuDetectorManager* detectorManager,
+                       const VetoNuGeometryManager* geometryManager,
+                       VetoNuMaterialManager* materials);
+
+  const std::string & getName() const {return m_name;}
+
+  // utility function to covert int to string
+  std::string intToString(int i) const;
+
+protected: 
+  ScintDD::VetoNuDetectorManager* m_detectorManager;
+  const VetoNuGeometryManager* m_geometryManager;
+  VetoNuMaterialManager* m_materials;
+
+  double epsilon() const;
+  virtual ~VetoNuComponentFactory();
+
+private:
+  std::string m_name;
+  static const double s_epsilon;
+
+};
+
+
+class VetoNuSharedComponentFactory : public VetoNuComponentFactory
+{
+
+public:
+  VetoNuSharedComponentFactory(const std::string & name,
+                             ScintDD::VetoNuDetectorManager* detectorManager,
+                             const VetoNuGeometryManager* geometryManager,
+                             VetoNuMaterialManager* materials=nullptr) :
+    VetoNuComponentFactory(name, detectorManager, geometryManager, materials),
+    m_physVolume(nullptr)
+  {};
+  
+  GeoVPhysVol * getVolume() {return  m_physVolume;}
+
+protected:
+  GeoVPhysVol * m_physVolume;
+  virtual GeoVPhysVol * build() = 0;
+
+};
+
+class VetoNuUniqueComponentFactory : public VetoNuComponentFactory
+{
+
+public:
+  VetoNuUniqueComponentFactory(const std::string & name,
+                             ScintDD::VetoNuDetectorManager* detectorManager,
+                             const VetoNuGeometryManager* geometryManager,
+                             VetoNuMaterialManager* materials=nullptr) :
+    VetoNuComponentFactory(name, detectorManager, geometryManager, materials),
+    m_logVolume{nullptr}
+  {};
+
+  virtual GeoVPhysVol * build(VetoNuIdentifier id) = 0;
+
+protected:
+  const GeoLogVol * m_logVolume;
+
+  virtual const GeoLogVol * preBuild() = 0;
+
+};
+
+#endif // VETOGEOMODEL_VETOCOMPONENTFACTORY_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDataBase.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDataBase.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..58c19908bda3299b05f846b04c24439d01d499ec
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDataBase.cxx
@@ -0,0 +1,109 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuDataBase.h"
+
+#include "RDBAccessSvc/IRDBAccessSvc.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+#include "RDBAccessSvc/IRDBRecord.h"
+
+#include "GeoModelInterfaces/IGeoDbTagSvc.h"
+#include "GeoModelFaserUtilities/DecodeFaserVersionKey.h"
+
+#include "VetoNuGeoModel/VetoNuGeoModelAthenaComps.h"
+
+#include <iostream>
+
+VetoNuDataBase::VetoNuDataBase(const VetoNuGeoModelAthenaComps * athenaComps)
+{
+  m_athenaComps = athenaComps;
+
+  IGeoDbTagSvc * geoDbTag = m_athenaComps->geoDbTagSvc();
+
+  // Get version tag and node for VetoNu
+  DecodeFaserVersionKey versionKey(geoDbTag,"VetoNu");
+  std::string versionTag  = versionKey.tag();
+  std::string versionNode = versionKey.node();
+
+  // Get version tag and node for Scintillator.
+  DecodeFaserVersionKey scintVersionKey(geoDbTag,"Scintillator");
+
+  // Access the RDB
+  IRDBAccessSvc* rdbSvc = m_athenaComps->rdbAccessSvc();
+
+  // VetoNu version tag
+  m_vetoVersionTag = rdbSvc->getChildTag("VetoNu", versionKey.tag(), versionKey.node(), "FASERDD");
+
+
+/////////////////////////////////////////////////////////
+//
+// Gets the structures
+//
+/////////////////////////////////////////////////////////
+
+    msg(MSG::INFO) << "Retrieving Record Sets from database ..." << endmsg;
+    msg(MSG::DEBUG) << " Using version tag: " << versionTag << endmsg;
+    msg(MSG::DEBUG) << "           at node: " << versionNode << endmsg;
+    msg(MSG::DEBUG) << " VetoNu Version:       " << m_vetoVersionTag << endmsg;
+
+  // ATLS - not sure I use it.
+  // General atlas parameters
+
+  //
+  // VetoNu General
+  //
+
+  // VetoNu TopLevel
+  m_topLevel = rdbSvc->getRecordsetPtr("VetoNuTopLevel", versionTag, versionNode, "FASERDD");
+  msg(MSG::DEBUG) << "Table VetoNuTopLevel Fetched" << endmsg;
+
+  // Weight Table
+  m_weightTable = rdbSvc->getRecordsetPtr("VetoNuWeights", versionTag, versionNode, "FASERDD");
+  msg(MSG::DEBUG) << "Table VetoNuWeights Fetched" << endmsg;
+
+  // Extra Scaling Table. This is used for extra material studies. For nominal material the table should be empty.
+  // NB this is at InnerDetector level node.
+  m_scalingTable = rdbSvc->getRecordsetPtr("VetoNuMatScaling", scintVersionKey.tag(), scintVersionKey.node(), "FASERDD");
+  msg(MSG::DEBUG) << "Table VetoNuMatScaling Fetched" << endmsg;
+
+//   // Default conditions
+//   m_conditions = rdbSvc->getRecordsetPtr("VetoNuConditions", versionTag, versionNode, "FASERDD");
+//   msg(MSG::DEBUG) << "Table VetoNuConditions Fetched" << endmsg;
+
+  m_stationGeneral = rdbSvc->getRecordsetPtr("VetoNuStationGeneral", versionTag, versionNode, "FASERDD");
+  msg(MSG::DEBUG) << "Table VetoNuStationGeneral Fetched" << endmsg;
+
+  m_plateGeneral = rdbSvc->getRecordsetPtr("VetoNuPlateGeneral", versionTag, versionNode, "FASERDD");
+  msg(MSG::DEBUG) << "Table VetoNuPlateGeneral Fetched" << endmsg;
+
+  m_radiatorGeneral = rdbSvc->getRecordsetPtr("VetoNuRadiatorGeneral", versionTag, versionNode, "FASERDD");
+  msg(MSG::DEBUG) << "Table VetoNuRadiatorGeneral Fetched" << endmsg;
+
+
+}
+
+const VetoNuGeoModelAthenaComps* VetoNuDataBase::athenaComps() const { return m_athenaComps; }
+
+IRDBRecordset_ptr VetoNuDataBase::weightTable() const {return m_weightTable;}
+
+IRDBRecordset_ptr VetoNuDataBase::scalingTable() const {return m_scalingTable;}
+
+// //const IRDBRecord* VetoNuDataBase::atls() const {return *m_atls)[0];}  
+IRDBRecordset_ptr VetoNuDataBase::topLevelTable() const {return m_topLevel;}
+
+// IRDBRecordset_ptr VetoNuDataBase::conditionsTable() const {return m_conditions;}
+// const IRDBRecord* VetoNuDataBase::conditions() const {return (*m_conditions)[0];}
+
+const IRDBRecord* VetoNuDataBase::stationGeneral() const {return (*m_stationGeneral)[0];}
+const IRDBRecord* VetoNuDataBase::plateGeneral() const {return (*m_plateGeneral)[0];}
+const IRDBRecord* VetoNuDataBase::radiatorGeneral() const {return (*m_radiatorGeneral)[0];}
+
+const std::string & VetoNuDataBase::versionTag() const {
+  return m_vetoVersionTag;
+}
+
+MsgStream&  VetoNuDataBase::msg (MSG::Level lvl) const 
+{ 
+  return m_athenaComps->msg(lvl); 
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDataBase.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDataBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..1028ed0198798fabf12c64604f9b25ddc355a427
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDataBase.h
@@ -0,0 +1,155 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuDataBase_H
+#define VetoNuGeoModel_VetoNuDataBase_H
+
+#include "VetoNuGeoModel/VetoNuGeoModelAthenaComps.h"
+#include <string>
+
+#include "RDBAccessSvc/IRDBAccessSvc.h"
+class IRDBRecord;
+
+
+class VetoNuDataBase
+{
+
+  
+public:
+
+  VetoNuDataBase(const VetoNuGeoModelAthenaComps* athenaComps);
+
+  const VetoNuGeoModelAthenaComps* athenaComps() const;
+
+  IRDBRecordset_ptr weightTable() const;
+  IRDBRecordset_ptr scalingTable() const;
+  IRDBRecordset_ptr topLevelTable() const;
+
+//   IRDBRecordset_ptr conditionsTable() const;
+//   const IRDBRecord* conditions() const;
+
+//   const IRDBRecord* brlSensor() const;
+//   const IRDBRecord* brlModule() const;
+
+//   const IRDBRecord* brlSki() const;
+//   const IRDBRecord* brlSkiZ(int i) const;
+//   int brlSkiZSize() const;
+//   const IRDBRecord* brlLayer(int i) const;
+//   const IRDBRecord* brlServices() const;
+//   const IRDBRecord* brlServPerLayer(int i) const;
+//   const IRDBRecord* brlThermalShield() const;
+  const IRDBRecord* stationGeneral() const;
+  const IRDBRecord* plateGeneral() const;
+  const IRDBRecord* radiatorGeneral() const;
+//   const IRDBRecord* brlFSI() const;
+//   int brlFSISize() const;
+//   const IRDBRecord* brlFSILocation(int i) const;
+
+//   const IRDBRecord* fwdSensor(int i) const;
+//   const IRDBRecord* fwdHybrid() const;
+//   const IRDBRecord* fwdSpine(int i) const;
+//   const IRDBRecord* fwdModule(int i) const;
+//   int fwdModuleSize() const;
+//   IRDBRecordset_ptr fwdModuleConnectorTable() const;
+//   const IRDBRecord* fwdModuleConnector() const;
+
+//   const IRDBRecord* fwdRing(int i) const;
+//   int fwdRingSize()  const;
+//   const IRDBRecord* fwdWheel(int i) const;
+//   const IRDBRecord* fwdWheelRingMap(int i) const;
+//   int fwdWheelRingMapSize() const;
+//   const IRDBRecord* fwdDiscSupport() const;
+//   const IRDBRecord* fwdPatchPanelLoc(int i) const;
+//   int fwdPatchPanelLocSize() const;
+//   const IRDBRecord* fwdPatchPanel(int i) const;
+//   int fwdPatchPanelSize() const;
+//   const IRDBRecord* fwdPPConnector() const;
+//   int fwdPPConnectorSize() const;
+//   const IRDBRecord* fwdPPCooling() const;
+//   int fwdPPCoolingSize() const;
+//   const IRDBRecord* fwdCoolingBlock(int i) const;
+//   const IRDBRecord* fwdRingServices(int i) const;
+//   const IRDBRecord* fwdServices() const;
+//   const IRDBRecord* fwdFSILocation(int i) const;
+//   int fwdFSILocationSize()  const;
+//   const IRDBRecord* fwdFSIType(int i) const;
+//   int fwdFSITypeSize() const;
+//   const IRDBRecord* fwdFSI(int i) const;
+//   int fwdFSISize() const;
+//   const IRDBRecord* fwdThermalShield(int i) const;
+//   int fwdThermalShieldSize()  const;
+//   const IRDBRecord* fwdGeneral() const;
+//   IRDBRecordset_ptr fwdOptoHarnessTable() const;
+//   const IRDBRecord* fwdOptoHarness(int i) const;
+//   IRDBRecordset_ptr fwdDiscFixationTable() const;
+//   const IRDBRecord* fwdDiscFixation() const;
+//   const IRDBRecord* fwdCylServ(int i) const;
+//   int fwdCylServSize() const;
+//   const IRDBRecord* fwdCylServLoc(int i) const;
+//   int fwdCylServLocSize() const;
+
+  // Return the VetoNu version tag.
+  const std::string & versionTag() const;
+
+  MsgStream& msg (MSG::Level lvl) const;
+
+private:
+  
+  VetoNuDataBase(const VetoNuDataBase &);
+  VetoNuDataBase& operator= (const VetoNuDataBase &);
+
+private:
+
+  const VetoNuGeoModelAthenaComps* m_athenaComps;
+
+  std::string m_vetoVersionTag;
+
+  IRDBRecordset_ptr m_weightTable;
+  IRDBRecordset_ptr m_scalingTable;
+  IRDBRecordset_ptr m_topLevel;
+  IRDBRecordset_ptr m_conditions;
+
+//   IRDBRecordset_ptr m_brlSensor;
+//   IRDBRecordset_ptr m_brlModule;
+//   IRDBRecordset_ptr m_brlSki;
+//   IRDBRecordset_ptr m_brlSkiZ;
+//   IRDBRecordset_ptr m_brlLayer;
+//   IRDBRecordset_ptr m_brlServices;
+//   IRDBRecordset_ptr m_brlServPerLayer;
+//   IRDBRecordset_ptr m_brlThermalShield;
+  IRDBRecordset_ptr m_stationGeneral;
+  IRDBRecordset_ptr m_plateGeneral;
+  IRDBRecordset_ptr m_radiatorGeneral;
+//   IRDBRecordset_ptr m_brlFSI;
+//   IRDBRecordset_ptr m_brlFSILocation;
+//   IRDBRecordset_ptr m_fwdSensor;
+//   IRDBRecordset_ptr m_fwdHybrid;
+//   IRDBRecordset_ptr m_fwdSpine;
+//   IRDBRecordset_ptr m_fwdModule;
+//   IRDBRecordset_ptr m_fwdModuleConnector;
+//   IRDBRecordset_ptr m_fwdRing;
+//   IRDBRecordset_ptr m_fwdWheel;
+//   IRDBRecordset_ptr m_fwdWheelRingMap;
+//   IRDBRecordset_ptr m_fwdDiscSupport;
+//   IRDBRecordset_ptr m_fwdPatchPanelLoc;
+//   IRDBRecordset_ptr m_fwdPatchPanel;
+//   IRDBRecordset_ptr m_fwdPPConnector;
+//   IRDBRecordset_ptr m_fwdPPCooling;
+//   IRDBRecordset_ptr m_fwdCoolingBlock;
+//   IRDBRecordset_ptr m_fwdRingServices;
+//   IRDBRecordset_ptr m_fwdServices;
+//   IRDBRecordset_ptr m_fwdFSILocation;
+//   IRDBRecordset_ptr m_fwdFSIType;
+//   IRDBRecordset_ptr m_fwdFSI;
+//   IRDBRecordset_ptr m_fwdThermalShield;
+//   IRDBRecordset_ptr m_fwdGeneral;
+//   IRDBRecordset_ptr m_fwdOptoHarness;
+//   IRDBRecordset_ptr m_fwdDiscFixation;
+//   IRDBRecordset_ptr m_fwdCylServ;
+//   IRDBRecordset_ptr m_fwdCylServLoc;
+
+
+};
+
+#endif //VetoNuGeoModel_VetoNuDataBase_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorFactory.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorFactory.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..2ac00cd600b65c83b8b483dc27b43b10da9e4cb6
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorFactory.cxx
@@ -0,0 +1,279 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+//
+// VetoNuDetectorFactory: This is the top level node 
+//
+
+
+#include "VetoNuDetectorFactory.h" 
+
+#include "VetoNuDataBase.h"
+#include "VetoNuIdentifier.h"
+#include "VetoNuGeometryManager.h" 
+#include "VetoNuMaterialManager.h"
+#include "VetoNuGeneralParameters.h"
+#include "ScintReadoutGeometry/Version.h" 
+#include "ScintReadoutGeometry/ScintCommonItems.h" 
+#include "ScintReadoutGeometry/ScintDD_Defs.h"
+#include "ScintReadoutGeometry/ScintDetectorDesign.h" 
+
+#include "VetoNuStation.h"
+#include "VetoNuRadiator.h"
+#include "VetoNuPlate.h"
+#include "VetoNuDataBase.h"
+#include "VetoNuGeoModel/VetoNuGeoModelAthenaComps.h"
+
+//
+// GeoModel include files:
+//
+#include "GeoModelKernel/GeoMaterial.h"  
+#include "GeoModelKernel/GeoTube.h"  
+#include "GeoModelKernel/GeoLogVol.h"  
+#include "GeoModelKernel/GeoNameTag.h"  
+#include "GeoModelKernel/GeoIdentifierTag.h"  
+#include "GeoModelKernel/GeoPhysVol.h"  
+#include "GeoModelKernel/GeoVPhysVol.h"  
+#include "GeoModelKernel/GeoTransform.h"  
+#include "GeoModelKernel/GeoAlignableTransform.h"  
+#include "GeoModelKernel/GeoShape.h"
+#include "GeoModelKernel/GeoShapeUnion.h"
+#include "GeoModelKernel/GeoShapeShift.h"
+#include "GeoModelInterfaces/StoredMaterialManager.h"
+#include "GeoModelInterfaces/IGeoDbTagSvc.h"
+#include "GeoModelFaserUtilities/DecodeFaserVersionKey.h"
+#include "RDBAccessSvc/IRDBAccessSvc.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "AthenaPoolUtilities/CondAttrListCollection.h"
+#include "DetDescrConditions/AlignableTransformContainer.h"
+#
+#include "StoreGate/StoreGateSvc.h"
+#include "GaudiKernel/ISvcLocator.h"
+
+#include "GeoModelKernel/GeoDefinitions.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+
+
+#include <iostream> 
+#include <iomanip> 
+#include <string>
+ 
+using ScintDD::VetoNuDetectorManager; 
+using ScintDD::ScintCommonItems; 
+
+VetoNuDetectorFactory::VetoNuDetectorFactory(const VetoNuGeoModelAthenaComps * athenaComps,
+					                     const VetoNuOptions & options)
+  : ScintDD::DetectorFactoryBase(athenaComps),
+    m_useDynamicAlignFolders(false)
+{ 
+  
+  // Create the detector manager
+  m_detectorManager = new VetoNuDetectorManager(detStore());
+  msg(MSG::DEBUG) << "Created VetoNuDetectorManager" << endmsg;
+
+  // Create the database
+  m_db = new VetoNuDataBase{athenaComps};
+  msg(MSG::DEBUG) << "Created VetoNuDataBase" << endmsg;
+
+  // Create the material manager
+  m_materials = new VetoNuMaterialManager{m_db};
+  msg(MSG::DEBUG) << "Created VetoNuMaterialManager" << endmsg;
+
+  // Create the geometry manager.
+  m_geometryManager = new VetoNuGeometryManager{m_db};
+  msg(MSG::DEBUG) << "Created VetoNuGeometryManager" << endmsg;
+  m_geometryManager->setOptions(options);
+  msg(MSG::DEBUG) << "Set options on VetoNuGeometryManager" << endmsg;
+
+  m_useDynamicAlignFolders = options.dynamicAlignFolders();
+ 
+  // Set Version information
+  // Get the geometry tag
+  DecodeFaserVersionKey versionKey(geoDbTagSvc(),"VetoNu");
+  IRDBRecordset_ptr switchSet
+    = rdbAccessSvc()->getRecordsetPtr("VetoNuSwitches", versionKey.tag(), versionKey.node(),"FASERDD");
+  const IRDBRecord    *switches   = (*switchSet)[0];
+  msg(MSG::DEBUG) << "Retrieved VetoNuSwitches" << endmsg;
+
+  std::string layout = "Final";
+  std::string description;
+  if (!switches->isFieldNull("LAYOUT")) {
+    layout = switches->getString("LAYOUT");
+  }
+  if (!switches->isFieldNull("DESCRIPTION")) {
+    description = switches->getString("DESCRIPTION");
+  }
+
+  std::string versionTag = rdbAccessSvc()->getChildTag("VetoNu", versionKey.tag(), versionKey.node(),"FASERDD");
+  std::string versionName = switches->getString("VERSIONNAME");
+  int versionMajorNumber = 1;
+  int versionMinorNumber = 0;
+  int versionPatchNumber = 0;
+  ScintDD::Version version(versionTag,
+                           versionName, 
+                           layout, 
+                           description, 
+                           versionMajorNumber,
+                           versionMinorNumber,
+                           versionPatchNumber);
+  m_detectorManager->setVersion(version);
+} 
+ 
+ 
+VetoNuDetectorFactory::~VetoNuDetectorFactory() 
+{ 
+  // NB the detector manager (m_detectorManager)is stored in the detector store by the
+  // Tool and so we don't delete it.
+  delete m_db;
+  delete m_materials;
+  delete m_geometryManager;
+} 
+
+void VetoNuDetectorFactory::create(GeoPhysVol *world) 
+{ 
+
+  msg(MSG::INFO) << "Building VetoNu Detector." << endmsg;
+  msg(MSG::INFO) << " " << m_detectorManager->getVersion().fullDescription() << endmsg;
+
+  // Change precision.
+  int oldPrecision = std::cout.precision(6);
+
+  // The tree tops get added to world. We name it "indet" though.
+  GeoPhysVol *scint = world;
+
+  const VetoNuGeneralParameters * vetoGeneral = m_geometryManager->generalParameters();
+
+  GeoTrf::Transform3D vetoTransform = vetoGeneral->partTransform("VetoNu");
+
+    std::string stationA_Label = "StationA";
+    // std::string stationB_Label = "StationB";
+    // std::string radiator_Label = "Radiator";
+
+    bool stationA_Present = vetoGeneral->partPresent(stationA_Label);
+    // bool stationB_Present = vetoGeneral->partPresent(stationB_Label);
+    // bool radiator_Present = vetoGeneral->partPresent(radiator_Label);
+  //
+  //  Plate is the same for all stations
+  //
+  VetoNuPlate plate("Plate", m_detectorManager, m_geometryManager, m_materials);
+  msg(MSG::DEBUG) << "Created VetoNu plate with dimensions (" << plate.thickness() << "," << plate.width() << "," << plate.length() << ")" << endmsg;
+  //
+  // Station A
+  //
+  if (stationA_Present)
+  {
+      msg(MSG::DEBUG) << "Building the VetoNu Station A." << endmsg;
+      m_detectorManager->numerology().addStation(0);
+
+    // Create the station
+    VetoNuStation stationA("VetoNuStationA", &plate, m_detectorManager, m_geometryManager, m_materials);
+    VetoNuIdentifier id{m_geometryManager->athenaComps()->getIdHelper()};
+    id.setStation(0);
+    GeoVPhysVol* stationA_PV = stationA.build(id);
+    GeoAlignableTransform* stationA_Transform = new GeoAlignableTransform(vetoTransform * vetoGeneral->partTransform(stationA_Label));
+
+    GeoNameTag* topLevelNameTag = new GeoNameTag("VetoNu");
+    scint->add(topLevelNameTag);
+    scint->add(new GeoIdentifierTag(0));
+    scint->add(stationA_Transform);
+    scint->add(stationA_PV);
+    m_detectorManager->addTreeTop(stationA_PV);
+
+    // Store alignable transform for station (level = 1)
+    m_detectorManager->addAlignableTransform(1, id.getPlateId(), stationA_Transform, stationA_PV);
+  }
+
+  // //
+  // // Station B
+  // //
+  // if (stationB_Present)
+  // {
+  //     msg(MSG::DEBUG) << "Building the VetoNu Station B." << endmsg;
+  //     m_detectorManager->numerology().addStation(1);
+
+  //   // Create the station
+  //   VetoNuStation stationB("VetoNuStationB", &plate, m_detectorManager, m_geometryManager, m_materials);
+  //   VetoNuIdentifier id{m_geometryManager->athenaComps()->getIdHelper()};
+  //   id.setStation(1);
+  //   GeoVPhysVol* stationB_PV = stationB.build(id);
+  //   GeoAlignableTransform* stationB_Transform = new GeoAlignableTransform(vetoTransform * vetoGeneral->partTransform(stationB_Label));
+
+  //   GeoNameTag* topLevelNameTag = new GeoNameTag("VetoNu");
+  //   scint->add(topLevelNameTag);
+  //   scint->add(new GeoIdentifierTag(0));
+  //   scint->add(stationB_Transform);
+  //   scint->add(stationB_PV);
+  //   m_detectorManager->addTreeTop(stationB_PV);
+
+  //   // Store alignable transform for station (level = 1)
+  //   m_detectorManager->addAlignableTransform(1, id.getPlateId(), stationB_Transform, stationB_PV);
+  // }
+
+  // // Passive radiator
+  // if (radiator_Present)
+  // {
+  //   msg(MSG::DEBUG) << "Building the VetoNu Radiator." << endmsg;
+
+  //   // Create the radiator
+  //   VetoNuRadiator radiator("VetoNuRadiator", m_detectorManager, m_geometryManager, m_materials);
+  //   GeoAlignableTransform* radiator_Transform = new GeoAlignableTransform(vetoTransform * vetoGeneral->partTransform(radiator_Label));
+
+  //   GeoNameTag* topLevelNameTag = new GeoNameTag("VetoNu");
+  //   scint->add(topLevelNameTag);
+  //   scint->add(new GeoIdentifierTag(0));
+  //   scint->add(radiator_Transform);
+  //   scint->add(radiator.getVolume());
+  //   m_detectorManager->addTreeTop(radiator.getVolume());
+
+  // }
+
+
+  // Set the neighbours
+  m_detectorManager->initNeighbours();
+
+  // Set number of pmts in numerology.
+  const ScintDD::ScintDetectorDesign* design = m_detectorManager->getVetoNuDesign();
+  if (design != nullptr)
+  {
+    m_detectorManager->numerology().setNumPmtsPerPlate(m_detectorManager->getVetoNuDesign()->cells());
+  }
+  else
+  {
+    m_detectorManager->numerology().setNumPmtsPerPlate(0);
+  }
+  
+
+  // Register the keys and the level corresponding to the key
+  // and whether it expects a global or local shift.
+  // level 0: sensor, level 1: module, level 2, layer/disc, level 3: whole barrel/enccap
+
+
+  if (!m_useDynamicAlignFolders){
+
+    m_detectorManager->addAlignFolderType(ScintDD::static_run1);
+    // m_detectorManager->addFolder("/Scint/Align");
+  }
+  else {
+    m_detectorManager->addAlignFolderType(ScintDD::timedependent_run2);
+    // m_detectorManager->addGlobalFolder("/Scint/AlignL1/Scint");
+    // m_detectorManager->addGlobalFolder("/Scint/AlignL2/VetoNu");
+    // m_detectorManager->addChannel("/Scint/AlignL1/Scint",3,ScintDD::global);
+    // m_detectorManager->addChannel("/Scint/AlignL2/VetoNu",2,ScintDD::global);
+    // m_detectorManager->addFolder("/Scint/AlignL3");
+  }
+
+  // Return precision to its original value
+  std::cout.precision(oldPrecision);
+
+} 
+ 
+
+const VetoNuDetectorManager * VetoNuDetectorFactory::getDetectorManager() const
+{
+  return m_detectorManager;
+}
+ 
+
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorFactory.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorFactory.h
new file mode 100644
index 0000000000000000000000000000000000000000..f9cc9e793dbd1a1cd4b088d79f5d3d30f81a0d1c
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorFactory.h
@@ -0,0 +1,50 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUGEOMODEL_VETONUDETECTORFACTORY_H 
+#define VETONUGEOMODEL_VETONUDETECTORFACTORY_H 
+ 
+#include "ScintGeoModelUtils/DetectorFactoryBase.h" 
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h"
+#include "ScintReadoutGeometry/ScintDD_Defs.h"
+
+class GeoPhysVol;
+class VetoNuDataBase;
+class VetoNuGeometryManager;
+class VetoNuGeoModelAthenaComps;
+class VetoNuMaterialManager;
+class VetoNuOptions;
+
+class VetoNuDetectorFactory : public ScintDD::DetectorFactoryBase  
+{ 
+  
+ public: 
+  // Constructor
+  VetoNuDetectorFactory(const VetoNuGeoModelAthenaComps * athenaComps, 
+		              const VetoNuOptions & options); 
+
+  // Destructor
+  virtual ~VetoNuDetectorFactory(); 
+
+  // Creation of geometry:
+  virtual void create(GeoPhysVol *world);   
+
+  // Access to the results: 
+  virtual const ScintDD::VetoNuDetectorManager * getDetectorManager() const; 
+
+ private: 
+  // Copy and assignments operations illegal and so are made private
+  VetoNuDetectorFactory(const VetoNuDetectorFactory &right); 
+  const VetoNuDetectorFactory & operator=(const VetoNuDetectorFactory &right); 
+
+  ScintDD::VetoNuDetectorManager *m_detectorManager;
+  VetoNuGeometryManager *m_geometryManager;
+  VetoNuDataBase* m_db;
+  VetoNuMaterialManager* m_materials;
+  bool m_useDynamicAlignFolders;
+
+}; 
+ 
+#endif 
+ 
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorTool.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9b74df9592c7c1e4226163a36a06cd0e4a872962
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuDetectorTool.cxx
@@ -0,0 +1,249 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuGeoModel/VetoNuDetectorTool.h"
+
+#include "VetoNuDetectorFactory.h" 
+#include "VetoNuDataBase.h" 
+// #include "VetoNuMaterialManager.h" 
+#include "VetoNuOptions.h" 
+
+// temporary
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h" 
+#include "ScintIdentifier/VetoNuID.h"
+#include "DetDescrConditions/AlignableTransformContainer.h"
+
+#include "GeoModelFaserUtilities/GeoModelExperiment.h"
+#include "GeoModelFaserUtilities/DecodeFaserVersionKey.h"
+#include "StoreGate/DataHandle.h"
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+
+#include "AthenaKernel/ClassID_traits.h"
+#include "SGTools/DataProxy.h"
+
+using ScintDD::VetoNuDetectorManager;
+using ScintDD::ScintDetectorManager;
+
+//
+// Constructor
+//
+VetoNuDetectorTool::VetoNuDetectorTool(const std::string& type,
+                                   const std::string& name, 
+                                   const IInterface* parent)
+  : GeoModelTool(type, name, parent),
+  m_cosmic{false},
+  m_manager{nullptr},
+  m_athenaComps{ },
+  m_geoDbTagSvc{"GeoDbTagSvc", name},
+  m_rdbAccessSvc{"RDBAccessSvc", name},
+  m_geometryDBSvc{"ScintGeometryDBSvc", name}
+{
+  // Get parameter values from jobOptions file
+  declareProperty("GeoDbTagSvc", m_geoDbTagSvc);
+  declareProperty("RDBAccessSvc", m_rdbAccessSvc);
+  declareProperty("GeometryDBSvc", m_geometryDBSvc);
+}
+
+//
+// Create the Geometry via the factory corresponding to this tool
+//
+
+StatusCode
+VetoNuDetectorTool::create()
+{ 
+  // Get the detector configuration.
+  ATH_CHECK(m_geoDbTagSvc.retrieve());
+  
+  DecodeFaserVersionKey versionKey{&*m_geoDbTagSvc, "VetoNu"};
+  // Issue error if AUTO.
+  if (versionKey.tag() == "AUTO") {
+    ATH_MSG_ERROR("AUTO Faser version. Please select a version.");
+  }
+  ATH_MSG_INFO("Building VetoNu with Version Tag: " << versionKey.tag() << " at Node: " << versionKey.node());
+
+  ATH_CHECK(m_rdbAccessSvc.retrieve());
+  // Print the VetoNu version tag:
+  std::string vetoVersionTag{m_rdbAccessSvc->getChildTag("VetoNu", versionKey.tag(), versionKey.node(), "FASERDD")};
+  ATH_MSG_INFO("VetoNu Version: " << vetoVersionTag);
+  // Check if version is empty. If so, then the VetoNu cannot be built. This may or may not be intentional. We
+  // just issue an INFO message. 
+  if (vetoVersionTag.empty()) {
+    ATH_MSG_INFO("No VetoNu Version. VetoNu will not be built.");
+  } else {
+    std::string versionName;
+    if (versionKey.custom()) {
+      ATH_MSG_WARNING("VetoNuDetectorTool:  Detector Information coming from a custom configuration!!");
+    } else {
+      ATH_MSG_DEBUG("VetoNuDetectorTool:  Detector Information coming from the database and job options IGNORED.");
+      ATH_MSG_DEBUG("Keys for VetoNu Switches are "  << versionKey.tag()  << "  " << versionKey.node());
+
+      IRDBRecordset_ptr switchSet{m_rdbAccessSvc->getRecordsetPtr("VetoNuSwitches", versionKey.tag(), versionKey.node(), "FASERDD")};
+      const IRDBRecord* switches{(*switchSet)[0]};
+      m_detectorName.setValue(switches->getString("DETECTORNAME"));
+
+      if (not switches->isFieldNull("COSMICLAYOUT")) 
+      {
+        m_cosmic = switches->getInt("COSMICLAYOUT");
+      }
+      if (not switches->isFieldNull("VERSIONNAME")) 
+      {
+        versionName = switches->getString("VERSIONNAME");
+      } 
+    }
+
+    ATH_MSG_DEBUG("Creating the VetoNu");
+    ATH_MSG_DEBUG("VetoNu Geometry Options: ");
+    ATH_MSG_DEBUG(" Alignable:             " << (m_alignable.value() ? "true" : "false"));
+    ATH_MSG_DEBUG(" CosmicLayout:          " << (m_cosmic ? "true" : "false"));
+    ATH_MSG_DEBUG(" VersionName:           " << versionName);
+
+    VetoNuOptions options;
+    options.setAlignable(m_alignable.value());
+    options.setDynamicAlignFolders(m_useDynamicAlignFolders.value());
+    m_manager = nullptr;
+
+    // 
+    // Locate the top level experiment node 
+    // 
+    GeoModelExperiment* theExpt{nullptr};
+    ATH_CHECK(detStore()->retrieve(theExpt, "FASER"));
+      
+    // Retrieve the Geometry DB Interface
+    ATH_CHECK(m_geometryDBSvc.retrieve());
+
+    // Pass athena services to factory, etc
+    m_athenaComps.setDetStore(detStore().operator->());
+    m_athenaComps.setGeoDbTagSvc(&*m_geoDbTagSvc);
+    m_athenaComps.setGeometryDBSvc(&*m_geometryDBSvc);
+    m_athenaComps.setRDBAccessSvc(&*m_rdbAccessSvc);
+    const VetoNuID* idHelper{nullptr};
+    ATH_CHECK(detStore()->retrieve(idHelper, "VetoNuID"));
+    m_athenaComps.setIdHelper(idHelper);
+    idHelper->test_plate_packing();
+    //
+    // This strange way of casting is to avoid an
+    // utterly brain damaged compiler warning.
+    //
+    GeoPhysVol* world{&*theExpt->getPhysVol()};
+    if (world != nullptr) ATH_MSG_INFO("Retrieved World PhysVol");
+
+    VetoNuDetectorFactory theVetoNu{&m_athenaComps, options};
+    ATH_MSG_INFO("Created instance of detector factory");
+    theVetoNu.create(world);
+    ATH_MSG_INFO("Called create methon on factory");
+    m_manager = theVetoNu.getDetectorManager();
+    ATH_MSG_INFO("Attempted to retrieve detector manager");
+
+    if (m_manager==nullptr) {
+      ATH_MSG_FATAL("VetoNuDetectorManager not created");
+      return StatusCode::FAILURE;
+    }
+      
+    // Get the manager from the factory and store it in the detector store.
+    //   m_detector is non constant so I can not set it to a const pointer.
+    //   m_detector = theSCT.getDetectorManager();
+      
+    ATH_MSG_DEBUG("Registering VetoNuDetectorManager. ");
+    ATH_CHECK(detStore()->record(m_manager, m_manager->getName()));
+    theExpt->addManager(m_manager);
+    
+    // Create a symLink to the ScintDetectorManager base class
+    const ScintDetectorManager* scintDetManager{m_manager};
+    ATH_CHECK(detStore()->symLink(m_manager, scintDetManager));
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode 
+VetoNuDetectorTool::clear()
+{
+  ATH_MSG_WARNING("Called untested VetoNuDetectorTool::clear()");
+  SG::DataProxy* proxy{detStore()->proxy(ClassID_traits<VetoNuDetectorManager>::ID(), m_manager->getName())};
+  if (proxy) {
+    proxy->reset();
+    m_manager = nullptr;
+  }
+  return StatusCode::SUCCESS;
+}
+
+StatusCode 
+VetoNuDetectorTool::registerCallback()
+{
+  StatusCode sc{StatusCode::FAILURE};
+  if (m_alignable.value()) {
+      ATH_MSG_WARNING("Called untested VetoNuDetectorTool::registerCallback()");
+    if (m_useDynamicAlignFolders.value()) {
+
+      if (detStore()->contains<CondAttrListCollection>(m_run2L1Folder.value())) {
+        ATH_MSG_DEBUG("Registering callback on global Container with folder " << m_run2L1Folder.value());
+        const DataHandle<CondAttrListCollection> calc;
+        ATH_CHECK(detStore()->regFcn(&IGeoModelTool::align, dynamic_cast<IGeoModelTool*>(this), calc, m_run2L1Folder.value()));
+        sc = StatusCode::SUCCESS;
+      } else {
+        ATH_MSG_WARNING("Unable to register callback on global Container with folder " << m_run2L1Folder.value());
+        return StatusCode::FAILURE;
+      }
+    
+      if (detStore()->contains<CondAttrListCollection>(m_run2L2Folder.value())) {
+        ATH_MSG_DEBUG("Registering callback on global Container with folder " << m_run2L2Folder.value());
+        const DataHandle<CondAttrListCollection> calc;
+        ATH_CHECK(detStore()->regFcn(&IGeoModelTool::align, dynamic_cast<IGeoModelTool*>(this), calc, m_run2L2Folder.value()));
+        sc = StatusCode::SUCCESS;
+      } else {
+        ATH_MSG_WARNING("Unable to register callback on global Container with folder " << m_run2L2Folder.value());
+        return StatusCode::FAILURE;
+      }
+    
+      if (detStore()->contains<AlignableTransformContainer>(m_run2L3Folder.value())) {
+        ATH_MSG_DEBUG("Registering callback on AlignableTransformContainer with folder " << m_run2L3Folder.value());
+        const DataHandle<AlignableTransformContainer> atc;
+        ATH_CHECK(detStore()->regFcn(&IGeoModelTool::align, dynamic_cast<IGeoModelTool*>(this), atc, m_run2L3Folder.value()));
+        sc = StatusCode::SUCCESS;
+      } else {
+        ATH_MSG_WARNING("Unable to register callback on AlignableTransformContainer with folder " << m_run2L3Folder.value());
+        return StatusCode::FAILURE;
+      }
+     
+    } else {
+    
+      if (detStore()->contains<AlignableTransformContainer>(m_run1Folder.value())) {
+        ATH_MSG_DEBUG("Registering callback on AlignableTransformContainer with folder " << m_run1Folder.value());
+        const DataHandle<AlignableTransformContainer> atc;
+        ATH_CHECK(detStore()->regFcn(&IGeoModelTool::align, dynamic_cast<IGeoModelTool*>(this), atc, m_run1Folder.value()));
+        sc = StatusCode::SUCCESS;
+      } else {
+        ATH_MSG_WARNING("Unable to register callback on AlignableTransformContainer with folder "
+                        << m_run1Folder.value() << ", Alignment disabled (only if no Run2 scheme is loaded)!");
+        return StatusCode::FAILURE;
+      }
+    }
+  } else {
+    ATH_MSG_INFO("Alignment disabled. No callback registered");
+    // We return failure otherwise it will try and register
+    // a GeoModelSvc callback associated with this callback. 
+    return StatusCode::FAILURE;
+  }
+  return sc;
+}
+  
+StatusCode 
+VetoNuDetectorTool::align(IOVSVC_CALLBACK_ARGS_P(I, keys))
+{
+  ATH_MSG_WARNING("Called untested VetoNuDetectorTool::align()");
+  void* i = &I;
+  void* k = &keys;
+  if (i == nullptr && k == nullptr) return StatusCode::SUCCESS; // suppress stupid warning
+  if (m_manager==nullptr) { 
+    ATH_MSG_FATAL("Manager does not exist");
+    return StatusCode::FAILURE;
+  }    
+  if (m_alignable.value()) {
+    return m_manager->align(I, keys);
+  } else {
+    ATH_MSG_DEBUG("Alignment disabled. No alignments applied");
+    return StatusCode::SUCCESS;
+  }
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeneralParameters.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeneralParameters.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..2e88fe60692993cd96c766032a9a396e8fb65416
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeneralParameters.cxx
@@ -0,0 +1,88 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuGeneralParameters.h"
+#include "VetoNuDataBase.h"
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+#include "GaudiKernel/SystemOfUnits.h"
+#include "GeoModelKernel/GeoDefinitions.h"
+#include "ScintGeoModelUtils/TopLevelPlacements.h"
+
+const double VetoNuSAFETY = 0.01 * Gaudi::Units::mm; // Used in some places to make envelopes slightly larger to ensure
+                                     // no overlaps due to rounding errors.
+
+
+VetoNuGeneralParameters::VetoNuGeneralParameters(VetoNuDataBase* rdb)
+{
+  m_rdb = rdb;
+  m_placements = new TopLevelPlacements(m_rdb->topLevelTable());
+}
+
+
+VetoNuGeneralParameters::~VetoNuGeneralParameters()
+{
+  delete m_placements;
+}
+
+
+const GeoTrf::Transform3D & 
+VetoNuGeneralParameters::partTransform(const std::string & partName) const 
+{
+  return m_placements->transform(partName);
+}
+
+
+bool 
+VetoNuGeneralParameters::partPresent(const std::string & partName) const
+{
+  return m_placements->present(partName);
+}
+
+
+
+//
+// General
+//
+double 
+VetoNuGeneralParameters::safety() const
+{
+  return VetoNuSAFETY;
+}
+
+// Default Conditions. Values should be come form conditions data base. These values provide
+// default vlaues if nothing from the conditions database is provided.
+
+
+// double 
+// VetoNuGeneralParameters::temperature() const
+// {
+//   if (m_rdb->conditionsTable()->size() == 0) {
+//     return 266.15 * Gaudi::Units::kelvin; // -7 C
+//   }
+//   return (m_rdb->conditions()->getDouble("TEMPERATURE") + 273.15) * Gaudi::Units::kelvin;
+// }
+
+
+// double 
+// SCT_GeneralParameters::biasVoltage() const
+// {
+//   if (m_rdb->conditionsTable()->size() == 0) {
+//     return 100 * Gaudi::Units::volt;
+//   }
+//   return m_rdb->conditions()->getDouble("BIASVOLT") * Gaudi::Units::volt;
+// }
+
+// double 
+// SCT_GeneralParameters::depletionVoltage() const
+// {
+//   if (m_rdb->conditionsTable()->size() == 0) {
+//     return 20 * Gaudi::Units::volt;
+//   }
+//   return m_rdb->conditions()->getDouble("DEPLETIONVOLT") * Gaudi::Units::volt;
+// }
+
+
+
+  
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeneralParameters.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeneralParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8a6d55788bf3ea6347204ecd8980f79f157854d
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeneralParameters.h
@@ -0,0 +1,45 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuGeneralParameters_H
+#define VetoNuGeoModel_VetoNuGeneralParameters_H
+
+#include "GeoModelKernel/GeoDefinitions.h"
+
+#include <map>
+#include <string>
+
+class VetoNuDataBase;
+class TopLevelPlacements;
+
+class VetoNuGeneralParameters {
+
+public:
+
+  VetoNuGeneralParameters(VetoNuDataBase* rdb);
+  ~VetoNuGeneralParameters();
+  //Explicitly disallow copy, assignment to appease coverity
+  VetoNuGeneralParameters(const VetoNuGeneralParameters &) = delete;
+  VetoNuGeneralParameters & operator=(const VetoNuGeneralParameters &) = delete;
+
+  // General
+  double safety() const;
+
+  //Default conditions.
+//   double temperature() const;
+//   double biasVoltage() const;
+//   double depletionVoltage() const;
+
+  const GeoTrf::Transform3D & partTransform(const std::string & partName) const; 
+  bool partPresent(const std::string & partName) const;
+  
+private:
+
+  VetoNuDataBase * m_rdb;
+  TopLevelPlacements * m_placements;
+    
+};
+
+
+#endif // SCT_GeoModel_SCT_GeneralParameters_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeoModelAthenaComps.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeoModelAthenaComps.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ad8256acbb791c752d6cf80272104e4f76944b98
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeoModelAthenaComps.cxx
@@ -0,0 +1,23 @@
+/*
+  Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuGeoModel/VetoNuGeoModelAthenaComps.h"
+
+VetoNuGeoModelAthenaComps::VetoNuGeoModelAthenaComps()
+  : ScintDD::AthenaComps("VetoNuGeoModel"),
+    m_idHelper(0)
+{}
+ 
+void 
+VetoNuGeoModelAthenaComps::setIdHelper(const VetoNuID* idHelper)
+{
+  m_idHelper = idHelper;
+}
+
+const VetoNuID* 
+VetoNuGeoModelAthenaComps::getIdHelper() const
+{
+  return m_idHelper;
+}
+
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeometryManager.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeometryManager.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..df334a198a6f6c6a8c61d66863327000a2e3c71d
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeometryManager.cxx
@@ -0,0 +1,123 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuGeometryManager.h"
+
+#include "ScintGeoModelUtils/DistortedMaterialManager.h"
+#include "ScintIdentifier/VetoNuID.h"
+#include "ScintReadoutGeometry/ScintCommonItems.h"
+#include "VetoNuStationParameters.h"
+#include "VetoNuPlateParameters.h"
+#include "VetoNuRadiatorParameters.h"
+#include "VetoNuDataBase.h"
+#include "VetoNuGeneralParameters.h"
+#include "VetoNuGeoModel/VetoNuGeoModelAthenaComps.h"
+
+VetoNuGeometryManager::VetoNuGeometryManager(VetoNuDataBase* rdb)
+  : m_athenaComps{rdb->athenaComps()},
+    m_rdb{rdb}
+{
+  // This class uses reference counting. Should not be delete'd in destructor.
+  m_commonItems = new ScintDD::ScintCommonItems(m_athenaComps->getIdHelper());
+
+  m_stationParameters = std::make_unique<VetoNuStationParameters>(m_rdb);
+  m_plateParameters = std::make_unique<VetoNuPlateParameters>(m_rdb);
+  m_radiatorParameters = std::make_unique<VetoNuRadiatorParameters>(m_rdb);
+  m_generalParameters = std::make_unique<VetoNuGeneralParameters>(m_rdb);
+  m_distortedMatManager = std::make_unique<ScintDD::DistortedMaterialManager>();
+}
+
+VetoNuGeometryManager::~VetoNuGeometryManager()
+{
+}
+
+//
+// Access to run time options.
+//
+const VetoNuOptions & 
+VetoNuGeometryManager::options() const
+{
+  return m_options;
+}
+
+void
+VetoNuGeometryManager::setOptions(const VetoNuOptions & options)
+{
+  m_options = options;
+}
+
+const VetoNuGeoModelAthenaComps *
+VetoNuGeometryManager::athenaComps() const 
+{
+  return m_athenaComps;
+}
+  
+//
+// ScintCommonItems which are passed to ScintDetectorElements.
+//
+
+const ScintDD::ScintCommonItems *
+VetoNuGeometryManager::commonItems() const
+{
+  return m_commonItems;
+}
+
+const VetoNuStationParameters * 
+VetoNuGeometryManager::stationParameters() const
+{    
+  return m_stationParameters.get();
+}
+
+const VetoNuPlateParameters * 
+VetoNuGeometryManager::plateParameters() const
+{    
+  return m_plateParameters.get();
+}
+
+const VetoNuRadiatorParameters * 
+VetoNuGeometryManager::radiatorParameters() const
+{    
+  return m_radiatorParameters.get();
+}
+
+
+const VetoNuGeneralParameters * 
+VetoNuGeometryManager::generalParameters() const
+{    
+  return m_generalParameters.get();
+}
+
+const ScintDD::DistortedMaterialManager * 
+VetoNuGeometryManager::distortedMatManager() const
+{    
+  return m_distortedMatManager.get();
+}
+
+VetoNuGeometryManager&
+VetoNuGeometryManager::operator=(const VetoNuGeometryManager& right) {
+  if (this != &right) {
+    m_options = right.m_options;
+    m_athenaComps = right.m_athenaComps;
+    m_commonItems = right.m_commonItems;
+    m_rdb = right.m_rdb;
+    m_stationParameters.reset(new VetoNuStationParameters(m_rdb));
+    m_plateParameters.reset(new VetoNuPlateParameters(m_rdb));
+    m_radiatorParameters.reset(new VetoNuRadiatorParameters(m_rdb));
+    m_generalParameters.reset(new VetoNuGeneralParameters(m_rdb));
+    m_distortedMatManager.reset(new ScintDD::DistortedMaterialManager());
+  }
+  return *this;
+}
+
+VetoNuGeometryManager::VetoNuGeometryManager(const VetoNuGeometryManager& right) {
+  m_options = right.m_options;
+  m_athenaComps = right.m_athenaComps;
+  m_commonItems = right.m_commonItems;
+  m_rdb = right.m_rdb;
+  m_stationParameters.reset(new VetoNuStationParameters(m_rdb));
+  m_plateParameters.reset(new VetoNuPlateParameters(m_rdb));
+  m_radiatorParameters.reset(new VetoNuRadiatorParameters(m_rdb));
+  m_generalParameters.reset(new VetoNuGeneralParameters(m_rdb));
+  m_distortedMatManager.reset(new ScintDD::DistortedMaterialManager());
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeometryManager.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeometryManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..0fa5eff1bc628cf691df5ee05269832b5aa62eb4
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuGeometryManager.h
@@ -0,0 +1,69 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuGeometryManager_H
+#define VetoNuGeoModel_VetoNuGeometryManager_H
+
+#include "VetoNuOptions.h"
+
+#include <memory>
+
+namespace ScintDD {
+  class ScintCommonItems;
+  class DistortedMaterialManager;
+}
+
+class VetoNuStationParameters;
+class VetoNuPlateParameters;
+class VetoNuRadiatorParameters;
+class VetoNuDataBase;
+class VetoNuGeneralParameters;
+class VetoNuGeoModelAthenaComps;
+
+class VetoNuGeometryManager {
+
+public:
+
+  // Constructor 
+  VetoNuGeometryManager(VetoNuDataBase* rdb);
+
+  // Destructor 
+  ~VetoNuGeometryManager();
+
+  // Access to run time options
+  const VetoNuOptions & options() const;
+  void setOptions(const VetoNuOptions & options);
+
+  // Access to athena components
+  const VetoNuGeoModelAthenaComps * athenaComps() const;
+
+  // To be passed to detector element.
+  const ScintDD::ScintCommonItems * commonItems() const;
+
+  const VetoNuStationParameters*             stationParameters() const;
+  const VetoNuPlateParameters*               plateParameters() const;
+  const VetoNuRadiatorParameters*            radiatorParameters() const;
+  const VetoNuGeneralParameters*             generalParameters() const;
+  const ScintDD::DistortedMaterialManager* distortedMatManager() const;
+
+  VetoNuGeometryManager& operator=(const VetoNuGeometryManager& right);
+  VetoNuGeometryManager(const VetoNuGeometryManager& right);
+
+private:
+
+  VetoNuOptions m_options;
+  const VetoNuGeoModelAthenaComps * m_athenaComps;
+  ScintDD::ScintCommonItems * m_commonItems;
+  VetoNuDataBase* m_rdb;
+ 
+  std::unique_ptr<VetoNuStationParameters> m_stationParameters;
+  std::unique_ptr<VetoNuPlateParameters> m_plateParameters;
+  std::unique_ptr<VetoNuRadiatorParameters> m_radiatorParameters;
+  std::unique_ptr<VetoNuGeneralParameters> m_generalParameters;
+  std::unique_ptr<ScintDD::DistortedMaterialManager> m_distortedMatManager;
+
+};
+
+
+#endif // VetoNuGeoModel_VetoNuGeometryManager_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuIdentifier.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuIdentifier.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..8ef8f33806fce3f29f70568fe1feeac9c623eaad
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuIdentifier.cxx
@@ -0,0 +1,23 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuIdentifier.h"
+#include "ScintIdentifier/VetoNuID.h"
+#include "Identifier/Identifier.h"
+
+#include <cassert>
+#include <iostream>
+
+Identifier 
+VetoNuIdentifier::getPlateId() 
+{
+  assert (m_idHelper);
+  return m_idHelper->plate_id(m_station, m_plate);
+}
+
+void VetoNuIdentifier::print()
+{
+  std::cout << "2/1/" << m_station << "/" 
+            << m_plate << std::endl;
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuIdentifier.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuIdentifier.h
new file mode 100644
index 0000000000000000000000000000000000000000..b19425ffe01c560ad0338cd28c6d04b05131e0a2
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuIdentifier.h
@@ -0,0 +1,41 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUGEOMODEL_VETONUIDENTIFIER_H
+#define VETONUGEOMODEL_VETONUIDENTIFIER_H
+
+class Identifier;
+class VetoNuID;
+
+class VetoNuIdentifier
+{
+public:
+
+  VetoNuIdentifier( const VetoNuID* idHelper,
+                  int station = 0,
+		          int plate = 0 )
+    : m_idHelper{idHelper},
+      m_station{station},
+      m_plate{plate}
+  {};
+
+
+  void setStation(int i) {m_station = i;}
+  int  getStation() const {return m_station;}
+
+  void setPlate(int i) {m_plate = i;}
+  int  getPlate() const {return m_plate;}
+
+  Identifier getPlateId();
+
+  // For debugging purposes.
+  void print();
+
+private:
+  const VetoNuID* m_idHelper;
+  int m_station;
+  int m_plate;
+};
+
+#endif // VETOGEOMODEL_VETOIDENTIFIER_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuMaterialManager.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuMaterialManager.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..da71318c2526648265235b17e51297ef98f9dde5
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuMaterialManager.cxx
@@ -0,0 +1,83 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuMaterialManager.h"
+#include "GeoModelKernel/GeoMaterial.h"
+#include "GeoModelKernel/GeoElement.h"
+#include "VetoNuDataBase.h"
+#include "RDBAccessSvc/IRDBRecordset.h"
+#include "StoreGate/StoreGateSvc.h"
+#include "GaudiKernel/Bootstrap.h"
+#include "GaudiKernel/ISvcLocator.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+#include <iostream>
+
+// Constructor 
+VetoNuMaterialManager::VetoNuMaterialManager(VetoNuDataBase* db)
+{
+  // Get my material manager.
+  ISvcLocator* svcLocator = Gaudi::svcLocator(); // from Bootstrap
+  StoreGateSvc* detStore;
+  StatusCode sc = svcLocator->service("DetectorStore", detStore );
+  if (sc.isFailure()) {
+    std::cout << "Could not locate DetectorStore" << std::endl;
+    return;
+  }
+
+  m_materialManager = std::make_unique<ScintMaterialManager>("VetoNuMaterialManager", db->athenaComps());
+  m_materialManager->addWeightTable(db->weightTable(), "veto");
+  m_materialManager->addScalingTable(db->scalingTable());
+
+  loadMaterials();
+
+  m_gasMaterial = m_materialManager->getMaterial("std::Air");
+}
+
+// Add materials not yet in the database 
+void
+VetoNuMaterialManager::loadMaterials()
+{
+}
+
+const GeoElement* 
+VetoNuMaterialManager::getElement(const std::string & elementName) const
+{
+  return m_materialManager->getElement(elementName);
+}
+
+const GeoMaterial* 
+VetoNuMaterialManager::getMaterial(const std::string & materialName) const
+{
+  return m_materialManager->getMaterial(materialName);
+}
+
+void
+VetoNuMaterialManager::addMaterial(GeoMaterial* material)
+{
+  return m_materialManager->addMaterial(material);
+}
+
+const GeoMaterial* 
+VetoNuMaterialManager::getMaterial(const std::string & originalMaterial, 
+                                 double density,  
+                                 const std::string & newName)
+{
+  
+  return m_materialManager->getMaterial(originalMaterial, density, newName);
+}
+
+const GeoMaterial *
+VetoNuMaterialManager::getMaterialForVolume(const std::string & materialName, double volume)
+{
+  return m_materialManager->getMaterialForVolume(materialName, volume);
+}
+
+
+
+const GeoMaterial* 
+VetoNuMaterialManager::gasMaterial() const
+{
+  return m_gasMaterial;
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuMaterialManager.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuMaterialManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..32f0ded52b1868e2b09b926d64d9bf3c6417dbff
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuMaterialManager.h
@@ -0,0 +1,48 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUMATERIALMANAGER_H
+#define VETONUMATERIALMANAGER_H
+
+// VetoNuMaterialManager. This provides an interface to the ScintMaterialManager which in turn
+// is an interface to GeoModel Material Manager with some additional functionality.
+#include "ScintGeoModelUtils/ScintMaterialManager.h"
+
+#include <memory>
+#include <string>
+
+class GeoMaterial;
+class GeoElement;
+class ScintMaterialManager;
+class VetoNuDataBase;
+
+class VetoNuMaterialManager
+{
+
+public:
+
+  VetoNuMaterialManager(VetoNuDataBase* db);
+
+  const GeoMaterial* getMaterial(const std::string & materialName) const;
+  const GeoElement* getElement(const std::string & elementName) const;
+
+  const GeoMaterial* getMaterial(const std::string & originalMaterial, 
+				 double density,  
+				 const std::string & newName = "");
+  const GeoMaterial *getMaterialForVolume(const std::string & materialName, double volume);
+
+  // Default gas material
+  const GeoMaterial* gasMaterial() const;
+
+private:
+  void loadMaterials();
+  void addMaterial(GeoMaterial* material);
+
+  std::unique_ptr<ScintMaterialManager> m_materialManager;
+  const GeoMaterial* m_gasMaterial;
+
+};
+
+
+#endif // VETONUMATERIALMANAGER_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuOptions.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuOptions.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0c63b320f4d5e2063e5047c4fa07a0ac1ebd011c
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuOptions.cxx
@@ -0,0 +1,47 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuOptions.h"
+
+VetoNuOptions::VetoNuOptions()
+  : m_alignable(true)
+//   , m_alignModule(true)
+  , m_dynAlignFolders(false)
+{}
+
+void 
+VetoNuOptions::setAlignable(bool flag)
+{
+  m_alignable = flag;
+}
+
+bool 
+VetoNuOptions::alignable() const
+{
+  return m_alignable;
+}
+
+// following may eventually become useful
+//
+// void 
+// SCT_Options::setAlignAtModuleLevel(bool flag)
+// {
+//   m_alignModule = flag;
+// }
+
+// bool 
+// SCT_Options::alignAtModuleLevel() const
+// {
+//   return m_alignModule;
+// }
+
+void VetoNuOptions::setDynamicAlignFolders(const bool flag)
+{
+  m_dynAlignFolders = flag;
+}
+
+bool VetoNuOptions::dynamicAlignFolders() const 
+{  
+  return m_dynAlignFolders;
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuOptions.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuOptions.h
new file mode 100644
index 0000000000000000000000000000000000000000..166ae9e6cb2116b3372b3d485e5b9df0f91402aa
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuOptions.h
@@ -0,0 +1,35 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuOptions_H
+#define VetoNuGeoModel_VetoNuOptions_H
+
+// Class for any run time options.
+
+
+class VetoNuOptions
+{
+
+public:
+  VetoNuOptions();
+  bool alignable() const;
+//   bool alignAtModuleLevel() const;
+
+  void setAlignable(bool flag = true);
+//   void setAlignAtModuleLevel(bool flag = true);
+
+  //dynamic alignment folders
+  void setDynamicAlignFolders(const bool flag = true);
+  bool dynamicAlignFolders() const;
+
+private:
+
+  bool m_alignable;
+//   bool m_alignModule;
+  bool m_dynAlignFolders;   //controls which set of alignment folders is used
+
+};
+
+
+#endif // VetoNuGeoModel_VetoNuOptions_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlate.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlate.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..e14c88cbe5cb09c886752d7be8d8a57c0dfbd836
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlate.cxx
@@ -0,0 +1,117 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuPlate.h"
+
+#include "VetoNuGeometryManager.h"
+#include "VetoNuMaterialManager.h"
+
+#include "VetoNuPlateParameters.h"
+
+#include "GeoModelKernel/GeoBox.h"
+#include "GeoModelKernel/GeoLogVol.h"
+#include "GeoModelKernel/GeoFullPhysVol.h"
+#include "GeoModelKernel/GeoMaterial.h"
+
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h"
+#include "ScintReadoutGeometry/ScintDetectorDesign.h"
+#include "ScintReadoutGeometry/ScintDetectorElement.h"
+#include "ScintReadoutGeometry/ScintDD_Defs.h"
+#include "ScintReadoutGeometry/ScintCommonItems.h"
+
+#include "GaudiKernel/SystemOfUnits.h"
+
+using namespace ScintDD;
+
+VetoNuPlate::VetoNuPlate(const std::string & name,
+                     ScintDD::VetoNuDetectorManager* detectorManager,
+                     const VetoNuGeometryManager* geometryManager,
+                     VetoNuMaterialManager* materials)
+  : VetoNuUniqueComponentFactory(name, detectorManager, geometryManager, materials),
+    m_noElementWarning{true}
+{
+  getParameters();
+  m_logVolume = preBuild();
+}
+
+
+void
+VetoNuPlate::getParameters()
+{
+  
+  const VetoNuPlateParameters * parameters = m_geometryManager->plateParameters();
+  m_material  = m_materials->getMaterial(parameters->plateMaterial());
+  m_thickness = parameters->plateThickness();
+  m_length = parameters->plateLength();
+  m_width     = parameters->plateWidth();
+  m_detectorManager->numerology().setNumPmtsPerPlate(parameters->platePmts());
+}
+
+const GeoLogVol * 
+VetoNuPlate::preBuild()
+{
+
+  // Build the plate. Just a simple box.
+  // const GeoBox * plateShape = new GeoBox(0.5*m_thickness, 0.5*m_width, 0.5*m_length);
+  const GeoBox * plateShape = new GeoBox(0.5*m_width, 0.5*m_length, 0.5*m_thickness);
+  GeoLogVol * plateLog = new GeoLogVol(getName(), plateShape, m_material);
+
+  // Make the scint design for this plate
+  makeDesign();
+
+  m_detectorManager->setDesign(m_design);
+  
+  return plateLog;
+}
+
+
+void
+VetoNuPlate::makeDesign()
+{
+  //SiDetectorDesign::Axis etaAxis   = SiDetectorDesign::zAxis;
+  //SiDetectorDesign::Axis phiAxis   = SiDetectorDesign::yAxis;
+  //SiDetectorDesign::Axis depthAxis = SiDetectorDesign::xAxis;
+
+  const VetoNuPlateParameters * parameters = m_geometryManager->plateParameters();
+  
+    m_design = new ScintDetectorDesign(m_thickness, 
+                                       m_length, 
+                                       m_width,
+                                       parameters->platePmts());
+}
+
+
+
+GeoVPhysVol * 
+VetoNuPlate::build(VetoNuIdentifier id)
+{
+  GeoFullPhysVol * plate = new GeoFullPhysVol(m_logVolume); 
+  
+  // Make detector element and add to collection
+  // Only do so if we have a valid id helper.
+
+  //id.print(); // for debugging only
+
+  const ScintCommonItems* commonItems =  m_geometryManager->commonItems();
+
+  if (commonItems->getIdHelper()) {
+
+    ScintDetectorElement * detElement;
+
+    detElement =  new ScintDetectorElement(id.getPlateId(), 
+                                           m_design, 
+                                           plate,  
+                                           commonItems);
+    
+    // Add the detector element.
+    m_detectorManager->addDetectorElement(detElement);
+
+  } else {
+    if (m_noElementWarning) {
+      std::cout << "WARNING!!!!: No VetoNu id helper and so no elements being produced." << std::endl;
+      m_noElementWarning = false;
+    }
+  }
+  return plate;
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlate.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlate.h
new file mode 100644
index 0000000000000000000000000000000000000000..efc8638052c8dcb8c083d63584dccf649354d7ce
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlate.h
@@ -0,0 +1,48 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUGEOMODEL_VETONUPLATE_H
+#define VETONUGEOMODEL_VETONUPLATE_H
+
+#include "VetoNuComponentFactory.h"
+
+#include <atomic>
+#include <string>
+
+class GeoMaterial;
+class GeoVPhysVol;
+namespace ScintDD{class ScintDetectorDesign;}
+
+class VetoNuPlate: public VetoNuUniqueComponentFactory
+{
+public:
+  VetoNuPlate(const std::string & name,
+             ScintDD::VetoNuDetectorManager* detectorManager,
+             const VetoNuGeometryManager* geometryManager,
+             VetoNuMaterialManager* materials);
+
+public:
+  const GeoMaterial * material() const {return m_material;} 
+  double thickness() const {return m_thickness;}
+  double width()     const {return m_width;}
+  double length()    const {return m_length;}
+
+  virtual GeoVPhysVol * build(VetoNuIdentifier id);
+  
+private:
+  void getParameters();
+  virtual const GeoLogVol * preBuild();
+  void makeDesign(); 
+ 
+  const GeoMaterial * m_material;
+  double m_thickness;
+  double m_width;
+  double m_length;
+  
+  ScintDD::ScintDetectorDesign * m_design;
+
+  mutable std::atomic_bool m_noElementWarning;
+};
+
+#endif // VETOGEOMODEL_VETOPLATE_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlateParameters.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlateParameters.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..121df4dcbc9c60ead0099ea05b22d9de56e78809
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlateParameters.cxx
@@ -0,0 +1,52 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuPlateParameters.h"
+#include "VetoNuGeometryManager.h"
+
+#include "VetoNuDataBase.h"
+
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+#include <cmath>
+
+
+VetoNuPlateParameters::VetoNuPlateParameters(VetoNuDataBase* rdb)
+{
+  m_rdb = rdb;
+}
+
+//
+// Plate General
+//
+int
+VetoNuPlateParameters::platePmts() const
+{
+  return m_rdb->plateGeneral()->getInt("NUMPMTS"); 
+}
+
+double 
+VetoNuPlateParameters::plateWidth() const
+{
+  return m_rdb->plateGeneral()->getDouble("WIDTH") * Gaudi::Units::mm; 
+}
+
+double 
+VetoNuPlateParameters::plateLength() const
+{
+  return m_rdb->plateGeneral()->getDouble("LENGTH") * Gaudi::Units::mm; 
+}
+
+double 
+VetoNuPlateParameters::plateThickness() const
+{
+  return m_rdb->plateGeneral()->getDouble("THICKNESS") * Gaudi::Units::mm; 
+}
+
+std::string VetoNuPlateParameters::plateMaterial() const
+{
+  return m_rdb->plateGeneral()->getString("MATERIAL");
+}
+
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlateParameters.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlateParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..11ebd07129db93286277420b25c532d2cf51bbce
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuPlateParameters.h
@@ -0,0 +1,32 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuPlateParameters_H
+#define VetoNuGeoModel_VetoNuPlateParameters_H
+
+#include <string>
+
+class VetoNuDataBase;
+
+class VetoNuPlateParameters {
+
+public:
+
+  // Constructor 
+  VetoNuPlateParameters(VetoNuDataBase* rdb);
+
+  // Barrel General
+  int    platePmts() const;
+  double plateThickness() const;
+  double plateWidth() const;
+  double plateLength() const;
+  std::string plateMaterial() const;
+
+ private:
+  VetoNuDataBase * m_rdb;
+
+};
+
+
+#endif // VetoNuGeoModel_VetoNuPlateParameters_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiator.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiator.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..49a9fe83c78c883f968f020d38e942fcd53ffdbe
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiator.cxx
@@ -0,0 +1,55 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuRadiator.h"
+
+#include "VetoNuMaterialManager.h"
+
+#include "VetoNuGeometryManager.h"
+#include "VetoNuGeneralParameters.h"
+#include "VetoNuRadiatorParameters.h"
+
+#include "GeoModelKernel/GeoBox.h"
+#include "GeoModelKernel/GeoLogVol.h"
+#include "GeoModelKernel/GeoPhysVol.h"
+#include "GeoModelKernel/GeoMaterial.h"
+
+VetoNuRadiator::VetoNuRadiator(const std::string & name,
+                            ScintDD::VetoNuDetectorManager* detectorManager,
+                            const VetoNuGeometryManager* geometryManager,
+                            VetoNuMaterialManager* materials)
+  : VetoNuSharedComponentFactory(name, detectorManager, geometryManager, materials)
+{
+  getParameters();
+  m_physVolume = build();
+}
+
+void
+VetoNuRadiator::getParameters() 
+{
+  const VetoNuRadiatorParameters * parameters = m_geometryManager->radiatorParameters();
+  const VetoNuGeneralParameters* generalParameters = m_geometryManager->generalParameters();
+
+  m_material  = m_materials->getMaterial(parameters->radiatorMaterial());
+  m_safety    = generalParameters->safety();
+  m_thickness = parameters->radiatorThickness();
+  m_width     = parameters->radiatorWidth();
+  m_length    = parameters->radiatorLength();
+}
+
+GeoVPhysVol * 
+VetoNuRadiator::build()
+{
+  // Just a simple box.
+  const GeoBox * simpleRadiatorShape = new GeoBox(0.5*m_width,
+                                                  0.5*m_length,
+                                                  0.5*m_thickness);
+
+  const GeoLogVol * simpleRadiatorLog = 
+    new GeoLogVol(getName(), simpleRadiatorShape, m_material);
+
+  GeoPhysVol * simpleRadiator = new GeoPhysVol(simpleRadiatorLog);
+
+  return simpleRadiator;
+}
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiator.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiator.h
new file mode 100644
index 0000000000000000000000000000000000000000..5c25fcec17f21b848c1a7f9e3f41d2b396e77277
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiator.h
@@ -0,0 +1,42 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUGEOMODEL_VETONURADIATOR_H
+#define VETONUGEOMODEL_VETONURADIATOR_H
+
+#include "VetoNuComponentFactory.h"
+
+class GeoVPhysVol;
+class GeoFullPhysVol;
+class GeoLogVol;
+class GeoMaterial;
+
+class VetoNuRadiator : public VetoNuSharedComponentFactory
+{
+
+public:
+  VetoNuRadiator(const std::string & name,
+               ScintDD::VetoNuDetectorManager* detectorManager,
+               const VetoNuGeometryManager* geometryManager,
+               VetoNuMaterialManager* materials);
+
+public:
+  const GeoMaterial * material() const {return m_material;}
+  double thickness()   const {return m_thickness;}
+  double width()       const {return m_width;}
+  double length()      const {return m_length;}
+ 
+private:
+  virtual GeoVPhysVol * build();
+  void getParameters();
+ 
+  const GeoMaterial * m_material;
+  double      m_thickness;
+  double      m_width;
+  double      m_length;
+
+  double      m_safety;
+};
+
+#endif // VETOGEOMODEL_VETORADIATOR_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiatorParameters.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiatorParameters.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..33391054eaba30e6fde505065921e3a2594cd0d2
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiatorParameters.cxx
@@ -0,0 +1,46 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuRadiatorParameters.h"
+#include "VetoNuGeometryManager.h"
+
+#include "VetoNuDataBase.h"
+
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+#include <cmath>
+
+
+VetoNuRadiatorParameters::VetoNuRadiatorParameters(VetoNuDataBase* rdb)
+{
+  m_rdb = rdb;
+}
+
+//
+// Radiator General
+//
+double 
+VetoNuRadiatorParameters::radiatorWidth() const
+{
+  return m_rdb->radiatorGeneral()->getDouble("WIDTH") * Gaudi::Units::mm; 
+}
+
+double 
+VetoNuRadiatorParameters::radiatorLength() const
+{
+  return m_rdb->radiatorGeneral()->getDouble("LENGTH") * Gaudi::Units::mm; 
+}
+
+double 
+VetoNuRadiatorParameters::radiatorThickness() const
+{
+  return m_rdb->radiatorGeneral()->getDouble("THICKNESS") * Gaudi::Units::mm; 
+}
+
+std::string VetoNuRadiatorParameters::radiatorMaterial() const
+{
+  return m_rdb->radiatorGeneral()->getString("MATERIAL");
+}
+
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiatorParameters.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiatorParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..eedd316d60e0c52a5b4f0b55640a5ddfea62f7b4
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuRadiatorParameters.h
@@ -0,0 +1,31 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuRadiatorParameters_H
+#define VetoNuGeoModel_VetoNuRadiatorParameters_H
+
+#include <string>
+
+class VetoNuDataBase;
+
+class VetoNuRadiatorParameters {
+
+public:
+
+  // Constructor 
+  VetoNuRadiatorParameters(VetoNuDataBase* rdb);
+
+  // Barrel General
+  double radiatorThickness() const;
+  double radiatorWidth() const;
+  double radiatorLength() const;
+  std::string radiatorMaterial() const;
+
+ private:
+  VetoNuDataBase * m_rdb;
+
+};
+
+
+#endif // VetoNuGeoModel_VetoNuRadiatorParameters_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStation.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStation.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..65fb5fd7b80d4eb2af761723476d75259923af43
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStation.cxx
@@ -0,0 +1,100 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuStation.h"
+
+#include "VetoNuMaterialManager.h"
+
+#include "VetoNuGeometryManager.h"
+#include "VetoNuStationParameters.h"
+#include "VetoNuGeneralParameters.h"
+#include "VetoNuIdentifier.h"
+#include "VetoNuPlate.h"
+
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h"
+
+#include "ScintGeoModelUtils/ExtraMaterial.h"
+
+#include "GeoModelKernel/GeoBox.h"
+#include "GeoModelKernel/GeoLogVol.h"
+#include "GeoModelKernel/GeoFullPhysVol.h"
+#include "GeoModelKernel/GeoPhysVol.h"
+#include "GeoModelKernel/GeoNameTag.h"
+#include "GeoModelKernel/GeoIdentifierTag.h"
+#include "GeoModelKernel/GeoTransform.h"
+#include "GeoModelKernel/GeoAlignableTransform.h"
+#include "GeoModelKernel/GeoMaterial.h"
+#include "GeoModelKernel/GeoShape.h"
+#include "GeoModelKernel/GeoShapeShift.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+#include <iostream>
+
+VetoNuStation::VetoNuStation(const std::string & name,
+                         VetoNuPlate* plate,
+                         ScintDD::VetoNuDetectorManager* detectorManager,
+                         const VetoNuGeometryManager* geometryManager,
+                         VetoNuMaterialManager* materials)
+  : VetoNuUniqueComponentFactory(name, detectorManager, geometryManager, materials),
+  m_plate { plate }
+{
+  getParameters();
+  m_logVolume = preBuild();
+}
+
+
+void
+VetoNuStation::getParameters()
+{
+  const VetoNuStationParameters * parameters = m_geometryManager->stationParameters();
+  
+  m_numPlates =   parameters->numPlates();
+  m_platePitch = parameters->platePitch();
+
+  const VetoNuGeneralParameters* generalParameters = m_geometryManager->generalParameters();
+  m_safety = generalParameters->safety();
+
+  m_width  = m_plate->width() + m_safety;
+  m_length = m_plate->length() + m_safety;
+  // pitch includes thickness of one plate
+  m_thickness = (m_numPlates - 1) * m_platePitch + m_plate->thickness() + m_safety;
+
+  // Set numerology
+  m_detectorManager->numerology().setNumPlatesPerStation(m_numPlates);
+}
+
+const GeoLogVol * 
+VetoNuStation::preBuild()
+{
+  // Create the station volume
+  // Box envelope containing the station.
+  const GeoBox* stationEnvelopeShape = new GeoBox(0.5 * m_width, 0.5 * m_length, 0.5 * m_thickness);
+  GeoLogVol* stationLog = new GeoLogVol(getName(), stationEnvelopeShape, m_materials->gasMaterial());
+  return stationLog;
+}
+
+GeoVPhysVol * 
+VetoNuStation::build(VetoNuIdentifier id)
+{
+
+  GeoFullPhysVol * station = new GeoFullPhysVol(m_logVolume);
+
+  double activeDepth = m_thickness - m_safety;
+  double plateThickness = m_plate->thickness();
+  for (int iPlate = 0; iPlate < m_numPlates; iPlate++)
+  {
+    station->add(new GeoNameTag("Plate#"+intToString(iPlate)));
+    station->add(new GeoIdentifierTag(iPlate));
+    id.setPlate(iPlate);
+    GeoAlignableTransform* transform = new GeoAlignableTransform(GeoTrf::Translate3D(0.0, 
+                                                                                     0.0,
+                                                                                     (plateThickness - activeDepth)/2 + iPlate * m_platePitch));
+    station->add(transform);
+    GeoVPhysVol* platePV = m_plate->build(id);
+    station->add(platePV);
+    m_detectorManager->addAlignableTransform(0, id.getPlateId(), transform, platePV);
+  }  
+  return station;
+}
+
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStation.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStation.h
new file mode 100644
index 0000000000000000000000000000000000000000..50702df10006c6d767c929f68750b797c8fcae56
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStation.h
@@ -0,0 +1,50 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VETONUGEOMODEL_VETONUSTATION_H
+#define VETONUGEOMODEL_VETONUSTATION_H
+
+#include "VetoNuComponentFactory.h"
+
+class GeoVPhysVol;
+class GeoFullPhysVol;
+class GeoLogVol;
+class VetoNuIdentifier;
+class VetoNuPlate;
+
+class VetoNuStation : public VetoNuUniqueComponentFactory
+{
+
+public:
+  VetoNuStation(const std::string & name,
+             VetoNuPlate* plate,
+             ScintDD::VetoNuDetectorManager* detectorManager,
+             const VetoNuGeometryManager* geometryManager,
+             VetoNuMaterialManager* materials);
+  virtual GeoVPhysVol * build(VetoNuIdentifier id);
+
+public:
+  int    numPlates()   const {return m_numPlates;}
+  double platePitch()  const {return m_platePitch;}
+  double thickness()   const {return m_thickness;}
+  double width()       const {return m_width;}
+  double length()      const {return m_length;}
+ 
+private:
+  void getParameters();
+  virtual const GeoLogVol * preBuild();
+ 
+  VetoNuPlate*  m_plate;
+
+  int         m_numPlates;
+  double      m_platePitch;
+
+  double      m_thickness;
+  double      m_width;
+  double      m_length;
+
+  double      m_safety;
+};
+
+#endif // VETONUGEOMODEL_VETONUSTATION_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStationParameters.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStationParameters.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9ec319eaa4377a7e5b58702f09b876d93c4752de
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStationParameters.cxx
@@ -0,0 +1,872 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "VetoNuStationParameters.h"
+#include "VetoNuGeometryManager.h"
+
+#include "VetoNuDataBase.h"
+
+#include "RDBAccessSvc/IRDBRecord.h"
+#include "GaudiKernel/SystemOfUnits.h"
+
+#include <cmath>
+
+
+VetoNuStationParameters::VetoNuStationParameters(VetoNuDataBase* rdb)
+{
+  m_rdb = rdb;
+}
+
+
+// //
+// // Barrel Ski
+// //
+// int 
+// SCT_BarrelParameters::skiFirstStagger() const
+// {
+//   return m_rdb->brlSki()->getInt("SKIFIRSTSTAGGER");
+// }
+
+// double 
+// SCT_BarrelParameters::skiRadialSep() const
+// {
+//   return m_rdb->brlSki()->getDouble("SKIRADIALSEP") * Gaudi::Units::mm;
+// }
+
+// int
+// SCT_BarrelParameters::modulesPerSki() const
+// {
+//   return m_rdb->brlSkiZSize();
+// }
+
+// double 
+// SCT_BarrelParameters::skiZPosition(int index) const
+// {
+//   return m_rdb->brlSkiZ(index)->getDouble("ZPOSITION") * Gaudi::Units::mm;
+// }
+
+// int 
+// SCT_BarrelParameters::skiModuleIdentifier(int index) const
+// {
+//   return  m_rdb->brlSkiZ(index)->getInt("MODULEID");
+// }
+
+// //
+// // Barrel Layer
+// //
+// double 
+// SCT_BarrelParameters::tilt(int iLayer) const
+// {
+//   return m_rdb->brlLayer(iLayer)->getDouble("TILT") * Gaudi::Units::degree;
+// }
+
+// int 
+// SCT_BarrelParameters::layerStereoSign(int iLayer) const
+// {
+//   return m_rdb->brlLayer(iLayer)->getInt("STEREOSIGN");
+// }
+
+
+// double 
+// SCT_BarrelParameters::radius(int iLayer) const
+// {
+//   return m_rdb->brlLayer(iLayer)->getDouble("RADIUS") * Gaudi::Units::mm;
+// }
+
+// int 
+// SCT_BarrelParameters::skisPerLayer(int iLayer) const
+// {
+//   return m_rdb->brlLayer(iLayer)->getInt("SKISPERLAYER");
+// }
+
+// double 
+// SCT_BarrelParameters::layerBracketPhiOffset(int iLayer) const
+// {
+//   return m_rdb->brlLayer(iLayer)->getDouble("BRACKETPHIOFFSET") * Gaudi::Units::deg;
+// }
+
+// double 
+// SCT_BarrelParameters::layerPhiOfRefModule(int iLayer) const
+// {
+//   // For backward compatibility, if field is null return (90 - tilt) 
+//   // as ref module is horizontal in old versions.
+//   if  (m_rdb->brlLayer(iLayer)->isFieldNull("PHIOFREFMODULE")) {
+//     return 90*Gaudi::Units::deg - tilt(iLayer);
+//   }
+//   return m_rdb->brlLayer(iLayer)->getDouble("PHIOFREFMODULE") * Gaudi::Units::deg;
+// }
+
+
+// //
+// // Barrel Bracket
+// //
+// double
+// SCT_BarrelParameters::bracketThickness() const
+// {
+//   return m_rdb->brlSki()->getDouble("BRACKETTHICKNESS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::bracketWidth() const
+// {
+//   return m_rdb->brlSki()->getDouble("BRACKETWIDTH") * Gaudi::Units::mm;
+// }
+ 
+// double
+// SCT_BarrelParameters::bracketLength() const
+// {
+//   return m_rdb->brlSki()->getDouble("BRACKETLENGTH") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::bracketMaterial() const
+// {
+//   return m_rdb->brlSki()->getString("BRACKETMATERIAL");
+// }
+
+// //
+// // Barrel Dogleg
+// //
+// double
+// SCT_BarrelParameters::doglegThickness() const
+// {
+//   return m_rdb->brlSki()->getDouble("DOGLEGTHICKNESS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::doglegWidth() const
+// {
+//   return m_rdb->brlSki()->getDouble("DOGLEGWIDTH") * Gaudi::Units::mm;
+// }
+ 
+// double
+// SCT_BarrelParameters::doglegLength() const
+// {
+//   return m_rdb->brlSki()->getDouble("DOGLEGLENGTH") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::doglegMaterial() const
+// {
+//   return m_rdb->brlSki()->getString("DOGLEGMATERIAL");
+// }
+
+// double
+// SCT_BarrelParameters::doglegOffsetX() const
+// {
+//   return m_rdb->brlSki()->getDouble("DOGLEGOFFSETX") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::doglegOffsetY() const
+// {
+//   return m_rdb->brlSki()->getDouble("DOGLEGOFFSETY") * Gaudi::Units::mm;
+// }
+
+// //
+// // Barrel CoolingBlock
+// //
+// double
+// SCT_BarrelParameters::coolingBlockThickness() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGBLOCKTHICK") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::coolingBlockWidth() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGBLOCKWIDTH") * Gaudi::Units::mm;
+// }
+ 
+// double
+// SCT_BarrelParameters::coolingBlockLength() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGBLOCKLENGTH") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::coolingBlockMaterial() const
+// {
+//   return m_rdb->brlSki()->getString("COOLINGBLOCKMATERIAL");
+// }
+
+// double
+// SCT_BarrelParameters::coolingBlockOffsetX() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGBLOCKOFFSETX") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::coolingBlockOffsetY() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGBLOCKOFFSETY") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::coolingBlockOffsetZ() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGBLOCKOFFSETZ") * Gaudi::Units::mm;
+// }
+
+// //
+// // Barrel CoolingPipe
+// //
+// double
+// SCT_BarrelParameters::coolingPipeRadius() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGPIPERADIUS") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::coolingPipeMaterial() const
+// {
+//   return m_rdb->brlSki()->getString("COOLINGPIPEMATERIAL");
+// }
+
+// double
+// SCT_BarrelParameters::coolingPipeOffsetX() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGPIPEOFFSETX") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::coolingPipeOffsetY() const
+// {
+//   return m_rdb->brlSki()->getDouble("COOLINGPIPEOFFSETY") * Gaudi::Units::mm;
+// }
+
+
+// //
+// // Barrel PowerTape
+// //
+// double
+// SCT_BarrelParameters::powerTapeThickness() const
+// {
+//   return m_rdb->brlSki()->getDouble("POWERTAPETHICKNESS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::powerTapeWidth() const
+// {
+//   return m_rdb->brlSki()->getDouble("POWERTAPEWIDTH") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::powerTapeMaterial() const
+// { 
+//   return m_rdb->brlSki()->getString("POWERTAPEMATERIAL");
+// }
+
+// double
+// SCT_BarrelParameters::powerTapeStartPointOffset() const
+// {
+//   return m_rdb->brlSki()->getDouble("POWERTAPESTARTOFFSET") * Gaudi::Units::mm;
+// }
+ 
+// //
+// // Barrel Harness
+// //
+// double
+// SCT_BarrelParameters::harnessThickness() const
+// {
+//   return m_rdb->brlSki()->getDouble("HARNESSTHICKNESS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::harnessWidth() const
+// {
+//   return m_rdb->brlSki()->getDouble("HARNESSWIDTH") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::harnessMaterial() const
+// { 
+//   return m_rdb->brlSki()->getString("HARNESSMATERIAL");
+// }
+
+// //
+// // Barrel SupportCyl
+// //
+// double 
+// SCT_BarrelParameters::supportCylInnerRadius(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("SUPPORTCYLINNERRAD") * Gaudi::Units::mm;
+// }
+
+// double 
+// SCT_BarrelParameters::supportCylOuterRadius(int iLayer) const
+// {
+//   return supportCylInnerRadius(iLayer) + supportCylDeltaR(iLayer);
+// }
+
+// double 
+// SCT_BarrelParameters::supportCylDeltaR(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("SUPPORTCYLDELTAR") * Gaudi::Units::mm;
+// }
+
+// std::string 
+// SCT_BarrelParameters::supportCylMaterial(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getString("SUPPORTCYLMATERIAL");
+// }
+
+
+// //
+// // Barrel Flange
+// //
+// double 
+// SCT_BarrelParameters::flangeDeltaZ(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("FLANGEDELTAZ") * Gaudi::Units::mm;
+// }
+
+// double 
+// SCT_BarrelParameters::flangeDeltaR(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("FLANGEDELTAR") * Gaudi::Units::mm;
+// }
+
+// std::string 
+// SCT_BarrelParameters::flangeMaterial(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getString("FLANGEMATERIAL");
+// }
+
+// //
+// // Barrel Clamp
+// //
+// double 
+// SCT_BarrelParameters::clampDeltaZ(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("CLAMPDELTAZ") * Gaudi::Units::mm;
+// }
+
+// double 
+// SCT_BarrelParameters::clampDeltaR(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("CLAMPDELTAR") * Gaudi::Units::mm;
+// }
+
+// std::string 
+// SCT_BarrelParameters::clampMaterial(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getString("CLAMPMATERIAL");
+// }
+
+// //
+// // Barrel Cooling Inlet/outlets
+// //
+// double 
+// SCT_BarrelParameters::coolingEndDeltaR(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("COOLINGENDDELTAR") * Gaudi::Units::mm;
+// }
+
+// std::string 
+// SCT_BarrelParameters::coolingEndMaterial(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getString("COOLINGENDMATERIAL");
+// }
+
+// //
+// // Barrel CloseOut
+// //
+// double 
+// SCT_BarrelParameters::closeOutDeltaZ(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getDouble("CLOSEOUTDELTAZ") * Gaudi::Units::mm;
+// }
+
+// std::string 
+// SCT_BarrelParameters::closeOutMaterial(int iLayer) const
+// {
+//   return m_rdb->brlServPerLayer(iLayer)->getString("CLOSEOUTMATERIAL");
+// }
+
+// //
+// // Barrel InterLink and B6 bearing
+// //
+// double 
+// SCT_BarrelParameters::interLinkDeltaZ() const
+// {
+//   return m_rdb->brlServices()->getDouble("INTERLINKDELTAZ") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::interLinkInnerRadius() const
+// {
+//   return m_rdb->brlServices()->getDouble("INTERLINKINNERRADIUS") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::interLinkOuterRadius() const
+// {
+//   return m_rdb->brlServices()->getDouble("INTERLINKOUTERRADIUS") * Gaudi::Units::mm; 
+// }
+
+// std::string 
+// SCT_BarrelParameters::interLinkMaterial() const
+// {
+//   return m_rdb->brlServices()->getString("INTERLINKMATERIAL");
+// }
+
+// double 
+// SCT_BarrelParameters::interLinkDeltaPhi() const
+// {
+//   if  (m_rdb->brlServices()->isFieldNull("INTERLINKDPHI")) {
+//     return 360.*Gaudi::Units::deg;
+//   }
+//   return m_rdb->brlServices()->getDouble("INTERLINKDPHI") * Gaudi::Units::deg; 
+// }
+
+// double 
+// SCT_BarrelParameters::interLinkPhiPos() const
+// {
+//   if  (m_rdb->brlServices()->isFieldNull("INTERLINKPHIPOS")) {
+//     return 0.;
+//   }
+//   return m_rdb->brlServices()->getDouble("INTERLINKPHIPOS") * Gaudi::Units::deg;
+// }
+
+// int
+// SCT_BarrelParameters::interLinkNRepeat() const
+// {
+//   if  (m_rdb->brlServices()->isFieldNull("INTERLINKNREPEAT")) {
+//     return 1;
+//   }
+//   return m_rdb->brlServices()->getInt("INTERLINKNREPEAT"); 
+// }
+
+// double 
+// SCT_BarrelParameters::bearingDeltaPhi() const
+// {
+//   if  (m_rdb->brlServices()->isFieldNull("BEARINGDPHI")) {
+//     return 0.;
+//   }
+//   return m_rdb->brlServices()->getDouble("BEARINGDPHI") * Gaudi::Units::deg; 
+// }
+
+// double 
+// SCT_BarrelParameters::bearingPhiPos() const
+// {
+//   if  (m_rdb->brlServices()->isFieldNull("BEARINGPHIPOS")) {
+//     return 0.;
+//   }
+//   return m_rdb->brlServices()->getDouble("BEARINGPHIPOS") * Gaudi::Units::deg;
+// }
+
+// int
+// SCT_BarrelParameters::bearingNRepeat() const
+// {
+//   if  (m_rdb->brlServices()->isFieldNull("BEARINGNREPEAT")) {
+//     return 0;
+//   }
+//   return m_rdb->brlServices()->getInt("BEARINGNREPEAT"); 
+// }
+
+// std::string 
+// SCT_BarrelParameters::bearingMaterial() const
+// {
+//   if  (m_rdb->brlServices()->isFieldNull("BEARINGMATERIAL")) {
+//     return " ";
+//   }
+//   return m_rdb->brlServices()->getString("BEARINGMATERIAL");
+// }
+
+// //
+// // Barrel FSI and FSI flange
+// //
+// bool 
+// SCT_BarrelParameters::includeFSI() const
+// {
+//   if (m_rdb->brlFSISize() > 0) {return true;}
+//   return false;
+// }
+
+// double 
+// SCT_BarrelParameters::fsiFlangeInnerRadius() const
+// {
+//   return m_rdb->brlFSI()->getDouble("FLANGEINNERRADIUS") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::fsiFlangeOuterRadius() const
+// {
+//   return m_rdb->brlFSI()->getDouble("FLANGEOUTERRADIUS") * Gaudi::Units::mm; 
+// }
+
+// std::string 
+// SCT_BarrelParameters::fsiFlangeMaterial() const
+// {
+//   return m_rdb->brlFSI()->getString("FLANGEMATERIAL");
+// }
+
+// double 
+// SCT_BarrelParameters::fsiFibreMaskDeltaR() const
+// {
+//   return m_rdb->brlFSI()->getDouble("FIBREMASKDELTAR") * Gaudi::Units::mm; 
+// }
+
+// std::string 
+// SCT_BarrelParameters::fsiFibreMaskMaterial() const
+// {
+//   return m_rdb->brlFSI()->getString("FIBREMASKMATERIAL");
+// }
+
+// double 
+// SCT_BarrelParameters::fsiEndJewelRadialWidth() const
+// {
+//   return m_rdb->brlFSI()->getDouble("ENDJEWELRADIALWIDTH") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::fsiEndJewelRPhiWidth() const
+// {
+//   return m_rdb->brlFSI()->getDouble("ENDJEWELRPHIWIDTH") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::fsiEndJewelLength() const
+// {
+//   return m_rdb->brlFSI()->getDouble("ENDJEWELLENGTH") * Gaudi::Units::mm; 
+// }
+
+// std::string 
+// SCT_BarrelParameters::fsiEndJewelMaterial() const
+// {
+//   return m_rdb->brlFSI()->getString("ENDJEWELMATERIAL");
+// }
+
+// int 
+// SCT_BarrelParameters::fsiEndJewelNRepeat(int iLayer) const
+// {
+//   return m_rdb->brlFSILocation(iLayer)->getInt("ENDJEWELNREPEAT");
+// }
+
+// double 
+// SCT_BarrelParameters::fsiEndJewelPhi(int iLayer) const
+// {
+//   return m_rdb->brlFSILocation(iLayer)->getDouble("ENDJEWELPHI") * Gaudi::Units::degree;
+// }
+
+// double 
+// SCT_BarrelParameters::fsiEndJewelZ(int iLayer) const
+// {
+//   return m_rdb->brlFSILocation(iLayer)->getDouble("ENDJEWELZ") * Gaudi::Units::mm;
+// }
+
+// double 
+// SCT_BarrelParameters::fsiScorpionRadialWidth() const
+// {
+//   return m_rdb->brlFSI()->getDouble("SCORPIONRADIALWIDTH") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::fsiScorpionRPhiWidth() const
+// {
+//   return m_rdb->brlFSI()->getDouble("SCORPIONRPHIWIDTH") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::fsiScorpionLength() const
+// {
+//   return m_rdb->brlFSI()->getDouble("SCORPIONLENGTH") * Gaudi::Units::mm; 
+// }
+
+// std::string 
+// SCT_BarrelParameters::fsiScorpionMaterial() const
+// {
+//   return m_rdb->brlFSI()->getString("SCORPIONMATERIAL");
+// }
+
+// int 
+// SCT_BarrelParameters::fsiScorpionNRepeat(int iLayer) const
+// {
+//   return m_rdb->brlFSILocation(iLayer)->getInt("SCORPIONNREPEAT");
+// }
+
+// double 
+// SCT_BarrelParameters::fsiScorpionPhi(int iLayer) const
+// {
+//   return m_rdb->brlFSILocation(iLayer)->getDouble("SCORPIONPHI") * Gaudi::Units::degree;
+// }
+
+// double 
+// SCT_BarrelParameters::fsiScorpionZ(int iLayer) const
+// {
+//   return m_rdb->brlFSILocation(iLayer)->getDouble("SCORPIONZ") * Gaudi::Units::mm;
+// }
+
+
+// //
+// // Barrel Cooling Spider
+// //
+// double 
+// SCT_BarrelParameters::spiderDeltaZ() const
+// {
+//   return m_rdb->brlServices()->getDouble("SPIDERDELTAZ") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::spiderInnerRadius() const
+// {
+//   return m_rdb->brlServices()->getDouble("SPIDERINNERRADIUS") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::spiderOuterRadius() const
+// {
+//   return m_rdb->brlServices()->getDouble("SPIDEROUTERRADIUS") * Gaudi::Units::mm; 
+// }
+
+// std::string 
+// SCT_BarrelParameters::spiderMaterial() const
+// {
+//   return m_rdb->brlServices()->getString("SPIDERMATERIAL");
+// }
+
+// //
+// // Barrel Thermal Shield
+// //
+// double
+// SCT_BarrelParameters::thermalShieldInnerRadius() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("INNERRADIUS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldOuterRadius() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("OUTERRADIUS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldEndZMax() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("ENDZMAX") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldCylTotalThickness() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("CYLTOTALTHICKNESS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldCylInnerWallThickness() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("CYLINNERWALLTHICK") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldCylOuterWallThickness() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("CYLOUTERWALLTHICK") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldSpacerZWidth() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("SPACERZWIDTH") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldFirstSpacerZMin() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("FIRSTSPACERZMIN") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldEndCapCylThickness() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("ENDCAPCYLTHICKNESS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldEndCapThickness() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("ENDCAPTHICKNESS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldBulkheadInnerRadius() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("BULKHEADINNERRADIUS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldBulkheadOuterRadius() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("BULKHEADOUTERRADIUS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldEndPanelInnerRadius() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("ENDPANELINNERRADIUS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::thermalShieldEndPanelOuterRadius() const
+// {
+//   return m_rdb->brlThermalShield()->getDouble("ENDPANELOUTERRADIUS") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::thermalShieldMaterialSpacer() const
+// {
+//   return m_rdb->brlThermalShield()->getString("MATERIALSPACER");
+// }
+
+// std::string
+// SCT_BarrelParameters::thermalShieldMaterialCyl() const
+// {
+//   return m_rdb->brlThermalShield()->getString("MATERIALCYL");
+// }
+
+// std::string
+// SCT_BarrelParameters::thermalShieldMaterialOuterSect() const
+// {
+//   return m_rdb->brlThermalShield()->getString("MATERIALOUTERSECT");
+// }
+
+// std::string
+// SCT_BarrelParameters::thermalShieldMaterialInnerSect() const
+// {
+//   return m_rdb->brlThermalShield()->getString("MATERIALINNERSECT");
+// }
+
+// //
+// // Barrel EMI Shield
+// //
+// double
+// SCT_BarrelParameters::emiShieldInnerRadius() const
+// {
+//   return m_rdb->brlServices()->getDouble("EMIINNERRADIUS") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::emiShieldDeltaR() const
+// {
+//   return m_rdb->brlServices()->getDouble("EMIDELTAR") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::emiShieldZMax() const
+// {
+//   return m_rdb->brlServices()->getDouble("EMIZMAX") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::emiShieldMaterial() const
+// {
+//   return m_rdb->brlServices()->getString("EMIMATERIAL");
+// }
+
+// double
+// SCT_BarrelParameters::emiJointDeltaR() const
+// {
+//   return m_rdb->brlServices()->getDouble("EMIJOINTDELTAR") * Gaudi::Units::mm;
+// }
+
+// double
+// SCT_BarrelParameters::emiJointRPhi() const
+// {
+//   return m_rdb->brlServices()->getDouble("EMIJOINTRPHI") * Gaudi::Units::mm;
+// }
+
+// std::string
+// SCT_BarrelParameters::emiJointMaterial() const
+// {
+//   return m_rdb->brlServices()->getString("EMIJOINTMATERIAL");
+// }
+
+
+
+// // 
+// // Attachment of pixel to SCT.
+// //
+// double 
+// SCT_BarrelParameters::pixelAttachmentInnerRadius() const
+// {
+//   return m_rdb->brlServices()->getDouble("PIXELATTACHINNERRAD") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::pixelAttachmentOuterRadius() const
+// {
+//   return m_rdb->brlServices()->getDouble("PIXELATTACHOUTERRAD") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::pixelAttachmentZMin() const
+// {
+//   return m_rdb->brlServices()->getDouble("PIXELATTACHZMIN") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::pixelAttachmentDeltaZ() const
+// {
+//   return m_rdb->brlServices()->getDouble("PIXELATTACHDELTAZ") * Gaudi::Units::mm; 
+// }
+
+// std::string 
+// SCT_BarrelParameters::pixelAttachmentMaterial() const
+// {
+//   return m_rdb->brlServices()->getString("PIXELATTACHMATERIAL");
+// }
+
+//
+// Barrel General
+//
+int
+VetoNuStationParameters::numPlates() const
+{
+  return m_rdb->stationGeneral()->getInt("NUMPLATES"); 
+}
+
+double
+VetoNuStationParameters::platePitch() const
+{
+  return m_rdb->stationGeneral()->getDouble("PLATEPITCH");
+}
+
+// double 
+// VetoNuStationParameters::stationWidth() const
+// {
+//   return m_rdb->stationGeneral()->getDouble("WIDTH") * Gaudi::Units::mm; 
+// }
+
+// double 
+// VetoNuStationParameters::stationLength() const
+// {
+//   return m_rdb->stationGeneral()->getDouble("LENGTH") * Gaudi::Units::mm; 
+// }
+
+// double 
+// VetoNuStationParameters::stationThickness() const
+// {
+//   return m_rdb->stationGeneral()->getDouble("THICKNESS") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::cylinderLength() const
+// {
+//   return m_rdb->brlGeneral()->getDouble("CYLINDERLENGTH") * Gaudi::Units::mm; 
+// }
+
+// double 
+// SCT_BarrelParameters::activeLength() const
+// {
+//   return m_rdb->brlGeneral()->getDouble("ACTIVELENGTH") * Gaudi::Units::mm; 
+// }
+
+// bool 
+// SCT_BarrelParameters::isOldGeometry() const
+// {
+//   if (m_rdb->brlGeneral()->isFieldNull("CYLINDERLENGTH")) {return true;}
+//   return false;
+// }
+
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStationParameters.h b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStationParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..843b6095e29ff703ab1605e1c8eb66cdd114f635
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/VetoNuStationParameters.h
@@ -0,0 +1,30 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef VetoNuGeoModel_VetoNuStationParameters_H
+#define VetoNuGeoModel_VetoNuStationParameters_H
+
+#include <string>
+
+class VetoNuDataBase;
+
+class VetoNuStationParameters {
+
+public:
+
+  // Constructor 
+  VetoNuStationParameters(VetoNuDataBase* rdb);
+
+
+  // General
+  int    numPlates() const;
+  double platePitch() const;
+
+ private:
+  VetoNuDataBase * m_rdb;
+
+};
+
+
+#endif // VetoNuGeoModel_VetoNuStationParameters_H
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/src/components/VetoNuGeoModel_entries.cxx b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/components/VetoNuGeoModel_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..cc2cd2581e8954fa96943ded066d9e6f5cb15544
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/src/components/VetoNuGeoModel_entries.cxx
@@ -0,0 +1,3 @@
+#include "VetoNuGeoModel/VetoNuDetectorTool.h"
+
+DECLARE_COMPONENT( VetoNuDetectorTool )
\ No newline at end of file
diff --git a/Scintillator/ScintDetDescr/VetoNuGeoModel/test/VetoNuGMConfig_test.py b/Scintillator/ScintDetDescr/VetoNuGeoModel/test/VetoNuGMConfig_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a8bca5989485cfa7c046e244e966d8d00c0d85f
--- /dev/null
+++ b/Scintillator/ScintDetDescr/VetoNuGeoModel/test/VetoNuGMConfig_test.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Run tests on VetoNuGeoModel configuration
+
+Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+if __name__ == "__main__":
+    from AthenaCommon.Configurable import Configurable
+    Configurable.configurableRun3Behavior=1
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    from AthenaConfiguration.TestDefaults import defaultTestFiles
+
+    ConfigFlags.Input.Files = defaultTestFiles.HITS
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"             # Always needed; must match FaserVersion
+    ConfigFlags.GeoModel.Align.Dynamic    = False
+    ConfigFlags.lock()
+
+    from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+    from VetoNuGeoModel.VetoNuGeoModelConfig import VetoNuGeometryCfg
+    acc = VetoNuGeometryCfg(ConfigFlags)
+    f=open('VetoNuGeometryCfg.pkl','wb')
+    acc.store(f)
+    f.close()
diff --git a/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/ScintIdCnv_entries.cxx b/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/ScintIdCnv_entries.cxx
index 18719ef20c15237f1febd9b268e819ce2b88f5e5..0b99ec46b38a725159e9ae7f914d7df3b99b2178 100644
--- a/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/ScintIdCnv_entries.cxx
+++ b/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/ScintIdCnv_entries.cxx
@@ -3,6 +3,7 @@
 // #include "SiliconIDDetDescrCnv.h"
 // #include "TRT_IDDetDescrCnv.h"
 #include "VetoIDDetDescrCnv.h"
+#include "VetoNuIDDetDescrCnv.h"
 #include "TriggerIDDetDescrCnv.h"
 #include "PreshowerIDDetDescrCnv.h"
 
@@ -11,5 +12,6 @@
 // DECLARE_CONVERTER(SiliconIDDetDescrCnv)
 // DECLARE_CONVERTER(TRT_IDDetDescrCnv)
 DECLARE_CONVERTER(VetoIDDetDescrCnv)
+DECLARE_CONVERTER(VetoNuIDDetDescrCnv)
 DECLARE_CONVERTER(TriggerIDDetDescrCnv)
 DECLARE_CONVERTER(PreshowerIDDetDescrCnv)
diff --git a/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/VetoNuIDDetDescrCnv.cxx b/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/VetoNuIDDetDescrCnv.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..82d3ab337ed23e70f62dd97bd5be675c656d7244
--- /dev/null
+++ b/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/VetoNuIDDetDescrCnv.cxx
@@ -0,0 +1,239 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+/***************************************************************************
+ Scint DetDescrCnv package
+ -----------------------------------------
+ ***************************************************************************/
+
+//<<<<<< INCLUDES                                                       >>>>>>
+
+#include "VetoNuIDDetDescrCnv.h"
+
+#include "DetDescrCnvSvc/DetDescrConverter.h"
+#include "DetDescrCnvSvc/DetDescrAddress.h"
+#include "GaudiKernel/MsgStream.h"
+#include "StoreGate/StoreGateSvc.h" 
+
+#include "IdDictDetDescr/IdDictManager.h"
+#include "ScintIdentifier/VetoNuID.h"
+
+
+//<<<<<< PRIVATE DEFINES                                                >>>>>>
+//<<<<<< PRIVATE CONSTANTS                                              >>>>>>
+//<<<<<< PRIVATE TYPES                                                  >>>>>>
+//<<<<<< PRIVATE VARIABLE DEFINITIONS                                   >>>>>>
+//<<<<<< PUBLIC VARIABLE DEFINITIONS                                    >>>>>>
+//<<<<<< CLASS STRUCTURE INITIALIZATION                                 >>>>>>
+//<<<<<< PRIVATE FUNCTION DEFINITIONS                                   >>>>>>
+//<<<<<< PUBLIC FUNCTION DEFINITIONS                                    >>>>>>
+//<<<<<< MEMBER FUNCTION DEFINITIONS                                    >>>>>>
+
+//--------------------------------------------------------------------
+
+long int   
+VetoNuIDDetDescrCnv::repSvcType() const
+{
+  return (storageType());
+}
+
+//--------------------------------------------------------------------
+
+StatusCode 
+VetoNuIDDetDescrCnv::initialize()
+{
+    // First call parent init
+    StatusCode sc = DetDescrConverter::initialize();
+    MsgStream log(msgSvc(), "VetoNuIDDetDescrCnv");
+    log << MSG::DEBUG << "in initialize" << endmsg;
+
+    if (sc.isFailure()) {
+        log << MSG::ERROR << "DetDescrConverter::initialize failed" << endmsg;
+	return sc;
+    }
+    
+    // The following is an attempt to "bootstrap" the loading of a
+    // proxy for VetoNuID into the detector store. However,
+    // VetoNuIDDetDescrCnv::initialize is NOT called by the conversion
+    // service.  So for the moment, this cannot be use. Instead the
+    // DetDescrCnvSvc must do the bootstrap from a parameter list.
+
+
+//      // Add Scint_DetDescrManager proxy as entry point to the detector store
+//      // - this is ONLY needed for the manager of each system
+//      sc = addToDetStore(classID(), "VetoNuID");
+//      if (sc.isFailure()) {
+//  	log << MSG::FATAL << "Unable to add proxy for VetoNuID to the Detector Store!" << endmsg;
+//  	return StatusCode::FAILURE;
+//      } else {}
+
+    return StatusCode::SUCCESS; 
+}
+
+//--------------------------------------------------------------------
+
+StatusCode 
+VetoNuIDDetDescrCnv::finalize()
+{
+    MsgStream log(msgSvc(), "VetoNuIDDetDescrCnv");
+    log << MSG::DEBUG << "in finalize" << endmsg;
+
+    return StatusCode::SUCCESS; 
+}
+
+//--------------------------------------------------------------------
+
+StatusCode
+VetoNuIDDetDescrCnv::createObj(IOpaqueAddress* pAddr, DataObject*& pObj) 
+{
+    //StatusCode sc = StatusCode::SUCCESS;
+    MsgStream log(msgSvc(), "VetoNuIDDetDescrCnv");
+    log << MSG::INFO << "in createObj: creating a VetoNuID helper object in the detector store" << endmsg;
+
+    // Create a new VetoNuID
+
+    DetDescrAddress* ddAddr;
+    ddAddr = dynamic_cast<DetDescrAddress*> (pAddr);
+    if(!ddAddr) {
+	log << MSG::FATAL << "Could not cast to DetDescrAddress." << endmsg;
+	return StatusCode::FAILURE;
+    }
+
+    // Get the StoreGate key of this container.
+    std::string helperKey  = *( ddAddr->par() );
+    if ("" == helperKey) {
+	log << MSG::DEBUG << "No Helper key " << endmsg;
+    }
+    else {
+	log << MSG::DEBUG << "Helper key is " << helperKey << endmsg;
+    }
+    
+
+    // get DetectorStore service
+    StoreGateSvc * detStore;
+    StatusCode status = serviceLocator()->service("DetectorStore", detStore);
+    if (status.isFailure()) {
+	log << MSG::FATAL << "DetectorStore service not found !" << endmsg;
+	return StatusCode::FAILURE;
+    } else {}
+ 
+    // Get the dictionary manager from the detector store
+    const DataHandle<IdDictManager> idDictMgr;
+    status = detStore->retrieve(idDictMgr, "IdDict");
+    if (status.isFailure()) {
+	log << MSG::FATAL << "Could not get IdDictManager !" << endmsg;
+	return StatusCode::FAILURE;
+    } 
+    else {
+	log << MSG::DEBUG << " Found the IdDictManager. " << endmsg;
+    }
+
+    // Only create new helper if it is the first pass or if there is a
+    // change in the the file or tag
+    bool initHelper               = false;
+
+    const IdDictMgr* mgr          = idDictMgr->manager();
+
+    // Internal Scint id tag
+    std::string   scintIDTag      = mgr->tag();
+
+    // DoChecks flag
+    bool doChecks                 = mgr->do_checks();
+
+    IdDictDictionary* dict = mgr->find_dictionary("Scintillator");  
+    if (!dict) {
+	log << MSG::ERROR 
+	    << "unable to find idDict for Scintillator" 
+	    << endmsg;
+	return StatusCode::FAILURE;
+    }
+
+    // File to be read for Scint ids
+    std::string   scintIDFileName = dict->file_name();
+
+    // Tag of RDB record for Scint ids
+    std::string   scintIdDictTag  = dict->dict_tag();
+
+
+    if (m_vetonuId) {
+
+	// VetoNu id helper already exists - second pass. Check for a
+	// change 
+	if (scintIDTag != m_scintIDTag) { 
+	    // Internal Scint id tag
+	    initHelper = true;
+	    log << MSG::DEBUG << " Changed internal Scint id tag: " 
+		<< scintIDTag << endmsg;
+	}
+	if (scintIDFileName != m_scintIDFileName) {
+	    // File to be read for Scint ids
+	    initHelper = true;
+	    log << MSG::DEBUG << " Changed ScintFileName:" 
+		<< scintIDFileName << endmsg;
+	}
+	if (scintIdDictTag != m_scintIdDictTag) {
+	    // Tag of RDB record for Scint ids
+	    initHelper = true;
+	    log << MSG::DEBUG << " Changed ScintIdDictTag: "
+		<< scintIdDictTag 
+		<< endmsg;
+	}
+	if (doChecks != m_doChecks) {
+	    // DoChecks flag
+	    initHelper = true;
+	    log << MSG::DEBUG << " Changed doChecks flag: "
+		<< doChecks
+		<< endmsg;
+        }
+    }
+    else {
+	// create the helper
+	m_vetonuId = new VetoNuID;
+	initHelper = true;
+        // add in message service for printout
+        m_vetonuId->setMessageSvc(msgSvc());
+    }
+    
+    if (initHelper) {
+	if (idDictMgr->initializeHelper(*m_vetonuId)) {
+	    log << MSG::ERROR << "Unable to initialize VetoNuID" << endmsg;
+	    return StatusCode::FAILURE;
+	} 
+	// Save state:
+	m_scintIDTag      = scintIDTag;
+	m_scintIDFileName = scintIDFileName;
+	m_scintIdDictTag  = scintIdDictTag;
+	m_doChecks        = doChecks;
+    }
+
+    // Pass a pointer to the container to the Persistency service by reference.
+    pObj = StoreGateSvc::asStorable(m_vetonuId);
+
+    return StatusCode::SUCCESS; 
+
+}
+
+//--------------------------------------------------------------------
+
+long
+VetoNuIDDetDescrCnv::storageType()
+{
+    return DetDescr_StorageType;
+}
+
+//--------------------------------------------------------------------
+const CLID& 
+VetoNuIDDetDescrCnv::classID() { 
+    return ClassID_traits<VetoNuID>::ID(); 
+}
+
+//--------------------------------------------------------------------
+VetoNuIDDetDescrCnv::VetoNuIDDetDescrCnv(ISvcLocator* svcloc) 
+    :
+    DetDescrConverter(ClassID_traits<VetoNuID>::ID(), svcloc),
+    m_vetonuId(0),
+    m_doChecks(false)
+
+{}
+
diff --git a/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/VetoNuIDDetDescrCnv.h b/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/VetoNuIDDetDescrCnv.h
new file mode 100644
index 0000000000000000000000000000000000000000..784d518879aec982ebcb3a96cb73c7e8aded80c8
--- /dev/null
+++ b/Scintillator/ScintDetDescrCnv/ScintIdCnv/src/VetoNuIDDetDescrCnv.h
@@ -0,0 +1,71 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+/***************************************************************************
+ Scint DetDescrCnv package
+ -----------------------------------------
+ ***************************************************************************/
+
+#ifndef SCINTIDCNV_VETONUIDDETDESCRCNV_H
+#define SCINTIDCNV_VETONUIDDETDESCRCNV_H
+
+//<<<<<< INCLUDES                                                       >>>>>>
+
+#include "DetDescrCnvSvc/DetDescrConverter.h"
+
+//<<<<<< PUBLIC DEFINES                                                 >>>>>>
+//<<<<<< PUBLIC CONSTANTS                                               >>>>>>
+//<<<<<< PUBLIC TYPES                                                   >>>>>>
+
+class VetoNuID;
+
+//<<<<<< PUBLIC VARIABLES                                               >>>>>>
+//<<<<<< PUBLIC FUNCTIONS                                               >>>>>>
+//<<<<<< CLASS DECLARATIONS                                             >>>>>>
+
+
+/**
+ **  This class is a converter for the VetoNuID an IdHelper which is
+ **  stored in the detector store. This class derives from
+ **  DetDescrConverter which is a converter of the DetDescrCnvSvc.
+ **
+ **/
+
+class VetoNuIDDetDescrCnv: public DetDescrConverter {
+
+public:
+    virtual long int   repSvcType() const;
+    virtual StatusCode initialize();
+    virtual StatusCode finalize();
+    virtual StatusCode createObj(IOpaqueAddress* pAddr, DataObject*& pObj);
+
+    // Storage type and class ID (used by CnvFactory)
+    static long storageType();
+    static const CLID& classID();
+
+    VetoNuIDDetDescrCnv(ISvcLocator* svcloc);
+
+private:
+    /// The helper - only will create it once
+    VetoNuID*       m_vetonuId;
+
+    /// File to be read for Scint ids
+    std::string   m_scintIDFileName;
+
+    /// Tag of RDB record for Scint ids
+    std::string   m_scintIdDictTag;
+
+    /// Internal Scint id tag
+    std::string   m_scintIDTag;
+
+    /// Whether or not 
+    bool          m_doChecks;
+
+};
+
+
+//<<<<<< INLINE PUBLIC FUNCTIONS                                        >>>>>>
+//<<<<<< INLINE MEMBER FUNCTIONS                                        >>>>>>
+
+#endif // SCINTIDCNV_VETONUIDDETDESCRCNV_H
diff --git a/Scintillator/ScintDigiAlgs/CMakeLists.txt b/Scintillator/ScintDigiAlgs/CMakeLists.txt
index 43695c5f90d96053a8b44fa3ac41c0f4c159ad99..5c1874e2502241f919efb21ade0e7c26de7369ca 100644
--- a/Scintillator/ScintDigiAlgs/CMakeLists.txt
+++ b/Scintillator/ScintDigiAlgs/CMakeLists.txt
@@ -9,7 +9,9 @@ atlas_subdir( ScintDigiAlgs )
 atlas_add_component( ScintDigiAlgs
                      src/*.cxx src/*.h
                      src/components/*.cxx
-                     LINK_LIBRARIES AthenaBaseComps Identifier StoreGateLib WaveRawEvent ScintSimEvent WaveDigiToolsLib)
+                     LINK_LIBRARIES AthenaBaseComps Identifier ScintIdentifier 
+		     WaveformConditionsToolsLib StoreGateLib WaveRawEvent 
+		     ScintSimEvent WaveDigiToolsLib)
 
 atlas_install_python_modules( python/*.py )
 
diff --git a/Scintillator/ScintDigiAlgs/python/ScintDigiAlgsConfig.py b/Scintillator/ScintDigiAlgs/python/ScintDigiAlgsConfig.py
index 7f2ba8530575eeb6a6c9b51779c482f84bdfc648..ddf3074618aff121e704b2214309a14952436837 100644
--- a/Scintillator/ScintDigiAlgs/python/ScintDigiAlgsConfig.py
+++ b/Scintillator/ScintDigiAlgs/python/ScintDigiAlgsConfig.py
@@ -5,6 +5,8 @@ from AthenaConfiguration.ComponentFactory import CompFactory
 
 from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
 
+from WaveformConditionsTools.WaveformCableMappingConfig import WaveformCableMappingCfg
+
 # Crystallball parameter dictionary used in simulated digitized wave reconstruction.
 # Crystalball function Parameters estimated from Deion's slides uploaded at
 # https://indico.cern.ch/event/1099652/contributions/4626975/attachments/2352595/4013927/Faser-Physics-run3933-plots.pdf  (20/01/2022)
@@ -13,8 +15,17 @@ dict_CB_param = {}
 dict_CB_param["Trigger"]=dict(CB_alpha=-0.38, CB_n=25, CB_mean=815, CB_sigma=7.7, CB_norm = 500 )
 dict_CB_param["Timing"]=dict(CB_alpha=-0.32, CB_n=65, CB_mean=846, CB_sigma=5.3, CB_norm = 500) # copy from Preshower; Timing was not in TestBeam
 dict_CB_param["Veto"]=dict(CB_alpha=-0.38, CB_n=25, CB_mean=815, CB_sigma=7.7, CB_norm = 1000) # copy from Trigger; Veto was not in TestBeam, but in sim "Veto" is the TestBeam Trigger component
+dict_CB_param["VetoNu"]=dict(CB_alpha=-0.38, CB_n=25, CB_mean=815, CB_sigma=7.7, CB_norm = 1000) # copy from Trigger; Veto was not in TestBeam, but in sim "Veto" is the TestBeam Trigger component
 dict_CB_param["Preshower"]=dict(CB_alpha=-0.32, CB_n=65, CB_mean=846, CB_sigma=5.3, CB_norm = 500)
 
+dict_baseline_params = {
+    "Trigger"   : {"mean" : 15000, "rms" : 3},
+    "Timing"    : {"mean" : 15000, "rms" : 3},
+    "Veto"      : {"mean" : 15000, "rms" : 3},
+    "VetoNu"    : {"mean" : 15000, "rms" : 3},
+    "Preshower" : {"mean" : 15000, "rms" : 3},        
+    }
+
 # One stop shopping for normal FASER data
 def ScintWaveformDigitizationCfg(flags):
     """ Return all algorithms and tools for Waveform digitization """
@@ -26,8 +37,10 @@ def ScintWaveformDigitizationCfg(flags):
     if "TB" not in flags.GeoModel.FaserVersion:
         acc.merge(ScintWaveformDigiCfg(flags, "TimingWaveformDigiAlg", "Trigger"))
     acc.merge(ScintWaveformDigiCfg(flags, "VetoWaveformDigiAlg", "Veto"))
+    acc.merge(ScintWaveformDigiCfg(flags, "VetoNuWaveformDigiAlg", "VetoNu"))
     acc.merge(ScintWaveformDigiCfg(flags, "PreshowerWaveformDigiAlg", "Preshower"))
     acc.merge(ScintWaveformDigitizationOutputCfg(flags))
+    acc.merge(WaveformCableMappingCfg(flags))
     return acc
 
 # Return configured digitization algorithm from SIM hits
@@ -48,8 +61,8 @@ def ScintWaveformDigiCfg(flags, name="ScintWaveformDigiAlg", source="", **kwargs
     digiAlg.CB_sigma = dict_CB_param[source]["CB_sigma"]
     digiAlg.CB_norm = dict_CB_param[source]["CB_norm"]    
 
-    digiAlg.base_mean = 15000
-    digiAlg.base_rms = 3
+    digiAlg.base_mean = dict_baseline_params[source]["mean"]
+    digiAlg.base_rms = dict_baseline_params[source]["rms"]
      
     kwargs.setdefault("WaveformDigitisationTool", tool)
 
@@ -65,6 +78,7 @@ def ScintWaveformDigitizationOutputCfg(flags, **kwargs):
     ]
     acc.merge(OutputStreamCfg(flags, "RDO"))
     ostream = acc.getEventAlgo("OutputStreamRDO")
-    ostream.TakeItemsFromInput = True # Copies all data from input file to output
+    # ostream.TakeItemsFromInput = True # Copies all data from input file to output
+    # ostream.TakeItemsFromInput = False
     ostream.ItemList += ItemList
     return acc
diff --git a/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.cxx b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.cxx
index 91cffd93552d222b90c200cf50fa7842e2b7177a..7eb12286567c42591314592ece065136a754d43d 100644
--- a/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.cxx
+++ b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.cxx
@@ -1,11 +1,8 @@
-
 #include "ScintWaveformDigiAlg.h"
 
-#include "Identifier/Identifier.h"
+#include "ScintSimEvent/ScintHitIdHelper.h"
 
-#include <vector>
 #include <map>
-#include <utility>
 
 
 ScintWaveformDigiAlg::ScintWaveformDigiAlg(const std::string& name, 
@@ -20,7 +17,7 @@ ScintWaveformDigiAlg::initialize() {
 
   // Initalize tools
   ATH_CHECK( m_digiTool.retrieve() );
-
+  ATH_CHECK( m_mappingTool.retrieve() );
 
   // Set key to read waveform from
   ATH_CHECK( m_scintHitContainerKey.initialize() );
@@ -28,18 +25,23 @@ ScintWaveformDigiAlg::initialize() {
   // Set key to write container
   ATH_CHECK( m_waveformContainerKey.initialize() );
 
-  // Will eventually depend on the type of detector
-  // TODO: Vary time at which centre it?
-  // TODO: Better parameters
- 
+  // Set up helpers
+  ATH_CHECK(detStore()->retrieve(m_vetoID, "VetoID"));
+  ATH_CHECK(detStore()->retrieve(m_vetoNuID, "VetoNuID"));
+  ATH_CHECK(detStore()->retrieve(m_triggerID, "TriggerID"));
+  ATH_CHECK(detStore()->retrieve(m_preshowerID, "PreshowerID"));
 
-  
+  // Create CB time kernel and pre-evaluate for number of samples
   m_kernel = new TF1("PDF", "[4] * ROOT::Math::crystalball_pdf(x, [0],[1],[2],[3])", 0, 1200);
   m_kernel->SetParameter(0, m_CB_alpha);
   m_kernel->SetParameter(1, m_CB_n);
   m_kernel->SetParameter(2, m_CB_sigma);
   m_kernel->SetParameter(3, m_CB_mean);
   m_kernel->SetParameter(4, m_CB_norm);
+
+  // Pre-evaluate time kernel for each bin
+  m_timekernel = m_digiTool->evaluate_timekernel(m_kernel);
+
   return StatusCode::SUCCESS;
 }
 
@@ -55,11 +57,9 @@ ScintWaveformDigiAlg::finalize() {
 StatusCode 
 ScintWaveformDigiAlg::execute(const EventContext& ctx) const {
   ATH_MSG_DEBUG("Executing");
+  ATH_MSG_DEBUG("Run: " << ctx.eventID().run_number() << " Event: " << ctx.eventID().event_number());
 
-  ATH_MSG_DEBUG("Run: " << ctx.eventID().run_number() 
-		<< " Event: " << ctx.eventID().event_number());
-
-  // Find the input HIT collection
+  // Find the input HITS collection
   SG::ReadHandle<ScintHitCollection> scintHitHandle(m_scintHitContainerKey, ctx);
 
   ATH_CHECK( scintHitHandle.isValid() );
@@ -70,16 +70,107 @@ ScintWaveformDigiAlg::execute(const EventContext& ctx) const {
   ATH_CHECK( waveformContainerHandle.record( std::make_unique<RawWaveformContainer>()) );
 
   ATH_MSG_DEBUG("WaveformsContainer '" << waveformContainerHandle.name() << "' initialized");
-
+  
   if (scintHitHandle->size() == 0) {
     ATH_MSG_DEBUG("ScintHitCollection found with zero length!");
     return StatusCode::SUCCESS;
   }
+  
+  // Create structure to store pulse for each channel
+  std::map<Identifier, std::vector<uint16_t>> waveforms;
+
+  auto first = *scintHitHandle->begin();
+  if (first.isVeto()) {
+    waveforms = m_digiTool->create_waveform_map(m_vetoID);
+  } else if (first.isVetoNu()) {
+    waveforms = m_digiTool->create_waveform_map(m_vetoNuID); 
+  } else if (first.isTrigger()) {
+    waveforms = m_digiTool->create_waveform_map(m_triggerID);
+  } else if (first.isPreshower()) {
+    waveforms = m_digiTool->create_waveform_map(m_preshowerID);
+  } 
+
+  // Loop over time samples
+  // Should really do sums first, as repeating these in the loop is a waste
+  for (const auto& tk : m_timekernel) {
+    std::map<Identifier, float> counts;
+   
+    // Convolve hit energy with evaluated kernel and sum for each hit id (i.e. channel)
+    Identifier id;
+    for (const auto& hit : *scintHitHandle) {            
+
+      // Special handling for trigger scintillator
+      if (first.isTrigger()) {
+	// Hits are created per plate
+	// Need to split between 2 PMTs
+
+	// Identifier from hit is plate ID
+	Identifier plate_id = m_triggerID->plate_id(hit.getIdentifier());
+
+	// Need to do something better here, but for now just fill both
+	id = m_triggerID->pmt_id(plate_id, 0); // ID for PMT 0
+	counts[id] += tk * hit.energyLoss();
+	id = m_triggerID->pmt_id(plate_id, 1); // ID for PMT 1
+	counts[id] += tk * hit.energyLoss();
+
+      } else {
+	// All others have only 1 PMT
+	// Use detector id (not hit id) throughout
+	id = hit.getIdentifier();
+	counts[id] += tk * hit.energyLoss();
+      }
+    }
+
+    // Subtract count from basleine and add result to correct waveform vector
+    for (const auto& c : counts) {
+
+      unsigned int baseline = m_digiTool->generate_baseline(m_base_mean, m_base_rms);
+      int value = baseline - c.second;
+
+      if (value < 0) {
+	ATH_MSG_WARNING("Found pulse " << c.second << " larger than baseline " << c.first);
+	value = 0; // Protect against scaling signal above baseline
+      }
+
+      // Convert hit id to Identifier and store 
+      id = c.first;
+      waveforms[id].push_back(value);
+    }
+  }
 
-  // Digitise the hits
-  CHECK( m_digiTool->digitise<ScintHitCollection>(scintHitHandle.ptr(),
-						  waveformContainerHandle.ptr(), m_kernel,
-						  std::pair<float, float>(m_base_mean, m_base_rms)) );
+  // This is a bit of a hack to make sure all waveforms have
+  // at least baseline entries.  Should really add this to the 
+  // logic above
+  for (const auto& w : waveforms) {
+    if (w.second.size() > 0) continue;
+
+    // Waveform was empty, fill with baseline
+    int channel = m_mappingTool->getChannelMapping(w.first);
+    ATH_MSG_DEBUG("Writing baseline into empty waveform in channel "<< channel);
+    int i = m_digiTool->nsamples();
+    while(i--) {  // Use while to avoid unused variable warning with for
+      int baseline = m_digiTool->generate_baseline(m_base_mean, m_base_rms);
+      waveforms[w.first].push_back(baseline);
+    }
+  }
+
+  // Loop over wavefrom vectors to make and store raw waveform
+  unsigned int nsamples = m_digiTool->nsamples();
+  for (const auto& w : waveforms) {
+    RawWaveform* wfm = new RawWaveform();
+    wfm->setWaveform(0, w.second);
+    wfm->setIdentifier(w.first);
+    wfm->setSamples(nsamples);
+    // Only save this waveform if channel mapping is valid
+    // This will avoid making a waveform for the missing veto counter
+    int channel = m_mappingTool->getChannelMapping(w.first);
+    if (channel < 0) {
+      ATH_MSG_DEBUG("Skipping waveform with unknown channel!");
+      continue;
+    }
+    wfm->setChannel(channel);
+    waveformContainerHandle->push_back(wfm);
+  }
 
   ATH_MSG_DEBUG("WaveformsHitContainer " << waveformContainerHandle.name() << "' filled with "<< waveformContainerHandle->size() <<" items");
 
diff --git a/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.h b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.h
index 9bf7843a1867eeffa4587a39875913ea0685d773..528325621b279bdc3f570c6e5ade93fe549b6c12 100644
--- a/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.h
+++ b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.h
@@ -10,6 +10,7 @@
 
 // Tool classes
 #include "WaveDigiTools/IWaveformDigitisationTool.h"
+#include "WaveformConditionsTools/IWaveformCableMappingTool.h"
 
 // Handles
 #include "StoreGate/ReadHandleKey.h"
@@ -19,12 +20,22 @@
 #include "GaudiKernel/ServiceHandle.h"
 #include "GaudiKernel/ToolHandle.h"
 
+//Helpers
+#include "ScintIdentifier/VetoID.h"
+#include "ScintIdentifier/VetoNuID.h"
+#include "ScintIdentifier/TriggerID.h"
+#include "ScintIdentifier/PreshowerID.h"
+#include "ScintSimEvent/ScintHitIdHelper.h"
+#include "Identifier/Identifier.h"
+
+
 // ROOT
 #include "TF1.h"
 
 
 // STL
 #include <string>
+#include <vector>
 
 class ScintWaveformDigiAlg : public AthReentrantAlgorithm {
 
@@ -50,6 +61,10 @@ class ScintWaveformDigiAlg : public AthReentrantAlgorithm {
   ScintWaveformDigiAlg &operator=(const ScintWaveformDigiAlg&) = delete;
   //@}
 
+  /// 
+
+  /** @name Steerable pameters for crystal ball and baseline **/
+  //@{
   Gaudi::Property<double> m_CB_alpha {this, "CB_alpha", 0, "Alpha of the crystal ball function"};
   Gaudi::Property<double> m_CB_n {this, "CB_n", 0, "n of the crystal ball function"};
   Gaudi::Property<double> m_CB_mean {this, "CB_mean", 0, "Mean of the crystal ball function"};  
@@ -58,10 +73,21 @@ class ScintWaveformDigiAlg : public AthReentrantAlgorithm {
 
   Gaudi::Property<double> m_base_mean {this, "base_mean", 0, "Mean of the baseline"};
   Gaudi::Property<double> m_base_rms {this, "base_rms", 0, "RMS of the baseline"};
+  //@}
 
-  /// Kernel PDF
-  TF1* m_kernel;
 
+  /** Kernel PDF and evaluated values **/
+  //@{
+  TF1*                            m_kernel;
+  std::vector<float>              m_timekernel;
+  //@}
+  
+
+  /// Detector ID helpers
+  const VetoID* m_vetoID{nullptr};
+  const VetoNuID* m_vetoNuID{nullptr};
+  const TriggerID* m_triggerID{nullptr};
+  const PreshowerID* m_preshowerID{nullptr};
 
   /**
    * @name Digitisation tool
@@ -69,6 +95,11 @@ class ScintWaveformDigiAlg : public AthReentrantAlgorithm {
   ToolHandle<IWaveformDigitisationTool> m_digiTool
     {this, "WaveformDigitisationTool", "WaveformDigitisationTool"};
 
+  /**
+   * @name Mapping tool
+   */
+  ToolHandle<IWaveformCableMappingTool> m_mappingTool
+    {this, "WaveformCableMappingTool", "WaveformCableMappingTool"};
 
   /**
    * @name Input HITS using SG::ReadHandleKey
@@ -91,4 +122,5 @@ class ScintWaveformDigiAlg : public AthReentrantAlgorithm {
 
 };
 
+
 #endif // SCINTDIGIALGS_SCINTDIGIALG_H
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/CMakeLists.txt b/Scintillator/ScintG4/VetoNuG4_SD/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..eb9aab190b1ea05e06536d8f0477d6909a0f8499
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/CMakeLists.txt
@@ -0,0 +1,22 @@
+################################################################################
+# Package: VetoNuG4_SD
+################################################################################
+
+# Declare the package name:
+atlas_subdir( VetoNuG4_SD )
+
+# External dependencies:
+find_package( CLHEP )
+find_package( Geant4 )
+find_package( XercesC )
+
+# Component(s) in the package:
+atlas_add_component( VetoNuG4_SD
+                     src/*.cxx
+                     src/components/*.cxx
+                     INCLUDE_DIRS ${GEANT4_INCLUDE_DIRS} ${XERCESC_INCLUDE_DIRS} ${CLHEP_INCLUDE_DIRS}
+                     LINK_LIBRARIES ${GEANT4_LIBRARIES} ${XERCESC_LIBRARIES} ${CLHEP_LIBRARIES} StoreGateLib SGtests GaudiKernel ScintSimEvent G4AtlasToolsLib FaserMCTruth )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py )
+
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDConfig.py b/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..053b12e58099026922369886fb8f4f22575733c2
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDConfig.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from ISF_Algorithms.collection_merger_helpersNew import CollectionMergerCfg
+
+VetoNuSensorSDTool=CompFactory.VetoNuSensorSDTool
+
+def getVetoNuSensorSD(ConfigFlags, name="VetoNuSensorSD", **kwargs):
+
+    result = ComponentAccumulator()
+    bare_collection_name = "VetoNuHits"
+    mergeable_collection_suffix = "_G4"
+    merger_input_property = "VetoNuHits"
+
+    acc, hits_collection_name = CollectionMergerCfg(ConfigFlags, bare_collection_name, mergeable_collection_suffix, merger_input_property)
+    kwargs.setdefault("LogicalVolumeNames", ["VetoNu::Plate"])
+    kwargs.setdefault("OutputCollectionNames", [hits_collection_name])
+
+    result.merge(acc)
+    return result, VetoNuSensorSDTool(name, **kwargs)
+
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDConfigDb.py b/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDConfigDb.py
new file mode 100644
index 0000000000000000000000000000000000000000..5909581e180436c7d1505a0581c5244155993ef4
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDConfigDb.py
@@ -0,0 +1,5 @@
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+from AthenaCommon.CfgGetter import addTool
+
+addTool("VetoNuG4_SD.VetoNuG4_SDConfig.getVetoNuSensorSD"              , "VetoNuSensorSD" )
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDToolConfig.py b/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDToolConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ea87d4239aa26a87481f39918dd59f8665508c8
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/python/VetoNuG4_SDToolConfig.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+# from ISF_Algorithms.collection_merger_helpersNew import CollectionMergerCfg
+
+VetoNuSensorSDTool=CompFactory.VetoNuSensorSDTool
+
+def VetoNuSensorSDCfg(ConfigFlags, name="VetoNuSensorSD", **kwargs):
+
+    result = ComponentAccumulator()
+    bare_collection_name = "VetoNuHits"
+    # mergeable_collection_suffix = "_G4"
+    # merger_input_property = "VetoNuHits"
+
+    # acc, hits_collection_name = CollectionMergerCfg(ConfigFlags, bare_collection_name, mergeable_collection_suffix, merger_input_property, 'SCINT')
+    # kwargs.setdefault("OutputCollectionNames", [hits_collection_name])
+    kwargs.setdefault("LogicalVolumeNames", ["VetoNu::Plate"])
+    kwargs.setdefault("OutputCollectionNames", [bare_collection_name])
+
+    result = ComponentAccumulator()
+    result.setPrivateTools(CompFactory.VetoNuSensorSDTool(name, **kwargs))
+    return result
+
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSD.cxx b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSD.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..85587985abcde4886e642fdf0ef72fe832e9160f
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSD.cxx
@@ -0,0 +1,116 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+//
+// VetoNu Sensitive Detector.
+// The Hits are processed here. For every hit I get the position and
+// an information on the sensor in which the interaction happened
+//
+
+// class headers
+#include "VetoNuSensorSD.h"
+
+// athena includes
+#include "FaserMCTruth/FaserTrackHelper.h"
+
+// Geant4 includes
+#include "G4Step.hh"
+#include "G4ThreeVector.hh"
+#include "G4SDManager.hh"
+#include "G4Geantino.hh"
+#include "G4ChargedGeantino.hh"
+
+// CLHEP transform
+#include "CLHEP/Geometry/Transform3D.h"
+
+#include <memory> // For make unique
+
+//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
+
+VetoNuSensorSD::VetoNuSensorSD( const std::string& name, const std::string& hitCollectionName )
+  : G4VSensitiveDetector( name )
+  , m_HitColl( hitCollectionName )
+{
+}
+
+//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
+
+void VetoNuSensorSD::Initialize(G4HCofThisEvent *)
+{
+  if (!m_HitColl.isValid()) m_HitColl = std::make_unique<ScintHitCollection>();
+}
+
+//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
+
+G4bool VetoNuSensorSD::ProcessHits(G4Step* aStep, G4TouchableHistory* /*ROhist*/)
+{
+  double edep = aStep->GetTotalEnergyDeposit();
+  if(edep==0.) {
+    if(aStep->GetTrack()->GetDefinition()!=G4Geantino::GeantinoDefinition() &&
+       aStep->GetTrack()->GetDefinition()!=G4ChargedGeantino::ChargedGeantinoDefinition())
+      return false;
+  }
+  edep *= CLHEP::MeV;
+  //
+  // Get the Touchable History:
+  //
+  const G4TouchableHistory *myTouch = dynamic_cast<const G4TouchableHistory*>(aStep->GetPreStepPoint()->GetTouchable());
+  //
+  // Get the hit coordinates. Start and End Point
+  //
+  G4ThreeVector coord1 = aStep->GetPreStepPoint()->GetPosition();
+  G4ThreeVector coord2 = aStep->GetPostStepPoint()->GetPosition();
+  //
+  // Calculate the local step begin and end position.
+  // From a G4 FAQ:
+  // http://geant4-hn.slac.stanford.edu:5090/HyperNews/public/get/geometry/17/1.html
+  //
+  const G4AffineTransform transformation = myTouch->GetHistory()->GetTopTransform();
+  G4ThreeVector localPosition1 = transformation.TransformPoint(coord1);
+  G4ThreeVector localPosition2 = transformation.TransformPoint(coord2);
+  //
+  // Get it into a vector in local coords and with the right units:
+  //
+  HepGeom::Point3D<double> lP1,lP2;
+ 
+  // No funny business with coordinates like ATLAS...
+  lP1[2] = localPosition1[2]*CLHEP::mm;
+  lP1[1] = localPosition1[1]*CLHEP::mm;
+  lP1[0] = localPosition1[0]*CLHEP::mm;
+
+  lP2[2] = localPosition2[2]*CLHEP::mm;
+  lP2[1] = localPosition2[1]*CLHEP::mm;
+  lP2[0] = localPosition2[0]*CLHEP::mm;
+
+  // Now Navigate the history to know in what detector the step is:
+  // and finally set the ID of det element in which the hit is.
+  //
+  //G4int History;
+  //
+  // Get station and plate
+  //
+  int station = 0;
+  int plate = 0;
+  this->indexMethod(myTouch, station, plate);
+  // get the HepMcParticleLink from the TrackHelper
+  FaserTrackHelper trHelp(aStep->GetTrack());
+  m_HitColl->Emplace(lP1,
+                     lP2,
+                     edep,
+                     aStep->GetPreStepPoint()->GetGlobalTime(),//use the global time. i.e. the time from the beginning of the event
+                     trHelp.GetParticleLink(),
+                     3,station,plate);
+  return true;
+}
+
+void VetoNuSensorSD::indexMethod(const G4TouchableHistory *myTouch, 
+                              int &station, int &plate) {
+
+
+  plate = myTouch->GetVolume()->GetCopyNo();
+  const std::string stationName = myTouch->GetVolume(1)->GetLogicalVolume()->GetName();
+  station = (stationName == "VetoNu::VetoNuStationA" ? 0 : 1 );
+   
+  return;
+}
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSD.h b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSD.h
new file mode 100644
index 0000000000000000000000000000000000000000..478121ccd5a3c27a19ee200153bc290a872ff1b6
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSD.h
@@ -0,0 +1,50 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+/****************************************************************
+   VetoNu Sensitive Detector class
+****************************************************************/
+
+#ifndef VETONUG4_SD_VETONUSENSORSD_H
+#define VETONUG4_SD_VETONUSENSORSD_H
+
+// Base class
+#include "G4VSensitiveDetector.hh"
+
+// For the hits
+#include "ScintSimEvent/ScintHitCollection.h"
+#include "StoreGate/WriteHandle.h"
+
+// G4 needed classes
+class G4Step;
+class G4TouchableHistory;
+
+class VetoNuSensorSD : public G4VSensitiveDetector
+{
+public:
+  // Constructor
+  VetoNuSensorSD(const std::string& name, const std::string& hitCollectionName);
+
+  // Destructor
+  ~VetoNuSensorSD() { /* If all goes well we do not own myHitColl here */ }
+
+  // Deal with each G4 hit
+  G4bool ProcessHits(G4Step*, G4TouchableHistory*) override;
+
+  // For setting up the hit collection
+  void Initialize(G4HCofThisEvent*) override final;
+
+  /** Templated method to stuff a single hit into the sensitive detector class.  This
+      could get rather tricky, but the idea is to allow fast simulations to use the very
+      same SD classes as the standard simulation. */
+  template <class... Args> void AddHit(Args&&... args){ m_HitColl->Emplace( args... ); }
+
+private:
+  void indexMethod(const G4TouchableHistory *myTouch, int &station, int &plate);
+protected:
+  // The hits collection
+  SG::WriteHandle<ScintHitCollection> m_HitColl;
+};
+
+#endif //VETONUG4_SD_VETONUSENSORSD_H
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSDTool.cxx b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSDTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..6f3a6f5a693e2fd25e42bf58121d79cc91de310f
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSDTool.cxx
@@ -0,0 +1,32 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+// VetoNu Sensitive Detector Tool.
+//
+
+// class header
+#include "VetoNuSensorSDTool.h"
+
+// package includes
+#include "VetoNuSensorSD.h"
+
+// STL includes
+#include <exception>
+
+//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
+
+VetoNuSensorSDTool::VetoNuSensorSDTool(const std::string& type, const std::string& name, const IInterface* parent)
+  : SensitiveDetectorBase( type , name , parent )
+{
+
+}
+
+//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
+
+G4VSensitiveDetector* VetoNuSensorSDTool::makeSD() const
+{
+  ATH_MSG_DEBUG( "Creating VetoNu SD: " << name() );
+  return new VetoNuSensorSD(name(), m_outputCollectionNames[0]);
+}
+
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSDTool.h b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSDTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..073a7cd07a136376d97e8b1361a9a4fc252a23d4
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/src/VetoNuSensorSDTool.h
@@ -0,0 +1,36 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+/****************************************************************
+   VetoNu Sensitive Detector Tool
+ ****************************************************************/
+
+#ifndef VETONUG4_SD_VETONUSENSORSDTOOL_H
+#define VETONUG4_SD_VETONUSENSORSDTOOL_H
+
+// Base class
+#include "G4AtlasTools/SensitiveDetectorBase.h"
+
+// STL headers
+#include <string>
+
+class G4VSensitiveDetector;
+
+//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo.....
+
+class VetoNuSensorSDTool : public SensitiveDetectorBase
+{
+ public:
+  // Constructor
+  VetoNuSensorSDTool(const std::string& type, const std::string& name, const IInterface *parent);
+
+  // Destructor
+  ~VetoNuSensorSDTool() { /* If all goes well we do not own myHitColl here */ }
+
+protected:
+  // Make me an SD!
+  G4VSensitiveDetector* makeSD() const override final;
+};
+
+#endif //VETOG4_SD_VETOSENSORSDTOOL_H
diff --git a/Scintillator/ScintG4/VetoNuG4_SD/src/components/VetoNuG4_SD_entries.cxx b/Scintillator/ScintG4/VetoNuG4_SD/src/components/VetoNuG4_SD_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5ae057c14079dc0241808a300e4487ec3638d53d
--- /dev/null
+++ b/Scintillator/ScintG4/VetoNuG4_SD/src/components/VetoNuG4_SD_entries.cxx
@@ -0,0 +1,3 @@
+#include "../VetoNuSensorSDTool.h"
+
+DECLARE_COMPONENT( VetoNuSensorSDTool )
diff --git a/Scintillator/ScintSimEvent/CMakeLists.txt b/Scintillator/ScintSimEvent/CMakeLists.txt
index 27b63871df2aa95ea230c6be47454573a7c14d16..07224a272694548a6c39740e081059d7652a9d91 100644
--- a/Scintillator/ScintSimEvent/CMakeLists.txt
+++ b/Scintillator/ScintSimEvent/CMakeLists.txt
@@ -18,11 +18,11 @@ atlas_add_library( ScintSimEvent
                    PRIVATE_INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} ${GEANT4_INCLUDE_DIRS}
                    DEFINITIONS ${CLHEP_DEFINITIONS}
                    LINK_LIBRARIES ${CLHEP_LIBRARIES} AthAllocators AthenaKernel CxxUtils GeneratorObjects HitManagement StoreGateLib SGtests
-                   PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES} ScintIdentifier )
+                   PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES} ScintIdentifier Identifier)
 
 atlas_add_dictionary( ScintSimEventDict
                       ScintSimEvent/ScintSimEventDict.h
                       ScintSimEvent/selection.xml
                       INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} ${CLHEP_INCLUDE_DIRS} ${GEANT4_INCLUDE_DIRS}
-                      LINK_LIBRARIES ${ROOT_LIBRARIES} ${CLHEP_LIBRARIES} AthAllocators CxxUtils GeneratorObjects HitManagement StoreGateLib SGtests ScintIdentifier ScintSimEvent )
+                      LINK_LIBRARIES ${ROOT_LIBRARIES} ${CLHEP_LIBRARIES} AthAllocators CxxUtils GeneratorObjects HitManagement StoreGateLib SGtests ScintIdentifier ScintSimEvent Identifier)
 
diff --git a/Scintillator/ScintSimEvent/ScintSimEvent/ScintHit.h b/Scintillator/ScintSimEvent/ScintSimEvent/ScintHit.h
index 5a0a4d17282cc234c09b697cdee2c028480a2b96..864217ffb2628ff0873ae4823345b6f5e7e10621 100644
--- a/Scintillator/ScintSimEvent/ScintSimEvent/ScintHit.h
+++ b/Scintillator/ScintSimEvent/ScintSimEvent/ScintHit.h
@@ -16,6 +16,8 @@
 #include "CLHEP/Geometry/Point3D.h"
 #include "GeneratorObjects/HepMcParticleLink.h"
 
+class Identifier;
+
 class ScintHit  {
 
   ///////////////////////////////////////////////////////////////////
@@ -80,8 +82,13 @@ public:
   // Const methods:
   ///////////////////////////////////////////////////////////////////
 
+  // This is the HitId, used in Geant. This is not the detector Identifier
   unsigned int identify() const;
 
+  // This is the detector readout Identifier (pmt Identifier)
+  // provided by ScintHitIdHelper::getIdentifier
+  Identifier getIdentifier() const;
+
   // local start position of the energy deposit:
   HepGeom::Point3D<double> localStartPosition() const;
 
@@ -106,6 +113,7 @@ public:
   bool isVeto() const;
   bool isTrigger() const;
   bool isPreshower() const;
+  bool isVetoNu() const;
 
   // Station
   int getStation() const;
diff --git a/Scintillator/ScintSimEvent/ScintSimEvent/ScintHitIdHelper.h b/Scintillator/ScintSimEvent/ScintSimEvent/ScintHitIdHelper.h
index 2874bac894b15684252de5a531081f338750c10c..a8740bf0f779f74f89f427b3b3458dbe275d4398 100644
--- a/Scintillator/ScintSimEvent/ScintSimEvent/ScintHitIdHelper.h
+++ b/Scintillator/ScintSimEvent/ScintSimEvent/ScintHitIdHelper.h
@@ -23,6 +23,16 @@
 
 // This class is singleton and static method and variable are used.
 #include "CxxUtils/checker_macros.h"
+
+//Helpers
+#include "ScintIdentifier/VetoID.h"
+#include "ScintIdentifier/TriggerID.h"
+#include "ScintIdentifier/PreshowerID.h"
+#include "ScintIdentifier/VetoNuID.h"
+
+#include "Identifier/Identifier.h"
+
+
 ATLAS_NO_CHECK_FILE_THREAD_SAFETY;
 
 class ScintHitIdHelper : HitIdHelper {
@@ -36,6 +46,7 @@ class ScintHitIdHelper : HitIdHelper {
   bool isVeto(const int& hid) const;
   bool isTrigger(const int& hid) const;
   bool isPreshower(const int& hid) const;
+  bool isVetoNu(const int& hid) const;
 
   // Barrel or Endcap
   int getStation(const int& hid) const;
@@ -43,6 +54,9 @@ class ScintHitIdHelper : HitIdHelper {
   // Layer/Disk
   int getPlate(const int& hid) const;
 
+  // identifier
+  Identifier getIdentifier(const int& hid) const;
+
   //
   // Info packing:
   int buildHitId(const int, const int, const int) const;
@@ -54,6 +68,13 @@ class ScintHitIdHelper : HitIdHelper {
   //
   // Initialize the helper, only called by the constructor
   void Initialize();
+
+  /// Detector ID helpers
+  const VetoID* m_vetoID{nullptr};
+  const TriggerID* m_triggerID{nullptr};
+  const PreshowerID* m_preshowerID{nullptr};
+  const VetoNuID* m_vetoNuID{nullptr};
+
 };
 
 #endif // SCINTSIMEVENT_SCINTHITIDHELPER
diff --git a/Scintillator/ScintSimEvent/src/ScintHit.cxx b/Scintillator/ScintSimEvent/src/ScintHit.cxx
index 2ef1cfd9b1361c75183fd1e399ffa31e453604db..8c2201a133f75676c44c284c61061556048d9600 100644
--- a/Scintillator/ScintSimEvent/src/ScintHit.cxx
+++ b/Scintillator/ScintSimEvent/src/ScintHit.cxx
@@ -138,6 +138,14 @@ bool ScintHit::isPreshower() const {
   return  ScintHitIdHelper::GetHelper()->isPreshower(m_ID);
 }
 
+bool ScintHit::isVetoNu() const {
+  return ScintHitIdHelper::GetHelper()->isVetoNu(m_ID);
+}
+
+Identifier ScintHit::getIdentifier() const {
+  return ScintHitIdHelper::GetHelper()->getIdentifier(m_ID);
+}
+
 HepGeom::Point3D<double> ScintHit::localStartPosition() const
 {
   return HepGeom::Point3D<double>((double) m_stX, (double) m_stY, (double) m_stZ);
@@ -162,7 +170,9 @@ void ScintHit::print() const {
   } else if (isTrigger()) {
     std::cout << "*** Trigger Hit " << std::endl;
   } else if (isPreshower()) {
-      std::cout << "*** Preshower Hit " << std::endl; 
+    std::cout << "*** Preshower Hit " << std::endl; 
+  } else if (isVetoNu()) {
+    std::cout << "*** VetoNu Hit " << std::endl;
   } else {
       std::cout << "*** Unrecognized Scintillator Hit " << std::endl;
   }
diff --git a/Scintillator/ScintSimEvent/src/ScintHitIdHelper.cxx b/Scintillator/ScintSimEvent/src/ScintHitIdHelper.cxx
index cacdb5d720bfdc7759188481346899d48069d856..52b0faa99ce77f29913d1f94de525becce3d450f 100644
--- a/Scintillator/ScintSimEvent/src/ScintHitIdHelper.cxx
+++ b/Scintillator/ScintSimEvent/src/ScintHitIdHelper.cxx
@@ -6,7 +6,6 @@
 
 #include "ScintSimEvent/ScintHitIdHelper.h"
 #include "StoreGate/StoreGateSvc.h"
-#include "ScintIdentifier/VetoID.h"
 
 #include "G4Types.hh"
 #ifdef G4MULTITHREADED
@@ -41,13 +40,15 @@ void ScintHitIdHelper::Initialize() {
   // determine whether hits were created with an SLHC dictionary
   // in which case eta module field is expanded.
   // Need to lock this thread-unsafe retrieval
-  const VetoID* pix;
   ServiceHandle<StoreGateSvc> detStore ("DetectorStore", "ScitHitIdHelper");
   if (detStore.retrieve().isSuccess()) {
-    if (detStore->retrieve(pix, "VetoID").isFailure()) { pix = 0; }
+    if (detStore->retrieve(m_vetoID, "VetoID").isFailure()) { m_vetoID = 0; }
+    if (detStore->retrieve(m_vetoNuID, "VetoNuID").isFailure()) { m_vetoNuID = 0; }
+    if (detStore->retrieve(m_triggerID, "TriggerID").isFailure()) { m_triggerID = 0; }
+    if (detStore->retrieve(m_preshowerID, "PreshowerID").isFailure()) { m_preshowerID = 0; }
   }
 
-  InitializeField("VetoTriggerPreshower", 0, 2);
+  InitializeField("VetoTriggerPreshower", 0, 3);
   InitializeField("Station", -2, 2);
   InitializeField("Plate", 0, 4);
 }
@@ -61,6 +62,13 @@ bool ScintHitIdHelper::isVeto(const int& hid) const
   else return false;
 }
 
+bool ScintHitIdHelper::isVetoNu(const int& hid) const
+{
+  int ps = this->GetFieldValue("VetoTriggerPreshower", hid);
+  if ( ps == 3 ) return true;
+  else return false;
+}
+
 bool ScintHitIdHelper::isTrigger(const int& hid) const
 {
   int ps = this->GetFieldValue("VetoTriggerPreshower", hid);
@@ -87,6 +95,22 @@ int ScintHitIdHelper::getPlate(const int& hid) const
   return this->GetFieldValue("Plate", hid);
 }
 
+
+// identifier
+Identifier ScintHitIdHelper::getIdentifier(const int& hid) const
+{
+  if (isVeto(hid)) {
+    return m_vetoID->pmt_id(getStation(hid), getPlate(hid), 0);
+  } else if (isTrigger(hid)) {
+    return m_triggerID->pmt_id(getStation(hid), getPlate(hid), 0);
+  } else if (isPreshower(hid)) {
+    return m_preshowerID->pmt_id(getStation(hid), getPlate(hid), 0);
+  } else if (isVetoNu(hid)) {
+    return m_vetoNuID->pmt_id(getStation(hid), getPlate(hid), 0);
+  }
+  return Identifier();
+}
+
 //
 // Info packing:
 int ScintHitIdHelper::buildHitId(const int veto_trigger_preshower,
diff --git a/Simulation/G4Faser/G4FaserAlg/CMakeLists.txt b/Simulation/G4Faser/G4FaserAlg/CMakeLists.txt
index dbd7917dbf989e1db8456a1ec6e929b7886d715d..35042d814e913677a0ddf4d643e792a75b362f2d 100644
--- a/Simulation/G4Faser/G4FaserAlg/CMakeLists.txt
+++ b/Simulation/G4Faser/G4FaserAlg/CMakeLists.txt
@@ -32,7 +32,7 @@ atlas_add_test( G4FaserAlgConfig_TestFaser
                 PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
 
 atlas_add_test( G4FaserAlgConfig_TestFaserNu
-                SCRIPT python ${CMAKE_CURRENT_SOURCE_DIR}/test/G4FaserAlgConfigNew_Test.py GeoModel.FaserVersion="'FASERNU-02'" IOVDb.GlobalTag="'OFLCOND-FASER-02'" Output.HITSFileName='faserNu.HITS.pool.root'
+                SCRIPT python ${CMAKE_CURRENT_SOURCE_DIR}/test/G4FaserAlgConfigNew_Test.py GeoModel.FaserVersion="'FASERNU-03'" IOVDb.GlobalTag="'OFLCOND-FASER-02'" Output.HITSFileName='faserNu.HITS.pool.root'
                 PROPERTIES TIMEOUT 300 
                 PROPERTIES WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
 
diff --git a/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py b/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py
index 82c35779d0225b979cf8d04c2f3b20bd17f5f930..e4b9721c03c8d697e101de48f9e279022051d7fb 100644
--- a/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py
+++ b/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py
@@ -125,6 +125,9 @@ def G4FaserAlgOutputCfg(ConfigFlags):
     if ConfigFlags.Detector.EnableVeto:
         ItemList += ["ScintHitCollection#VetoHits"]
 
+    if ConfigFlags.Detector.EnableVetoNu:
+        ItemList += ["ScintHitCollection#VetoNuHits"]
+
     if ConfigFlags.Detector.EnableTrigger:
         ItemList += ["ScintHitCollection#TriggerHits"]
 
diff --git a/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py b/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py
old mode 100644
new mode 100755
index 709797ae28bec863f565eba2aa662d8bcefc0e2e..998b40c286837a3436e6fc714f4977b355ccdd9d
--- a/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py
+++ b/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py
@@ -12,7 +12,7 @@ if __name__ == '__main__':
 # Set up logging and config behaviour
 #
     from AthenaCommon.Logging import log
-    from AthenaCommon.Constants import DEBUG
+    from AthenaCommon.Constants import DEBUG, VERBOSE
     from AthenaCommon.Configurable import Configurable
     log.setLevel(DEBUG)
     Configurable.configurableRun3Behavior = 1
@@ -43,8 +43,12 @@ if __name__ == '__main__':
     ConfigFlags.Sim.ReleaseGeoModel = False
     ConfigFlags.Sim.IncludeParentsInG4Event = True # Controls whether BeamTruthEvent is written to output HITS file
     ConfigFlags.addFlag("Sim.Gun",{"Generator" : "SingleParticle"})  # Property bag for particle gun keyword:argument pairs
+    ConfigFlags.addFlag("Sim.Beam.xangle", 0)  # Potential beam crossing angles
+    ConfigFlags.addFlag("Sim.Beam.yangle", 0)
+    ConfigFlags.addFlag("Sim.Beam.xshift", 0)  # Potential beam shift
+    ConfigFlags.addFlag("Sim.Beam.yshift", 0)        
 
-    ConfigFlags.GeoModel.FaserVersion = "FASERNU-02"             # Geometry set-up
+    ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"             # Geometry set-up
     ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"             # Conditions set-up
     ConfigFlags.addFlag("Input.InitialTimeStamp", 0)             # To avoid autoconfig 
     ConfigFlags.GeoModel.Align.Dynamic = False
@@ -53,6 +57,23 @@ if __name__ == '__main__':
 #
     import sys
     ConfigFlags.fillFromArgs(sys.argv[1:])
+
+    doShiftLOS = (ConfigFlags.Sim.Beam.xangle or ConfigFlags.Sim.Beam.yangle or
+                  ConfigFlags.Sim.Beam.xshift or ConfigFlags.Sim.Beam.yshift)
+
+#     from math import atan
+#     from AthenaCommon.SystemOfUnits import GeV, TeV, cm, m
+#     from AthenaCommon.PhysicalConstants import pi
+#     import ParticleGun as PG
+#     ConfigFlags.Sim.Gun = {"Generator" : "SingleParticle", "pid" : 11, "energy" : PG.LogSampler(10*GeV, 1*TeV), "theta" :
+#                            PG.GaussianSampler(0, atan((10*cm)/(7*m)), oneside = True), "phi" : [0, 2*pi], "mass" : 0.511, "radius" : -10*cm, "randomSeed" : 12345}
+
+    if doShiftLOS:
+        pgConfig = ConfigFlags.Sim.Gun
+        pgConfig["McEventKey"] = "BeamTruthEvent_ATLASCoord"
+        ConfigFlags.Sim.Gun = pgConfig
+
+
 #
 # By being a little clever, we can steer the geometry setup from the command line using GeoModel.FaserVersion
 #
@@ -63,6 +84,8 @@ if __name__ == '__main__':
         detectors += ['Trigger', 'Dipole']
     if ConfigFlags.GeoModel.FaserVersion.count("FASERNU") > 0 :
         detectors += ['Emulsion']
+    if ConfigFlags.GeoModel.FaserVersion.count("FASERNU-03") > 0:
+        detectors += ['VetoNu', 'Trench']
 #
 # Setup detector flags
 #
@@ -82,11 +105,34 @@ if __name__ == '__main__':
 #
     if ConfigFlags.Input.Files and ConfigFlags.Input.Files != ["_CALYPSO_GENERIC_INPUTFILE_NAME_"] :
         print("Input.Files = ",ConfigFlags.Input.Files)
+
+#
+# If so, and only one file that ends in .events or .hepmc read as HepMC
+#
+        if len(ConfigFlags.Input.Files) == 1 and (ConfigFlags.Input.Files[0].endswith(".events") or ConfigFlags.Input.Files[0].endswith(".hepmc")):
+
+            from HEPMCReader.HepMCReaderConfig import HepMCReaderCfg
+
+            if doShiftLOS:
+                cfg.merge(HepMCReaderCfg(ConfigFlags, McEventKey = "BeamTruthEvent_ATLASCoord"))
+            else:
+                cfg.merge(HepMCReaderCfg(ConfigFlags))
+
+            from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+            cfg.merge(McEventSelectorCfg(ConfigFlags))
+
 #
-# If so, set up to read it
+# Else, set up to read it as a pool.root file
 #
-        from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
-        cfg.merge(PoolReadCfg(ConfigFlags))
+        else:
+            from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+            cfg.merge(PoolReadCfg(ConfigFlags))
+
+            if doShiftLOS:
+                from SGComps.AddressRemappingConfig import InputOverwriteCfg
+                # Rename old truth collection to add ATLAS coord to can still use BeamTruthEvent for the one in FASER Coords
+                cfg.merge(InputOverwriteCfg("McEventCollection", "BeamTruthEvent", "McEventCollection", "BeamTruthEvent_ATLASCoord"))
+
 #
 # If not, configure the particle gun as requested, or using defaults
 #
@@ -96,18 +142,35 @@ if __name__ == '__main__':
 #
         from FaserParticleGun.FaserParticleGunConfig import FaserParticleGunCfg
         cfg.merge(FaserParticleGunCfg(ConfigFlags))
+        
         from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
         cfg.merge(McEventSelectorCfg(ConfigFlags))
+
 #
 # Output file
 #
     from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
     cfg.merge(PoolWriteCfg(ConfigFlags))
+
+#
+# Shift LOS
+#
+
+    if doShiftLOS:
+
+        import McParticleEvent.Pythonizations
+        from GeneratorUtils.ShiftLOSConfig import ShiftLOSCfg
+        cfg.merge(ShiftLOSCfg(ConfigFlags, 
+                              xcross = ConfigFlags.Sim.Beam.xangle, ycross = ConfigFlags.Sim.Beam.yangle,
+                              xshift = ConfigFlags.Sim.Beam.xshift, yshift = ConfigFlags.Sim.Beam.yshift))
+    
 #
 # Add the G4FaserAlg
 #
     from G4FaserAlg.G4FaserAlgConfigNew import G4FaserAlgCfg
     cfg.merge(G4FaserAlgCfg(ConfigFlags))
+
+    #cfg.getEventAlgo("OutputStreamHITS").ItemList += ["McEventCollection#BeamTruthEvent_ATLASCoord"]    
 #
 # Dump config
 #
@@ -124,6 +187,8 @@ if __name__ == '__main__':
 #
 # Execute and finish
 #
+    #cfg.foreach_component("*").OutputLevel = VERBOSE
+
     sc = cfg.run()
 
     b = time.time()
diff --git a/Simulation/G4Faser/G4FaserTools/python/G4FaserToolsConfigNew.py b/Simulation/G4Faser/G4FaserTools/python/G4FaserToolsConfigNew.py
index f4bb4333d7b62b77b377663d9181e30e64b9f2f8..8f01d6b15fca1ff7a591132160aed2a2e31ff283 100644
--- a/Simulation/G4Faser/G4FaserTools/python/G4FaserToolsConfigNew.py
+++ b/Simulation/G4Faser/G4FaserTools/python/G4FaserToolsConfigNew.py
@@ -50,6 +50,9 @@ def ScintSensitiveDetectorListCfg(ConfigFlags):
     if ConfigFlags.Detector.EnableVeto:
         from VetoG4_SD.VetoG4_SDToolConfig import VetoSensorSDCfg
         tools += [ result.popToolsAndMerge(VetoSensorSDCfg(ConfigFlags)) ]
+    if ConfigFlags.Detector.EnableVetoNu:
+        from VetoNuG4_SD.VetoNuG4_SDToolConfig import VetoNuSensorSDCfg
+        tools += [ result.popToolsAndMerge(VetoNuSensorSDCfg(ConfigFlags)) ]
     if ConfigFlags.Detector.EnableTrigger:
         from TriggerG4_SD.TriggerG4_SDToolConfig import TriggerSensorSDCfg
         tools += [ result.popToolsAndMerge(TriggerSensorSDCfg(ConfigFlags)) ]
diff --git a/Simulation/G4Faser/G4FaserTools/python/G4FieldConfigNew.py b/Simulation/G4Faser/G4FaserTools/python/G4FieldConfigNew.py
index 33d34f728c6e0b0d7f6b451b5e81464ecf9c5f5b..4f4de1b8984b40d2cb5a8ed10f2239ff63d20e06 100644
--- a/Simulation/G4Faser/G4FaserTools/python/G4FieldConfigNew.py
+++ b/Simulation/G4Faser/G4FaserTools/python/G4FieldConfigNew.py
@@ -54,7 +54,7 @@ def BasicDetectorConstantFieldManagerToolCfg(ConfigFlags, name='BasicDetectorCon
 #
 
 def EmulsionFieldManagerToolCfg(ConfigFlags, name='EmulsionFieldManager', **kwargs):
-    kwargs.setdefault("LogicalVolumes", ['Emulsion::Emulsion'])
+    kwargs.setdefault("LogicalVolumes", ['Emulsion::EmulsionStationA'])
     #kwargs.setdefault('DeltaChord',         0.00001)
     kwargs.setdefault('DeltaIntersection',  0.00001)
     kwargs.setdefault('DeltaOneStep',       0.0001)
@@ -63,7 +63,16 @@ def EmulsionFieldManagerToolCfg(ConfigFlags, name='EmulsionFieldManager', **kwar
     return BasicDetectorFieldManagerToolCfg(ConfigFlags, name, **kwargs)
 
 def VetoFieldManagerToolCfg(ConfigFlags, name='VetoFieldManager', **kwargs):
-    kwargs.setdefault("LogicalVolumes", ['Veto::Veto'])
+    kwargs.setdefault("LogicalVolumes", ['Veto::VetoStationA'])
+    #kwargs.setdefault('DeltaChord',         0.00001)
+    kwargs.setdefault('DeltaIntersection',  0.00001)
+    kwargs.setdefault('DeltaOneStep',       0.0001)
+    kwargs.setdefault('MaximumEpsilonStep', 0.001)
+    kwargs.setdefault('MinimumEpsilonStep', 0.00001)
+    return BasicDetectorFieldManagerToolCfg(ConfigFlags, name, **kwargs)
+
+def VetoNuFieldManagerToolCfg(ConfigFlags, name='VetoNuFieldManager', **kwargs):
+    kwargs.setdefault("LogicalVolumes", ['VetoNu::VetoNuStationA'])
     #kwargs.setdefault('DeltaChord',         0.00001)
     kwargs.setdefault('DeltaIntersection',  0.00001)
     kwargs.setdefault('DeltaOneStep',       0.0001)
@@ -119,3 +128,12 @@ def EcalFieldManagerToolCfg(ConfigFlags, name='EcalFieldManager', **kwargs):
     kwargs.setdefault('MinimumEpsilonStep', 0.00001)
     return BasicDetectorFieldManagerToolCfg(ConfigFlags, name, **kwargs)
 
+def TrenchFieldManagerToolCfg(ConfigFlags, name='TrenchFieldManager', **kwargs):
+    kwargs.setdefault("LogicalVolumes", ['Trench::Trench'])
+    #kwargs.setdefault('DeltaChord',         0.00001)
+    kwargs.setdefault('DeltaIntersection',  0.00001)
+    kwargs.setdefault('DeltaOneStep',       0.0001)
+    kwargs.setdefault('MaximumEpsilonStep', 0.001)
+    kwargs.setdefault('MinimumEpsilonStep', 0.00001)
+    return BasicDetectorFieldManagerToolCfg(ConfigFlags, name, **kwargs)
+
diff --git a/Simulation/G4Faser/G4FaserTools/python/G4GeometryToolConfig.py b/Simulation/G4Faser/G4FaserTools/python/G4GeometryToolConfig.py
index 064cde911986868ace4708e37374a17e20a6de16..4e6f59490a97baee0020453bb9572fab2b090fbc 100644
--- a/Simulation/G4Faser/G4FaserTools/python/G4GeometryToolConfig.py
+++ b/Simulation/G4Faser/G4FaserTools/python/G4GeometryToolConfig.py
@@ -7,21 +7,23 @@ from AthenaConfiguration.ComponentFactory import CompFactory
 from AthenaCommon import Logging
 
 #the physics region tools
-from G4FaserTools.G4PhysicsRegionConfigNew import NeutrinoPhysicsRegionToolCfg, TrackerPhysicsRegionToolCfg, ScintillatorPhysicsRegionToolCfg, EcalPhysicsRegionToolCfg
+from G4FaserTools.G4PhysicsRegionConfigNew import NeutrinoPhysicsRegionToolCfg, TrackerPhysicsRegionToolCfg, ScintillatorPhysicsRegionToolCfg, EcalPhysicsRegionToolCfg, CavernPhysicsRegionToolCfg
 
 #the field config tools
-from G4FaserTools.G4FieldConfigNew import FASERFieldManagerToolCfg, EmulsionFieldManagerToolCfg, VetoFieldManagerToolCfg, TriggerFieldManagerToolCfg, PreshowerFieldManagerToolCfg, TrackerFieldManagerToolCfg, DipoleFieldManagerToolCfg, EcalFieldManagerToolCfg
+from G4FaserTools.G4FieldConfigNew import FASERFieldManagerToolCfg, EmulsionFieldManagerToolCfg, VetoFieldManagerToolCfg, VetoNuFieldManagerToolCfg, TriggerFieldManagerToolCfg, PreshowerFieldManagerToolCfg, TrackerFieldManagerToolCfg, DipoleFieldManagerToolCfg, EcalFieldManagerToolCfg, TrenchFieldManagerToolCfg
 
 from G4FaserTools.G4FaserToolsConfigNew import SensitiveDetectorMasterToolCfg
 
 GeoDetectorTool=CompFactory.GeoDetectorTool
 from EmulsionGeoModel.EmulsionGeoModelConfig import EmulsionGeometryCfg
 from VetoGeoModel.VetoGeoModelConfig import VetoGeometryCfg
+from VetoNuGeoModel.VetoNuGeoModelConfig import VetoNuGeometryCfg
 from TriggerGeoModel.TriggerGeoModelConfig import TriggerGeometryCfg
 from PreshowerGeoModel.PreshowerGeoModelConfig import PreshowerGeometryCfg
 from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
 from DipoleGeoModel.DipoleGeoModelConfig import DipoleGeometryCfg
 from EcalGeoModel.EcalGeoModelConfig import EcalGeometryCfg
+from FaserGeoModel.TrenchGMConfig import TrenchGeometryCfg
 
 BoxEnvelope, MaterialDescriptionTool, VoxelDensityTool, G4AtlasDetectorConstructionTool = CompFactory.getComps("BoxEnvelope", "MaterialDescriptionTool", "VoxelDensityTool", "G4AtlasDetectorConstructionTool",)
 
@@ -59,6 +61,16 @@ def VetoGeoDetectorToolCfg(ConfigFlags, name='Veto', **kwargs):
     result.setPrivateTools(GeoDetectorTool(name, **kwargs))
     return result
 
+def VetoNuGeoDetectorToolCfg(ConfigFlags, name='VetoNu', **kwargs):
+    #set up geometry
+    result=VetoNuGeometryCfg(ConfigFlags)
+    kwargs.setdefault("DetectorName", "VetoNu")
+    #add the GeometryNotifierSvc
+    result.addService(G4GeometryNotifierSvcCfg(ConfigFlags))
+    kwargs.setdefault("GeometryNotifierSvc", result.getService("G4GeometryNotifierSvc"))
+    result.setPrivateTools(GeoDetectorTool(name, **kwargs))
+    return result
+
 def TriggerGeoDetectorToolCfg(ConfigFlags, name='Trigger', **kwargs):
     #set up geometry
     result=TriggerGeometryCfg(ConfigFlags)
@@ -109,6 +121,16 @@ def EcalGeoDetectorToolCfg(ConfigFlags, name='Ecal', **kwargs):
     result.setPrivateTools(GeoDetectorTool(name, **kwargs))
     return result
 
+def TrenchGeoDetectorToolCfg(ConfigFlags, name='Trench', **kwargs):
+    #set up geometry
+    result=TrenchGeometryCfg(ConfigFlags)
+    kwargs.setdefault("DetectorName", "Trench")
+    #add the GeometryNotifierSvc
+    result.addService(G4GeometryNotifierSvcCfg(ConfigFlags))
+    kwargs.setdefault("GeometryNotifierSvc", result.getService("G4GeometryNotifierSvc"))
+    result.setPrivateTools(GeoDetectorTool(name, **kwargs))
+    return result
+
 def generateSubDetectorList(ConfigFlags):
     result = ComponentAccumulator()
     SubDetectorList=[]
@@ -121,6 +143,10 @@ def generateSubDetectorList(ConfigFlags):
         toolVeto = result.popToolsAndMerge(VetoGeoDetectorToolCfg(ConfigFlags))
         SubDetectorList += [ toolVeto ]
 
+    if ConfigFlags.Detector.GeometryVetoNu:
+        toolVetoNu = result.popToolsAndMerge(VetoNuGeoDetectorToolCfg(ConfigFlags))
+        SubDetectorList += [ toolVetoNu ]
+
     if ConfigFlags.Detector.GeometryTrigger:
         toolTrigger = result.popToolsAndMerge(TriggerGeoDetectorToolCfg(ConfigFlags))
         SubDetectorList += [ toolTrigger ]
@@ -140,6 +166,11 @@ def generateSubDetectorList(ConfigFlags):
     if ConfigFlags.Detector.GeometryEcal:
         toolEcal = result.popToolsAndMerge(EcalGeoDetectorToolCfg(ConfigFlags))
         SubDetectorList += [ toolEcal ]
+
+    if ConfigFlags.Detector.GeometryTrench:
+        toolTrench = result.popToolsAndMerge(TrenchGeoDetectorToolCfg(ConfigFlags))
+        SubDetectorList += [ toolTrench ]
+
     result.setPrivateTools(SubDetectorList)
     return result
 
@@ -196,6 +227,9 @@ def getFASER_RegionCreatorList(ConfigFlags):
 
     if ConfigFlags.Detector.GeometryFaserCalo:
         regionCreatorList += [EcalPhysicsRegionToolCfg(ConfigFlags)]
+    
+    if ConfigFlags.Detector.GeometryFaserCavern:
+        regionCreatorList += [CavernPhysicsRegionToolCfg(ConfigFlags)]
 
     return regionCreatorList
 
@@ -217,6 +251,11 @@ def FASER_FieldMgrListCfg(ConfigFlags):
         tool  = result.popToolsAndMerge(acc)
         fieldMgrList += [tool]
 
+    if ConfigFlags.Detector.GeometryVetoNu:
+        acc = VetoNuFieldManagerToolCfg(ConfigFlags)
+        tool  = result.popToolsAndMerge(acc)
+        fieldMgrList += [tool]
+
     if ConfigFlags.Detector.GeometryTrigger:
         acc = TriggerFieldManagerToolCfg(ConfigFlags)
         tool  = result.popToolsAndMerge(acc)
@@ -242,6 +281,12 @@ def FASER_FieldMgrListCfg(ConfigFlags):
         tool  = result.popToolsAndMerge(acc)
         fieldMgrList += [tool]
 
+    if ConfigFlags.Detector.GeometryTrench:
+        acc = TrenchFieldManagerToolCfg(ConfigFlags)
+        tool  = result.popToolsAndMerge(acc)
+        fieldMgrList += [tool]
+
+
     result.setPrivateTools(fieldMgrList)
     return result
 
diff --git a/Simulation/G4Faser/G4FaserTools/python/G4PhysicsRegionConfigNew.py b/Simulation/G4Faser/G4FaserTools/python/G4PhysicsRegionConfigNew.py
index 040853dc2f46d08f0a92843d8b2f0dfef945ef11..42016881857b3de6bd2aee6c3f682419b52fe2de 100644
--- a/Simulation/G4Faser/G4FaserTools/python/G4PhysicsRegionConfigNew.py
+++ b/Simulation/G4Faser/G4FaserTools/python/G4PhysicsRegionConfigNew.py
@@ -16,7 +16,7 @@ def NeutrinoPhysicsRegionToolCfg(ConfigFlags, name="NeutrinoPhysicsRegionTool",
 
 def ScintillatorPhysicsRegionToolCfg(ConfigFlags, name='ScintillatorPhysicsRegionTool', **kwargs):
     kwargs.setdefault("RegionName", 'Scintillator')
-    volumeList = ['Veto::Plate' , 'Trigger::Plate', 'Preshower::Plate']
+    volumeList = ['Veto::Plate' , 'Trigger::Plate', 'Preshower::Plate', 'VetoNu::Plate']
     kwargs.setdefault("VolumeList",  volumeList)
     kwargs.setdefault("ElectronCut", 0.05)
     kwargs.setdefault("PositronCut", 0.05)
@@ -45,3 +45,13 @@ def EcalPhysicsRegionToolCfg(ConfigFlags, name='EcalPhysicsRegionTool', **kwargs
     kwargs.setdefault("PositronCut", rangeEMB)
     kwargs.setdefault("GammaCut",    rangeEMB)
     return RegionCreator(name, **kwargs)
+
+def CavernPhysicsRegionToolCfg(ConfigFlags, name='CavernPhysicsRegionTool', **kwargs):
+    kwargs.setdefault("RegionName", 'Cavern')
+    volumeList = ['Trench::Trench']
+    kwargs.setdefault("VolumeList",  volumeList)
+    kwargs.setdefault("ElectronCut", 0.05)
+    kwargs.setdefault("PositronCut", 0.05)
+    kwargs.setdefault("GammaCut",    0.05)
+    return RegionCreator(name, **kwargs)
+
diff --git a/Simulation/ISF/ISF_Core/FaserISF_Services/python/FaserISF_ServicesConfigNew.py b/Simulation/ISF/ISF_Core/FaserISF_Services/python/FaserISF_ServicesConfigNew.py
index e507545cd536d1a85889e6768330a77c5755c5c5..4bf06b76f66ba0af57e64646cebc12b18b978612 100644
--- a/Simulation/ISF/ISF_Core/FaserISF_Services/python/FaserISF_ServicesConfigNew.py
+++ b/Simulation/ISF/ISF_Core/FaserISF_Services/python/FaserISF_ServicesConfigNew.py
@@ -82,7 +82,8 @@ def FaserTruthServiceCfg(ConfigFlags, name="FaserISF_TruthService", **kwargs):
                                                FaserRegion.fFaserTracker,
                                                FaserRegion.fFaserDipole,
                                                FaserRegion.fFaserCalorimeter,
-                                               FaserRegion.fFaserCavern])
+                                               FaserRegion.fFaserCavern,
+                                               FaserRegion.fFaserTrench])
                                                
     #long_lived_simulators = ['LongLived', 'longLived', 'QS']
     #from ISF_Config.ISF_jobProperties import ISF_Flags
diff --git a/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserGeoIDSvc.cxx b/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserGeoIDSvc.cxx
index ed4a3370a690cd833e68a4a930fba0967df54541..18ad0de36e3548ac39c804ed92385ef4f6aef0ea 100644
--- a/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserGeoIDSvc.cxx
+++ b/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserGeoIDSvc.cxx
@@ -123,13 +123,16 @@ FaserDetDescr::FaserRegion ISF::FaserGeoIDSvc::identifyGeoID(const Amg::Vector3D
 
     if (lvName == "Veto::Veto"           || lvName == "Veto::VetoStationA"          ||
         lvName == "Trigger::Trigger"     || lvName == "Trigger::TriggerStationA"    ||
-        lvName == "Preshower::Preshower" || lvName == "Preshower::PreshowerStationA") return FaserDetDescr::fFaserScintillator;
+        lvName == "Preshower::Preshower" || lvName == "Preshower::PreshowerStationA" ||
+        lvName == "VetoNu::VetoNu"       || lvName == "VetoNu::VetoNuStationA" ) return FaserDetDescr::fFaserScintillator;
     
     if (lvName == "Ecal::Ecal") return FaserDetDescr::fFaserCalorimeter;
 
     if (lvName == "Emulsion::Emulsion" || lvName == "Emulsion::EmulsionStationA" ) return FaserDetDescr::fFaserNeutrino;
 
     if (lvName == "Dipole::Dipole") return FaserDetDescr::fFaserDipole;
+
+    if (lvName == "Trench::Trench") return FaserDetDescr::fFaserTrench;
   }
 
   // If all else fails
diff --git a/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/FaserISF_Geant4Event/FaserISFG4GeoHelper.h b/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/FaserISF_Geant4Event/FaserISFG4GeoHelper.h
index 8dc36356a51a161846307e0a677eaf5fa861a24b..29081a1b99180ad28fd7bcb338f2f45bb657b2b3 100644
--- a/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/FaserISF_Geant4Event/FaserISFG4GeoHelper.h
+++ b/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/FaserISF_Geant4Event/FaserISFG4GeoHelper.h
@@ -21,7 +21,7 @@ class FaserISFG4GeoHelper
   static bool checkVolumeDepth(G4LogicalVolume* logicalVol, int volLevel, int depth=0);
 
   private:
-  static G4LogicalVolume *s_vetoLV, *s_triggerLV, *s_preshowerLV, *s_sctLV, *s_dipoleLV, *s_ecalLV, *s_emulsionLV;
+  static G4LogicalVolume *s_vetoLV, *s_triggerLV, *s_preshowerLV, *s_vetonuLV, *s_sctLV, *s_dipoleLV, *s_ecalLV, *s_emulsionLV, *s_trenchLV;
 
 };
 
diff --git a/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/src/FaserISFG4GeoHelper.cxx b/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/src/FaserISFG4GeoHelper.cxx
index 6697607742a1a4cb0cdebf08aa138ba1fca4ba6c..80942559c6c8c46cf9f0e9e7cedf78181dc9b264 100644
--- a/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/src/FaserISFG4GeoHelper.cxx
+++ b/Simulation/ISF/ISF_Geant4/FaserISF_Geant4Event/src/FaserISFG4GeoHelper.cxx
@@ -19,18 +19,20 @@
 
 G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_sctLV = nullptr;
 G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_vetoLV = nullptr;
+G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_vetonuLV = nullptr;
 G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_triggerLV = nullptr;
 G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_preshowerLV = nullptr;
 G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_ecalLV = nullptr;
 G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_dipoleLV = nullptr;
 G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_emulsionLV = nullptr;
+G4LogicalVolume* iGeant4::FaserISFG4GeoHelper::s_trenchLV = nullptr;
 
 //________________________________________________________________________
 FaserDetDescr::FaserRegion
 iGeant4::FaserISFG4GeoHelper::nextGeoId(const G4Step* aStep, int truthVolLevel)
 {
 
-  if (s_sctLV == nullptr && s_vetoLV == nullptr && s_triggerLV == nullptr && s_preshowerLV == nullptr && s_ecalLV == nullptr && s_emulsionLV == nullptr && s_dipoleLV == nullptr) // Initialize
+  if (s_sctLV == nullptr && s_vetoLV == nullptr && s_triggerLV == nullptr && s_preshowerLV == nullptr && s_vetonuLV == nullptr && s_ecalLV == nullptr && s_emulsionLV == nullptr && s_dipoleLV == nullptr && s_trenchLV == nullptr) // Initialize
   { 
     G4LogicalVolumeStore * lvs = G4LogicalVolumeStore::GetInstance();
     for (size_t i = 0; i < lvs->size(); ++i) {
@@ -39,11 +41,13 @@ iGeant4::FaserISFG4GeoHelper::nextGeoId(const G4Step* aStep, int truthVolLevel)
       G4String thisName = thisLV->GetName();
       if ( ( s_sctLV == nullptr && thisName == "SCT::Station" ) || thisName == "SCT::SCT" ) { s_sctLV = thisLV; }
       else if ( ( s_vetoLV == nullptr && thisName == "Veto::VetoStationA" ) || thisName == "Veto::Veto" ) { s_vetoLV = thisLV; }
+      else if ( ( s_vetonuLV == nullptr && thisName == "VetoNu::VetoNuStationA" ) || thisName == "VetoNu::VetoNu" ) { s_vetonuLV = thisLV; }
       else if ( ( s_triggerLV == nullptr && thisName == "Trigger::TriggerStationA" ) || thisName == "Trigger::Trigger" ) { s_triggerLV = thisLV; }
       else if ( ( s_preshowerLV == nullptr && thisName == "Preshower::PreshowerStationA" ) || thisName == "Preshower::Preshower" ) { s_preshowerLV = thisLV; }
       else if ( thisName == "Ecal::Ecal" ) { s_ecalLV = thisLV; }
       else if ( thisName == "Dipole::Dipole" ) { s_dipoleLV = thisLV; }
       else if ( ( s_emulsionLV == nullptr && thisName == "Emulsion::EmulsionStationA" ) || thisName == "Emulsion::Emulsion" )  { s_emulsionLV = thisLV; }
+      else if ( thisName == "Trench::Trench" ) { s_trenchLV = thisLV; }
     }
 
     const auto& worldVolume = G4TransportationManager::GetTransportationManager()->GetNavigatorForTracking()->GetWorldVolume()->GetLogicalVolume();
@@ -73,6 +77,7 @@ iGeant4::FaserISFG4GeoHelper::nextGeoId(const G4Step* aStep, int truthVolLevel)
     nextGeoID = FaserDetDescr::fFaserTracker;
   }
   else if ((s_vetoLV != nullptr && s_vetoLV == postStepVolume) || 
+           (s_vetonuLV != nullptr && s_vetonuLV == postStepVolume) ||
            (s_triggerLV != nullptr && s_triggerLV == postStepVolume) || 
            (s_preshowerLV != nullptr && s_preshowerLV == postStepVolume))
   {
@@ -90,6 +95,10 @@ iGeant4::FaserISFG4GeoHelper::nextGeoId(const G4Step* aStep, int truthVolLevel)
   {
     nextGeoID = FaserDetDescr::fFaserDipole;
   }
+  else if (s_trenchLV != nullptr && s_trenchLV == postStepVolume)
+  {
+    nextGeoID = FaserDetDescr::fFaserTrench;
+  }
   else
   {
     nextGeoID = FaserDetDescr::fFaserCavern;
@@ -107,11 +116,13 @@ bool iGeant4::FaserISFG4GeoHelper::checkVolumeDepth(G4LogicalVolume* lv, int vol
 
   if ( ((s_sctLV != nullptr) && (lv->GetName() == s_sctLV->GetName())) ||
        ((s_vetoLV != nullptr) && (lv->GetName() == s_vetoLV->GetName())) ||
+       ((s_vetonuLV != nullptr) && (lv->GetName() == s_vetonuLV->GetName())) ||
        ((s_triggerLV != nullptr) && (lv->GetName() == s_triggerLV->GetName())) ||
        ((s_preshowerLV != nullptr) && (lv->GetName() == s_preshowerLV->GetName())) ||
        ((s_dipoleLV != nullptr) && (lv->GetName() == s_dipoleLV->GetName())) ||
        ((s_ecalLV != nullptr) && (lv->GetName() == s_ecalLV->GetName())) ||
-       ((s_emulsionLV != nullptr) && (lv->GetName() == s_emulsionLV->GetName())) ) {
+       ((s_emulsionLV != nullptr) && (lv->GetName() == s_emulsionLV->GetName())) ||
+       ((s_trenchLV != nullptr) && (lv->GetName() == s_trenchLV->GetName())) ) {
     if(depth!=volLevel) {
       G4ExceptionDescription description;
       description << "Volume " << lv->GetName() << " at depth " << depth << " instead of depth " << volLevel;
diff --git a/Tracker/TrackerConditions/FaserSCT_ConditionsTools/CMakeLists.txt b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/CMakeLists.txt
index b19cee24e9260527698b0040339c7560a97feb49..1450b0a07133ff3ea8a5517244cbdb9a54b0262a 100644
--- a/Tracker/TrackerConditions/FaserSCT_ConditionsTools/CMakeLists.txt
+++ b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/CMakeLists.txt
@@ -19,6 +19,8 @@ atlas_add_component ( FaserSCT_ConditionsTools
 
 
 atlas_add_library( FaserSCT_ConditionsToolsLib
+                  FaserSCT_ConditionsTools/*.h
+                  src/*.h
                   src/*.cxx
                   PUBLIC_HEADERS FaserSCT_ConditionsTools
                   INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} ${CLHEP_INCLUDE_DIRS}
diff --git a/Tracker/TrackerConditions/FaserSCT_ConditionsTools/FaserSCT_ConditionsTools/ISCT_NoisyStripTool.h b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/FaserSCT_ConditionsTools/ISCT_NoisyStripTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..9fcf6b9a54f891e52edcedb729a3ff8510af319a
--- /dev/null
+++ b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/FaserSCT_ConditionsTools/ISCT_NoisyStripTool.h
@@ -0,0 +1,23 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS and FAsER collaborations
+*/
+
+#ifndef ISCT_NOISY_STRIP_TOOL
+#define ISCT_NOISY_STRIP_TOOL
+
+#include "GaudiKernel/IAlgTool.h"
+#include "GaudiKernel/EventContext.h"
+#include <vector>
+
+
+class ISCT_NoisyStripTool: virtual public IAlgTool {
+public:
+  virtual ~ISCT_NoisyStripTool() = default;
+
+  DeclareInterfaceID(ISCT_NoisyStripTool, 1, 0);
+
+  virtual std::map<std::pair<int,int>, double> getNoisyStrips(const EventContext& ctx) const = 0;
+  virtual std::map<std::pair<int,int>, double> getNoisyStrips(void) const = 0;
+};
+
+#endif  // ISCT_NOISY_STRIP_TOOL
diff --git a/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/FaserSCT_NoisyStripTool.cxx b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/FaserSCT_NoisyStripTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0e2b5c444a8839e0421a073fd79b0737a89ed635
--- /dev/null
+++ b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/FaserSCT_NoisyStripTool.cxx
@@ -0,0 +1,113 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and FASER collaborations
+*/
+
+#include "FaserSCT_NoisyStripTool.h"
+
+
+FaserSCT_NoisyStripTool::FaserSCT_NoisyStripTool (const std::string& type,
+                                                  const std::string& name, const IInterface* parent) :
+    base_class(type, name, parent) {}
+
+
+StatusCode FaserSCT_NoisyStripTool::initialize() {
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode FaserSCT_NoisyStripTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+std::map<std::pair<int, int>, double>
+FaserSCT_NoisyStripTool::getNoisyStrips(const EventContext& /*ctx*/) const {
+  // TODO fix hard-coded definition of noisy strip and read from NoisyPixels.xml database
+  std::map<std::pair<int, int>, double> noisyStrips {};
+  // noisyStrips.insert({std::make_pair(10, 150), 0.25279282895176935});
+  // noisyStrips.insert({std::make_pair(20, 155), 0.9950721341449819});
+  // noisyStrips.insert({std::make_pair(38, 41), 1.0});
+  // noisyStrips.insert({std::make_pair(48, 643), 0.6209110977322898});
+  // noisyStrips.insert({std::make_pair(49, 69), 1.0});
+  // noisyStrips.insert({std::make_pair(49, 508), 0.1250027872544429});
+  // noisyStrips.insert({std::make_pair(49, 660), 1.0});
+  // noisyStrips.insert({std::make_pair(61, 350), 0.3669587709322809});
+  // noisyStrips.insert({std::make_pair(61, 351), 0.32956496532655477});
+  // noisyStrips.insert({std::make_pair(64, 287), 1.0});
+  // noisyStrips.insert({std::make_pair(65, 323), 0.5987691484380226});
+  // noisyStrips.insert({std::make_pair(67, 147), 1.0});
+  // noisyStrips.insert({std::make_pair(67, 207), 1.0});
+  // noisyStrips.insert({std::make_pair(67, 346), 0.6463977523580173});
+  // noisyStrips.insert({std::make_pair(83, 114), 0.9968113809173412});
+  // noisyStrips.insert({std::make_pair(86, 544), 0.46609583695676415});
+  // noisyStrips.insert({std::make_pair(96, 79), 1.0});
+  // noisyStrips.insert({std::make_pair(96, 183), 1.0});
+  // noisyStrips.insert({std::make_pair(97, 550), 1.0});
+  // noisyStrips.insert({std::make_pair(100, 215), 1.0});
+  // noisyStrips.insert({std::make_pair(100, 610), 1.0});
+  // noisyStrips.insert({std::make_pair(130, 722), 1.0});
+  // noisyStrips.insert({std::make_pair(132, 297), 0.8765803732691151});
+  // noisyStrips.insert({std::make_pair(144, 597), 1.0});
+  // noisyStrips.insert({std::make_pair(144, 665), 1.0});
+  // noisyStrips.insert({std::make_pair(145, 9), 1.0});
+  // noisyStrips.insert({std::make_pair(145, 492), 1.0});
+  // noisyStrips.insert({std::make_pair(145, 566), 1.0});
+  // noisyStrips.insert({std::make_pair(146, 267), 1.0});
+  // noisyStrips.insert({std::make_pair(147, 393), 0.4758623765246282});
+  // noisyStrips.insert({std::make_pair(147, 394), 0.5172252324570206});
+  // noisyStrips.insert({std::make_pair(147, 395), 0.5058532343300556});
+  // noisyStrips.insert({std::make_pair(147, 396), 0.5272816464869445});
+  // noisyStrips.insert({std::make_pair(147, 397), 0.4782036702566504});
+  // noisyStrips.insert({std::make_pair(147, 398), 0.5092202376970589});
+  // noisyStrips.insert({std::make_pair(147, 399), 0.4811247129127924});
+  // noisyStrips.insert({std::make_pair(147, 600), 1.0});
+  // noisyStrips.insert({std::make_pair(151, 417), 0.6434767097018753});
+  // noisyStrips.insert({std::make_pair(152, 698), 0.2651013445715433});
+  // noisyStrips.insert({std::make_pair(152, 699), 0.3080250629919504});
+  // noisyStrips.insert({std::make_pair(153, 396), 0.4862532610876982});
+  // noisyStrips.insert({std::make_pair(153, 397), 0.44509108747519344});
+  // noisyStrips.insert({std::make_pair(154, 620), 0.4075634936562089});
+  // noisyStrips.insert({std::make_pair(154, 621), 0.5001449372310299});
+  // noisyStrips.insert({std::make_pair(155, 146), 0.4566637679220461});
+  // noisyStrips.insert({std::make_pair(155, 147), 0.4941021695988584});
+  // noisyStrips.insert({std::make_pair(158, 737), 0.5486208665016612});
+  // noisyStrips.insert({std::make_pair(158, 738), 0.5591901353490758});
+  // noisyStrips.insert({std::make_pair(158, 739), 0.5590786451713604});
+  // noisyStrips.insert({std::make_pair(160, 7), 1.0});
+  // noisyStrips.insert({std::make_pair(161, 763), 1.0});
+  // noisyStrips.insert({std::make_pair(164, 613), 1.0});
+  // noisyStrips.insert({std::make_pair(167, 175), 1.0});
+  // noisyStrips.insert({std::make_pair(170, 90), 0.48484848484848486});
+  // noisyStrips.insert({std::make_pair(170, 91), 0.4570874305973644});
+  // noisyStrips.insert({std::make_pair(173, 18), 1.0});
+  // noisyStrips.insert({std::make_pair(173, 484), 1.0});
+  // noisyStrips.insert({std::make_pair(174, 230), 1.0});
+  // noisyStrips.insert({std::make_pair(174, 530), 1.0});
+  // noisyStrips.insert({std::make_pair(175, 683), 1.0});
+  // noisyStrips.insert({std::make_pair(176, 418), 1.0});
+  // noisyStrips.insert({std::make_pair(177, 149), 1.0});
+  // noisyStrips.insert({std::make_pair(177, 345), 1.0});
+  // noisyStrips.insert({std::make_pair(178, 214), 1.0});
+  // noisyStrips.insert({std::make_pair(178, 508), 0.5192097576203537});
+  // noisyStrips.insert({std::make_pair(178, 673), 0.6028496889424042});
+  // noisyStrips.insert({std::make_pair(179, 651), 0.999977701964457});
+  // noisyStrips.insert({std::make_pair(182, 525), 0.5044707561263853});
+  // noisyStrips.insert({std::make_pair(182, 526), 0.5083506143108792});
+  // noisyStrips.insert({std::make_pair(185, 493), 0.42738644725399694});
+  // noisyStrips.insert({std::make_pair(185, 494), 0.43757664949717934});
+  // noisyStrips.insert({std::make_pair(187, 427), 0.9203737150757019});
+  // noisyStrips.insert({std::make_pair(188, 696), 0.6201752625593685});
+  // noisyStrips.insert({std::make_pair(188, 752), 1.0});
+  // noisyStrips.insert({std::make_pair(189, 249), 0.1250027872544429});
+  // noisyStrips.insert({std::make_pair(189, 338), 0.25925925925925924});
+  // noisyStrips.insert({std::make_pair(191, 170), 1.0});
+  // noisyStrips.insert({std::make_pair(191, 406), 1.0});
+  return noisyStrips;
+}
+
+
+std::map<std::pair<int, int>, double>
+FaserSCT_NoisyStripTool::getNoisyStrips() const {
+  const EventContext& ctx{Gaudi::Hive::currentContext()};
+  return getNoisyStrips(ctx);
+}
diff --git a/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/FaserSCT_NoisyStripTool.h b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/FaserSCT_NoisyStripTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..c554620885b0cf28beb5c3d93c6d6fa31377e3a4
--- /dev/null
+++ b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/FaserSCT_NoisyStripTool.h
@@ -0,0 +1,25 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS and CERN collaborations
+*/
+
+#ifndef FASERSCT_NOISY_STRIP_TOOL
+#define FASERSCT_NOISY_STRIP_TOOL
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "FaserSCT_ConditionsTools/ISCT_NoisyStripTool.h"
+#include "GaudiKernel/ICondSvc.h"
+#include "GaudiKernel/EventContext.h"
+
+
+class FaserSCT_NoisyStripTool: public extends<AthAlgTool, ISCT_NoisyStripTool> {
+public:
+  FaserSCT_NoisyStripTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~FaserSCT_NoisyStripTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+
+  virtual std::map<std::pair<int, int>, double> getNoisyStrips(const EventContext& ctx) const override;
+  virtual std::map<std::pair<int, int>, double> getNoisyStrips() const override;
+};
+
+#endif  // FASERSCT_NOISY_STRIP_TOOL
diff --git a/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/components/FaserSCT_ConditionsTools_entries.cxx b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/components/FaserSCT_ConditionsTools_entries.cxx
index cae3df3c65e74cecd79c4729d2dc57cc8e7e4cf3..adad7ee37337a3d347bedc9c1f5b28cf731fb837 100644
--- a/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/components/FaserSCT_ConditionsTools_entries.cxx
+++ b/Tracker/TrackerConditions/FaserSCT_ConditionsTools/src/components/FaserSCT_ConditionsTools_entries.cxx
@@ -20,6 +20,7 @@
 // #include "../SCT_StripVetoTool.h"
 // #include "../SCT_TdaqEnabledTool.h"
 #include "../FaserSCT_CableMappingTool.h"
+#include "../FaserSCT_NoisyStripTool.h"
 
 // DECLARE_COMPONENT( SCT_ByteStreamErrorsTool )
 // DECLARE_COMPONENT( SCT_ChargeTrappingTool )
@@ -42,4 +43,5 @@ DECLARE_COMPONENT( FaserSCT_ReadCalibChipDataTool )
 DECLARE_COMPONENT( FaserSCT_SiliconConditionsTool )
 // DECLARE_COMPONENT( SCT_StripVetoTool )
 // DECLARE_COMPONENT( SCT_TdaqEnabledTool )
-DECLARE_COMPONENT( FaserSCT_CableMappingTool )
\ No newline at end of file
+DECLARE_COMPONENT( FaserSCT_CableMappingTool )
+DECLARE_COMPONENT( FaserSCT_NoisyStripTool )
diff --git a/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py b/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py
index c9f267da7d9d2ba0c6bc973c3fa949b65c217ecc..f952b5766a0acf1085974f3af9332a8ac3de0462 100644
--- a/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py
+++ b/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py
@@ -239,10 +239,11 @@ def FaserSCT_OutputCfg(flags):
     ItemList = ["FaserSCT_RDO_Container#*"]
     if flags.Digitization.TruthOutput:
         ItemList += ["TrackerSimDataCollection#*"]
+        ItemList += ["FaserSiHitCollection#*"] # Also write out the raw hits
         acc.merge(TruthDigitizationOutputCfg(flags))
     acc.merge(OutputStreamCfg(flags, "RDO"))
     ostream = acc.getEventAlgo("OutputStreamRDO")
-    ostream.TakeItemsFromInput = True
+    #ostream.TakeItemsFromInput = True   # Don't write hits to RDO by default
     ostream.ItemList += ItemList
     return acc
 
diff --git a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.cxx b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.cxx
index bd6ff38f7fc46fe44c62e5ccb339744e46cad90c..87bb2437c36c0d0c4f2cbe0cbfb4f3a1270c1e1a 100644
--- a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.cxx
+++ b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.cxx
@@ -28,6 +28,7 @@ TrackerByteStreamCnv::TrackerByteStreamCnv(ISvcLocator* svcloc)
   , m_tool("TrackerDataDecoderTool")
   , m_mappingTool("FaserSCT_CableMappingTool")
   , m_rdpSvc("FaserROBDataProviderSvc", "TrackerByteStreamCnv")
+  , m_noisyStripTool("FaserSCT_NoisyStripTool")
   , m_detStoreSvc("StoreGateSvc/DetectorStore", "TrackerByteStreamCnv")
 {
 }
@@ -48,6 +49,7 @@ StatusCode TrackerByteStreamCnv::initialize()
   CHECK(m_rdpSvc.retrieve());
   CHECK(m_tool.retrieve());
   CHECK(m_mappingTool.retrieve());
+  CHECK(m_noisyStripTool.retrieve());
 
   ATH_CHECK(m_detStoreSvc.retrieve());
   ATH_CHECK(m_detStoreSvc->retrieve(m_sctID, "FaserSCT_ID"));
@@ -98,10 +100,13 @@ StatusCode TrackerByteStreamCnv::createObj(IOpaqueAddress* pAddr, DataObject*& p
 
   auto mapping = m_mappingTool->getCableMapping();
   ATH_MSG_DEBUG("Cable mapping contains " << mapping.size() << " entries");
-  
+
+  auto noisyStrips = m_noisyStripTool->getNoisyStrips();
+  ATH_MSG_DEBUG(noisyStrips.size() << " noisy strips");
+
   // Convert raw data into this container
 
-  CHECK( m_tool->convert(re, cont, key, mapping) );
+  CHECK( m_tool->convert(re, cont, key, mapping, noisyStrips) );
   
   pObj = SG::asStorable(cont);
 
diff --git a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.h b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.h
index de0095b637bc99a9b0e30e2b34fba5842bcdbcf6..86262fbc244530aecac352d968a13d54d0943728 100644
--- a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.h
+++ b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerByteStreamCnv.h
@@ -15,6 +15,7 @@
 #include "AthenaBaseComps/AthMessaging.h"
 #include "FaserByteStreamCnvSvcBase/FaserByteStreamAddress.h"
 #include "FaserSCT_ConditionsTools/ISCT_CableMappingTool.h"
+#include "FaserSCT_ConditionsTools/ISCT_NoisyStripTool.h"
 
 class TrackerDataDecoderTool;
 class IFaserROBDataProviderSvc;
@@ -40,10 +41,11 @@ public:
   static const CLID& classID();
   
 private:
-  ToolHandle<TrackerDataDecoderTool>      m_tool;
-  ToolHandle<ISCT_CableMappingTool>       m_mappingTool;
   ServiceHandle<IFaserROBDataProviderSvc> m_rdpSvc;
   ServiceHandle<StoreGateSvc>             m_detStoreSvc;
+  ToolHandle<TrackerDataDecoderTool>      m_tool;
+  ToolHandle<ISCT_CableMappingTool>       m_mappingTool;
+  ToolHandle<ISCT_NoisyStripTool>         m_noisyStripTool;
   const FaserSCT_ID*                      m_sctID{nullptr};
 };
 
diff --git a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.cxx b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.cxx
index 9971d0292356a1e8437f5734c2703a0bac10ee2a..612978a068a931bf4a85b267537d89b626ba53e5 100644
--- a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.cxx
+++ b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.cxx
@@ -45,7 +45,7 @@ TrackerDataDecoderTool::initialize()
 
   ATH_CHECK(detStore()->retrieve(m_sctID, "FaserSCT_ID"));
 
-  auto first_wafer = m_sctID->wafer_begin();
+  // auto first_wafer = m_sctID->wafer_begin();
   // m_trb0Station = m_sctID->station(*first_wafer);
 
   m_sctContext = m_sctID->wafer_context();
@@ -81,7 +81,9 @@ StatusCode
 TrackerDataDecoderTool::convert(const DAQFormats::EventFull* re, 
     FaserSCT_RDO_Container* container,
     std::string key,
-    const std::map<int, std::pair<int, int> >& cableMapping)
+    const std::map<int, std::pair<int, int> >& cableMapping,
+    const std::map<std::pair<int, int>, double>& noisyStrips)
+
 {
   ATH_MSG_DEBUG("TrackerDataDecoderTool::convert()");
 
@@ -231,6 +233,12 @@ TrackerDataDecoderTool::convert(const DAQFormats::EventFull* re,
                   ATH_MSG_ERROR("Invalid strip number on side: " << stripOnSide);
                   continue;
                 }
+                // check if strip is noisy
+                auto it = noisyStrips.find(std::make_pair(waferHash, stripOnSide));
+                if (it != noisyStrips.end() && it->second >= m_occupancyCut) {
+                  ATH_MSG_VERBOSE("Mask wafer " << waferHash << ", strip " << stripOnSide << " with an occupancy of " << it->second);
+                  continue;
+                }
                 Identifier digitID {m_sctID->strip_id(id, stripOnSide)};
                 int errors{0};
                 int groupSize{1};
diff --git a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.h b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.h
index 32b42ced60b494d93661aa4f3ea8dbcbdc6ce63b..3bc337276a3f8eaba28fa51d90f7eb4f903592c7 100644
--- a/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.h
+++ b/Tracker/TrackerEventCnv/TrackerByteStream/src/TrackerDataDecoderTool.h
@@ -31,7 +31,9 @@ class TrackerDataDecoderTool : public AthAlgTool {
   virtual StatusCode initialize();
   virtual StatusCode finalize();
 
-  StatusCode convert(const DAQFormats::EventFull* re, FaserSCT_RDO_Container* cont, std::string key, const std::map<int, std::pair<int, int> >& cableMapping);
+  StatusCode convert(const DAQFormats::EventFull* re, FaserSCT_RDO_Container* cont, std::string key,
+                     const std::map<int, std::pair<int, int> >& cableMapping,
+                     const std::map<std::pair<int, int>, double>& noisyStrips);
 
 private:
   const FaserSCT_ID*                      m_sctID{nullptr};
@@ -41,7 +43,8 @@ private:
                                                         "ModuleMap", 
                                                         {7, 2, 5, 0, 3, 6, 1, 4}, 
                                                         "Mapping from online to offline module numbers" };
-  
+  Gaudi::Property<double> m_occupancyCut {this, "OccupancyCut", 0.1, "Mask strips with an occupancy larger than the OccupancyCut"};
+
   // Gaudi::Property<uint32_t>               m_trb0Station { this, "Trb0StationNumber", 1, "Station number for TRB #0" };
 };
 
diff --git a/Tracker/TrackerRecAlgs/FaserSpacePoints/CMakeLists.txt b/Tracker/TrackerRecAlgs/FaserSpacePoints/CMakeLists.txt
index 4978495c385798463bd84f20276920cdde578765..e88d445536b1770103382549c17e502e4f5f8b09 100644
--- a/Tracker/TrackerRecAlgs/FaserSpacePoints/CMakeLists.txt
+++ b/Tracker/TrackerRecAlgs/FaserSpacePoints/CMakeLists.txt
@@ -1,12 +1,9 @@
-################################################################################
-# Package: FaserSpacePoints
-################################################################################
+atlas_subdir(FaserSpacePoints)
 
-# Declare the package name:
-atlas_subdir( FaserSpacePoints )
+atlas_add_component(
+  FaserSpacePoints
+  src/components/*.cxx src/*.cxx src/*.h
+  LINK_LIBRARIES AthenaBaseComps TrackerSimData TrackerSimEvent TrackerSpacePoint TrackerPrepRawData)
 
-atlas_add_component( FaserSpacePoints
-                     src/components/*.cxx src/*.cxx src/*.h
-	                   LINK_LIBRARIES AthenaBaseComps TrackerSpacePoint TrackerPrepRawData)
-
-atlas_install_python_modules( python/*.py )
\ No newline at end of file
+atlas_install_python_modules( python/*.py )
+atlas_install_scripts( test/*.py )
diff --git a/Tracker/TrackerRecAlgs/FaserSpacePoints/python/FaserSpacePointsConfig.py b/Tracker/TrackerRecAlgs/FaserSpacePoints/python/FaserSpacePointsConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..7fa948ab3072fbbd11340eafb6c6c59cd92595dc
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/FaserSpacePoints/python/FaserSpacePointsConfig.py
@@ -0,0 +1,17 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+
+
+def FaserSpacePointsCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    Tracker__FaserSpacePoints = CompFactory.Tracker.FaserSpacePoints
+    acc.addEventAlgo(Tracker__FaserSpacePoints(**kwargs))
+
+    thistSvc = CompFactory.THistSvc()
+    thistSvc.Output += ["HIST DATAFILE='SpacePoints.root' OPT='RECREATE'"]
+    acc.addService(thistSvc)
+    return acc
diff --git a/Tracker/TrackerRecAlgs/FaserSpacePoints/python/FaserSpacePointsCosmics.py b/Tracker/TrackerRecAlgs/FaserSpacePoints/python/FaserSpacePointsCosmics.py
index 48197207d0c1b321e658b74ddb2d40d74ee244dd..78bae901d8c5319dd35ad4f101ff5cb291740395 100644
--- a/Tracker/TrackerRecAlgs/FaserSpacePoints/python/FaserSpacePointsCosmics.py
+++ b/Tracker/TrackerRecAlgs/FaserSpacePoints/python/FaserSpacePointsCosmics.py
@@ -22,7 +22,6 @@ Tracker__TruthSeededTrackFinder, THistSvc=CompFactory.getComps("Tracker::FaserSp
 def TruthSeededTrackFinderBasicCfg(flags, **kwargs):
     """Return ComponentAccumulator for TruthSeededTrackFinder"""
     acc = FaserSCT_GeometryCfg(flags)
-    kwargs.setdefault("SpacePointsSCTName", "SCT_SpacePointContainer")
     acc.addEventAlgo(Tracker__TruthSeededTrackFinder(**kwargs))
    # attach ToolHandles
     return acc
diff --git a/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.cxx b/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.cxx
index b9722a7c2f4318fd6d8e4cf2c76437ced6078d8e..caa4af80a79286b6bdb76e18754541f7ee498568 100644
--- a/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.cxx
+++ b/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.cxx
@@ -1,104 +1,128 @@
 #include "FaserSpacePoints.h"
 #include "Identifier/Identifier.h"
+#include "TrackerSimEvent/FaserSiHit.h"
 #include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
 #include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+
 
 namespace Tracker {
-  FaserSpacePoints::FaserSpacePoints(const std::string& name, ISvcLocator* pSvcLocator)
-    : AthReentrantAlgorithm(name, pSvcLocator) { }
 
-  
-  StatusCode FaserSpacePoints::initialize() {
-    ATH_MSG_INFO(name() << "::" << __FUNCTION__);
+FaserSpacePoints::FaserSpacePoints(const std::string& name, ISvcLocator* pSvcLocator)
+  : AthReentrantAlgorithm(name, pSvcLocator), AthHistogramming(name),
+    m_histSvc("THistSvc/THistSvc", name) {}
+
+
+StatusCode FaserSpacePoints::initialize() {
+  ATH_CHECK(m_spacePointContainerKey.initialize());
+  ATH_CHECK(m_siHitCollectionKey.initialize());
+  ATH_CHECK(m_simDataCollectionKey.initialize());
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detMgr, "SCT"));
+  m_tree = new TTree("tree", "tree");
+  m_tree->Branch("run", &m_run, "run/I");
+  m_tree->Branch("event", &m_event, "event/I");
+  m_tree->Branch("station", &m_station, "station/I");
+  m_tree->Branch("layer", &m_layer, "layer/I");
+  m_tree->Branch("phi", &m_phi, "phi/I");
+  m_tree->Branch("eta", &m_eta, "eta/I");
+  m_tree->Branch("side", &m_side, "side/I");
+  m_tree->Branch("x", &m_x, "x/D");
+  m_tree->Branch("y", &m_y, "y/D");
+  m_tree->Branch("z", &m_z, "z/D");
+  m_tree->Branch("muon_hit", &m_muon_hit, "muon_hit/B");
+  m_tree->Branch("tx", &m_tx, "tx/D");
+  m_tree->Branch("ty", &m_ty, "ty/D");
+  m_tree->Branch("tz", &m_tz, "tz/D");
+  ATH_CHECK(histSvc()->regTree("/HIST/spacePoints", m_tree));
+  return StatusCode::SUCCESS;
+}
 
-    if ( m_sct_spcontainer.key().empty()) {
-      ATH_MSG_FATAL( "SCTs selected and no name set for SCT clusters");
-      return StatusCode::FAILURE;
-    }
-    ATH_CHECK( m_sct_spcontainer.initialize() );
 
-    ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+StatusCode FaserSpacePoints::finalize() {
+  return StatusCode::SUCCESS;
+}
 
-    std::string filePath = m_filePath;
-    m_outputFile = TFile::Open(filePath.c_str(), "RECREATE");
-    if (m_outputFile == nullptr) {
-      ATH_MSG_ERROR("Unable to open output file at " << m_filePath);
-      return StatusCode::FAILURE;
-    }
-    m_outputFile->cd();
+bool FaserSpacePoints::muon_hit(const TrackerSimDataCollection* simDataMap, Identifier id) const {
+  // check if Identifier in simDataMap
+  if (!simDataMap->count(id)) {
+    return false;
+  }
+  // check if hit originates from a muon (if the pdg code of the hit is -13)
+  TrackerSimData simData = simDataMap->at(id);
+  const std::vector<TrackerSimData::Deposit>& deposits = simData.getdeposits();
+  auto it = std::find_if(
+      deposits.begin(), deposits.end(), [](TrackerSimData::Deposit deposit) {
+        return deposit.first->pdg_id() == -13;
+      });
+  return it != deposits.end();
+}
 
-    std::string treeName = m_treeName;
-    m_outputTree = new TTree(treeName.c_str(), "tree");
-    if (m_outputTree == nullptr) {
-      ATH_MSG_ERROR("Unable to create TTree");
-      return StatusCode::FAILURE;
-    }
+StatusCode FaserSpacePoints::execute(const EventContext& ctx) const {
+  m_run = ctx.eventID().run_number();
+  m_event = ctx.eventID().event_number();
 
-    FaserSpacePoints::initializeTree();
-    return StatusCode::SUCCESS;
-  }
+  SG::ReadHandle<FaserSCT_SpacePointContainer> spacePointContainer {m_spacePointContainerKey, ctx};
+  ATH_CHECK(spacePointContainer.isValid());
 
-  
-  StatusCode FaserSpacePoints::finalize() {
-    ATH_MSG_INFO(name() << "::" << __FUNCTION__);
-    m_outputFile->cd();
-    m_outputTree->Write();
-    return StatusCode::SUCCESS;
-  }
+  SG::ReadHandle<FaserSiHitCollection> siHitCollection {m_siHitCollectionKey, ctx};
+  ATH_CHECK(siHitCollection.isValid());
+
+  SG::ReadHandle<TrackerSimDataCollection> simDataCollection {m_simDataCollectionKey, ctx};
+  ATH_CHECK(simDataCollection.isValid());
 
-  
-  StatusCode FaserSpacePoints::execute(const EventContext& ctx) const {
-    ATH_MSG_INFO(name() << "::" << __FUNCTION__);
-  
-    m_run = ctx.eventID().run_number();
-    m_event = ctx.eventID().event_number();
-    ATH_MSG_DEBUG("run = " << m_run);
-    ATH_MSG_DEBUG("event = " << m_event);
-
-    SG::ReadHandle<FaserSCT_SpacePointContainer> sct_spcontainer( m_sct_spcontainer, ctx );
-    if (!sct_spcontainer.isValid()){
-      ATH_MSG_FATAL("Could not find the data object " << sct_spcontainer.name());
-      return StatusCode::RECOVERABLE;
+
+  // create map of the IdentifierHash of a SCT sensor and the true position of the corresponding hit
+  std::map<IdentifierHash, HepGeom::Point3D<double>> truePositions;
+  for (const FaserSiHit& hit: *siHitCollection) {
+    if ((!hit.particleLink()) || (hit.particleLink()->pdg_id() != -13)) {
+      continue;
+    }
+    Identifier waferId = m_idHelper->wafer_id(hit.getStation(), hit.getPlane(), hit.getRow(), hit.getModule(), hit.getSensor());
+    IdentifierHash waferHash = m_idHelper->wafer_hash(waferId);
+    const HepGeom::Point3D<double> localPosition = hit.localStartPosition();
+    const TrackerDD::SiDetectorElement* element = m_detMgr->getDetectorElement(waferId);
+    if (element) {
+      const HepGeom::Point3D<double> globalPosition =
+          Amg::EigenTransformToCLHEP(element->transformHit()) * localPosition;
+      truePositions[waferHash] = globalPosition;
     }
+  }
+
 
-    m_nCollections = sct_spcontainer->size();
-    ATH_MSG_DEBUG("nCollections = " << m_nCollections);
-    FaserSCT_SpacePointContainer::const_iterator it = sct_spcontainer->begin();
-    FaserSCT_SpacePointContainer::const_iterator it_end = sct_spcontainer->end();
-
-    for(; it != it_end; ++it) {
-      const FaserSCT_SpacePointCollection* col = *it;
-      m_nSpacepoints = col->size();
-      ATH_MSG_DEBUG("nSpacepoints = " << m_nSpacepoints);
-
-      FaserSCT_SpacePointCollection::const_iterator it_sp = col->begin();
-      FaserSCT_SpacePointCollection::const_iterator it_sp_end = col->end();
-      for (; it_sp != it_sp_end; ++it_sp) {
-        const FaserSCT_SpacePoint* sp = *it_sp;
-        const auto id = sp->clusterList().first->identify();
-        int station = m_idHelper->station(id);
-        int plane = m_idHelper->layer(id);
-        m_layer = (station - 1) * 3 + plane;
-
-        Amg::Vector3D position = sp->globalPosition();
-        m_x = position.x();
-        m_y = position.y();
-        m_z = position.z();
-
-        m_outputTree->Fill();
+  for (const FaserSCT_SpacePointCollection* spacePointCollection : *spacePointContainer) {
+    for (const FaserSCT_SpacePoint* spacePoint : *spacePointCollection) {
+      Identifier id1 = spacePoint->cluster1()->identify();
+      Identifier id2 = spacePoint->cluster2()->identify();
+      m_station = m_idHelper->station(id1);
+      m_layer = m_idHelper->layer(id1);
+      m_phi = m_idHelper->phi_module(id1);
+      m_eta = m_idHelper->eta_module(id1);
+      m_side = m_idHelper->side(id1);
+      // check if both clusters originate from a muon
+      m_muon_hit = (muon_hit(simDataCollection.get(), id1) && muon_hit(simDataCollection.get(), id2));
+      // global reconstructed position of space point
+      Amg::Vector3D position = spacePoint->globalPosition();
+      m_x = position.x();
+      m_y = position.y();
+      m_z = position.z();
+      // global true position of simulated hit
+      IdentifierHash waferHash = spacePoint->elementIdList().first;
+      if (truePositions.count(waferHash)) {
+        auto truePosition = truePositions[waferHash];
+        m_tx = truePosition.x();
+        m_ty = truePosition.y();
+        m_tz = truePosition.z();
+      } else {
+        m_tx = -1;
+        m_ty = -1;
+        m_tz = -1;
       }
+      m_tree->Fill();
     }
-    return StatusCode::SUCCESS;
   }
+  return StatusCode::SUCCESS;
+}
 
-  void FaserSpacePoints::initializeTree() {
-    m_outputTree->Branch("run", &m_run);
-    m_outputTree->Branch("event", &m_event);
-    m_outputTree->Branch("layer", &m_layer);
-    m_outputTree->Branch("n_collections", &m_nCollections);
-    m_outputTree->Branch("n_spacepoints", &m_nSpacepoints);
-    m_outputTree->Branch("x", &m_x);
-    m_outputTree->Branch("y", &m_y);
-    m_outputTree->Branch("z", &m_z);
-  }
 }
diff --git a/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.h b/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.h
index 28807d73e7ac7939c5b830a758846028feedb815..1cc43f65e2f1a674b692487a26421ad526b466f3 100644
--- a/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.h
+++ b/Tracker/TrackerRecAlgs/FaserSpacePoints/src/FaserSpacePoints.h
@@ -2,45 +2,70 @@
 #define FASERSPACEPOINTS_H
 
 #include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthHistogramming.h"
 #include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
 #include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
 #include <string>
 #include <atomic>
 
 #include "TTree.h"
 #include "TFile.h"
 
+class FaserSCT_ID;
+namespace  TrackerDD {
+class SCT_DetectorManager;
+}
+
+
 namespace Tracker {
-  class FaserSpacePoints : public AthReentrantAlgorithm {
-   public:
-    FaserSpacePoints(const std::string& name, ISvcLocator* pSvcLocator);
-    virtual ~FaserSpacePoints() = default;
-
-    virtual StatusCode initialize() override;
-    virtual StatusCode execute(const EventContext& ctx) const override;
-    virtual StatusCode finalize() override;
-
-   private:
-    void initializeTree();
-
-    mutable int m_run{0};
-    mutable int m_event{0};
-    mutable int m_layer{0};
-    mutable int m_nCollections{0};
-    mutable int m_nSpacepoints{0};
-    mutable float m_x{0};
-    mutable float m_y{0};
-    mutable float m_z{0};
-
-    const FaserSCT_ID* m_idHelper{nullptr};
-    SG::ReadHandleKey<FaserSCT_SpacePointContainer> m_sct_spcontainer{this, "SpacePointsSCTName", "SCT spacepoint container"};
-
-    Gaudi::Property<std::string> m_filePath{this, "FilePath", "spacepoints.root", "Output root file for spacepoints"};
-    Gaudi::Property<std::string> m_treeName{this, "TreeName", "tree", ""};
-
-    TFile* m_outputFile;
-    TTree* m_outputTree;
-  };
+
+class FaserSpacePoints : public AthReentrantAlgorithm, AthHistogramming {
+ public:
+  FaserSpacePoints(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~FaserSpacePoints() = default;
+
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext& ctx) const override;
+  virtual StatusCode finalize() override;
+
+  const ServiceHandle<ITHistSvc>& histSvc() const;
+
+ private:
+  const FaserSCT_ID* m_idHelper{nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detMgr {nullptr};
+  bool muon_hit(const TrackerSimDataCollection* simDataMap, Identifier id) const;
+  ServiceHandle<ITHistSvc> m_histSvc;
+
+  SG::ReadHandleKey<FaserSCT_SpacePointContainer> m_spacePointContainerKey {
+    this, "SpacePoints", "SCT_SpacePointContainer"};
+  SG::ReadHandleKey<FaserSiHitCollection> m_siHitCollectionKey {
+    this, "FaserSiHitCollection", "SCT_Hits"};
+  SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey {
+    this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+
+  mutable TTree* m_tree;
+  mutable int m_run {0};
+  mutable int m_event {0};
+  mutable int m_station {0};
+  mutable int m_layer {0};
+  mutable int m_phi {0};
+  mutable int m_eta {0};
+  mutable int m_side {0};
+  mutable double m_x {0};
+  mutable double m_y {0};
+  mutable double m_z {0};
+  mutable bool m_muon_hit {false};
+  mutable double m_tx {0};
+  mutable double m_ty {0};
+  mutable double m_tz {0};
+};
+
+inline const ServiceHandle<ITHistSvc>& FaserSpacePoints::histSvc() const {
+  return m_histSvc;
+}
+
 }
 
 #endif // FASERSPACEPOINTS_H
diff --git a/Tracker/TrackerRecAlgs/FaserSpacePoints/test/FaserSpacePointsDbg.py b/Tracker/TrackerRecAlgs/FaserSpacePoints/test/FaserSpacePointsDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3d50f97a2127b4b33fec1c9cf50ab85218e8621
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/FaserSpacePoints/test/FaserSpacePointsDbg.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+from FaserSpacePoints.FaserSpacePointsConfig import FaserSpacePointsCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "spacePoints.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.lock()
+
+# Core components
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+acc.merge(FaserSpacePointsCfg(ConfigFlags))
+acc.getEventAlgo("Tracker::FaserSpacePoints").OutputLevel = DEBUG
+
+sc = acc.run(maxEvents=1000)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/MCEvents/CMakeLists.txt b/Tracker/TrackerRecAlgs/MCEvents/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..edb6005ad67fab4e0fbb377aadee3a5fe3934235
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MCEvents/CMakeLists.txt
@@ -0,0 +1,10 @@
+atlas_subdir(MCEvents)
+
+atlas_add_component(
+        MCEvents
+        src/MCEventsAlg.h
+        src/MCEventsAlg.cxx
+        src/components/MCEvents_entries.cxx
+        LINK_LIBRARIES AthenaBaseComps GeneratorObjects StoreGateLib TrackerIdentifier TrackerSimEvent GaudiKernel)
+atlas_install_python_modules(python/*.py)
+atlas_install_scripts(test/*.py)
diff --git a/Tracker/TrackerRecAlgs/MCEvents/python/MCEventsConfig.py b/Tracker/TrackerRecAlgs/MCEvents/python/MCEventsConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..215d7a0ba1b878434beeb126931b32d832699155
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MCEvents/python/MCEventsConfig.py
@@ -0,0 +1,12 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+
+
+def MCEventsCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    mc_events_alg = CompFactory.MCEventsAlg
+    acc.addEventAlgo(mc_events_alg (**kwargs))
+    return acc
diff --git a/Tracker/TrackerRecAlgs/MCEvents/python/__init__.py b/Tracker/TrackerRecAlgs/MCEvents/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tracker/TrackerRecAlgs/MCEvents/src/MCEventsAlg.cxx b/Tracker/TrackerRecAlgs/MCEvents/src/MCEventsAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..e825850298b692e6841528cd61a897c3c3f584ff
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MCEvents/src/MCEventsAlg.cxx
@@ -0,0 +1,53 @@
+#include "MCEventsAlg.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+
+
+MCEventsAlg::MCEventsAlg(const std::string& name, ISvcLocator* pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator) {}
+
+
+StatusCode MCEventsAlg::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper,"FaserSCT_ID"));
+  ATH_CHECK(m_mcEventKey.initialize());
+  ATH_CHECK(m_faserSiHitKey.initialize());
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MCEventsAlg::execute(const EventContext& ctx) const {
+  SG::ReadHandle<FaserSiHitCollection> siHitCollection (m_faserSiHitKey, ctx);
+  std::map<int, std::vector<const FaserSiHit*>> particle_map {};
+  ATH_CHECK(siHitCollection.isValid());
+  for (const FaserSiHit& hit : *siHitCollection) {
+    int barcode = hit.particleLink().barcode();
+    particle_map[barcode].push_back(&hit);
+  }
+
+  SG::ReadHandle<McEventCollection> mcEvents (m_mcEventKey, ctx);
+  ATH_CHECK(mcEvents.isValid());
+  for (const HepMC::GenEvent* evt : *mcEvents) {
+    for (const HepMC::GenParticle* p : evt->particle_range()) {
+      const HepMC::FourVector vertex = p->production_vertex()->position();
+      const HepMC::FourVector momentum = p->momentum();
+      ATH_MSG_DEBUG("pdg: " << p->pdg_id() << ", barcode: " << p->barcode());
+      ATH_MSG_DEBUG("vertex: " << vertex.x() << ", " << vertex.y() << ", " << vertex.z());
+      ATH_MSG_DEBUG("momentum: " << momentum.px() << ", " << momentum.py() << ", " << momentum.pz());
+      auto hits = particle_map[p->barcode()];
+      for (const FaserSiHit* hit : hits) {
+        int station = hit->getStation();
+        int layer = hit->getPlane();
+        int phi = hit->getRow();
+        int eta = hit->getModule();
+        int side = hit->getSensor();
+        ATH_MSG_DEBUG(station << "/" << layer << "/" << phi << "/" << eta << "/" << side);
+      }
+    }
+  }
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MCEventsAlg::finalize() {
+  return StatusCode::SUCCESS;
+}
diff --git a/Tracker/TrackerRecAlgs/MCEvents/src/MCEventsAlg.h b/Tracker/TrackerRecAlgs/MCEvents/src/MCEventsAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..6b28d076f21f593e1ea0530e6148e55b22f28fd5
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MCEvents/src/MCEventsAlg.h
@@ -0,0 +1,26 @@
+#ifndef MCEVENTS_MCEVENTSALG_H
+#define MCEVENTS_MCEVENTSALG_H
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "GeneratorObjects/McEventCollection.h"
+
+class FaserSCT_ID;
+
+class MCEventsAlg : public AthReentrantAlgorithm {
+public:
+  MCEventsAlg(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~MCEventsAlg() = default;
+
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext& ctx) const override;
+  virtual StatusCode finalize() override;
+
+private:
+  const FaserSCT_ID* m_idHelper {nullptr};
+  SG::ReadHandleKey<McEventCollection> m_mcEventKey { this, "McEventCollection", "BeamTruthEvent" };
+  SG::ReadHandleKey<FaserSiHitCollection> m_faserSiHitKey { this, "FaserSiHitCollection", "SCT_Hits" };
+};
+
+
+#endif // MCEVENTS_MCEVENTSALG_H
diff --git a/Tracker/TrackerRecAlgs/MCEvents/src/components/MCEvents_entries.cxx b/Tracker/TrackerRecAlgs/MCEvents/src/components/MCEvents_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..2fbef91d7d22c81a1d230bee04fc9a88b5a1daaa
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MCEvents/src/components/MCEvents_entries.cxx
@@ -0,0 +1,3 @@
+#include "../MCEventsAlg.h"
+
+DECLARE_COMPONENT(MCEventsAlg)
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/MCEvents/test/MCEventsDbg.py b/Tracker/TrackerRecAlgs/MCEvents/test/MCEventsDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8e6ad587cdb132f93b7e1d57c5b958c9270fd9b
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MCEvents/test/MCEventsDbg.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from MCEvents.MCEventsConfig import MCEventsCfg
+
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+# Configure
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "MCEvents.ESD.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(MCEventsCfg(ConfigFlags))
+acc.getEventAlgo("MCEventsAlg").OutputLevel = DEBUG
+
+sc = acc.run(maxEvents=10)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/MyClusters/CMakeLists.txt b/Tracker/TrackerRecAlgs/MyClusters/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..751e1f04a43c5f154013f81a2a74b44495df5dea
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyClusters/CMakeLists.txt
@@ -0,0 +1,11 @@
+atlas_subdir(MyClusters)
+
+atlas_add_component(
+  MyClusters
+  src/MyClustersAlg.h
+  src/MyClustersAlg.cxx
+  src/components/MyClusters_entries.cxx
+  LINK_LIBRARIES AthenaBaseComps GeneratorObjects TrackerPrepRawData StoreGateLib GaudiKernel)
+
+atlas_install_python_modules(python/*.py)
+atlas_install_scripts(test/*.py)
diff --git a/Tracker/TrackerRecAlgs/MyClusters/python/MyClustersConfig.py b/Tracker/TrackerRecAlgs/MyClusters/python/MyClustersConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..64757e1a96f8dff63993a8a2275ca5cc2db0b7c2
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyClusters/python/MyClustersConfig.py
@@ -0,0 +1,17 @@
+"""
+Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+"""
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+
+
+def MyClustersCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    my_clusters_alg = CompFactory.MyClustersAlg
+    acc.addEventAlgo(my_clusters_alg (**kwargs))
+
+    thistSvc = CompFactory.THistSvc()
+    thistSvc.Output += ["HIST DATAFILE='MyClusters.root' OPT='RECREATE'"]
+    acc.addService(thistSvc)
+    return acc
diff --git a/Tracker/TrackerRecAlgs/MyClusters/python/__init__.py b/Tracker/TrackerRecAlgs/MyClusters/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tracker/TrackerRecAlgs/MyClusters/src/MyClustersAlg.cxx b/Tracker/TrackerRecAlgs/MyClusters/src/MyClustersAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..050e5bed4949ad32855e899196bac03f91634ba8
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyClusters/src/MyClustersAlg.cxx
@@ -0,0 +1,70 @@
+#include "MyClustersAlg.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+
+
+MyClustersAlg::MyClustersAlg(const std::string& name, ISvcLocator* pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator), AthHistogramming(name),
+      m_histSvc("THistSvc/THistSvc", name) {}
+
+
+StatusCode MyClustersAlg::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+
+  ATH_CHECK(m_clusterContainerKey.initialize());
+
+  m_tree = new TTree("tree", "tree");
+  m_tree->Branch("run", &m_run, "run/I");
+  m_tree->Branch("event", &m_event, "event/I");
+  m_tree->Branch("station", &m_station, "station/I");
+  m_tree->Branch("layer", &m_layer, "layer/I");
+  m_tree->Branch("phi", &m_phi, "phi/I");
+  m_tree->Branch("eta", &m_eta, "eta/I");
+  m_tree->Branch("side", &m_side, "side/I");
+  m_tree->Branch("x", &m_x, "x/D");
+  m_tree->Branch("y", &m_y, "y/D");
+  m_tree->Branch("z", &m_z, "z/D");
+  m_tree->Branch("lx", &m_lx, "lx/D");
+  m_tree->Branch("ly", &m_ly, "ly/D");
+  ATH_CHECK(histSvc()->regTree("/HIST/clusters", m_tree));
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MyClustersAlg::execute(const EventContext& ctx) const {
+  m_run = ctx.eventID().run_number();
+  m_event = ctx.eventID().event_number();
+
+  SG::ReadHandle<Tracker::FaserSCT_ClusterContainer> clusterContainer {m_clusterContainerKey, ctx};
+  ATH_CHECK(clusterContainer.isValid());
+
+  for (const auto& clusterCollection : *clusterContainer) {
+    for (const auto& cluster : *clusterCollection) {
+      auto id = cluster->identify();
+      m_station = m_idHelper->station(id);
+      m_layer = m_idHelper->layer(id);
+      m_eta = m_idHelper->eta_module(id);
+      m_phi = m_idHelper->phi_module(id);
+      m_side = m_idHelper->side(id);
+      auto localPos = cluster->localPosition();
+      m_lx = localPos.x();
+      m_ly = localPos.y();
+      auto localCov = cluster->localCovariance();
+      auto pos = cluster->globalPosition();
+      m_x = pos.x();
+      m_y = pos.y();
+      m_z = pos.z();
+      m_tree->Fill();
+      ATH_MSG_DEBUG("global position\n" << pos);
+      ATH_MSG_DEBUG("local position\n" << localPos);
+      ATH_MSG_DEBUG("local covariance\n" << localCov);
+    }
+  }
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MyClustersAlg::finalize() {
+  return StatusCode::SUCCESS;
+}
diff --git a/Tracker/TrackerRecAlgs/MyClusters/src/MyClustersAlg.h b/Tracker/TrackerRecAlgs/MyClusters/src/MyClustersAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..04c6202a7e7f36a886a25d854ef31fae2e555946
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyClusters/src/MyClustersAlg.h
@@ -0,0 +1,53 @@
+/*
+  Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+*/
+
+#ifndef MYCLUSTERS_MYCLUSTERSALG_H
+#define MYCLUSTERS_MYCLUSTERSALG_H
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthHistogramming.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+
+class TTree;
+class FaserSCT_ID;
+
+class MyClustersAlg : public AthReentrantAlgorithm, AthHistogramming {
+public:
+  MyClustersAlg(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~MyClustersAlg() = default;
+
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext& ctx) const override;
+  virtual StatusCode finalize() override;
+
+  const ServiceHandle<ITHistSvc>& histSvc() const;
+
+private:
+  SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainerKey {
+      this, "FaserSCT_ClusterContainer", "SCT_ClusterContainer", "cluster container"};
+  ServiceHandle<ITHistSvc> m_histSvc;
+  const FaserSCT_ID* m_idHelper {nullptr};
+  mutable TTree* m_tree;
+
+  mutable unsigned int m_run;
+  mutable unsigned int m_event;
+  mutable unsigned int m_station;
+  mutable unsigned int m_layer;
+  mutable int m_eta;
+  mutable int m_phi;
+  mutable int m_side;
+  mutable double m_lx;
+  mutable double m_ly;
+  mutable double m_x;
+  mutable double m_y;
+  mutable double m_z;
+};
+
+
+inline const ServiceHandle<ITHistSvc>& MyClustersAlg::histSvc() const {
+  return m_histSvc;
+}
+
+
+#endif  // MYCLUSTERS_MYCLUSTERSALG_H
diff --git a/Tracker/TrackerRecAlgs/MyClusters/src/components/MyClusters_entries.cxx b/Tracker/TrackerRecAlgs/MyClusters/src/components/MyClusters_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..93cf0f53b350ced62ddc1c00a401a082fbb68f2a
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyClusters/src/components/MyClusters_entries.cxx
@@ -0,0 +1,3 @@
+#include "../MyClustersAlg.h"
+
+DECLARE_COMPONENT(MyClustersAlg)
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/MyClusters/test/MyClustersDbg.py b/Tracker/TrackerRecAlgs/MyClusters/test/MyClustersDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4ae4953adbe00bf94bcb2933d20bff32fcc107b
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyClusters/test/MyClustersDbg.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from MyClusters.MyClustersConfig import MyClustersCfg
+
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+# Configure
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "MyClusters.ESD.root"
+ConfigFlags.Output.AODFileName = "MyClsuters.AOD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(MyClustersCfg(ConfigFlags))
+acc.getEventAlgo("MyClustersAlg").OutputLevel = DEBUG
+
+sc = acc.run(maxEvents=10)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/MyExtrapolationExample/CMakeLists.txt b/Tracker/TrackerRecAlgs/MyExtrapolationExample/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..72f4a7ac272d63a1060b39cb89a222fb4fc4e026
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyExtrapolationExample/CMakeLists.txt
@@ -0,0 +1,12 @@
+atlas_subdir(MyExtrapolationExample)
+
+atlas_add_component(
+        MyExtrapolationExample
+        src/MyExtrapolationExample.h
+        src/MyExtrapolationExample.cxx
+        src/components/MyExtrapolationExample_entries.cxx
+        LINK_LIBRARIES AthenaBaseComps StoreGateLib GeneratorObjects FaserActsGeometryLib TrackerSimEvent TrackerIdentifier TrackerReadoutGeometry
+)
+
+atlas_install_python_modules(python/*.py)
+atlas_install_scripts(test/*.py)
diff --git a/Tracker/TrackerRecAlgs/MyExtrapolationExample/python/MyExtrapolationExampleConfig.py b/Tracker/TrackerRecAlgs/MyExtrapolationExample/python/MyExtrapolationExampleConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..d87ae7f5973b3f69dc4206fbb0cc5830f64344ae
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyExtrapolationExample/python/MyExtrapolationExampleConfig.py
@@ -0,0 +1,34 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
+
+# def FaserActsTrackingGeometrySvcCfg(flags, **kwargs):
+#     acc = ComponentAccumulator()
+#     FaserActsTrackingGeometrySvc = CompFactory.FaserActsTrackingGeometrySvc
+#     acc.addService(FaserActsTrackingGeometrySvc(name="FaserActsTrackingGeometrySvc", **kwargs))
+#     return acc
+
+# def FaserActsAlignmentCondAlgCfg(flags, **kwargs):
+#     acc = ComponentAccumulator()
+#     acc.addCondAlgo(CompFactory.FaserActsAlignmentCondAlg(name="FaserActsAlignmentCondAlg", **kwargs))
+#     return acc
+
+def MyExtrapolationExampleCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    acc.merge(MagneticFieldSvcCfg(flags))
+    # acc.merge(FaserActsTrackingGeometrySvcCfg(flags))
+    # acc.merge(FaserActsAlignmentCondAlgCfg(flags))
+
+    actsExtrapolationTool = CompFactory.FaserActsExtrapolationTool("FaserActsExtrapolationTool")
+    actsExtrapolationTool.MaxSteps = 1000
+    actsExtrapolationTool.TrackingGeometryTool = CompFactory.FaserActsTrackingGeometryTool("TrackingGeometryTool")
+
+    my_extrapolation_example = CompFactory.Tracker.MyExtrapolationExample(**kwargs)
+    my_extrapolation_example.ExtrapolationTool = actsExtrapolationTool
+    acc.addEventAlgo(my_extrapolation_example)
+    return acc
diff --git a/Tracker/TrackerRecAlgs/MyExtrapolationExample/python/__init__.py b/Tracker/TrackerRecAlgs/MyExtrapolationExample/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/MyExtrapolationExample.cxx b/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/MyExtrapolationExample.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..aecebc372e415c6c58f1464706c6f25ea6001dc2
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/MyExtrapolationExample.cxx
@@ -0,0 +1,114 @@
+#include "MyExtrapolationExample.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "GeoPrimitives/CLHEPtoEigenConverter.h"
+#include "Acts/Surfaces/PerigeeSurface.hpp"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include <cmath>
+
+
+namespace Tracker {
+
+MyExtrapolationExample::MyExtrapolationExample(const std::string &name, ISvcLocator *pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator) {}
+
+StatusCode MyExtrapolationExample::initialize() {
+  ATH_CHECK(m_mcEventCollectionKey.initialize());
+  ATH_CHECK(m_faserSiHitKey.initialize());
+  ATH_CHECK(m_extrapolationTool.retrieve());
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detMgr, "SCT"));
+  return StatusCode::SUCCESS;
+}
+
+StatusCode MyExtrapolationExample::execute(const EventContext &ctx) const {
+  const Acts::GeometryContext gctx =
+      m_extrapolationTool->trackingGeometryTool()->getNominalGeometryContext().context();
+
+  std::vector<double> z_positions {};
+  SG::ReadHandle<FaserSiHitCollection> siHitCollection {m_faserSiHitKey, ctx};
+      ATH_CHECK(siHitCollection.isValid());
+  for (const FaserSiHit& hit : *siHitCollection) {
+    if ((hit.getStation() == 1) && (hit.getPlane() == 0) && (hit.getSensor() == 0)) {
+      if (hit.particleLink()) {
+        if (std::abs(hit.particleLink()->pdg_id()) != 13)
+          continue;
+        Identifier id = m_idHelper->wafer_id(hit.getStation(), hit.getPlane(), hit.getRow(), hit.getModule(), hit.getSensor());
+        const TrackerDD::SiDetectorElement* element = m_detMgr->getDetectorElement(id);
+        const HepGeom::Point3D<double> globalStartPosition =
+            Amg::EigenTransformToCLHEP(element->transformHit()) * hit.localStartPosition();
+        z_positions.push_back(globalStartPosition.z());
+        ATH_MSG_DEBUG("SiHit: " << globalStartPosition.x() << ", " << globalStartPosition.y() << ", " << globalStartPosition.z());
+      }
+    }
+  }
+
+  double z_mean = 0;
+  for (double z : z_positions) {
+    z_mean += z;
+  }
+  z_mean /= z_positions.size();
+
+  SG::ReadHandle<McEventCollection> mcEvents {m_mcEventCollectionKey, ctx};
+      ATH_CHECK(mcEvents.isValid());
+  if (mcEvents->size() != 1) {
+    ATH_MSG_ERROR("There should be exactly one event in the McEventCollection.");
+    return StatusCode::FAILURE;
+  }
+
+
+  for (const HepMC::GenParticle* particle : mcEvents->front()->particle_range()) {
+    if ((std::abs(particle->pdg_id()) != 13)) {
+      continue;
+    }
+    const HepMC::FourVector& vertex = particle->production_vertex()->position();
+    if (vertex.z() > 0) {
+      continue;
+    }
+    const HepMC::FourVector& momentum = particle->momentum();
+    double phi = momentum.phi();
+    double theta = momentum.theta();
+    double charge = particle->pdg_id() > 0 ? -1 : 1;
+    double abs_momentum = momentum.rho() * m_MeV2GeV;
+    double qop = charge / abs_momentum;
+    // The coordinate system of the Acts::PlaneSurface is defined as
+    // T = Z = normal, U = X x T = -Y, V = T x U = x
+//    Acts::BoundVector pars;
+//    pars << -vertex.y(), vertex.x(), phi, theta, qop, vertex.t();
+    Acts::BoundVector pars = Acts::BoundVector::Zero();
+    pars[Acts::eBoundLoc0] = -vertex.y();
+    pars[Acts::eBoundLoc1] = vertex.x();
+    pars[Acts::eBoundPhi] = phi;
+    pars[Acts::eBoundTheta] = theta;
+    pars[Acts::eBoundQOverP] = qop;
+    pars[Acts::eBoundTime] = vertex.t();
+
+    auto startSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+        Acts::Vector3(0, 0, vertex.z()), Acts::Vector3(0, 0, 1));
+    auto targetSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+        Acts::Vector3(0, 0, z_mean), Acts::Vector3(0, 0, 1));
+    Acts::BoundTrackParameters startParameters(
+        std::move(startSurface), pars, charge);
+    ATH_MSG_DEBUG("vertex: " << vertex.x() << ", " << vertex.y() << ", " << vertex.z());
+    ATH_MSG_DEBUG("vertex momentum: " << momentum.x() * m_MeV2GeV << ", " << momentum.y() * m_MeV2GeV << ", " << momentum.z() * m_MeV2GeV);
+    std::unique_ptr<const Acts::BoundTrackParameters> targetParameters =
+        m_extrapolationTool->propagate(ctx, startParameters, *targetSurface);
+    if (targetParameters) {
+      Acts::Vector3 targetPosition = targetParameters->position(gctx);
+      Acts::Vector3 targetMomentum = targetParameters->momentum();
+      ATH_MSG_DEBUG("vertex: " << vertex.x() << ", " << vertex.y() << ", " << vertex.z());
+      ATH_MSG_DEBUG("origin: " << targetPosition.x() << ", " << targetPosition.y() << ", " << targetPosition.z());
+      ATH_MSG_DEBUG("vertex momentum: " << momentum.x() * m_MeV2GeV << ", " << momentum.y() * m_MeV2GeV << ", " << momentum.z() * m_MeV2GeV);
+      ATH_MSG_DEBUG("origin momentum: " << targetMomentum.x() << ", " << targetMomentum.y() << ", " << targetMomentum.z());
+    }
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode MyExtrapolationExample::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+} // Tracker
diff --git a/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/MyExtrapolationExample.h b/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/MyExtrapolationExample.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd5a2ed077310d3a07e5edd41787033593642c18
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/MyExtrapolationExample.h
@@ -0,0 +1,42 @@
+/*
+Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+*/
+
+#ifndef MYEXTRAPOLATIONEXAMPLE_MYEXTRAPOLATIONEXAMPLE_H
+#define MYEXTRAPOLATIONEXAMPLE_MYEXTRAPOLATIONEXAMPLE_H
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "FaserActsGeometryInterfaces/IFaserActsExtrapolationTool.h"
+#include "GeneratorObjects/McEventCollection.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+
+class FaserSCT_ID;
+namespace  TrackerDD {
+class SCT_DetectorManager;
+
+}
+namespace Tracker {
+
+class MyExtrapolationExample : public AthReentrantAlgorithm {
+ public:
+  MyExtrapolationExample(const std::string& name, ISvcLocator* pSvcLocator);
+
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext& ctx) const override;
+  virtual StatusCode finalize() override;
+
+private:
+  double m_MeV2GeV = 1e-3;
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detMgr {nullptr};
+  SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey {
+      this, "McEventCollection", "TruthEvent"};
+  SG::ReadHandleKey <FaserSiHitCollection> m_faserSiHitKey {
+    this, "FaserSiHitCollection", "SCT_Hits"};
+  ToolHandle<IFaserActsExtrapolationTool> m_extrapolationTool {
+    this, "ExtrapolationTool", "FaserActsExtrapolationTool"};
+};
+
+} // namespace Tracker
+
+#endif // MYEXTRAPOLATIONEXAMPLE_MYEXTRAPOLATIONEXAMPLE_H
diff --git a/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/components/MyExtrapolationExample_entries.cxx b/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/components/MyExtrapolationExample_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..6601e0a3fe0e99d45ca137db49d8b63bb89573d1
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyExtrapolationExample/src/components/MyExtrapolationExample_entries.cxx
@@ -0,0 +1,3 @@
+#include "../MyExtrapolationExample.h"
+
+DECLARE_COMPONENT(Tracker::MyExtrapolationExample)
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/MyExtrapolationExample/test/MyExtrapolationExampleDbg.py b/Tracker/TrackerRecAlgs/MyExtrapolationExample/test/MyExtrapolationExampleDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe83560fa4fe694fb643bfd514cfd2d2c8df4c14
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/MyExtrapolationExample/test/MyExtrapolationExampleDbg.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG, VERBOSE
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from MyExtrapolationExample.MyExtrapolationExampleConfig import MyExtrapolationExampleCfg
+
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+# Configure
+ConfigFlags.Input.Files = ['my.HITS.pool.root']
+ConfigFlags.Output.ESDFileName = "MyExtrapolationExample.ESD.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(MyExtrapolationExampleCfg(ConfigFlags))
+acc.getEventAlgo("Tracker::MyExtrapolationExample").OutputLevel = VERBOSE
+
+sc = acc.run(maxEvents=10)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/NoisyStripFinder/python/NoisyStripFinderConfig.py b/Tracker/TrackerRecAlgs/NoisyStripFinder/python/NoisyStripFinderConfig.py
index 5e94a1fb0e944666f4f245e8f47bc1100fcd6929..b408cb0e0d8c255833b5e51d47dafe461012a48b 100644
--- a/Tracker/TrackerRecAlgs/NoisyStripFinder/python/NoisyStripFinderConfig.py
+++ b/Tracker/TrackerRecAlgs/NoisyStripFinder/python/NoisyStripFinderConfig.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 """
 
 from AthenaConfiguration.ComponentFactory import CompFactory
diff --git a/Tracker/TrackerRecAlgs/NoisyStripFinder/test/NoisyStripFinderDbg.py b/Tracker/TrackerRecAlgs/NoisyStripFinder/test/NoisyStripFinderDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3c0697cfcef6539190aeeebf26e16b4e0d3b100
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/NoisyStripFinder/test/NoisyStripFinderDbg.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+"""
+    Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from FaserByteStreamCnvSvc.FaserByteStreamCnvSvcConfig import FaserByteStreamCnvSvcCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from NoisyStripFinder.NoisyStripFinderConfig import NoisyStripFinderCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+run = 1792
+ConfigFlags.Input.Files = [f"/home/tboeckh/Documents/data/TI12/Faser-Physics-00{run}-00000.raw"]
+ConfigFlags.Output.ESDFileName = f"run00{run}-00000.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data21"
+ConfigFlags.Input.isMC = False
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolWriteCfg(ConfigFlags))
+acc.merge(FaserByteStreamCnvSvcCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(
+    ConfigFlags,
+    name="LevelClustering",
+    DataObjectName="SCT_LEVELMODE_RDOs",
+    ClusterToolTimingPattern="X1X"))
+acc.merge(NoisyStripFinderCfg(ConfigFlags))
+
+# Hack to avoid problem with our use of MC databases when isMC = False
+replicaSvc = acc.getService("DBReplicaSvc")
+replicaSvc.COOLSQLiteVetoPattern = ""
+replicaSvc.UseCOOLSQLite = True
+replicaSvc.UseCOOLFrontier = False
+replicaSvc.UseGeomSQLite = True
+
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/TrackCounts/python/TrackCountsConfig.py b/Tracker/TrackerRecAlgs/TrackCounts/python/TrackCountsConfig.py
index 934579044bcf3cd9d731f4199ed374cda045aebf..da136d0381b211477497550e2072cae6fec029d9 100644
--- a/Tracker/TrackerRecAlgs/TrackCounts/python/TrackCountsConfig.py
+++ b/Tracker/TrackerRecAlgs/TrackCounts/python/TrackCountsConfig.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 """
 
 from AthenaConfiguration.ComponentFactory import CompFactory
diff --git a/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/CMakeLists.txt b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f712d3881883fb0cdff9e8186b5b36578d48bf62
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/CMakeLists.txt
@@ -0,0 +1,12 @@
+atlas_subdir(TrackSeedPerformanceWriter)
+
+atlas_add_component(
+        TrackSeedPerformanceWriter
+        src/TrackSeedPerformanceWriter.h
+        src/TrackSeedPerformanceWriter.cxx
+        src/components/TrackSeedPerformanceWriter_entries.cxx
+        LINK_LIBRARIES AthenaBaseComps StoreGateLib TrkTrack TrackerSimData TrackerPrepRawData TrkRIO_OnTrack TrackerRIO_OnTrack
+)
+
+atlas_install_python_modules(python/*.py)
+atlas_install_scripts(test/*.py)
diff --git a/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/python/TrackSeedPerformanceWriterConfig.py b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/python/TrackSeedPerformanceWriterConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e064cf0c8a8247426d4f7d613d33e6b8c88626e
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/python/TrackSeedPerformanceWriterConfig.py
@@ -0,0 +1,30 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+
+
+
+def TrackSeedPerformanceWriterCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    kwargs.setdefault("TrackCollection", "SegmentFit")
+    TrackSeedPerformanceWriter = CompFactory.Tracker.TrackSeedPerformanceWriter
+    acc.addEventAlgo(TrackSeedPerformanceWriter(**kwargs))
+
+    itemList = ["xAOD::EventInfo#*",
+                "xAOD::EventAuxInfo#*",
+                "xAOD::FaserTriggerData#*",
+                "xAOD::FaserTriggerDataAux#*",
+                "FaserSCT_RDO_Container#*",
+                "Tracker::FaserSCT_ClusterContainer#*",
+                "TrackCollection#*"
+                ]
+    acc.merge(OutputStreamCfg(flags, "ESD", itemList))
+
+    thistSvc = CompFactory.THistSvc()
+    thistSvc.Output += ["HIST1 DATAFILE='TrackSeedPerformanceWriter.root' OPT='RECREATE'"]
+    acc.addService(thistSvc)
+    return acc
diff --git a/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/python/__init__.py b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/TrackSeedPerformanceWriter.cxx b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/TrackSeedPerformanceWriter.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..cc0df45c1115acf86eab441652e420953aef8280
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/TrackSeedPerformanceWriter.cxx
@@ -0,0 +1,124 @@
+#include "TrackSeedPerformanceWriter.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+
+
+namespace Tracker {
+
+  TrackSeedPerformanceWriter::TrackSeedPerformanceWriter(const std::string &name, ISvcLocator *pSvcLocator)
+      : AthReentrantAlgorithm(name, pSvcLocator), AthHistogramming(name),
+        m_histSvc("THistSvc/THistSvc", name), m_idHelper(nullptr){}
+
+
+  StatusCode TrackSeedPerformanceWriter::initialize() {
+    ATH_CHECK(m_trackCollectionKey.initialize());
+    ATH_CHECK(m_simDataCollectionKey.initialize());
+    ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+
+    m_tree = new TTree("tree", "tree");
+    m_tree->Branch("run", &m_run, "run/I");
+    m_tree->Branch("event", &m_event, "event/I");
+    m_tree->Branch("station", &m_station, "station/I");
+    m_tree->Branch("chi2", &m_chi2, "chi2/D");
+    m_tree->Branch("dof", &m_dof, "dof/I");
+    m_tree->Branch("nHits", &m_nHits, "nHits/I");
+    m_tree->Branch("x", &m_x, "x/D");
+    m_tree->Branch("y", &m_y, "y/D");
+    m_tree->Branch("z", &m_z, "z/D");
+    m_tree->Branch("px", &m_px, "px/D");
+    m_tree->Branch("py", &m_py, "py/D");
+    m_tree->Branch("pz", &m_pz, "pz/D");
+    // m_tree->Branch("barcode", &m_barcode, "barcode/I");
+    m_tree->Branch("barcodes", &m_barcodes);
+    m_tree->Branch("nMajorityHits", &m_nMajorityHits, "nMajorityHits/I");
+    m_tree->Branch("nMajorityParticle", &m_majorityParticle, "nMajorityParticle/I");
+    // m_tree->Branch("run", &m_run);
+    // m_tree->Branch("event", &m_event);
+    // m_tree->Branch("station", &m_station);
+    // m_tree->Branch("barcodes", &m_barcodes);
+    ATH_CHECK(histSvc()->regTree("/HIST1/TrackSeedPerformance", m_tree));
+
+    return StatusCode::SUCCESS;
+  }
+
+
+  StatusCode TrackSeedPerformanceWriter::execute(const EventContext &ctx) const {
+    m_run = ctx.eventID().run_number();
+    m_event = ctx.eventID().event_number();
+
+    SG::ReadHandle<TrackCollection> trackCollection{m_trackCollectionKey, ctx};
+    ATH_CHECK(trackCollection.isValid());
+
+    SG::ReadHandle<TrackerSimDataCollection> simDataCollection {m_simDataCollectionKey, ctx};
+    ATH_CHECK(simDataCollection.isValid());
+
+    for (const Trk::Track *track: *trackCollection) {
+      m_chi2 = track->fitQuality()->chiSquared();
+      m_dof = track->fitQuality()->numberDoF();
+      const Amg::Vector3D trackPosition = track->trackParameters()->front()->position();
+      const Amg::Vector3D trackMomentum = track->trackParameters()->front()->momentum();
+      m_x = trackPosition.x();
+      m_y = trackPosition.y();
+      m_z = trackPosition.z();
+      m_px = trackMomentum.x();
+      m_py = trackMomentum.y();
+      m_pz = trackMomentum.z();
+      m_nHits = track->measurementsOnTrack()->size();
+      m_barcodes = {};
+      m_hitCounts = {};
+      for (const auto meas: *track->measurementsOnTrack()) {
+        const auto *clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack *>(meas);
+        if (clusterOnTrack) {
+          const Tracker::FaserSCT_Cluster *cluster = clusterOnTrack->prepRawData();
+          Identifier id = cluster->identify();
+          m_barcode = matchHit(id, simDataCollection.get());
+          // fill hit map
+          if (m_hitCounts.count(m_barcode) > 0) {
+            m_hitCounts[m_barcode] += 1;
+          } else {
+            m_hitCounts[m_barcode] = 1;
+          }
+          m_barcodes.push_back(m_barcode);
+          m_station = m_idHelper->station(id);
+        }
+      }
+
+      // find majority particle
+      m_nMajorityHits = 0;
+      m_majorityParticle = 0;
+      for (const auto& hit : m_hitCounts) {
+        if (hit.second > m_nMajorityHits) {
+          m_majorityParticle = hit.first;
+          m_nMajorityHits = hit.second;
+        }
+      }
+      m_tree->Fill();
+    }
+
+    return StatusCode::SUCCESS;
+  }
+
+
+  StatusCode TrackSeedPerformanceWriter::finalize() {
+    return StatusCode::SUCCESS;
+  }
+
+
+  int TrackSeedPerformanceWriter::matchHit(
+      Identifier id, const TrackerSimDataCollection *simDataCollection) const {
+    int barcode = 0;
+    if (simDataCollection->count(id) != 0) {
+      const auto& deposits = simDataCollection->find(id)->second.getdeposits();
+      float highestDep = 0;
+      for (const TrackerSimData::Deposit &deposit : deposits) {
+        if (deposit.second > highestDep) {
+          highestDep = deposit.second;
+          barcode = deposit.first->barcode();
+        }
+      }
+    }
+    return barcode;
+  }
+
+}  // namespace Tracker
diff --git a/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/TrackSeedPerformanceWriter.h b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/TrackSeedPerformanceWriter.h
new file mode 100644
index 0000000000000000000000000000000000000000..86192d77c69193ee86b41a2123e42819d27051cc
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/TrackSeedPerformanceWriter.h
@@ -0,0 +1,61 @@
+#ifndef FASERACTSKALMANFILTER_TRACKSEEDPERFORMANCEWRITER_H
+#define FASERACTSKALMANFILTER_TRACKSEEDPERFORMANCEWRITER_H
+
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthHistogramming.h"
+#include "TrkTrack/TrackCollection.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+
+class TTree;
+class FaserSCT_ID;
+
+
+namespace Tracker {
+  class TrackSeedPerformanceWriter : public AthReentrantAlgorithm, AthHistogramming {
+  public:
+    TrackSeedPerformanceWriter(const std::string &name, ISvcLocator *pSvcLocator);
+    virtual ~TrackSeedPerformanceWriter() = default;
+    virtual StatusCode initialize() override;
+    virtual StatusCode execute(const EventContext &ctx) const override;
+    virtual StatusCode finalize() override;
+    const ServiceHandle<ITHistSvc> &histSvc() const;
+
+  private:
+    int matchHit(Identifier id, const TrackerSimDataCollection *simDataCollection) const;
+    SG::ReadHandleKey<TrackCollection> m_trackCollectionKey {
+        this, "TrackCollection", "SegmentFit", "Input track collection name"};
+    SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey {
+        this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+    ServiceHandle<ITHistSvc> m_histSvc;
+    const FaserSCT_ID *m_idHelper;
+    mutable TTree *m_tree;
+
+    mutable unsigned int m_run;
+    mutable unsigned int m_event;
+    mutable unsigned int m_station;
+    mutable double m_chi2;
+    mutable int m_dof;
+    mutable int m_nHits;
+    mutable double m_x;
+    mutable double m_y;
+    mutable double m_z;
+    mutable double m_px;
+    mutable double m_py;
+    mutable double m_pz;
+    mutable int m_barcode;
+    mutable int m_majorityParticle;
+    mutable int m_nMajorityHits;
+    mutable std::vector<int> m_barcodes;
+    mutable std::map<int, int> m_hitCounts;
+  };
+
+
+  inline const ServiceHandle<ITHistSvc> &TrackSeedPerformanceWriter::histSvc() const {
+    return m_histSvc;
+  }
+
+}  // namespace Tracker
+
+
+#endif // FASERACTSKALMANFILTER_TRACKSEEDPERFORMANCEWRITER_H
diff --git a/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/components/TrackSeedPerformanceWriter_entries.cxx b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/components/TrackSeedPerformanceWriter_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9e569f003a45ef87af33da385a561d3b6a77c95a
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/src/components/TrackSeedPerformanceWriter_entries.cxx
@@ -0,0 +1,3 @@
+#include "../TrackSeedPerformanceWriter.h"
+
+DECLARE_COMPONENT(Tracker::TrackSeedPerformanceWriter)
diff --git a/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/test/TrackSeedPerformanceWriterDbg.py b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/test/TrackSeedPerformanceWriterDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..b00b9a623561d8b7ef3b57bc0f7bca7e824699c0
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackSeedPerformanceWriter/test/TrackSeedPerformanceWriterDbg.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+"""
+Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from AthenaConfiguration.TestDefaults import defaultTestFiles
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from TrackSeedPerformanceWriter.TrackSeedPerformanceWriterConfig import TrackSeedPerformanceWriterCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "seeds.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data21"
+ConfigFlags.Input.isMC = True
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, MaxClusters=20, TanThetaCut=0.1))
+acc.merge(TrackSeedPerformanceWriterCfg(ConfigFlags))
+#acc.getEventAlgo("Tracker::SegmentFitAlg").OutputLevel = DEBUG
+acc.getEventAlgo("Tracker::TrackSeedPerformanceWriter").OutputLevel = DEBUG
+
+sc = acc.run(maxEvents=1000)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/TrackerClusterFit/python/TrackerClusterFitConfig.py b/Tracker/TrackerRecAlgs/TrackerClusterFit/python/TrackerClusterFitConfig.py
index 19fb699dae47f023b5a20665461f2cfe3a3510d6..61d1927154c731009a78be77a8c3b2431ae00871 100644
--- a/Tracker/TrackerRecAlgs/TrackerClusterFit/python/TrackerClusterFitConfig.py
+++ b/Tracker/TrackerRecAlgs/TrackerClusterFit/python/TrackerClusterFitConfig.py
@@ -1,6 +1,5 @@
 """Define methods to construct configured SCT Cluster Fit tools and algorithms
-
-Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 """
 from AthenaConfiguration.ComponentFactory import CompFactory
 from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
diff --git a/Tracker/TrackerRecAlgs/TrackerData/CMakeLists.txt b/Tracker/TrackerRecAlgs/TrackerData/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..62e3b8e58115a157382c1e6b434e35ad0b4cb842
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/CMakeLists.txt
@@ -0,0 +1,15 @@
+atlas_subdir(TrackerData)
+
+atlas_add_component(
+  TrackerData
+  src/TrackerSegmentFitDataAlg.h
+  src/TrackerSegmentFitDataAlg.cxx
+  src/TrackerTruthDataAlg.h
+  src/TrackerTruthDataAlg.cxx
+  src/components/TrackerData_entries.cxx
+  LINK_LIBRARIES AthenaBaseComps GeneratorObjects TrackerSimEvent TrackerIdentifier
+    TrackerReadoutGeometry TrackerRIO_OnTrack TrackerPrepRawData TrkTrack
+)
+
+atlas_install_python_modules(python/*.py)
+atlas_install_scripts(test/*.py)
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/TrackerData/README b/Tracker/TrackerRecAlgs/TrackerData/README
new file mode 100644
index 0000000000000000000000000000000000000000..5c7cd869e46e21b3f82e68d2d55811f8d5b9b0d9
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/README
@@ -0,0 +1,9 @@
+This package can be used to write out the data from simulated hits and from
+tracks reconstructed with the TrackerSegmentFit algorithm.
+
+To write out information for the simulated hits run G4FaserAlgConfigNew_Test.py
+to simulate hits and TrackerTruthDbg.py to write out the information.
+
+To write out information for the tracks run G4FaserAlgConfigNew_Test.py to
+simulate hits, FaserSCT_DigitizationDbg.py to digitize them and
+TrackerSegmentFitDbg.py to write out the information.
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/TrackerData/python/TrackerSegmentFitDataConfig.py b/Tracker/TrackerRecAlgs/TrackerData/python/TrackerSegmentFitDataConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb46c5d7a8b748375ffcdca1178b2aa8ef3bdd3e
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/python/TrackerSegmentFitDataConfig.py
@@ -0,0 +1,19 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+
+
+def TrackerSegmentFitDataCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    kwargs.setdefault("TrackCollection", "SegmentFit")
+    TrackerSegmentFitDataAlg = CompFactory.Tracker.TrackerSegmentFitDataAlg
+    acc.addEventAlgo(TrackerSegmentFitDataAlg(**kwargs))
+
+    thistSvc = CompFactory.THistSvc()
+    thistSvc.Output += ["HIST2 DATAFILE='TrackerSegmentFitData.root' OPT='RECREATE'"]
+    acc.addService(thistSvc)
+
+    return acc
diff --git a/Tracker/TrackerRecAlgs/TrackerData/python/TrackerTruthDataConfig.py b/Tracker/TrackerRecAlgs/TrackerData/python/TrackerTruthDataConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..206fda5f90eebb94b06f61b2176938daee08c3b6
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/python/TrackerTruthDataConfig.py
@@ -0,0 +1,19 @@
+"""
+Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+"""
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+
+
+def TrackerTruthDataCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    kwargs.setdefault("FaserSiHitCollection", "SCT_Hits")
+    TrackerTruthDataAlg = CompFactory.Tracker.TrackerTruthDataAlg
+    acc.addEventAlgo(TrackerTruthDataAlg(**kwargs))
+
+    thistSvc = CompFactory.THistSvc()
+    thistSvc.Output += ["HIST1 DATAFILE='TrackerTruthData.root' OPT='RECREATE'"]
+    acc.addService(thistSvc)
+
+    return acc
diff --git a/Tracker/TrackerRecAlgs/TrackerData/python/__init__.py b/Tracker/TrackerRecAlgs/TrackerData/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tracker/TrackerRecAlgs/TrackerData/src/TrackerSegmentFitDataAlg.cxx b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerSegmentFitDataAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d6666a34b3ffddd84d8f215ff5f895439862504b
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerSegmentFitDataAlg.cxx
@@ -0,0 +1,107 @@
+#include "TrackerSegmentFitDataAlg.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include "TrkTrack/Track.h"
+#include "TrkTrack/TrackStateOnSurface.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include <TTree.h>
+
+
+namespace Tracker {
+
+TrackerSegmentFitDataAlg::TrackerSegmentFitDataAlg(const std::string &name, ISvcLocator *pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator), AthHistogramming(name),
+      m_histSvc("THistSvc/THistSvc", name), m_detMgr(nullptr), m_idHelper(nullptr) {}
+
+
+StatusCode TrackerSegmentFitDataAlg::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_detMgr, "SCT"));
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(m_trackCollection.initialize());
+
+  m_tracks = new TTree("tracks", "tracks");
+  m_tracks->Branch("run_number", &m_run_number, "run_number/I");
+  m_tracks->Branch("event_number", &m_event_number, "event_number/I");
+  m_tracks->Branch("chi2", &m_chi2, "chi2/I");
+  m_tracks->Branch("dof", &m_nDOF, "dof/I");
+  m_tracks->Branch("number_clusters", &m_nClusters, "clusters/I");
+  m_tracks->Branch("x", &m_track_x, "x/D");
+  m_tracks->Branch("y", &m_track_y, "y/D");
+  m_tracks->Branch("z", &m_track_z, "z/D");
+  m_tracks->Branch("px", &m_track_px, "px/D");
+  m_tracks->Branch("py", &m_track_py, "py/D");
+  m_tracks->Branch("pz", &m_track_pz, "pz/D");
+  ATH_CHECK(histSvc()->regTree("/HIST2/tracks", m_tracks));
+
+  m_hits = new TTree("hits", "hits");
+  m_hits->Branch("run_number", &m_run_number, "run_number/I");
+  m_hits->Branch("event_number", &m_event_number, "event_number/I");
+  m_hits->Branch("station", &m_station, "station/I");
+  m_hits->Branch("plane", &m_plane, "plane/I");
+  m_hits->Branch("module", &m_module, "module/I");
+  m_hits->Branch("row", &m_row, "row/I");
+  m_hits->Branch("sensor", &m_sensor, "sensor/I");
+  m_hits->Branch("x", &m_x, "x/D");
+  m_hits->Branch("y", &m_y, "y/D");
+  m_hits->Branch("z", &m_z, "z/D");
+  ATH_CHECK(histSvc()->regTree("/HIST2/hits", m_hits));
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode TrackerSegmentFitDataAlg::execute(const EventContext &ctx) const {
+
+  m_run_number = ctx.eventID().run_number();
+  m_event_number = ctx.eventID().event_number();
+
+  // Fill tree with clusters from tracker segment fit
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection, ctx};
+  ATH_CHECK(trackCollection.isValid());
+
+  for (const Trk::Track* track : *trackCollection) {
+    m_chi2 = track->fitQuality()->chiSquared();
+    m_nDOF = track->fitQuality()->numberDoF();
+    m_nClusters = track->trackParameters()->size() - 1;
+    auto fitParameters = track->trackParameters()->front();
+    const Amg::Vector3D fitPosition = fitParameters->position();
+    const Amg::Vector3D fitMomentum = fitParameters->momentum();
+    m_track_x = fitPosition.x();
+    m_track_y = fitPosition.y();
+    m_track_z = fitPosition.z();
+    m_track_px = fitMomentum.x();
+    m_track_py = fitMomentum.y();
+    m_track_pz = fitMomentum.z();
+    m_tracks->Fill();
+
+    for (const Trk::TrackStateOnSurface* trackState : *track->trackStateOnSurfaces()) {
+      const auto* clusterOnTrack =
+          dynamic_cast<const FaserSCT_ClusterOnTrack*>(trackState->measurementOnTrack());
+      if (!clusterOnTrack)
+        continue;
+      const FaserSCT_Cluster* cluster = clusterOnTrack->prepRawData();
+      Identifier id = cluster->identify();
+      m_station = m_idHelper->station(id);
+      m_plane = m_idHelper->layer(id);
+      m_module = m_idHelper->eta_module(id);
+      m_row = m_idHelper->phi_module(id);
+      m_sensor = m_idHelper->side(id);
+      m_strip = m_idHelper->strip(id);
+      Amg::Vector3D position = cluster->globalPosition();
+      m_x = position.x();
+      m_y = position.y();
+      m_z = position.z();
+      m_hits->Fill();
+    }
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TrackerSegmentFitDataAlg::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+}  // namespace Tracker
diff --git a/Tracker/TrackerRecAlgs/TrackerData/src/TrackerSegmentFitDataAlg.h b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerSegmentFitDataAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..08ce4dd73290eadadc12c95a24932c5e52a781ea
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerSegmentFitDataAlg.h
@@ -0,0 +1,73 @@
+/*
+  Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+*/
+
+#ifndef TRACKERDATA_TRACKERSEGMENTFITDATAALG_H
+#define TRACKERDATA_TRACKERSEGMENTFITDATAALG_H
+
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthHistogramming.h"
+#include "TrkTrack/TrackCollection.h"
+
+class TTree;
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+
+namespace Tracker {
+
+class TrackerSegmentFitDataAlg : public AthReentrantAlgorithm, AthHistogramming {
+public:
+  TrackerSegmentFitDataAlg(const std::string &name, ISvcLocator *pSvcLocator);
+  virtual ~TrackerSegmentFitDataAlg() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext &ctx) const override;
+  virtual StatusCode finalize() override;
+  const ServiceHandle <ITHistSvc> &histSvc() const;
+
+private:
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {this, "TrackCollection", "SegmentFit", "Input track collection name"};
+
+  ServiceHandle <ITHistSvc> m_histSvc;
+
+  const TrackerDD::SCT_DetectorManager *m_detMgr;
+  const FaserSCT_ID *m_idHelper;
+
+  mutable TTree* m_tracks;
+  mutable TTree* m_hits;
+
+  mutable unsigned int m_run_number;
+  mutable unsigned int m_event_number;
+
+  mutable double m_chi2;
+  mutable unsigned int m_nDOF;
+  mutable unsigned int m_nClusters;
+  mutable double m_track_x;
+  mutable double m_track_y;
+  mutable double m_track_z;
+  mutable double m_track_px;
+  mutable double m_track_py;
+  mutable double m_track_pz;
+
+  mutable unsigned int m_station;
+  mutable unsigned int m_plane;
+  mutable unsigned int m_module;
+  mutable unsigned int m_row;
+  mutable unsigned int m_sensor;
+  mutable unsigned int m_strip;
+  mutable double m_x;
+  mutable double m_y;
+  mutable double m_z;
+};
+
+
+inline const ServiceHandle <ITHistSvc> &TrackerSegmentFitDataAlg::histSvc() const {
+  return m_histSvc;
+}
+
+}  // namespace Tracker
+
+#endif  // TRACKERDATA_TRACKERSEGMENTFITDATAALG_H
diff --git a/Tracker/TrackerRecAlgs/TrackerData/src/TrackerTruthDataAlg.cxx b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerTruthDataAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0f3c8afe610ce69417d5acb2b1b0680b7f7c5ffc
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerTruthDataAlg.cxx
@@ -0,0 +1,123 @@
+#include "TrackerTruthDataAlg.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include <TTree.h>
+#include <cmath>
+
+
+namespace Tracker {
+
+TrackerTruthDataAlg::TrackerTruthDataAlg(const std::string &name, ISvcLocator *pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator), AthHistogramming(name),
+      m_histSvc("THistSvc/THistSvc", name), m_sct(nullptr), m_sID(nullptr) {}
+
+
+StatusCode TrackerTruthDataAlg::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_sct, "SCT"));
+  ATH_CHECK(detStore()->retrieve(m_sID, "FaserSCT_ID"));
+  ATH_CHECK(m_mcEventCollectionKey.initialize());
+  ATH_CHECK(m_faserSiHitKey.initialize());
+
+  m_hits = new TTree("hits", "hits");
+  m_hits->Branch("run_number", &m_run_number, "run_number/I");
+  m_hits->Branch("event_number", &m_event_number, "event_number/I");
+  m_hits->Branch("station", &m_station, "station/I");
+  m_hits->Branch("plane", &m_plane, "plane/I");
+  m_hits->Branch("module", &m_module, "module/I");
+  m_hits->Branch("row", &m_row, "row/I");
+  m_hits->Branch("sensor", &m_sensor, "sensor/I");
+  m_hits->Branch("pdg", &m_pdg, "pdg/I");
+  m_hits->Branch("local_x", &m_local_x, "local_x/D");
+  m_hits->Branch("local_y", &m_local_y, "local_y/D");
+  m_hits->Branch("global_x", &m_global_x, "global_x/D");
+  m_hits->Branch("global_y", &m_global_y, "global_y/D");
+  m_hits->Branch("global_z", &m_global_z, "global_z/D");
+  m_hits->Branch("phi", &m_phi, "phi/D");
+  m_hits->Branch("theta", &m_theta, "theta/D");
+  ATH_CHECK(histSvc()->regTree("/HIST1/hits", m_hits));
+
+  m_particles = new TTree("particles", "particles");
+  m_particles->Branch("run_number", &m_run_number, "run_number/I");
+  m_particles->Branch("event_number", &m_event_number, "event_number/I");
+  m_particles->Branch("pdg", &m_pdg, "pdg/I");
+  m_particles->Branch("x", &m_vertex_x, "x/D");
+  m_particles->Branch("y", &m_vertex_y, "y/D");
+  m_particles->Branch("z", &m_vertex_z, "z/D");
+  m_particles->Branch("px", &m_px, "px/D");
+  m_particles->Branch("py", &m_py, "py/D");
+  m_particles->Branch("pz", &m_pz, "pz/D");
+  ATH_CHECK(histSvc()->regTree("/HIST1/particles", m_particles));
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TrackerTruthDataAlg::execute(const EventContext &ctx) const {
+
+  m_run_number = ctx.eventID().run_number();
+  m_event_number = ctx.eventID().event_number();
+
+  // Fill tree with simulated particles
+  SG::ReadHandle<McEventCollection> mcEventCollection(m_mcEventCollectionKey, ctx);
+  ATH_CHECK(mcEventCollection.isValid());
+  for (const HepMC::GenEvent* event : *mcEventCollection) {
+    for (const HepMC::GenParticle* particle : event->particle_range()) {
+      m_pdg = particle->pdg_id();
+      const auto vertex = particle->production_vertex()->position();
+      m_vertex_x = vertex.x();
+      m_vertex_y = vertex.y();
+      m_vertex_z = vertex.z();
+      const HepMC::FourVector& momentum = particle->momentum();
+      m_px = momentum.x();
+      m_py = momentum.y();
+      m_pz = momentum.z();
+      m_particles->Fill();
+    }
+  }
+
+  // Fill tree with simulated hits
+  SG::ReadHandle<FaserSiHitCollection> siHitCollection(m_faserSiHitKey, ctx);
+  ATH_CHECK(siHitCollection.isValid());
+  for (const FaserSiHit &hit: *siHitCollection) {
+    if (!hit.particleLink().isValid())
+      continue;
+    m_station = hit.getStation();
+    m_plane = hit.getPlane();
+    m_module = hit.getModule();
+    m_row = hit.getRow();
+    m_sensor = hit.getSensor();
+    Identifier id = m_sID->wafer_id(m_station, m_plane, m_row, m_module, m_sensor);
+    const TrackerDD::SiDetectorElement *geoelement = m_sct->getDetectorElement(id);
+    if (!geoelement)
+      continue;
+    const auto localStartPos = hit.localStartPosition();
+    const HepGeom::Point3D<double> globalStartPos =
+        Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(localStartPos);
+    const auto localEndPos = hit.localEndPosition();
+    const HepGeom::Point3D<double> globalEndPos =
+        Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(localEndPos);
+    m_pdg = hit.particleLink()->pdg_id();
+    HepGeom::Vector3D<double> v = globalEndPos - globalStartPos;
+    m_phi = std::atan(v.y()/v.x());
+    m_theta = std::atan(std::sqrt(v.x() * v.x() + v.y() * v.y())/v.z());
+    ATH_MSG_DEBUG("local pos: " << localStartPos);
+    ATH_MSG_DEBUG("global pos: " << globalStartPos);
+    m_local_x = m_sensor == 0 ? -localStartPos.y() : localStartPos.y();
+    m_local_y = -localStartPos.z();
+    m_global_x = globalStartPos.x();
+    m_global_y = globalStartPos.y();
+    m_global_z = globalStartPos.z();
+    m_hits->Fill();
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TrackerTruthDataAlg::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+}  // namespace Tracker
diff --git a/Tracker/TrackerRecAlgs/TrackerData/src/TrackerTruthDataAlg.h b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerTruthDataAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c4f51d2a6f19a44931457b3d91c94b935b0c0a2
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/src/TrackerTruthDataAlg.h
@@ -0,0 +1,74 @@
+/*
+  Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+*/
+
+#ifndef TRACKERDATA_TRACKERTRUTHDATAALG_H
+#define TRACKERDATA_TRACKERTRUTHDATAALG_H
+
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthHistogramming.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "GeneratorObjects/McEventCollection.h"
+
+class TTree;
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+
+namespace Tracker {
+
+class TrackerTruthDataAlg : public AthReentrantAlgorithm, AthHistogramming {
+public:
+  TrackerTruthDataAlg(const std::string &name, ISvcLocator *pSvcLocator);
+  virtual ~TrackerTruthDataAlg() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext &ctx) const override;
+  virtual StatusCode finalize() override;
+  const ServiceHandle <ITHistSvc> &histSvc() const;
+
+private:
+  SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey {this, "McEventCollection", "BeamTruthEvent"};
+  SG::ReadHandleKey <FaserSiHitCollection> m_faserSiHitKey{this, "FaserSiHitCollection", "SCT_Hits"};
+
+  ServiceHandle <ITHistSvc> m_histSvc;
+
+  const TrackerDD::SCT_DetectorManager *m_sct;
+  const FaserSCT_ID *m_sID;
+
+  mutable TTree *m_particles;
+  mutable TTree *m_hits;
+
+  mutable unsigned int m_run_number;
+  mutable unsigned int m_event_number;
+  mutable unsigned int m_station;
+  mutable unsigned int m_plane;
+  mutable unsigned int m_module;
+  mutable unsigned int m_row;
+  mutable unsigned int m_sensor;
+  mutable int m_pdg;
+  mutable double m_local_x;
+  mutable double m_local_y;
+  mutable double m_global_x;
+  mutable double m_global_y;
+  mutable double m_global_z;
+  mutable double m_vertex_x;
+  mutable double m_vertex_y;
+  mutable double m_vertex_z;
+  mutable double m_px;
+  mutable double m_py;
+  mutable double m_pz;
+  mutable double m_phi;
+  mutable double m_theta;
+};
+
+
+inline const ServiceHandle <ITHistSvc> &TrackerTruthDataAlg::histSvc() const {
+  return m_histSvc;
+}
+
+}  // namespace Tracker
+
+#endif  // TRACKERDATA_TRACKERTRUTHDATAALG_H
diff --git a/Tracker/TrackerRecAlgs/TrackerData/src/components/TrackerData_entries.cxx b/Tracker/TrackerRecAlgs/TrackerData/src/components/TrackerData_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..01018d29702f995ccf656c466989a2d06a74e30a
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/src/components/TrackerData_entries.cxx
@@ -0,0 +1,5 @@
+#include "../TrackerTruthDataAlg.h"
+#include "../TrackerSegmentFitDataAlg.h"
+
+DECLARE_COMPONENT(Tracker::TrackerTruthDataAlg )
+DECLARE_COMPONENT(Tracker::TrackerSegmentFitDataAlg)
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/TrackerData/test/TI12TrackerSegmentFitDataDbg.py b/Tracker/TrackerRecAlgs/TrackerData/test/TI12TrackerSegmentFitDataDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..b20d313f99654d07487651c645616b93a266789f
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/test/TI12TrackerSegmentFitDataDbg.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+""""
+Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+from FaserByteStreamCnvSvc.FaserByteStreamCnvSvcConfig import FaserByteStreamCnvSvcCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from TrackerData.TrackerSegmentFitDataConfig import TrackerSegmentFitDataCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ['/home/tboeckh/tmp/Faser-Physics-006470-00093.raw_middleStation.SPs']
+ConfigFlags.Output.ESDFileName = "TrackerSegmentFitData.ESD.pool.root"
+ConfigFlags.addFlag("Output.xAODFileName", f"TrackerSegmentFitData_xAOD.root")
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data21"
+ConfigFlags.Input.isMC = False
+ConfigFlags.GeoModel.FaserVersion = "FASER-02"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(FaserByteStreamCnvSvcCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, name="LevelClustering", DataObjectName="SCT_LEVELMODE_RDOs", ClusterToolTimingPattern="X1X"))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, name=f"LevelFit", MaxClusters=44))
+acc.merge(TrackerSegmentFitDataCfg(ConfigFlags))
+acc.getEventAlgo("Tracker::TrackerSegmentFitDataAlg").OutputLevel = DEBUG
+itemList = ["xAOD::EventInfo#*",
+            "xAOD::EventAuxInfo#*",
+            "xAOD::FaserTriggerData#*",
+            "xAOD::FaserTriggerDataAux#*",
+            "FaserSCT_RDO_Container#*",
+            "Tracker::FaserSCT_ClusterContainer#*",
+            "TrackCollection#*"
+            ]
+acc.merge(OutputStreamCfg(ConfigFlags, "xAOD", itemList))
+
+replicaSvc = acc.getService("DBReplicaSvc")
+replicaSvc.COOLSQLiteVetoPattern = ""
+replicaSvc.UseCOOLSQLite = True
+replicaSvc.UseCOOLFrontier = False
+replicaSvc.UseGeomSQLite = True
+
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/TrackerData/test/TrackerSegmentFitDataDbg.py b/Tracker/TrackerRecAlgs/TrackerData/test/TrackerSegmentFitDataDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..bac4dd4035a48d731d77cdc98af54fd649f73948
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/test/TrackerSegmentFitDataDbg.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+""""
+Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from TrackerData.TrackerSegmentFitDataConfig import TrackerSegmentFitDataCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ["my.RDO.pool.root"]
+ConfigFlags.Output.ESDFileName = "TrackerSegmentFitData.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data22"
+ConfigFlags.Input.isMC = True
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.addFlag("Output.xAODFileName", f"TrackerSegmentFitData_xAOD.root")
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, DataObjectName="SCT_RDOs"))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, SharedHitFraction=0.5, MinClustersPerFit=5, TanThetaCut=0.25))
+acc.merge(TrackerSegmentFitDataCfg(ConfigFlags))
+acc.getEventAlgo("Tracker::TrackerSegmentFitDataAlg").OutputLevel = DEBUG
+itemList = ["xAOD::EventInfo#*",
+            "xAOD::EventAuxInfo#*",
+            "xAOD::FaserTriggerData#*",
+            "xAOD::FaserTriggerDataAux#*",
+            "FaserSCT_RDO_Container#*",
+            "Tracker::FaserSCT_ClusterContainer#*",
+            "TrackCollection#*"
+            ]
+acc.merge(OutputStreamCfg(ConfigFlags, "ESD", itemList))
+
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/TrackerData/test/TrackerTruthDataDbg.py b/Tracker/TrackerRecAlgs/TrackerData/test/TrackerTruthDataDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6b366d6ba9543d4bba4f87384d25b0e9b756603
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerData/test/TrackerTruthDataDbg.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+""""
+Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from TrackerData.TrackerTruthDataConfig import TrackerTruthDataCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ["my.HITS.pool.root"]
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data22"
+ConfigFlags.Input.isMC = True
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.lock()
+
+# Core components
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+
+acc.merge(TrackerTruthDataCfg(ConfigFlags))
+acc.getEventAlgo("Tracker::TrackerTruthDataAlg").OutputLevel = DEBUG
+
+# Execute and finish
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/TrackerPrepRawDataFormation/python/TrackerPrepRawDataFormationConfig.py b/Tracker/TrackerRecAlgs/TrackerPrepRawDataFormation/python/TrackerPrepRawDataFormationConfig.py
index 8a620210bdcf8ef20d9d505770da6c4581e0293b..820cc73c5976089bea53e5fe2def1f87967837a9 100644
--- a/Tracker/TrackerRecAlgs/TrackerPrepRawDataFormation/python/TrackerPrepRawDataFormationConfig.py
+++ b/Tracker/TrackerRecAlgs/TrackerPrepRawDataFormation/python/TrackerPrepRawDataFormationConfig.py
@@ -65,5 +65,6 @@ def FaserSCT_OutputCfg(flags):
 def FaserSCT_ClusterizationCfg(flags, **kwargs):
     """Return ComponentAccumulator for SCT Clusterization and Output"""
     acc = FaserSCT_ClusterizationBasicCfg(flags, **kwargs)
-    acc.merge(FaserSCT_OutputCfg(flags))
+    if flags.Output.doWriteESD:
+        acc.merge(FaserSCT_OutputCfg(flags))
     return acc
diff --git a/Tracker/TrackerRecAlgs/TrackerSPFit/src/TrackerSPFit.cxx b/Tracker/TrackerRecAlgs/TrackerSPFit/src/TrackerSPFit.cxx
index 6902a70138cdad5f8957f0f7e0886d36e6d8213e..e78f7a8c5b72a0ad06d4b360388b3a35f995392b 100755
--- a/Tracker/TrackerRecAlgs/TrackerSPFit/src/TrackerSPFit.cxx
+++ b/Tracker/TrackerRecAlgs/TrackerSPFit/src/TrackerSPFit.cxx
@@ -1,6 +1,6 @@
 /*
-   Copyright (C) 2002-2021 ChRN for the benefit of the ATLAS collaboration
-   */
+   Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+*/
 
 /***************************************************************************
   -------------------
diff --git a/Tracker/TrackerRecAlgs/TrackerSegmentFit/python/TrackerSegmentFitConfig.py b/Tracker/TrackerRecAlgs/TrackerSegmentFit/python/TrackerSegmentFitConfig.py
index 1ac1f94d55b030b729a1fc1f000142f275d127fb..f0de1582f2ccf47270203d4d1d6eb9a6bca341e9 100644
--- a/Tracker/TrackerRecAlgs/TrackerSegmentFit/python/TrackerSegmentFitConfig.py
+++ b/Tracker/TrackerRecAlgs/TrackerSegmentFit/python/TrackerSegmentFitConfig.py
@@ -1,6 +1,5 @@
 """Define methods to construct configured SCT Cluster Fit tools and algorithms
-
-Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
 """
 from AthenaConfiguration.ComponentFactory import CompFactory
 from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
diff --git a/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.cxx b/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.cxx
index ff163e105d51b02267ce35e3fdfe43737c5a184c..2481c6f2eeb0109ac08c55a6e133e341e6645268 100644
--- a/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.cxx
+++ b/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.cxx
@@ -408,6 +408,7 @@ SegmentFitAlg::checkFit(const Tracker::SegmentFitAlg::fitInfo& seed) const
     return false;
   }
   if (m_tanThetaCut > 0.0 && pow(seed.fitParams(2),2.0) + pow(seed.fitParams(3), 2.0) > m_tanThetaCut * m_tanThetaCut) return false;
+  if (m_tanThetaXZCut > 0.0 && pow(seed.fitParams(2), 2.0) > m_tanThetaXZCut * m_tanThetaXZCut) return false;
   for (auto i : seed.candidates)
   {
     double z = seed.clusters[i]->cluster.globalPosition().z();
@@ -507,16 +508,19 @@ SegmentFitAlg::selectFits(const FitMap& fits) const
     auto content = *it;
     ATH_MSG_VERBOSE("clusters: " << content->clusterMask.count() << " / chi2: " << content->fitChi2) ;
   }
-  ClusterSet usedClusters { clusterInfo::nClusters };
 
   while (info.size() > 0)
   {
     auto selected = info.front();
     ATH_MSG_VERBOSE("Selected nclust = " << selected->clusterMask.count() << " with chi2 = " << selected->fitChi2);
     selectedFits[selected->clusterMask] = selected;
-    usedClusters |= selected->clusterMask;
 
-    info.remove_if([&](std::shared_ptr<fitInfo> p) {return (p->clusterMask & ~usedClusters) != p->clusterMask;});
+    // remove fits for which the fraction of shared hits is larger than m_sharedFraction or the number of hits is
+    // smaller than m_minClustersPerFit
+    info.remove_if([&](std::shared_ptr<fitInfo> p) {return
+        ((p->clusterMask & selected->clusterMask).count() > m_sharedHitFraction * p->clusterMask.count())
+        || ((selectedFits.size() >= 4) && (p->clusterMask.count() < m_minClustersPerFit)) ;}
+    );
   }
 
   m_numberOfSelectedFits += selectedFits.size();
diff --git a/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.h b/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.h
index eb41946cd0a9094a24919eaf6824705094afa234..068e1eeab0f2280d38227fd6d3c376c0ffb49e18 100644
--- a/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.h
+++ b/Tracker/TrackerRecAlgs/TrackerSegmentFit/src/SegmentFitAlg.h
@@ -413,7 +413,10 @@ class SegmentFitAlg : public AthReentrantAlgorithm
     DoubleProperty m_waferTolerance { this, "WaferTolerance", 3 * Gaudi::Units::mm, "Wafer boundary tolerance for extrapolated fit." };
     DoubleProperty m_yResidualCut { this, "ResidualCut", 3 * Gaudi::Units::mm, "Cluster y tolerance for compatibility with extrapolated fit." };
     DoubleProperty m_tanThetaCut { this, "TanThetaCut", 0.0, "Maximum accepted tangent of track angle from beam axis; 0 means no cut."};
+    DoubleProperty m_tanThetaXZCut { this, "TanThetaXZCut", 0.0, "Maximum accepted tangent of track angle from beam axis in x direction; 0 means no cut."};
     DoubleProperty m_reducedChi2Cut { this, "ReducedChi2Cut", 10.0, "Maximum accepted chi^2 per degree of freedom for final fits; 0 means no cut." };
+    DoubleProperty m_sharedHitFraction { this, "SharedHitFraction", -1., "Fraction of hits which are allowed to be shared between two fits." };
+    UnsignedIntegerProperty m_minClustersPerFit { this, "MinClustersPerFit", 4, "Minimum number of clusters a fit has to have." };
 
     mutable std::atomic<int> m_numberOfEvents{0};
     mutable std::atomic<int> m_numberExcessOccupancy{0};
diff --git a/Tracker/TrackerRecAlgs/TrackerSegmentFit/test/MCTrackerSegmentFit.py b/Tracker/TrackerRecAlgs/TrackerSegmentFit/test/MCTrackerSegmentFit.py
new file mode 100644
index 0000000000000000000000000000000000000000..05d4be97bc18d8598512b8a22a79f8cdf6436af7
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerSegmentFit/test/MCTrackerSegmentFit.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+"""Test various ComponentAccumulator Digitization configuration modules
+
+Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from AthenaConfiguration.TestDefaults import defaultTestFiles
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+#from Digitization.DigitizationParametersConfig import writeDigitizationMetadata
+from WaveRecAlgs.WaveRecAlgsConfig import WaveformReconstructionCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+#from MCTruthSimAlgs.RecoTimingConfig import MergeRecoTimingObjCfg
+
+# Set up logging and new style config
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+# Configure
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "segmentFit.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data21"
+ConfigFlags.Input.isMC = True
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.addFlag("Output.xAODFileName", "segmentFit.xAOD.pool.root")
+# ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.lock()
+
+# Core components
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags))
+#acc.getEventAlgo("Tracker::SegmentFitAlg").OutputLevel = DEBUG
+
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+itemList = ["xAOD::EventInfo#*",
+            "xAOD::EventAuxInfo#*",
+            "xAOD::FaserTriggerData#*",
+            "xAOD::FaserTriggerDataAux#*",
+            "FaserSCT_RDO_Container#*",
+            "Tracker::FaserSCT_ClusterContainer#*",
+            "TrackCollection#*"
+            ]
+acc.merge(OutputStreamCfg(ConfigFlags, "ESD", itemList))
+
+# Dump config
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = INFO
+# acc.getCondAlgo("FaserSCT_AlignCondAlg").OutputLevel = VERBOSE
+# acc.getCondAlgo("FaserSCT_DetectorElementCondAlg").OutputLevel = VERBOSE
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
+
+# Execute and finish
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/CMakeLists.txt b/Tracker/TrackerRecAlgs/TrackerTruth/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b52a694c458489bb341116be7568c18ffd9de8e6
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerTruth/CMakeLists.txt
@@ -0,0 +1,12 @@
+atlas_subdir(TrackerTruth)
+
+atlas_add_component(
+  TrackerTruth
+  src/TrackerTruthAlg.h
+  src/TrackerTruthAlg.cxx
+  src/components/TrackerTruth_entries.cxx
+  LINK_LIBRARIES AthenaBaseComps GeneratorObjects TrackerSimEvent TrackerIdentifier TrackerReadoutGeometry
+)
+
+atlas_install_python_modules( python/*.py )
+atlas_install_scripts( test/*.py )
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/ReadMe b/Tracker/TrackerRecAlgs/TrackerTruth/ReadMe
new file mode 100644
index 0000000000000000000000000000000000000000..a763932b380cc4982774d5e409328dadea867411
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerTruth/ReadMe
@@ -0,0 +1,6 @@
+This package can be used to write out the truth information of particles and hits from MC generated events.
+For particles the run number, event number, pdg code, vertex and momentum is written out and for hits the
+run number, event number, station, plane, module, row, sensor, local position, global position and pdg code of the
+corresponding particle is written out.
+
+To write out truth information generate MC data using the G4FaserAlgConfigNew_Test.py and run TrackerTruthDbg.py
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/python/TrackerTruthConfig.py b/Tracker/TrackerRecAlgs/TrackerTruth/python/TrackerTruthConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..92f56a8edc34c173636f16fb9162c8ab17d8850f
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerTruth/python/TrackerTruthConfig.py
@@ -0,0 +1,19 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+
+
+def TrackerTruthCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    kwargs.setdefault("FaserSiHitCollection", "SCT_Hits")
+    TrackerTruthAlg = CompFactory.Tracker.TrackerTruthAlg
+    acc.addEventAlgo(TrackerTruthAlg(**kwargs))
+
+    thistSvc = CompFactory.THistSvc()
+    thistSvc.Output += ["HIST1 DATAFILE='TrackerTruth.root' OPT='RECREATE'"]
+    acc.addService(thistSvc)
+
+    return acc
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/python/__init__.py b/Tracker/TrackerRecAlgs/TrackerTruth/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/src/TrackerTruthAlg.cxx b/Tracker/TrackerRecAlgs/TrackerTruth/src/TrackerTruthAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..84249c0cc4e2bde1dd54bf7192f0fbc67295c189
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerTruth/src/TrackerTruthAlg.cxx
@@ -0,0 +1,112 @@
+#include "TrackerTruthAlg.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include <TTree.h>
+
+
+namespace Tracker {
+
+TrackerTruthAlg::TrackerTruthAlg(const std::string &name, ISvcLocator *pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator), AthHistogramming(name),
+    m_histSvc("THistSvc/THistSvc", name), m_sct(nullptr), m_sID(nullptr) {}
+
+
+StatusCode TrackerTruthAlg::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_sct, "SCT"));
+  ATH_CHECK(detStore()->retrieve(m_sID, "FaserSCT_ID"));
+  ATH_CHECK(m_mcEventCollectionKey.initialize());
+  ATH_CHECK(m_faserSiHitKey.initialize());
+
+  m_hits = new TTree("hits", "hits");
+  m_hits->Branch("run_number", &m_run_number, "run_number/I");
+  m_hits->Branch("event_number", &m_event_number, "event_number/I");
+  m_hits->Branch("station", &m_station, "station/I");
+  m_hits->Branch("plane", &m_plane, "plane/I");
+  m_hits->Branch("module", &m_module, "module/I");
+  m_hits->Branch("row", &m_row, "row/I");
+  m_hits->Branch("sensor", &m_sensor, "sensor/I");
+  m_hits->Branch("pdg", &m_pdg, "pdg/I");
+  m_hits->Branch("local_x", &m_local_x, "local_x/D");
+  m_hits->Branch("local_y", &m_local_y, "local_y/D");
+  m_hits->Branch("global_x", &m_global_x, "global_x/D");
+  m_hits->Branch("global_y", &m_global_y, "global_y/D");
+  m_hits->Branch("global_z", &m_global_z, "global_z/D");
+  ATH_CHECK(histSvc()->regTree("/HIST1/hits", m_hits));
+
+  m_particles = new TTree("particles", "particles");
+  m_particles->Branch("run_number", &m_run_number, "run_number/I");
+  m_particles->Branch("event_number", &m_event_number, "event_number/I");
+  m_particles->Branch("pdg", &m_pdg, "pdg/I");
+  m_particles->Branch("x", &m_vertex_x, "x/D");
+  m_particles->Branch("y", &m_vertex_y, "y/D");
+  m_particles->Branch("z", &m_vertex_z, "z/D");
+  m_particles->Branch("px", &m_px, "px/D");
+  m_particles->Branch("py", &m_px, "py/D");
+  m_particles->Branch("pz", &m_px, "pz/D");
+  ATH_CHECK(histSvc()->regTree("/HIST1/particles", m_particles));
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TrackerTruthAlg::execute(const EventContext &ctx) const {
+
+  m_run_number = ctx.eventID().run_number();
+  m_event_number = ctx.eventID().event_number();
+
+  // Fill tree with simulated particles
+  SG::ReadHandle<McEventCollection> mcEventCollection(m_mcEventCollectionKey, ctx);
+  ATH_CHECK(mcEventCollection.isValid());
+  for (const HepMC::GenEvent* event : *mcEventCollection) {
+    for (const HepMC::GenParticle* particle : event->particle_range()) {
+      m_pdg = particle->pdg_id();
+      const auto vertex = particle->production_vertex()->position();
+      m_vertex_x = vertex.x();
+      m_vertex_y = vertex.y();
+      m_vertex_z = vertex.z();
+      const HepMC::FourVector& momentum = particle->momentum();
+      m_px = momentum.x();
+      m_py = momentum.y();
+      m_pz = momentum.z();
+      m_particles->Fill();
+    }
+  }
+
+  // Fill tree with simulated hits
+  SG::ReadHandle<FaserSiHitCollection> siHitCollection(m_faserSiHitKey, ctx);
+  ATH_CHECK(siHitCollection.isValid());
+  for (const FaserSiHit &hit: *siHitCollection) {
+    if (!hit.particleLink().isValid())
+      continue;
+    const HepGeom::Point3D<double> localStartPos = hit.localStartPosition();
+    Identifier id = m_sID->wafer_id(hit.getStation(), hit.getPlane(), hit.getRow(), hit.getModule(), hit.getSensor());
+    const TrackerDD::SiDetectorElement *geoelement = m_sct->getDetectorElement(id);
+    if (!geoelement)
+      continue;
+    const HepGeom::Point3D<double> globalStartPos =
+        Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(localStartPos);
+    m_station = hit.getStation();
+    m_plane = hit.getPlane();
+    m_module = hit.getModule();
+    m_row = hit.getRow();
+    m_sensor = hit.getSensor();
+    m_pdg = hit.particleLink()->pdg_id();
+    m_local_x = localStartPos.x();
+    m_local_y = localStartPos.y();
+    m_global_x = globalStartPos.x();
+    m_global_y = globalStartPos.y();
+    m_global_z = globalStartPos.z();
+    m_hits->Fill();
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TrackerTruthAlg::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+}  // namespace Tracker
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/src/TrackerTruthAlg.h b/Tracker/TrackerRecAlgs/TrackerTruth/src/TrackerTruthAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f21acc718500b5881bdb4956980fc1893c80747
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerTruth/src/TrackerTruthAlg.h
@@ -0,0 +1,72 @@
+/*
+  Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+*/
+
+#ifndef TRACKERTRUTH_TRACKERTRUTHALG_H
+#define TRACKERTRUTH_TRACKERTRUTHALG_H
+
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthHistogramming.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "GeneratorObjects/McEventCollection.h"
+
+class TTree;
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+
+namespace Tracker {
+
+class TrackerTruthAlg : public AthReentrantAlgorithm, AthHistogramming {
+public:
+  TrackerTruthAlg(const std::string &name, ISvcLocator *pSvcLocator);
+  virtual ~TrackerTruthAlg() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext &ctx) const override;
+  virtual StatusCode finalize() override;
+  const ServiceHandle <ITHistSvc> &histSvc() const;
+
+private:
+  SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey {this, "McEventCollection", "BeamTruthEvent"};
+  SG::ReadHandleKey <FaserSiHitCollection> m_faserSiHitKey{this, "FaserSiHitCollection", "SCT_Hits"};
+
+  ServiceHandle <ITHistSvc> m_histSvc;
+
+  const TrackerDD::SCT_DetectorManager *m_sct;
+  const FaserSCT_ID *m_sID;
+
+  mutable TTree *m_particles;
+  mutable TTree *m_hits;
+
+  mutable unsigned int m_run_number;
+  mutable unsigned int m_event_number;
+  mutable unsigned int m_station;
+  mutable unsigned int m_plane;
+  mutable unsigned int m_module;
+  mutable unsigned int m_row;
+  mutable unsigned int m_sensor;
+  mutable int m_pdg;
+  mutable double m_local_x;
+  mutable double m_local_y;
+  mutable double m_global_x;
+  mutable double m_global_y;
+  mutable double m_global_z;
+  mutable double m_vertex_x;
+  mutable double m_vertex_y;
+  mutable double m_vertex_z;
+  mutable double m_px;
+  mutable double m_py;
+  mutable double m_pz;
+};
+
+
+inline const ServiceHandle <ITHistSvc> &TrackerTruthAlg::histSvc() const {
+  return m_histSvc;
+}
+
+}  // namespace Tracker
+
+#endif
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/src/components/TrackerTruth_entries.cxx b/Tracker/TrackerRecAlgs/TrackerTruth/src/components/TrackerTruth_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0f49f0e7edefeccc3908ebc6822928ad08abbe7c
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerTruth/src/components/TrackerTruth_entries.cxx
@@ -0,0 +1,3 @@
+#include "../TrackerTruthAlg.h"
+
+DECLARE_COMPONENT(Tracker::TrackerTruthAlg )
\ No newline at end of file
diff --git a/Tracker/TrackerRecAlgs/TrackerTruth/test/TrackerTruthDbg.py b/Tracker/TrackerRecAlgs/TrackerTruth/test/TrackerTruthDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..479edc81bb284720c94ef4b16196a5aa7ac0f5d3
--- /dev/null
+++ b/Tracker/TrackerRecAlgs/TrackerTruth/test/TrackerTruthDbg.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+""""
+Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+"""
+import sys
+from AthenaCommon.Logging import log
+from AthenaCommon.Constants import DEBUG
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from TrackerTruth.TrackerTruthConfig import TrackerTruthCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ["my.HITS.pool.root"]
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data22"
+ConfigFlags.Input.isMC = True
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.lock()
+
+# Core components
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+
+acc.merge(TrackerTruthCfg(ConfigFlags))
+acc.getEventAlgo("Tracker::TrackerTruthAlg").OutputLevel = DEBUG
+
+# Execute and finish
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracker/TrackerRecAlgs/TruthSeededTrackFinder/src/TruthSeededTrackFinder.cxx b/Tracker/TrackerRecAlgs/TruthSeededTrackFinder/src/TruthSeededTrackFinder.cxx
index d23327e1c5b6c8d59bcf7e41e7a9dbf58ca164bf..f0a7b64cd20c02d0e13a5357b9fe3f05f36f6f5a 100755
--- a/Tracker/TrackerRecAlgs/TruthSeededTrackFinder/src/TruthSeededTrackFinder.cxx
+++ b/Tracker/TrackerRecAlgs/TruthSeededTrackFinder/src/TruthSeededTrackFinder.cxx
@@ -113,10 +113,10 @@ namespace Tracker
     ATH_CHECK( m_faserSiHitKey.initialize() );
     ATH_CHECK( m_faserRdoKey.initialize());
     ATH_CHECK( m_sctMap.initialize());
-    ATH_MSG_INFO( "Using GenEvent collection with key " << m_mcEventKey.key());
-    ATH_MSG_INFO( "Using Faser SiHit collection with key " << m_faserSiHitKey.key());
-    ATH_MSG_INFO( "Using FaserSCT RDO Container with key " << m_faserRdoKey.key());
-    ATH_MSG_INFO( "Using SCT_SDO_Map with key "<< m_sctMap.key());
+    ATH_MSG_DEBUG( "Using GenEvent collection with key " << m_mcEventKey.key());
+    ATH_MSG_DEBUG( "Using Faser SiHit collection with key " << m_faserSiHitKey.key());
+    ATH_MSG_DEBUG( "Using FaserSCT RDO Container with key " << m_faserRdoKey.key());
+    ATH_MSG_DEBUG( "Using SCT_SDO_Map with key "<< m_sctMap.key());
 
     m_hist_n=new TH1D("sp_n","sp_n",20,0,20);
     m_hist_x=new TH1D("sp_x","sp_x",100,-200,200);
@@ -180,7 +180,7 @@ namespace Tracker
     CHECK(m_thistSvc->regHist("/TruthTrackSeeds/sp/InitReso_py",m_hist_InitReso_py));
     CHECK(m_thistSvc->regHist("/TruthTrackSeeds/sp/InitReso_pz",m_hist_InitReso_pz));
 	//!!!!!!!!!!!!!!!!!!!!
-    ATH_MSG_INFO( "TruthTrackSeeds::initialized" );
+    ATH_MSG_DEBUG( "TruthTrackSeeds::initialized" );
     return StatusCode::SUCCESS;
   }
 
@@ -194,11 +194,11 @@ namespace Tracker
 
     // Handles created from handle keys behave like pointers to the corresponding container
     SG::ReadHandle<McEventCollection> h_mcEvents(m_mcEventKey, ctx);
-    ATH_MSG_INFO("Read McEventContainer with " << h_mcEvents->size() << " events");
+    ATH_MSG_DEBUG("Read McEventContainer with " << h_mcEvents->size() << " events");
     if (h_mcEvents->size() == 0) return StatusCode::FAILURE;
 
     SG::ReadHandle<FaserSiHitCollection> h_siHits(m_faserSiHitKey, ctx);
-    ATH_MSG_INFO("Read FaserSiHitCollection with " << h_siHits->size() << " hits");
+    ATH_MSG_DEBUG("Read FaserSiHitCollection with " << h_siHits->size() << " hits");
 
     SG::ReadHandle<FaserSCT_RDO_Container> h_sctRDO(m_faserRdoKey, ctx);
 
@@ -206,7 +206,7 @@ namespace Tracker
     SG::WriteHandle<SpacePointForSeedCollection> seedContainer_SCT(m_seed_spcontainerKey, ctx );
 
     ATH_CHECK( seedContainer_SCT.record( std::make_unique<SpacePointForSeedCollection>() ) );
-    ATH_MSG_INFO("Created SpacePointContainer " << m_seed_spcontainerKey.key() << " N= " << m_idHelper->wafer_hash_max());
+    ATH_MSG_DEBUG("Created SpacePointContainer " << m_seed_spcontainerKey.key() << " N= " << m_idHelper->wafer_hash_max());
 
 
     const TrackerDD::SiDetectorElementCollection* elements = nullptr;
@@ -288,12 +288,12 @@ namespace Tracker
 	  SG::ReadHandle<TrackerSimDataCollection> h_collectionMap(m_sctMap, ctx);
 	  ATH_MSG_DEBUG("map size "<<h_collectionMap->size());
 	//!!!!!!!!!!!!!!!!!!!!
-		  std::cout<<"!!!!!!!!!  cluster: "<<sp->clusterList().first<<std::endl;
+//		  std::cout<<"!!!!!!!!!  cluster: "<<sp->clusterList().first<<std::endl;
 		  ATH_MSG_DEBUG("!!!!!!!!!  cluster: "<<sp->clusterList().first->rdoList() );
 	//!!!!!!!!!!!!!!!!!!!!
 	  if( h_collectionMap->count(identifier) == 0)
 	  {
-	    ATH_MSG_INFO("no map found w/identifier "<<identifier);
+	    ATH_MSG_DEBUG("no map found w/identifier "<<identifier);
 	    ++m_numberOfNoMap;
 	    continue;
 	  }
@@ -309,14 +309,14 @@ namespace Tracker
 	  for( const auto& depositPair : deposits)
 	  {
 	      //!!!!!!!!!!!!!!!!!!!!!!!!
-	      depositPair.first->print(std::cout);
-	        std::cout<<"!!!!!!!!!!!  pdg id =  "<<depositPair.first->pdg_id()<<std::endl;
+//	      depositPair.first->print(std::cout);
+//	        std::cout<<"!!!!!!!!!!!  pdg id =  "<<depositPair.first->pdg_id()<<std::endl;
 		if (depositPair.first->pdg_id() == -13) {
-		HepMC::FourVector pv = depositPair.first->production_vertex()->position();
-		HepMC::FourVector ev = depositPair.first->end_vertex()->position ();
+        // HepMC::FourVector pv = depositPair.first->production_vertex()->position();
+        // HepMC::FourVector ev = depositPair.first->end_vertex()->position ();
 	        truthmom = depositPair.first->momentum();
-	        std::cout<<"!!!!!!!!!!!  production_vertex: ( "<<pv.x()<<",  "<<pv.y()<<",  "<<pv.z()<<" )  "<<std::endl;
-	        std::cout<<"!!!!!!!!!!!  end_vertex: ( "<<ev.x()<<",  "<<ev.y()<<",  "<<ev.z()<<" )  "<<std::endl;
+        // std::cout<<"!!!!!!!!!!!  production_vertex: ( "<<pv.x()<<",  "<<pv.y()<<",  "<<pv.z()<<" )  "<<std::endl;
+        // std::cout<<"!!!!!!!!!!!  end_vertex: ( "<<ev.x()<<",  "<<ev.y()<<",  "<<ev.z()<<" )  "<<std::endl;
 		}
 	      //!!!!!!!!!!!!!!!!!!!!!!!!
 	    if( depositPair.second > highestDep)
@@ -338,14 +338,14 @@ namespace Tracker
 	      for (auto daughter : primary->particles_out())
 		{
 		  // TODO: Check that daughter->production_vertex() and daughter->end_vertex() exist, and bracket the point you're interested in
-	          if (daughter->barcode() % 1000000 == primary->barcode()) {
-		     ATH_MSG_INFO("    daughter barcode: " << daughter->barcode() << " with energy " << daughter->momentum().e() << "  px = " << daughter->momentum().px() << "  py = " << daughter->momentum().py() << "  pz = " << daughter->momentum().pz() );
-		 if (daughter->production_vertex() != nullptr && daughter->end_vertex() != nullptr) { 
-		HepMC::FourVector pv = daughter->production_vertex()->position();
-		HepMC::FourVector ev = daughter->end_vertex()->position ();
-	        std::cout<<"!!!!!!!!!!!  production_vertex: ( "<<pv.x()<<",  "<<pv.y()<<",  "<<pv.z()<<" )  "<<std::endl;
-	        std::cout<<"!!!!!!!!!!!  end_vertex: ( "<<ev.x()<<",  "<<ev.y()<<",  "<<ev.z()<<" )  "<<std::endl;
-		 }
+	    if (daughter->barcode() % 1000000 == primary->barcode()) {
+		     ATH_MSG_DEBUG("    daughter barcode: " << daughter->barcode() << " with energy " << daughter->momentum().e() << "  px = " << daughter->momentum().px() << "  py = " << daughter->momentum().py() << "  pz = " << daughter->momentum().pz() );
+      // if (daughter->production_vertex() != nullptr && daughter->end_vertex() != nullptr) { 
+  		// HepMC::FourVector pv = daughter->production_vertex()->position();
+	  	// HepMC::FourVector ev = daughter->end_vertex()->position ();
+      // std::cout<<"!!!!!!!!!!!  production_vertex: ( "<<pv.x()<<",  "<<pv.y()<<",  "<<pv.z()<<" )  "<<std::endl;
+      // std::cout<<"!!!!!!!!!!!  end_vertex: ( "<<ev.x()<<",  "<<ev.y()<<",  "<<ev.z()<<" )  "<<std::endl;
+  		//  }
 		  }
 		}
 	   }
@@ -387,8 +387,8 @@ namespace Tracker
 	m_lp_end=hit.localEndPosition();
 	m_new_pos=m_lp_start+(m_lp_end-m_lp_start)*m_ran_pos;
 	//muPos=Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(m_new_pos);
-	std::cout<<"!!!!!!!!!!!  hit start pos: "<<Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(m_lp_start)<<std::endl;
-	std::cout<<"!!!!!!!!!!!  hit end pos: "<<Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(m_lp_end)<<std::endl;
+//	std::cout<<"!!!!!!!!!!!  hit start pos: "<<Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(m_lp_start)<<std::endl;
+//	std::cout<<"!!!!!!!!!!!  hit end pos: "<<Amg::EigenTransformToCLHEP(geoelement->transformHit()) * HepGeom::Point3D<double>(m_lp_end)<<std::endl;
 	}
 	      //!!!!!!!!!!!!!!!!!!!!!!!!
 	  }
@@ -430,8 +430,8 @@ namespace Tracker
 	if (istation==2 && ilayer==0) { match_N_2_0++; }
 	if (istation==2 && ilayer==1) { match_N_2_1++; }
 	if (istation==2 && ilayer==2) { match_N_2_2++; }
-	std::cout<<"!!!!!!!!!!! (station, layer) = ("<<istation<<", "<<ilayer<<")     positopn = ("<<gloPos.x()<<", "<<gloPos.y()<<", "<<gloPos.z()<<") "<<std::endl;
-       if (std::fabs(gloPos.x())>100) std::cout<<"!!!!!!!!!!! badXXX "<<std::endl;
+//	std::cout<<"!!!!!!!!!!! (station, layer) = ("<<istation<<", "<<ilayer<<")     positopn = ("<<gloPos.x()<<", "<<gloPos.y()<<", "<<gloPos.z()<<") "<<std::endl;
+//       if (std::fabs(gloPos.x())>100) std::cout<<"!!!!!!!!!!! badXXX "<<std::endl;
 	//!!!!!!!!!!!!!!!!!!!!
 	}
 	ATH_MSG_VERBOSE( nReceivedSPSCT << " SpacePoints successfully added to Container !" );
@@ -441,8 +441,8 @@ namespace Tracker
     m_hist_n->Fill(nTruthSP);
 
     //!!!!!!!!!!!!!!!!!!!!!!!!
- std::cout<<"!!!!!!!!!!!  N_1_0 = "<<N_1_0<<",  N_1_1 = "<<N_1_1<<",  N_1_2 = "<<N_1_2<<",  N_2_0 = "<<N_2_0<<",  N_2_1 = "<<N_2_1<<",  N_2_2 = "<<N_2_2<<std::endl;
- std::cout<<"!!!!!!!!!!!  match_N_1_0 = "<<match_N_1_0<<",  match_N_1_1 = "<<match_N_1_1<<",  match_N_1_2 = "<<match_N_1_2<<",  match_N_2_0 = "<<match_N_2_0<<",  match_N_2_1 = "<<match_N_2_1<<",  match_N_2_2 = "<<match_N_2_2<<std::endl;
+// std::cout<<"!!!!!!!!!!!  N_1_0 = "<<N_1_0<<",  N_1_1 = "<<N_1_1<<",  N_1_2 = "<<N_1_2<<",  N_2_0 = "<<N_2_0<<",  N_2_1 = "<<N_2_1<<",  N_2_2 = "<<N_2_2<<std::endl;
+// std::cout<<"!!!!!!!!!!!  match_N_1_0 = "<<match_N_1_0<<",  match_N_1_1 = "<<match_N_1_1<<",  match_N_1_2 = "<<match_N_1_2<<",  match_N_2_0 = "<<match_N_2_0<<",  match_N_2_1 = "<<match_N_2_1<<",  match_N_2_2 = "<<match_N_2_2<<std::endl;
   if ( (N_1_0==1 && match_N_1_0==1) && (N_1_1==1 && match_N_1_1==1) && (N_1_2==1 && match_N_1_2==1) && (N_2_0==1 && match_N_2_0==1) && (N_2_1==1 && match_N_2_1==1) && (N_2_2==1 && match_N_2_2==1)) {
   Acts::Vector3 pos1_0(0., 0., 0.);
   Acts::Vector3 pos1_1(0., 0., 0.);
@@ -482,12 +482,12 @@ namespace Tracker
     }
   }
 	
-  std::cout<<"!!!!!!!!!!!  pos1_0 = ("<<pos1_0.x()<<", "<<pos1_0.y()<<", "<<pos1_0.z()<<") "<<std::endl; 
-  std::cout<<"!!!!!!!!!!!  pos1_1 = ("<<pos1_1.x()<<", "<<pos1_1.y()<<", "<<pos1_1.z()<<") "<<std::endl;
-  std::cout<<"!!!!!!!!!!!  pos1_2 = ("<<pos1_2.x()<<", "<<pos1_2.y()<<", "<<pos1_2.z()<<") "<<std::endl;
-  std::cout<<"!!!!!!!!!!!  pos2_0 = ("<<pos2_0.x()<<", "<<pos2_0.y()<<", "<<pos2_0.z()<<") "<<std::endl; 
-  std::cout<<"!!!!!!!!!!!  pos2_1 = ("<<pos2_1.x()<<", "<<pos2_1.y()<<", "<<pos2_1.z()<<") "<<std::endl;
-  std::cout<<"!!!!!!!!!!!  pos2_2 = ("<<pos2_2.x()<<", "<<pos2_2.y()<<", "<<pos2_2.z()<<") "<<std::endl;
+//  std::cout<<"!!!!!!!!!!!  pos1_0 = ("<<pos1_0.x()<<", "<<pos1_0.y()<<", "<<pos1_0.z()<<") "<<std::endl;
+//  std::cout<<"!!!!!!!!!!!  pos1_1 = ("<<pos1_1.x()<<", "<<pos1_1.y()<<", "<<pos1_1.z()<<") "<<std::endl;
+//  std::cout<<"!!!!!!!!!!!  pos1_2 = ("<<pos1_2.x()<<", "<<pos1_2.y()<<", "<<pos1_2.z()<<") "<<std::endl;
+//  std::cout<<"!!!!!!!!!!!  pos2_0 = ("<<pos2_0.x()<<", "<<pos2_0.y()<<", "<<pos2_0.z()<<") "<<std::endl;
+//  std::cout<<"!!!!!!!!!!!  pos2_1 = ("<<pos2_1.x()<<", "<<pos2_1.y()<<", "<<pos2_1.z()<<") "<<std::endl;
+//  std::cout<<"!!!!!!!!!!!  pos2_2 = ("<<pos2_2.x()<<", "<<pos2_2.y()<<", "<<pos2_2.z()<<") "<<std::endl;
 
   double charge = 1;
   double B = 0.57;
@@ -527,21 +527,21 @@ namespace Tracker
 
   // the direction of momentum in the first station
   Acts::Vector3 direct1 = d1.normalized();
-  std::cout<<"!!!!!!!!!!!  direct1 = ("<<direct1.x()<<", "<<direct1.y()<<", "<<direct1.z()<<") "<<std::endl; 
+//  std::cout<<"!!!!!!!!!!!  direct1 = ("<<direct1.x()<<", "<<direct1.y()<<", "<<direct1.z()<<") "<<std::endl;
   // the direction of momentum in the second station
   Acts::Vector3 direct2 = d2.normalized();
-  std::cout<<"!!!!!!!!!!!  direct2 = ("<<direct2.x()<<", "<<direct2.y()<<", "<<direct2.z()<<") "<<std::endl; 
+//  std::cout<<"!!!!!!!!!!!  direct2 = ("<<direct2.x()<<", "<<direct2.y()<<", "<<direct2.z()<<") "<<std::endl;
   // the vector pointing from the center of circle to the particle at layer 2 in Y-Z plane
   double R1_z = charge * direct1.y() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
-  double R1_y = -charge * direct1.z() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
-  std::cout<<"!!!!!!!!!!!  (R1_y, R1_z) = ("<<R1_y<<", "<<R1_z<<") "<<std::endl; 
+  // double R1_y = -charge * direct1.z() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
+//  std::cout<<"!!!!!!!!!!!  (R1_y, R1_z) = ("<<R1_y<<", "<<R1_z<<") "<<std::endl;
   // the vector pointing from the center of circle to the particle at layer 3 in Y-Z plane
   double R2_z = charge * direct2.y() / std::sqrt(direct2.y()*direct2.y() + direct2.z()*direct2.z());
-  double R2_y = -charge * direct2.z() / std::sqrt(direct2.y()*direct2.y() + direct2.z()*direct2.z());
-  std::cout<<"!!!!!!!!!!!  (R2_y, R2_z) = ("<<R2_y<<", "<<R2_z<<") "<<std::endl; 
+  // double R2_y = -charge * direct2.z() / std::sqrt(direct2.y()*direct2.y() + direct2.z()*direct2.z());
+//  std::cout<<"!!!!!!!!!!!  (R2_y, R2_z) = ("<<R2_y<<", "<<R2_z<<") "<<std::endl;
   // the norm of radius
   double R = (pos2_0.z() - pos1_2.z()) / (R2_z - R1_z);
-  std::cout<<"!!!!!!!!!!!  R = "<<R<<std::endl; 
+//  std::cout<<"!!!!!!!!!!!  R = "<<R<<std::endl;
   // the norm of momentum in Y-Z plane
   double p_yz = 0.3*B*R / 1000.0;  // R(mm), p(GeV), B(T)
   double p_z = p_yz * direct1.z() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
@@ -549,9 +549,9 @@ namespace Tracker
   double p_x = direct1.x() * p_z / direct1.z();
   // total momentum at the layer 0
   const Acts::Vector3 mom(p_x, p_y, p_z);
-  double p = mom.norm();
-  std::cout<<"!!!!!!!!!!!  InitTrack momentum on layer 0: ( "<<mom.x()*1000<<",  "<<mom.y()*1000<<",  "<<mom.z()*1000<<",  "<<p*1000<<")  "<<std::endl;
-  std::cout<<"!!!!!!!!!!!  truth momentum: ( "<<truthmom.px()<<",  "<<truthmom.py()<<",  "<<truthmom.pz()<<",  "<<truthmom.m()<<" )  "<<std::endl;
+  // double p = mom.norm();
+//  std::cout<<"!!!!!!!!!!!  InitTrack momentum on layer 0: ( "<<mom.x()*1000<<",  "<<mom.y()*1000<<",  "<<mom.z()*1000<<",  "<<p*1000<<")  "<<std::endl;
+//  std::cout<<"!!!!!!!!!!!  truth momentum: ( "<<truthmom.px()<<",  "<<truthmom.py()<<",  "<<truthmom.pz()<<",  "<<truthmom.m()<<" )  "<<std::endl;
 
   if ((p_x*1000 - truthmom.px()) / std::fabs(truthmom.px()) < -10) m_hist_InitReso_px->Fill( -9.5 );
     else if ((p_x*1000 - truthmom.px()) / std::fabs(truthmom.px()) > 10) m_hist_InitReso_px->Fill( 9.5 );
@@ -593,15 +593,15 @@ namespace Tracker
   //---------------------------------------------------------------------------
   StatusCode TruthSeededTrackFinder::finalize()
   {
-    ATH_MSG_INFO( "Finalizing" );
-    ATH_MSG_INFO( m_numberOfEvents << " events processed" );
-    ATH_MSG_INFO( m_numberOfSPCollection << " spacepoint collections processed" );
-    ATH_MSG_INFO( m_numberOfEmptySPCollection<< " spacepoint collections empty" );
-    ATH_MSG_INFO( m_numberOfSP<< " sct SP collections processed" );
-    ATH_MSG_INFO( m_numberOfNoMap<< " not maped spacepoint" );
-    ATH_MSG_INFO( m_numberOfHits<< " sim hits" );
-    ATH_MSG_INFO( m_numberOfMatchSP<< " matched spacepoint" );
-    ATH_MSG_INFO( m_numberOfFills<< " spacepoint saved" );
+    ATH_MSG_DEBUG( "Finalizing" );
+    ATH_MSG_DEBUG( m_numberOfEvents << " events processed" );
+    ATH_MSG_DEBUG( m_numberOfSPCollection << " spacepoint collections processed" );
+    ATH_MSG_DEBUG( m_numberOfEmptySPCollection<< " spacepoint collections empty" );
+    ATH_MSG_DEBUG( m_numberOfSP<< " sct SP collections processed" );
+    ATH_MSG_DEBUG( m_numberOfNoMap<< " not maped spacepoint" );
+    ATH_MSG_DEBUG( m_numberOfHits<< " sim hits" );
+    ATH_MSG_DEBUG( m_numberOfMatchSP<< " matched spacepoint" );
+    ATH_MSG_DEBUG( m_numberOfFills<< " spacepoint saved" );
     return StatusCode::SUCCESS;
   }
 
diff --git a/Tracking/Acts/ActsInterop/CMakeLists.txt b/Tracking/Acts/ActsInterop/CMakeLists.txt
index 41632f128825078ea3e7bc471117c1e35caae2fa..63c79212852d5d2213c74f3bb27f7f145cd604a6 100755
--- a/Tracking/Acts/ActsInterop/CMakeLists.txt
+++ b/Tracking/Acts/ActsInterop/CMakeLists.txt
@@ -4,7 +4,9 @@ atlas_subdir( ActsInterop )
 
 # External dependencies:
 
-find_package(Acts COMPONENTS Core)
+#set( Acts_DIR /home/tboeckh/Documents/acts/run )
+#find_package( Acts REQUIRED COMPONENTS Core PATHS /home/tboeckh/Documents/acts/run NO_DEFAULT_PATH )
+find_package( Acts COMPONENTS Core )
 
 # Component(s) in the package:
 atlas_add_library( ActsInteropLib
diff --git a/Tracking/Acts/FaserActsGeometry/CMakeLists.txt b/Tracking/Acts/FaserActsGeometry/CMakeLists.txt
index a121316e188cc27c35f3d90f9a81b569089b9d19..48c9abd929200339bfaa071bfa6625cf3e2a98cd 100755
--- a/Tracking/Acts/FaserActsGeometry/CMakeLists.txt
+++ b/Tracking/Acts/FaserActsGeometry/CMakeLists.txt
@@ -8,6 +8,8 @@ find_package( Eigen )
 find_package( Boost )
 find_package( nlohmann_json )
 
+#set( Acts_DIR /home/tboeckh/Documents/acts/run )
+#find_package( Acts REQUIRED COMPONENTS Core PATHS /home/tboeckh/Documents/acts/run NO_DEFAULT_PATH )
 find_package( Acts COMPONENTS Core )
 
 atlas_add_library( FaserActsGeometryLib 
diff --git a/Tracking/Acts/FaserActsGeometry/python/FaserActsMaterialMapping_jobOptions.py b/Tracking/Acts/FaserActsGeometry/python/FaserActsMaterialMapping_jobOptions.py
index 6466848fb515373c2b33bd8c0978d4f162e053bb..095edc7648474af1510d8c1f1174f8c955eccaa8 100644
--- a/Tracking/Acts/FaserActsGeometry/python/FaserActsMaterialMapping_jobOptions.py
+++ b/Tracking/Acts/FaserActsGeometry/python/FaserActsMaterialMapping_jobOptions.py
@@ -1,4 +1,6 @@
-# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and FASER collaborations
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
 
 ###############################################################
 #
diff --git a/Tracking/Acts/FaserActsGeometry/src/CuboidVolumeBuilder.cxx b/Tracking/Acts/FaserActsGeometry/src/CuboidVolumeBuilder.cxx
index 76416ff7b7a917a9e47a18a87ac125686b6d386a..a8a8bd2dec4016916236128d6c78e815b8015bf2 100644
--- a/Tracking/Acts/FaserActsGeometry/src/CuboidVolumeBuilder.cxx
+++ b/Tracking/Acts/FaserActsGeometry/src/CuboidVolumeBuilder.cxx
@@ -75,12 +75,12 @@ std::shared_ptr<const Acts::Layer> CuboidVolumeBuilder::buildLayer(
 
   if ( !cfg.surfaces.empty() ) {
     return layerCreator.planeLayer(gctx, cfg.surfaces, cfg.binsX, cfg.binsY,
-                                 Acts::BinningValue::binZ, std::nullopt, trafo, 
-				 std::move(ap));
+                                 Acts::BinningValue::binZ, std::nullopt, trafo);
+//				 std::move(ap));
   } else {
     return layerCreator.planeLayer(gctx, {cfg.surface}, cfg.binsX, cfg.binsY,
-                                 Acts::BinningValue::binZ, std::nullopt, trafo, 
-				 std::move(ap));
+                                 Acts::BinningValue::binZ, std::nullopt, trafo);
+//				 std::move(ap));
   }
 }
 
diff --git a/Tracking/Acts/FaserActsGeometry/src/FaserActsExtrapolationTool.cxx b/Tracking/Acts/FaserActsGeometry/src/FaserActsExtrapolationTool.cxx
index e84a69c6a3fe54ee30ecb046a0606a1e264da5e5..c5351bfaaea0780c843fb1aa3a6ec6fd31cdb3ce 100644
--- a/Tracking/Acts/FaserActsGeometry/src/FaserActsExtrapolationTool.cxx
+++ b/Tracking/Acts/FaserActsGeometry/src/FaserActsExtrapolationTool.cxx
@@ -71,7 +71,8 @@ FaserActsExtrapolationTool::initialize()
 
   ATH_MSG_INFO("Initializing ACTS extrapolation");
 
-  m_logger = makeActsAthenaLogger(this, "Prop", "FaserActsExtrapTool");
+  m_logger = Acts::getDefaultLogger("ExtrapolationTool", Acts::Logging::INFO);
+//  m_logger = makeActsAthenaLogger(this, "Prop", "FaserActsExtrapTool");
 
   ATH_CHECK( m_trackingGeometryTool.retrieve() );
   std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
@@ -318,8 +319,8 @@ FaserActsExtrapolationTool::propagate(const EventContext& ctx,
   ATH_MSG_VERBOSE(name() << "::" << __FUNCTION__ << " begin");
 
   Acts::MagneticFieldContext mctx = getMagneticFieldContext(ctx);
-  const FaserActsGeometryContext& gctx
-    = m_trackingGeometryTool->getGeometryContext(ctx);
+  const FaserActsGeometryContext& gctx = m_trackingGeometryTool->getNominalGeometryContext();
+//  const FaserActsGeometryContext& gctx = m_trackingGeometryTool->getGeometryContext(ctx);
 
   auto anygctx = gctx.context();
 
diff --git a/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx b/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx
index df260a0f6e8da85b268940795f33775f54b60d5b..f8803a279be6565afe9597c701d5bf3f221fef94 100644
--- a/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx
+++ b/Tracking/Acts/FaserActsGeometry/src/FaserActsLayerBuilder.cxx
@@ -39,9 +39,11 @@ FaserActs::CuboidVolumeBuilder::Config FaserActsLayerBuilder::buildVolume(const
   FaserActs::CuboidVolumeBuilder::Config cvbConfig;
   //std::vector<Acts::CuboidVolumeBuilder::VolumeConfig> volumeConfigs = {};
   std::vector<FaserActs::CuboidVolumeBuilder::VolumeConfig> volumeConfigs = {};
-  
 
-  for (int iStation=1; iStation<4; iStation++) {
+  // iStation starts from 1 for the main detector (Faser-01 geometry) and from 0 if we include the IFT (Faser-02 geometry)
+  auto siDetMng = static_cast<const TrackerDD::SCT_DetectorManager*>(m_cfg.mng);
+  int nStations = siDetMng->numerology().numStations();
+  for (int iStation=4-nStations; iStation<4; iStation++) {
 
       //Acts::CuboidVolumeBuilder::VolumeConfig volumeConfig;
       FaserActs::CuboidVolumeBuilder::VolumeConfig volumeConfig;
diff --git a/Tracking/Acts/FaserActsGeometry/src/FaserActsTrackingGeometrySvc.cxx b/Tracking/Acts/FaserActsGeometry/src/FaserActsTrackingGeometrySvc.cxx
index f32a02990835e96bc0d56bfe887604bf3667a4eb..94c852743ebd4dec46d3ee734a7f800dd4384a3e 100644
--- a/Tracking/Acts/FaserActsGeometry/src/FaserActsTrackingGeometrySvc.cxx
+++ b/Tracking/Acts/FaserActsGeometry/src/FaserActsTrackingGeometrySvc.cxx
@@ -25,6 +25,8 @@
 #include "FaserActsGeometry/CuboidVolumeBuilder.h"
 #include "Acts/ActsVersion.hpp"
 #include "Acts/Definitions/Units.hpp"
+#include <Acts/Plugins/Json/JsonMaterialDecorator.hpp>
+#include <Acts/Plugins/Json/MaterialMapJsonConverter.hpp>
 
 // PACKAGE
 #include "FaserActsGeometry/FaserActsLayerBuilder.h"
@@ -73,6 +75,21 @@ FaserActsTrackingGeometrySvc::initialize()
         cvhConfig, makeActsAthenaLogger(this, "CylVolHlpr", "ActsTGSvc"));
 
   Acts::TrackingGeometryBuilder::Config tgbConfig;
+
+  if (m_useMaterialMap) {
+    std::shared_ptr<const Acts::IMaterialDecorator> matDeco = nullptr;
+    std::string matFile = m_materialMapInputFile;
+    ATH_MSG_INFO("Configured to use material input: " << matFile);
+    if (matFile.find(".json") != std::string::npos) {
+      // Set up the converter first
+      Acts::MaterialMapJsonConverter::Config jsonGeoConvConfig;
+      // Set up the json-based decorator
+      matDeco = std::make_shared<const Acts::JsonMaterialDecorator>(
+          jsonGeoConvConfig, m_materialMapInputFile, Acts::Logging::INFO);
+    }
+    tgbConfig.materialDecorator = matDeco;
+  }
+
   try {
     // SCT
     tgbConfig.trackingVolumeBuilders.push_back([&](
diff --git a/Tracking/Acts/FaserActsGeometryInterfaces/CMakeLists.txt b/Tracking/Acts/FaserActsGeometryInterfaces/CMakeLists.txt
index 7529f472a8de702fb76046cb78624a9111e65a03..b3f81e4ac59f82e9d53ebb75f68eaddbe3e4f74c 100644
--- a/Tracking/Acts/FaserActsGeometryInterfaces/CMakeLists.txt
+++ b/Tracking/Acts/FaserActsGeometryInterfaces/CMakeLists.txt
@@ -4,6 +4,8 @@ atlas_subdir( FaserActsGeometryInterfaces )
 
 # External dependencies:
 find_package( Eigen )
+#set( Acts_DIR /home/tboeckh/Documents/acts/run )
+#find_package( Acts REQUIRED COMPONENTS Core PATHS /home/tboeckh/Documents/acts/run NO_DEFAULT_PATH )
 find_package( Acts COMPONENTS Core )
 find_package( nlohmann_json )
 # Component(s) in the package:
diff --git a/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt b/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt
index 21e5ed799d3caeaf33d4fc85102b9be48e18e599..22f075abb55848488419bc7129574a6a6a6741fe 100755
--- a/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt
+++ b/Tracking/Acts/FaserActsKalmanFilter/CMakeLists.txt
@@ -5,8 +5,11 @@ atlas_subdir(FaserActsKalmanFilter)
 find_package( CLHEP )
 find_package( Eigen )
 find_package( Boost )
+#set( Acts_DIR /home/tboeckh/Documents/acts/run )
+#find_package( Acts REQUIRED COMPONENTS Core PATHS /home/tboeckh/Documents/acts/run NO_DEFAULT_PATH )
 find_package( Acts COMPONENTS Core )
 
+
 # Component(s) in the package:
 atlas_add_library( FaserActsKalmanFilterLib
     PUBLIC_HEADERS FaserActsKalmanFilter
@@ -22,23 +25,83 @@ atlas_add_library( FaserActsKalmanFilterLib
 )
 
 atlas_add_component(FaserActsKalmanFilter
+    FaserActsKalmanFilter/ActsTrackSeedTool.h
+    FaserActsKalmanFilter/CircleFit.h
+    FaserActsKalmanFilter/CircleFitTrackSeedTool.h
+    FaserActsKalmanFilter/CKF2.h
     FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h
+    FaserActsKalmanFilter/EffPlotTool.h
+    FaserActsKalmanFilter/FASERSourceLink.h
     FaserActsKalmanFilter/FaserActsGeometryContainers.h
+    FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h
     FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h
+    FaserActsKalmanFilter/GhostBusters.h
+    FaserActsKalmanFilter/MyTrackSeedTool.h
+    FaserActsKalmanFilter/IdentifierLink.h
     FaserActsKalmanFilter/IndexSourceLink.h
+    FaserActsKalmanFilter/ITrackFinderTool.h
+    FaserActsKalmanFilter/ITrackSeedTool.h
+    FaserActsKalmanFilter/KalmanFitterTool.h
+    FaserActsKalmanFilter/LinearFit.h
+#    FaserActsKalmanFilter/ClusterTrackSeedTool.h
+#    FaserActsKalmanFilter/TruthTrackFinderTool.h
     FaserActsKalmanFilter/Measurement.h
+#    FaserActsKalmanFilter/MultiTrackFinderTool.h
+    FaserActsKalmanFilter/PerformanceWriterTool.h
+    FaserActsKalmanFilter/PlotHelpers.h
+    FaserActsKalmanFilter/ResPlotTool.h
+    FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h
+    FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h
+    FaserActsKalmanFilter/SeedingAlg.h
+#    FaserActsKalmanFilter/SegmentFitClusterTrackFinderTool.h
+#    FaserActsKalmanFilter/SegmentFitTrackFinderTool.h
     FaserActsKalmanFilter/SimWriterTool.h
     FaserActsKalmanFilter/SPSeedBasedInitialParameterTool.h
     FaserActsKalmanFilter/SPSimpleInitialParameterTool.h
+    FaserActsKalmanFilter/SummaryPlotTool.h
+    FaserActsKalmanFilter/TrackClassification.h
+    FaserActsKalmanFilter/TrackSeedWriterTool.h
+    FaserActsKalmanFilter/TrackSelection.h
     FaserActsKalmanFilter/TrajectoryWriterTool.h
+#    FaserActsKalmanFilter/ProtoTrackWriterTool.h
     FaserActsKalmanFilter/TruthBasedInitialParameterTool.h
-    src/CombinatorialKalmbanFilterAlg.cxx
+#    FaserActsKalmanFilter/TruthSeededTrackFinderTool.h
+    FaserActsKalmanFilter/ThreeStationTrackSeedTool.h
+    src/ActsTrackSeedTool.cxx
+    src/CircleFit.cxx
+    src/CircleFitTrackSeedTool.cxx
+    src/CKF2.cxx
+#    src/ClusterTrackSeedTool.cxx
+    src/CombinatorialKalmanFilterAlg.cxx
+    src/EffPlotTool.cxx
+    src/FaserActsKalmanFilterAlg.cxx
+    src/GhostBusters.cxx
+    src/MyTrackSeedTool.cxx
+    src/KalmanFitterTool.cxx
+#    src/MultiTrackFinderTool.cxx
+    src/PerformanceWriterTool.cxx
+    src/PlotHelpers.cxx
+    src/ResPlotTool.cxx
+    src/SeedingAlg.cxx
+    src/RootTrajectoryStatesWriterTool.cxx
+    src/RootTrajectorySummaryWriterTool.cxx
+#    src/SegmentFitClusterTrackFinderTool.cxx
+#    src/SegmentFitTrackFinderTool.cxx
     src/SimWriterTool.cxx
     src/SPSeedBasedInitialParameterTool.cxx
     src/SPSimpleInitialParameterTool.cxx
+#    src/ProtoTrackWriterTool.cxx
     src/TrackFindingAlgorithmFunction.cxx
+    src/TrackFittingFunction.cxx
     src/TrajectoryWriterTool.cxx
     src/TruthBasedInitialParameterTool.cxx
+    src/SummaryPlotTool.cxx
+    src/TrackClassification.cxx
+    src/TrackSeedWriterTool.cxx
+    src/TrackSelection.cxx
+#    src/TruthTrackFinderTool.cxx
+#    src/TruthSeededTrackFinderTool.cxx
+    src/ThreeStationTrackSeedTool.cxx
     src/components/FaserActsKalmanFilter_entries.cxx
     PUBLIC_HEADERS FaserActsKalmanFilter
     INCLUDE_DIRS ${CLHEP_INCLUDE_DIRS} ${EIGEN_INCLUDE_DIRS} ${BOOST_INCLUDE_DIRS}
@@ -63,6 +126,7 @@ atlas_add_component(FaserActsKalmanFilter
     TrackerIdentifier
     TrackerReadoutGeometry
     Identifier
+    TrackerSimEvent
     TrackerSimData
     TrackerSeedFinderLib
 )
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ActsTrackSeedTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ActsTrackSeedTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..95311206b6300722344935b535c4f0d963728392
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ActsTrackSeedTool.h
@@ -0,0 +1,116 @@
+#ifndef FASERACTSKALMANFILTER_ACTSTRACKSEEDTOOL_H
+#define FASERACTSKALMANFILTER_ACTSTRACKSEEDTOOL_H
+
+
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "TrkTrack/TrackCollection.h"
+
+class FaserSCT_ID;
+namespace TrackerDD { class SCT_DetectorManager; }
+
+
+class ActsTrackSeedTool : public extends<AthAlgTool, ITrackSeedTool> {
+public:
+  ActsTrackSeedTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~ActsTrackSeedTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const override;
+  const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override;
+  const std::shared_ptr<IdentifierLink> idLinks() const override;
+  const std::shared_ptr<std::vector<Measurement>> measurements() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters() const override;
+  const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> seedClusters() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> spacePoints() const override;
+  double targetZPosition() const override;
+
+private:
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> m_clusters {};
+  std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> m_seedClusters {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> m_spacePoints {};
+  double m_targetZPosition {0.};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {
+      this, "TrackCollection", "SegmentFit", "Input track collection name" };
+  SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainerKey {
+      this, "ClusterContainer", "SCT_ClusterContainer"};
+
+  // position resolution of a cluster
+  Gaudi::Property<double> m_std_cluster {this, "std_cluster", 0.023};
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+  Gaudi::Property<double> m_origin {this, "origin", 0, "z position of the reference surface"};
+
+  static Acts::CurvilinearTrackParameters get_params(
+      const Acts::GeometryContext& gctx, const Amg::Vector3D& position_st1,
+      const Amg::Vector3D& position_st2, const Amg::Vector3D& position_st3,
+      const Acts::BoundSymMatrix& cov, double origin);
+};
+
+inline const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>
+ActsTrackSeedTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+ActsTrackSeedTool::initialSurface() const {
+  return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<IndexSourceLink>>
+ActsTrackSeedTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+ActsTrackSeedTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<Measurement>>
+ActsTrackSeedTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>>
+ActsTrackSeedTool::clusters() const {
+  return m_clusters;
+}
+
+inline const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>>
+ActsTrackSeedTool::seedClusters() const {
+  return m_seedClusters;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>>
+ActsTrackSeedTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline double ActsTrackSeedTool::targetZPosition() const {
+  return m_targetZPosition;
+}
+
+#endif // FASERACTSKALMANFILTER_ACTSTRACKSEEDTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CKF2.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CKF2.h
new file mode 100644
index 0000000000000000000000000000000000000000..da7baf0c88406e764b454796e58239e3de7d93b0
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CKF2.h
@@ -0,0 +1,156 @@
+#ifndef FASERACTSKALMANFILTER_CKF2_H
+#define FASERACTSKALMANFILTER_CKF2_H
+
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthAlgorithm.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
+#include "Acts/TrackFinding/MeasurementSelector.hpp"
+#include "FaserActsKalmanFilter/Measurement.h"
+#include "MagFieldConditions/FaserFieldCacheCondObj.h"
+#include "FaserActsKalmanFilter/TrajectoryWriterTool.h"
+#include "TrkTrack/TrackCollection.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include "FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h"
+#include "FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h"
+#include "FaserActsKalmanFilter/PerformanceWriterTool.h"
+#include "FaserActsKalmanFilter/KalmanFitterTool.h"
+#include <boost/dynamic_bitset.hpp>
+using ConstTrackStateProxy = Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+using ClusterSet = boost::dynamic_bitset<>;
+
+class FaserSCT_ID;
+
+namespace Trk {
+class TrackStateOnSurface;
+}
+
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+class CKF2 : public AthAlgorithm {
+public:
+  CKF2(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~CKF2() = default;
+
+  StatusCode initialize() override;
+  StatusCode execute() override;
+  StatusCode finalize() override;
+
+  using TrackFinderOptions =
+  Acts::CombinatorialKalmanFilterOptions<IndexSourceLinkAccessor,
+      MeasurementCalibrator,
+      Acts::MeasurementSelector>;
+  using CKFResult = Acts::CombinatorialKalmanFilterResult<IndexSourceLink>;
+  using TrackFitterResult = Acts::Result<CKFResult>;
+  using TrackFinderResult = std::vector<TrackFitterResult>;
+
+  using KFResult =
+    Acts::Result<Acts::KalmanFitterResult<IndexSourceLink>>;
+
+  using TrackFitterOptions =
+  Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder,
+      Acts::VoidReverseFilteringLogic>;
+
+  using TrackParameters = Acts::CurvilinearTrackParameters;
+  using TrackParametersContainer = std::vector<TrackParameters>;
+
+  // Track Finding
+  class TrackFinderFunction {
+  public:
+    virtual ~TrackFinderFunction() = default;
+    virtual TrackFinderResult operator()(const IndexSourceLinkContainer&,
+                                         const TrackParametersContainer&,
+                                         const TrackFinderOptions&) const = 0;
+  };
+
+  static std::shared_ptr<TrackFinderFunction> makeTrackFinderFunction(
+      std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+      bool resolvePassive, bool resolveMaterial, bool resolveSensitive);
+
+  // Track Fitting
+  class TrackFitterFunction {
+  public:
+    virtual ~TrackFitterFunction() = default;
+    virtual KFResult operator()(const std::vector<IndexSourceLink>&,
+                                const Acts::BoundTrackParameters&,
+                                const TrackFitterOptions&) const = 0;
+  };
+
+  struct TrajectoryInfo {
+    TrajectoryInfo(const FaserActsRecMultiTrajectory &traj) :
+        trajectory{traj}, clusterSet{nClusters} {
+      auto state = Acts::MultiTrajectoryHelpers::trajectoryState(traj.multiTrajectory(), traj.tips().front());
+      traj.multiTrajectory().visitBackwards(traj.tips().front(), [&](const ConstTrackStateProxy& state) {
+        auto typeFlags = state.typeFlags();
+        if (not typeFlags.test(Acts::TrackStateFlag::MeasurementFlag)) {
+          return true;
+        }
+        clusterSet.set(state.uncalibrated().index());
+        return true;
+      });
+      nMeasurements = state.nMeasurements;
+      chi2 = state.chi2Sum;
+    }
+
+    static size_t nClusters;
+    FaserActsRecMultiTrajectory trajectory;
+    ClusterSet clusterSet;
+    size_t nMeasurements;
+    double chi2;
+  };
+
+  static std::shared_ptr<TrackFitterFunction> makeTrackFitterFunction(
+      std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry);
+
+  virtual Acts::MagneticFieldContext getMagneticFieldContext(const EventContext& ctx) const;
+
+private:
+  size_t m_numberOfEvents {0};
+  size_t m_numberOfTrackSeeds {0};
+  size_t m_numberOfFittedTracks {0};
+  size_t m_numberOfSelectedTracks {0};
+
+  void computeSharedHits(std::vector<IndexSourceLink>* sourceLinks, TrackFinderResult& results) const;
+  std::shared_ptr<TrackFinderFunction> m_fit;
+  std::shared_ptr<TrackFitterFunction> m_kf;
+  std::unique_ptr<const Acts::Logger> m_logger;
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+
+  Gaudi::Property<std::string> m_actsLogging {this, "ActsLogging", "VERBOSE"};
+  Gaudi::Property<int> m_minNumberMeasurements {this, "MinNumberMeasurements", 12};
+  Gaudi::Property<bool> m_backwardPropagation {this, "BackwardPropagation", false};
+  Gaudi::Property<bool> m_performanceWriter {this, "PerformanceWriter", true};
+  Gaudi::Property<bool> m_summaryWriter {this, "SummaryWriter", true};
+  Gaudi::Property<bool> m_noDiagnostics {this, "noDiagnostics", true, "Set ACTS logging level to INFO and do not run performance writer, states writer or summary writer"};
+  Gaudi::Property<bool> m_statesWriter {this, "StatesWriter", false};
+  Gaudi::Property<bool> m_resolvePassive {this, "resolvePassive", false};
+  Gaudi::Property<bool> m_resolveMaterial {this, "resolveMaterial", true};
+  Gaudi::Property<bool> m_resolveSensitive {this, "resolveSensitive", true};
+  Gaudi::Property<double> m_maxSteps {this, "maxSteps", 10000};
+  Gaudi::Property<double> m_chi2Max {this, "chi2Max", 15};
+  Gaudi::Property<unsigned long> m_nMax {this, "nMax", 10};
+  SG::ReadCondHandleKey<FaserFieldCacheCondObj> m_fieldCondObjInputKey {this, "FaserFieldCacheCondObj", "fieldCondObj", "Name of the Magnetic Field conditions object key"};
+  ToolHandle<ITrackSeedTool> m_trackSeedTool {this, "TrackSeed", "ClusterTrackSeedTool"};
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  ToolHandle<PerformanceWriterTool> m_performanceWriterTool {this, "PerformanceWriterTool", "PerformanceWriterTool"};
+  ToolHandle<RootTrajectoryStatesWriterTool> m_trajectoryStatesWriterTool {this, "RootTrajectoryStatesWriterTool", "RootTrajectoryStatesWriterTool"};
+  ToolHandle<RootTrajectorySummaryWriterTool> m_trajectorySummaryWriterTool {this, "RootTrajectorySummaryWriterTool", "RootTrajectorySummaryWriterTool"};
+  ToolHandle<KalmanFitterTool> m_kalmanFitterTool1 {this, "KalmanFitterTool1", "KalmanFitterTool"};
+  ToolHandle<KalmanFitterTool> m_kalmanFitterTool2 {this, "KalmanFitterTool2", "KalmanFitterTool"};
+  Gaudi::Property<bool> m_isMC {this, "isMC", false};
+
+  std::unique_ptr<Trk::Track> makeTrack(Acts::GeometryContext& tgContext, TrackFitterResult& fitResult) const;
+  std::unique_ptr<Trk::Track> makeTrack(const Acts::GeometryContext &geoCtx, const FaserActsRecMultiTrajectory &traj) const;
+  const Trk::TrackParameters* ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const;
+  SG::WriteHandleKey<TrackCollection> m_trackCollection { this, "CKFTrackCollection", "CKFTrackCollection" };
+};
+
+#endif // FASERACTSKALMANFILTER_CKF2_H
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CircleFit.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CircleFit.h
new file mode 100644
index 0000000000000000000000000000000000000000..780a476a722707f280b427a1678f16fcfb5b1cdf
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CircleFit.h
@@ -0,0 +1,46 @@
+#ifndef FASERACTSKALMANFILTER_CIRCLEFIT_H
+#define FASERACTSKALMANFILTER_CIRCLEFIT_H
+
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include <cmath>
+#include <vector>
+
+namespace CircleFit {
+
+
+class CircleData {
+public:
+  CircleData(const std::vector<const Tracker::FaserSCT_SpacePoint *> &spacePoints);
+  CircleData(const std::vector<Amg::Vector3D> &spacePoints);
+  double meanX() const;
+  double meanY() const;
+  double x(int i) const;
+  double y(int i) const;
+  int size() const;
+
+private:
+  std::vector<double> m_x{};
+  std::vector<double> m_y{};
+  int m_size = 0;
+};
+
+
+struct Circle {
+public:
+  Circle() = default;
+  Circle(double cx, double cy, double r) : cx(cx), cy(cy), r(r) {};
+
+  double cx = 0;
+  double cy = 0;
+  double r = 0;
+  double s = 0;
+  double i = 0;
+  double j = 0;
+};
+
+double sigma(const CircleData &data, const Circle &circle);
+Circle circleFit(const CircleData &data);
+
+}  // namespace CircleFit
+
+#endif // FASERACTSKALMANFILTER_CIRCLEFIT_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CircleFitTrackSeedTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CircleFitTrackSeedTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..368abb1f228e45e69d0c38da02e63dc65c15fb4d
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CircleFitTrackSeedTool.h
@@ -0,0 +1,165 @@
+#ifndef FASERACTSKALMANFILTER_CIRCLEFITTRACKSEEDTOOL_H
+#define FASERACTSKALMANFILTER_CIRCLEFITTRACKSEEDTOOL_H
+
+#include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include "TrkTrack/TrackCollection.h"
+// #include "TrackerSimData/TrackerSimDataCollection.h"
+// #include "GeneratorObjects/McEventCollection.h"
+#include <array>
+#include <memory>
+#include <string>
+#include <vector>
+#include <boost/dynamic_bitset.hpp>
+
+typedef boost::dynamic_bitset<> ClusterSet;
+
+class FaserSCT_ID;
+namespace TrackerDD { class SCT_DetectorManager; }
+
+class CircleFitTrackSeedTool : public extends<AthAlgTool, ITrackSeedTool> {
+public:
+  CircleFitTrackSeedTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~CircleFitTrackSeedTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const override;
+  const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override;
+  const std::shared_ptr<IdentifierLink> idLinks() const override;
+  const std::shared_ptr<std::vector<Measurement>> measurements() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters() const override;
+  const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> seedClusters() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> spacePoints() const override;
+  double targetZPosition() const override;
+
+private:
+  struct Segment {
+  public:
+    Segment(const Trk::Track* track, const FaserSCT_ID *idHelper);
+    int station;
+    std::vector<const Tracker::FaserSCT_Cluster*> clusters;
+    std::vector<const Tracker::FaserSCT_SpacePoint*> spacePoints;
+    ClusterSet clusterSet;
+    Acts::Vector3 position;
+    std::vector<Acts::Vector3> fakePositions;
+    Acts::Vector3 momentum;
+  };
+
+  struct Seed {
+    Seed(const std::vector<Segment> &segments);
+
+    ClusterSet clusterSet;
+    std::vector<const Tracker::FaserSCT_Cluster*> clusters;
+    std::vector<const Tracker::FaserSCT_SpacePoint*> spacePoints;
+    std::vector<Acts::Vector3> positions;
+    std::vector<Acts::Vector3> fakePositions;
+
+    double c0, c1, cx, cy, r, chi2, momentum, charge, minZ;
+    Acts::Vector3 direction;
+    size_t size;
+    Acts::CurvilinearTrackParameters get_params(double origin, Acts::BoundSymMatrix cov) const;
+
+  private:
+    void getChi2();
+    double getX(double z);
+    double getY(double z);
+    void fit();
+    void fakeFit(double B=0.55);
+    void linearFit(const std::vector<Acts::Vector2> &points);
+    double m_dx, m_dy;
+    double m_MeV2GeV = 1e-3;
+    double m_sigma_x = 0.8;
+    double m_sigma_y = 0.016;
+  };
+
+  void go(const std::array<std::vector<Segment>, 4> &v, std::vector<Segment> &combination,
+          std::vector<Seed> &seeds, int offset, int k);
+
+  static std::map<Identifier, Index> s_indexMap;
+  static std::map<Identifier, const Tracker::FaserSCT_SpacePoint*> s_spacePointMap;
+
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> m_clusters {};
+  std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> m_seedClusters {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> m_spacePoints {};
+  double m_targetZPosition {0};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool { this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection { this, "TrackCollection", "SegmentFit", "Input track collection name" };
+  SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainerKey { this, "ClusterContainer", "SCT_ClusterContainer"};
+  SG::ReadHandleKey<FaserSCT_SpacePointContainer> m_spacePointContainerKey { this, "SpacePoints", "SCT_SpacePointContainer"};
+  // SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey { this, "McEventCollection", "TruthEvent"};
+  // SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey { this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+
+  Gaudi::Property<double> m_std_cluster {this, "std_cluster", 0.04};
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+};
+
+inline const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>
+CircleFitTrackSeedTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+CircleFitTrackSeedTool::initialSurface() const {
+  return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<IndexSourceLink>>
+CircleFitTrackSeedTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+CircleFitTrackSeedTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<Measurement>>
+CircleFitTrackSeedTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>>
+CircleFitTrackSeedTool::clusters() const {
+  return m_clusters;
+}
+
+inline const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>>
+CircleFitTrackSeedTool::seedClusters() const {
+  return m_seedClusters;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>>
+CircleFitTrackSeedTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline double CircleFitTrackSeedTool::targetZPosition() const {
+  return m_targetZPosition;
+}
+
+#endif  // FASERACTSKALMANFILTER_CIRCLEFITTRACKSEEDTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ClusterTrackSeedTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ClusterTrackSeedTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..fa869dc1bd7242f5096a16d879b73ab86a9e2173
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ClusterTrackSeedTool.h
@@ -0,0 +1,100 @@
+#ifndef FASERACTSKALMANFILTER_CLUSTERTRACKFINDERTOOL_H
+#define FASERACTSKALMANFILTER_CLUSTERTRACKFINDERTOOL_H
+
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include "TrkTrack/TrackCollection.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+class FaserSCT_ID;
+namespace TrackerDD { class SCT_DetectorManager; }
+
+class ClusterTrackSeedTool : public extends<AthAlgTool, ITrackSeedTool> {
+public:
+  ClusterTrackSeedTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~ClusterTrackSeedTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const override;
+  const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override;
+  const std::shared_ptr<IdentifierLink> idLinks() const override;
+  const std::shared_ptr<std::vector<Measurement>> measurements() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters() const override;
+
+private:
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> m_clusters {};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {
+      this, "TrackCollection", "SegmentFit", "Input track collection name" };
+  SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainerKey {
+    this, "ClusterContainer", "SCT_ClusterContainer"};
+
+  // position resolution of a cluster
+  Gaudi::Property<double> m_std_cluster {this, "std_cluster", 0.04};
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+
+  Gaudi::Property<double> m_origin {this, "origin", 0, "z position of the reference surface"};
+
+  static Acts::CurvilinearTrackParameters get_params(const Amg::Vector3D& position_st1, const Amg::Vector3D& position_st2, const Acts::BoundSymMatrix& cov, double origin);
+};
+
+inline const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>
+ClusterTrackSeedTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+ClusterTrackSeedTool::initialSurface() const {
+  return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<IndexSourceLink>>
+ClusterTrackSeedTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+ClusterTrackSeedTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<Measurement>>
+ClusterTrackSeedTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>>
+ClusterTrackSeedTool::clusters() const {
+  return m_clusters;
+}
+
+
+#endif  // FASERACTSKALMANFILTER_CLUSTERTRACKFINDERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h
index 4b750614a3f496a9cabef0352c223d9b9e5a9658..28058887c9b6f05d384962206c8cca1a64a9d66c 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h
@@ -2,82 +2,132 @@
 #define COMBINATORIALKALMANFILTERALG_H
 
 #include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthAlgorithm.h"
 #include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
 #include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
 #include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
-#include "FaserActsKalmanFilter/TruthBasedInitialParameterTool.h"
-#include "FaserActsKalmanFilter/SPSimpleInitialParameterTool.h"
-#include "FaserActsKalmanFilter/SPSeedBasedInitialParameterTool.h"
+//#include "FaserActsKalmanFilter/SPSimpleInitialParameterTool.h"
+//#include "FaserActsKalmanFilter/SPSeedBasedInitialParameterTool.h"
 #include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
 #include "Acts/TrackFinding/MeasurementSelector.hpp"
 #include "FaserActsKalmanFilter/Measurement.h"
 #include "MagFieldConditions/FaserFieldCacheCondObj.h"
 #include "FaserActsKalmanFilter/TrajectoryWriterTool.h"
 #include "TrkTrack/TrackCollection.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include "FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h"
+#include "FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h"
+#include "FaserActsKalmanFilter/PerformanceWriterTool.h"
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include <boost/dynamic_bitset.hpp>
+using ConstTrackStateProxy = Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+using ClusterSet = boost::dynamic_bitset<>;
+
 
 class FaserSCT_ID;
 
-namespace Trk
-{
+namespace Trk {
 class TrackStateOnSurface;
 }
 
 namespace TrackerDD {
-  class SCT_DetectorManager;
+class SCT_DetectorManager;
 } 
 
-class CombinatorialKalmanFilterAlg : public AthReentrantAlgorithm { 
- public:
+class CombinatorialKalmanFilterAlg : public AthAlgorithm {
+public:
   CombinatorialKalmanFilterAlg(const std::string& name, ISvcLocator* pSvcLocator);
   virtual ~CombinatorialKalmanFilterAlg() = default;
 
   StatusCode initialize() override;
-  StatusCode execute(const EventContext& ctx) const override;
+  StatusCode execute() override;
   StatusCode finalize() override;
 
   using TrackFinderOptions =
-      Acts::CombinatorialKalmanFilterOptions<IndexSourceLinkAccessor, MeasurementCalibrator, Acts::MeasurementSelector>;
-      using FitterResult = Acts::Result<Acts::CombinatorialKalmanFilterResult<IndexSourceLink>>;
-      using TrackFinderResult = std::vector<FitterResult>;
-//  using TrackFinderResult = std::vector<
-//      Acts::Result<Acts::CombinatorialKalmanFilterResult<IndexSourceLink>>>;
-  using TrackFinderFunction = std::function<TrackFinderResult(
-      const IndexSourceLinkContainer&, const std::vector<Acts::CurvilinearTrackParameters>&,
-      const TrackFinderOptions&)>;
+      Acts::CombinatorialKalmanFilterOptions<IndexSourceLinkAccessor,
+                                             MeasurementCalibrator,
+                                             Acts::MeasurementSelector>;
+  using CKFResult = Acts::CombinatorialKalmanFilterResult<IndexSourceLink>;
+  using TrackFitterResult = Acts::Result<CKFResult>;
+  using TrackFinderResult = std::vector<TrackFitterResult>;
+  using TrackParameters = Acts::CurvilinearTrackParameters;
+  using TrackParametersContainer = std::vector<TrackParameters>;
+
+  class TrackFinderFunction {
+  public:
+    virtual ~TrackFinderFunction() = default;
+    virtual TrackFinderResult operator()(const IndexSourceLinkContainer&,
+                                         const TrackParametersContainer&,
+                                         const TrackFinderOptions&) const = 0;
+  };
 
-  static TrackFinderFunction makeTrackFinderFunction(
-      std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry);
+  struct TrajectoryInfo {
+    TrajectoryInfo(const FaserActsRecMultiTrajectory &traj) :
+        trajectory{traj}, clusterSet{nClusters} {
+      auto state = Acts::MultiTrajectoryHelpers::trajectoryState(traj.multiTrajectory(), traj.tips().front());
+      traj.multiTrajectory().visitBackwards(traj.tips().front(), [&](const ConstTrackStateProxy& state) {
+        auto typeFlags = state.typeFlags();
+        if (not typeFlags.test(Acts::TrackStateFlag::MeasurementFlag)) {
+          return true;
+        }
+        clusterSet.set(state.uncalibrated().index());
+        return true;
+      });
+      nMeasurements = state.nMeasurements;
+      chi2 = state.chi2Sum;
+    }
 
-  Acts::MagneticFieldContext getMagneticFieldContext(const EventContext& ctx) const;
+    static size_t nClusters;
+    FaserActsRecMultiTrajectory trajectory;
+    ClusterSet clusterSet;
+    size_t nMeasurements;
+    double chi2;
+  };
 
+  static std::shared_ptr<TrackFinderFunction> makeTrackFinderFunction(
+      std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+      bool resolvePassive, bool resolveMaterial, bool resolveSensitive);
+  virtual Acts::MagneticFieldContext getMagneticFieldContext(const EventContext& ctx) const;
 
  private:
-  const FaserSCT_ID* m_idHelper{nullptr};
-  const TrackerDD::SCT_DetectorManager* m_detManager{nullptr};
+  size_t m_numberOfEvents {0};
+  size_t m_numberOfTrackSeeds {0};
+  size_t m_numberOfFittedTracks {0};
+  size_t m_numberOfSelectedTracks {0};
 
-//  std::unique_ptr<Trk::Track> makeTrack(Acts::GeometryContext& tgContext, FitterResult& fitResult, std::vector<Tracker::FaserSCT_Cluster>  seed_spcollection) const;
-  std::unique_ptr<Trk::Track> makeTrack(Acts::GeometryContext& tgContext, FitterResult& fitResult, std::vector<Tracker::FaserSCT_SpacePoint>  seed_spcollection) const;
-  const Trk::TrackParameters* ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const;
+  void computeSharedHits(std::vector<IndexSourceLink>* sourceLinks, TrackFinderResult& results) const;
+  std::shared_ptr<TrackFinderFunction> m_fit;
+  std::unique_ptr<const Acts::Logger> m_logger;
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
 
-  SG::WriteHandleKey<TrackCollection> m_trackCollection { this, "FaserActsCKFTrackCollection", "FaserActsCKFTrackCollection", "Output trackcollectionname" };
+  Gaudi::Property<std::string> m_actsLogging {this, "ActsLogging", "VERBOSE"};
+  Gaudi::Property<int> m_minNumberMeasurements {this, "MinNumberMeasurements", 12};
+  Gaudi::Property<bool> m_backwardPropagation {this, "BackwardPropagation", false};
+  Gaudi::Property<bool> m_performanceWriter {this, "PerformanceWriter", true};
+  Gaudi::Property<bool> m_summaryWriter {this, "SummaryWriter", true};
+  Gaudi::Property<bool> m_statesWriter {this, "StatesWriter", false};
+  Gaudi::Property<bool> m_resolvePassive {this, "resolvePassive", false};
+  Gaudi::Property<bool> m_resolveMaterial {this, "resolveMaterial", true};
+  Gaudi::Property<bool> m_resolveSensitive {this, "resolveSensitive", true};
+  Gaudi::Property<bool> m_noDiagnostics {this, "noDiagnostics", true, "Set ACTS logging level to INFO and do not run performance writer, states writer or summary writer"};
+  Gaudi::Property<double> m_maxSteps {this, "maxSteps", 1000};
+  Gaudi::Property<double> m_chi2Max {this, "chi2Max", 15};
+  Gaudi::Property<unsigned long> m_nMax {this, "nMax", 10};
+  Gaudi::Property<bool> m_isMC {this, "isMC", false};
 
-  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool{this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
-//  ToolHandle<SPSeedBasedInitialParameterTool> m_initialParameterTool{this, "InitialParameterTool", "SPSeedBasedInitialParameterTool"};
-//  ToolHandle<SPSimpleInitialParameterTool> m_initialParameterTool{this, "InitialParameterTool", "SPSimpleInitialParameterTool"};
-  ToolHandle<TruthBasedInitialParameterTool> m_initialParameterTool{this, "InitialParameterTool", "TruthBasedInitialParameterTool"};
-  ToolHandle<TrajectoryWriterTool> m_trajectoryWriterTool{this, "OutputTool", "TrajectoryWriterTool"};
   SG::ReadCondHandleKey<FaserFieldCacheCondObj> m_fieldCondObjInputKey {this, "FaserFieldCacheCondObj", "fieldCondObj", "Name of the Magnetic Field conditions object key"};
-  SG::ReadHandleKey<FaserSCT_SpacePointContainer> m_SpacePointContainerKey{this, "SpacePointsSCTName", "SCT_SpacePointContainer", "SCT space point container"};
-  SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer>  m_Sct_clcontainerKey{this, "SCT_ClusterContainer", "SCT_ClusterContainer"};
-  mutable int m_nevents;
-  mutable int m_ntracks;
-  mutable int m_nseeds;
-  mutable int m_nsp1;
-  mutable int m_nsp2;
-  mutable int m_nsp3;
-  mutable int m_nsp10;
-  mutable int m_nsp11;
-  mutable int m_ncom;
+  ToolHandle<ITrackSeedTool> m_trackSeedTool {this, "TrackSeed", "ClusterTrackSeedTool"};
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  ToolHandle<PerformanceWriterTool> m_performanceWriterTool {this, "PerformanceWriterTool", "PerformanceWriterTool"};
+  ToolHandle<RootTrajectoryStatesWriterTool> m_trajectoryStatesWriterTool {this, "RootTrajectoryStatesWriterTool", "RootTrajectoryStatesWriterTool"};
+  ToolHandle<RootTrajectorySummaryWriterTool> m_trajectorySummaryWriterTool {this, "RootTrajectorySummaryWriterTool", "RootTrajectorySummaryWriterTool"};
+
+  std::unique_ptr<Trk::Track> makeTrack(Acts::GeometryContext& tgContext, TrackFitterResult& fitResult) const;
+  std::unique_ptr<Trk::Track> makeTrack(const Acts::GeometryContext &geoCtx, const FaserActsRecMultiTrajectory &traj) const;
+  const Trk::TrackParameters* ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const;
+  SG::WriteHandleKey<TrackCollection> m_trackCollection { this, "CKFTrackCollection", "CKFTrackCollection" };
 };
 
 #endif // COMBINATORIALKALMANFILTERALG_H
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/EffPlotTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/EffPlotTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..e5af125d6021d5ab19b71f73eebc63a99604aea5
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/EffPlotTool.h
@@ -0,0 +1,57 @@
+#ifndef FASERACTSKALMANFILTER_EFFPLOTTOOL_H
+#define FASERACTSKALMANFILTER_EFFPLOTTOOL_H
+
+#include "FaserActsKalmanFilter/PlotHelpers.h"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "HepMC/GenParticle.h"
+#include "TEfficiency.h"
+#include "TProfile.h"
+#include <map>
+#include <string>
+
+class EffPlotTool {
+public:
+  std::map<std::string, PlotHelpers::Binning> m_varBinning = {
+      {"Eta", PlotHelpers::Binning("#eta", 40, 4, 12)},
+      {"Phi", PlotHelpers::Binning("#phi", 100, -3.15, 3.15)},
+      {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 20)}
+  };
+
+  /// @brief Nested Cache struct
+  struct EffPlotCache {
+    TEfficiency* trackEff_vs_pT;   ///< Tracking efficiency vs pT
+    TEfficiency* trackEff_vs_eta;  ///< Tracking efficiency vs eta
+    TEfficiency* trackEff_vs_phi;  ///< Tracking efficiency vs phi
+  };
+
+  /// Constructor
+  ///
+  EffPlotTool() = default;
+
+  /// @brief book the efficiency plots
+  ///
+  /// @param effPlotCache the cache for efficiency plots
+  void book(EffPlotCache& effPlotCache) const;
+
+  /// @brief fill efficiency plots
+  ///
+  /// @param effPlotCache cache object for efficiency plots
+  /// @param truthParticle the truth Particle
+  /// @param status the reconstruction status
+  void fill(EffPlotCache& effPlotCache, const HepMC::GenParticle* truthParticle, bool status) const;
+
+  /// @brief write the efficiency plots to file
+  ///
+  /// @param effPlotCache cache object for efficiency plots
+  void write(const EffPlotCache& effPlotCache) const;
+
+  /// @brief delete the efficiency plots
+  ///
+  /// @param effPlotCache cache object for efficiency plots
+  void clear(EffPlotCache& effPlotCache) const;
+
+private:
+  const double m_MeV2GeV = 0.001;
+};
+
+#endif // FASERACTSKALMANFILTER_EFFPLOTTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FASERSourceLink.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FASERSourceLink.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ec1c46b9ad12ff6822c070a4ea320d6079dd387
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FASERSourceLink.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include <cassert>
+#include <boost/container/flat_map.hpp>
+#include "FaserActsKalmanFilter/FaserActsGeometryContainers.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+
+using Index = uint32_t;
+
+class FASERSourceLink final {
+public:
+  /// Construct from geometry identifier and index.
+  constexpr FASERSourceLink(Acts::GeometryIdentifier gid, Index idx, Tracker::FaserSCT_Cluster hit)
+      : m_geometryId(gid), m_index(idx), m_hit(hit) {}
+
+  // Construct an invalid source link. Must be default constructible to
+  /// satisfy SourceLinkConcept.
+  FASERSourceLink() = default;
+  FASERSourceLink(const FASERSourceLink&) = default;
+  FASERSourceLink(FASERSourceLink&&) = default;
+  FASERSourceLink& operator=(const FASERSourceLink&) = default;
+  FASERSourceLink& operator=(FASERSourceLink&&) = default;
+
+  /// Access the geometry identifier.
+  constexpr Acts::GeometryIdentifier geometryId() const { return m_geometryId; }
+  /// Access the index.
+  constexpr Index index() const { return m_index; }
+  /// Access the Tracker::FaserSCT_Cluster hit
+  constexpr Tracker::FaserSCT_Cluster hit() const { return m_hit; }
+
+private:
+  Acts::GeometryIdentifier m_geometryId;
+  Index m_index;
+  Tracker::FaserSCT_Cluster m_hit;
+
+  friend constexpr bool operator==(const FASERSourceLink& lhs,
+                                   const FASERSourceLink& rhs) {
+    return (lhs.m_geometryId == rhs.m_geometryId) and
+           (lhs.m_index == rhs.m_index);
+  }
+  friend constexpr bool operator!=(const FASERSourceLink& lhs,
+                                   const FASERSourceLink& rhs) {
+    return not(lhs == rhs);
+  }
+};
+
+/// Container of index source links.
+///
+/// Since the source links provide a `.geometryId()` accessor, they can be
+/// stored in an ordered geometry container.
+using FASERSourceLinkContainer = GeometryIdMultiset<FASERSourceLink>;
+/// Accessor for the above source link container
+///
+/// It wraps up a few lookup methods to be used in the Combinatorial Kalman
+/// Filter
+using FASERSourceLinkAccessor = GeometryIdMultisetAccessor<FASERSourceLink>;
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h
index 8e90ed858c09efc3d46985a7edb354ea2de6c280..34dd7c63e7ef5842ac1416dc30d895bef3ecf997 100755
--- a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h
@@ -6,8 +6,8 @@
 #define FASERACTSKALMANFILTER_FASERACTSKALMANFILTERALG_H
 
 // ATHENA
-#include "AthenaBaseComps/AthAlgorithm.h"
 #include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthAlgorithm.h"
 #include "GaudiKernel/ServiceHandle.h"
 #include "GaudiKernel/ITHistSvc.h"
 #include "Gaudi/Property.h"  /*no forward decl: typedef*/
@@ -24,6 +24,7 @@
 #include "GeneratorObjects/McEventCollection.h"
 #include "TrackerSimData/TrackerSimDataCollection.h"
 #include "TrkTrack/TrackCollection.h"
+#include "FaserActsKalmanFilter/TrajectoryWriterTool.h"
 
 // ACTS
 #include "Acts/MagneticField/ConstantBField.hpp"
@@ -47,6 +48,9 @@
 #include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
 #include "FaserActsKalmanFilter/IndexSourceLink.h"
 #include "FaserActsKalmanFilter/Measurement.h"
+#include "FaserActsKalmanFilter/ITrackFinderTool.h"
+//#include "FaserActsKalmanFilter/ProtoTrackWriterTool.h"
+#include "FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h"
 
 // STL
 #include <memory>
@@ -73,216 +77,59 @@ using BField_t = FASERMagneticFieldWrapper;
 //class FaserActsKalmanFilterAlg : public AthReentrantAlgorithm {
 class FaserActsKalmanFilterAlg : public AthAlgorithm {
 public:
-  FaserActsKalmanFilterAlg (const std::string& name, ISvcLocator* pSvcLocator);
+  FaserActsKalmanFilterAlg(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~FaserActsKalmanFilterAlg() = default;
+
   StatusCode initialize() override;
-  //StatusCode execute(const EventContext& ctx) const override;
+//  StatusCode execute(const EventContext& ctx) const override;
   StatusCode execute() override;
   StatusCode finalize() override;
 
-  using FitterResult = Acts::Result<Acts::KalmanFitterResult<IndexSourceLink>>;
-  // Fit function that takes input measurements, initial trackstate and fitter
-  // options and returns some fit-specific result.
-  using FitterFunction = std::function<FitterResult(
-      const std::vector<IndexSourceLink>&,
-      const Acts::CurvilinearTrackParameters&,
-      const Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder>&,
-      const std::vector<const Acts::Surface*>&)>;
+  using IndexedParams = std::unordered_map<size_t, Acts::BoundTrackParameters>;
+  using TrackFitterOptions =
+    Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder,
+                              Acts::VoidReverseFilteringLogic>;
+  using TrackFitterResult =
+    Acts::Result<Acts::KalmanFitterResult<IndexSourceLink>>;
 
-  using BoundVector = Acts::ActsVector<6>;
+  using TrackParameters = Acts::CurvilinearTrackParameters;
 
-  // Create the fitter function implementation.
-  static FitterFunction
-  makeFitterFunction(
-      ActsExtrapolationDetail::VariantPropagator* varProp);
+  class TrackFitterFunction {
+  public:
+    virtual ~TrackFitterFunction() = default;
+    virtual TrackFitterResult operator()(const std::vector<IndexSourceLink>&,
+                                         const TrackParameters&,
+                                         const TrackFitterOptions&) const = 0;
+  };
 
-  virtual
-  Acts::MagneticFieldContext
-  //getMagneticFieldContext(const EventContext& ctx) const;
-  getMagneticFieldContext() const;
+  static std::shared_ptr<TrackFitterFunction> makeTrackFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry);
 
-  void initializeTree();
+  virtual Acts::MagneticFieldContext getMagneticFieldContext(const EventContext& ctx) const;
 
-  void fillFitResult(const Acts::GeometryContext& geoctx, const TrajectoryContainer& trajectories, const Acts::BoundTrackParameters& truthParam);
+private:
+  const FaserSCT_ID* m_idHelper {nullptr};
+  std::shared_ptr<TrackFitterFunction> m_fit;
+  std::unique_ptr<const Acts::Logger> m_logger;
 
-  // Create a track from the fitter result
-  std::unique_ptr<Trk::Track> makeTrack(Acts::GeometryContext& tgContext, FitterResult& fitResult,const SpacePointForSeedCollection*  seed_spcollection ) const;                                                                 
+  Gaudi::Property<std::string> m_actsLogging {this, "ActsLogging", "VERBOSE"};
+  std::unique_ptr<Trk::Track> makeTrack(Acts::GeometryContext& tgContext, TrackFitterResult& fitResult, std::vector<const Tracker::FaserSCT_Cluster*> clusters) const;
   const Trk::TrackParameters* ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const;
 
-  void clearTrackVariables();
-
-private:
-  const FaserSCT_ID* m_idHelper{nullptr};
-
   // Read handle for conditions object to get the field cache
   SG::ReadCondHandleKey<FaserFieldCacheCondObj> m_fieldCondObjInputKey {this, "FaserFieldCacheCondObj", "fieldCondObj", "Name of the Magnetic Field conditions object key"};
 
-  ToolHandle<IFaserActsExtrapolationTool> m_extrapolationTool{this, "ExtrapolationTool", "FaserActsExtrapolationTool"};
-
-  Gaudi::Property<std::string> m_fieldMode{this, "FieldMode", "FASER"};
-  Gaudi::Property<std::vector<double>> m_constantFieldVector{this, "ConstantFieldVector", {0, 0, 0}};
+  ToolHandle<ITrackFinderTool> m_trackFinderTool {this, "TrackFinderTool", "TruthTrackFinderTool"};
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  ToolHandle<TrajectoryWriterTool> m_trajectoryWriterTool {this, "OutputTool", "TrajectoryWriterTool"};
+  ToolHandle<RootTrajectoryStatesWriterTool> m_trajectoryStatesWriterTool {this, "RootTrajectoryStatesWriterTool", "RootTrajectoryStatesWriterTool"};
+//  ToolHandle<ProtoTrackWriterTool> m_protoTrackWriterTool {this, "ProtoTrackWriterTool", "ProtoTrackWriterTool"};
 
-  SG::ReadHandleKey<SpacePointForSeedCollection>  m_seed_spcollectionKey{this, "FaserSpacePointsSeedsName", "SpacePointForSeedCollection", "SpacePointForSeedCollection"};
-
-  SG::ReadHandleKey<McEventCollection> m_mcEventKey       { this, "McEventCollection", "BeamTruthEvent" };
+  SG::ReadHandleKey<McEventCollection> m_mcEventKey { this, "McEventCollection", "BeamTruthEvent" };
   SG::ReadHandleKey<TrackerSimDataCollection> m_sctMap {this, "TrackerSimDataCollection", "SCT_SDO_Map"};
   const TrackerDD::SCT_DetectorManager* m_detManager{nullptr};
 
-  SG::WriteHandleKey<TrackCollection> m_trackCollection { this, "FaserActsCKFTrackCollection", "FaserActsCKFTrackCollection", "Output trackcollection name" };
-
-  ServiceHandle<ITHistSvc> m_thistSvc;
-
-  TTree *m_trackTree{nullptr};
-
-  Acts::GeometryIdentifier getGeometryIdentifier(const Identifier id);
-  int getGeometryIdentifierVolume(int station);
-  int getGeometryIdentifierLayer(int layer);
-  int getGeometryIdentifierSensitive(int row, int column);
-
-  /// Acts tree values
-  int m_eventNr{0};
-  int m_trajNr{0};
-  int m_trackNr{0};
-
-  unsigned long m_t_barcode{0};  /// Truth particle barcode
-  int m_t_charge{0};             /// Truth particle charge
-  float m_t_eT{0};               /// Truth particle time on the first layer
-  float m_t_eLOC0{-99.};         /// Truth local x on the first layer
-  float m_t_eLOC1{-99.};         /// Truth local y on the first layer
-  float m_t_eTHETA{-99.};        /// Truth particle momentum theta on the first layer
-  float m_t_ePHI{-99.};          /// Truth particle momentum phi on the first layer
-  float m_t_eQOP{-99.};          /// Truth particle momentum qop on the first layer
-  float m_t_x{-99.};             /// Truth particle position x on the first layer
-  float m_t_y{-99.};             /// Truth particle position y on the first layer
-  float m_t_z{-99.};             /// Truth particle position z on the first layer
-  float m_t_px{-99.};            /// Truth particle momentum px on the first layer
-  float m_t_py{-99.};            /// Truth particle momentum py on the first layer
-  float m_t_pz{-99.};            /// Truth particle momentum pz on the first layer
-
-  int m_nHoles{0};                  /// number of holes in the track fit
-  int m_nOutliers{0};               /// number of outliers in the track fit
-  int m_nStates{0};                 /// number of all states
-  int m_nMeasurements{0};           /// number of states with measurements
-  std::vector<int> m_volumeID;      /// volume identifier
-  std::vector<int> m_layerID;       /// layer identifier
-  std::vector<int> m_moduleID;      /// surface identifier
-  std::vector<float> m_lx_hit;      /// uncalibrated measurement local x
-  std::vector<float> m_ly_hit;      /// uncalibrated measurement local y
-  std::vector<float> m_x_hit;       /// uncalibrated measurement global x
-  std::vector<float> m_y_hit;       /// uncalibrated measurement global y
-  std::vector<float> m_z_hit;       /// uncalibrated measurement global z
-  std::vector<float> m_res_x_hit;   /// hit residual x
-  std::vector<float> m_res_y_hit;   /// hit residual y
-  std::vector<float> m_err_x_hit;   /// hit err x
-  std::vector<float> m_err_y_hit;   /// hit err y
-  std::vector<float> m_pull_x_hit;  /// hit pull x
-  std::vector<float> m_pull_y_hit;  /// hit pull y
-  std::vector<int> m_dim_hit;       /// dimension of measurement
-
-  bool m_hasFittedParams{false};  /// if the track has fitted parameter
-  float m_eLOC0_fit{-99.};        /// fitted parameter eLOC_0
-  float m_eLOC1_fit{-99.};        /// fitted parameter eLOC_1
-  float m_ePHI_fit{-99.};         /// fitted parameter ePHI
-  float m_eTHETA_fit{-99.};       /// fitted parameter eTHETA
-  float m_eQOP_fit{-99.};         /// fitted parameter eQOP
-  float m_eT_fit{-99.};           /// fitted parameter eT
-  float m_err_eLOC0_fit{-99.};    /// fitted parameter eLOC_-99.err
-  float m_err_eLOC1_fit{-99.};    /// fitted parameter eLOC_1 err
-  float m_err_ePHI_fit{-99.};     /// fitted parameter ePHI err
-  float m_err_eTHETA_fit{-99.};   /// fitted parameter eTHETA err
-  float m_err_eQOP_fit{-99.};     /// fitted parameter eQOP err
-  float m_err_eT_fit{-99.};       /// fitted parameter eT err
-  float m_px_fit{-99.};           /// fitted parameter global px
-  float m_py_fit{-99.};           /// fitted parameter global py
-  float m_pz_fit{-99.};           /// fitted parameter global pz
-  float m_x_fit{-99.};            /// fitted parameter global PCA x
-  float m_y_fit{-99.};            /// fitted parameter global PCA y
-  float m_z_fit{-99.};            /// fitted parameter global PCA z
-  float m_chi2_fit{-99.};         /// fitted parameter chi2
-  float m_ndf_fit{-99.};          /// fitted parameter ndf
-  int m_charge_fit{-99};          /// fitted parameter charge
-
-  int m_nPredicted{0};                   /// number of states with predicted parameter
-  std::vector<bool> m_prt;               /// predicted status
-  std::vector<float> m_eLOC0_prt;        /// predicted parameter eLOC0
-  std::vector<float> m_eLOC1_prt;        /// predicted parameter eLOC1
-  std::vector<float> m_ePHI_prt;         /// predicted parameter ePHI
-  std::vector<float> m_eTHETA_prt;       /// predicted parameter eTHETA
-  std::vector<float> m_eQOP_prt;         /// predicted parameter eQOP
-  std::vector<float> m_eT_prt;           /// predicted parameter eT
-  std::vector<float> m_res_eLOC0_prt;    /// predicted parameter eLOC0 residual
-  std::vector<float> m_res_eLOC1_prt;    /// predicted parameter eLOC1 residual
-  std::vector<float> m_err_eLOC0_prt;    /// predicted parameter eLOC0 error
-  std::vector<float> m_err_eLOC1_prt;    /// predicted parameter eLOC1 error
-  std::vector<float> m_err_ePHI_prt;     /// predicted parameter ePHI error
-  std::vector<float> m_err_eTHETA_prt;   /// predicted parameter eTHETA error
-  std::vector<float> m_err_eQOP_prt;     /// predicted parameter eQOP error
-  std::vector<float> m_err_eT_prt;       /// predicted parameter eT error
-  std::vector<float> m_pull_eLOC0_prt;   /// predicted parameter eLOC0 pull
-  std::vector<float> m_pull_eLOC1_prt;   /// predicted parameter eLOC1 pull
-  std::vector<float> m_x_prt;            /// predicted global x
-  std::vector<float> m_y_prt;            /// predicted global y
-  std::vector<float> m_z_prt;            /// predicted global z
-  std::vector<float> m_px_prt;           /// predicted momentum px
-  std::vector<float> m_py_prt;           /// predicted momentum py
-  std::vector<float> m_pz_prt;           /// predicted momentum pz
-  std::vector<float> m_eta_prt;          /// predicted momentum eta
-  std::vector<float> m_pT_prt;           /// predicted momentum pT
-
-  int m_nFiltered{0};                    /// number of states with filtered parameter
-  std::vector<bool> m_flt;               /// filtered status
-  std::vector<float> m_eLOC0_flt;        /// filtered parameter eLOC0
-  std::vector<float> m_eLOC1_flt;        /// filtered parameter eLOC1
-  std::vector<float> m_ePHI_flt;         /// filtered parameter ePHI
-  std::vector<float> m_eTHETA_flt;       /// filtered parameter eTHETA
-  std::vector<float> m_eQOP_flt;         /// filtered parameter eQOP
-  std::vector<float> m_eT_flt;           /// filtered parameter eT
-  std::vector<float> m_res_eLOC0_flt;    /// filtered parameter eLOC0 residual
-  std::vector<float> m_res_eLOC1_flt;    /// filtered parameter eLOC1 residual
-  std::vector<float> m_err_eLOC0_flt;    /// filtered parameter eLOC0 error
-  std::vector<float> m_err_eLOC1_flt;    /// filtered parameter eLOC1 error
-  std::vector<float> m_err_ePHI_flt;     /// filtered parameter ePHI error
-  std::vector<float> m_err_eTHETA_flt;   /// filtered parameter eTHETA error
-  std::vector<float> m_err_eQOP_flt;     /// filtered parameter eQOP error
-  std::vector<float> m_err_eT_flt;       /// filtered parameter eT error
-  std::vector<float> m_pull_eLOC0_flt;   /// filtered parameter eLOC0 pull
-  std::vector<float> m_pull_eLOC1_flt;   /// filtered parameter eLOC1 pull
-  std::vector<float> m_x_flt;            /// filtered global x
-  std::vector<float> m_y_flt;            /// filtered global y
-  std::vector<float> m_z_flt;            /// filtered global z
-  std::vector<float> m_px_flt;           /// filtered momentum px
-  std::vector<float> m_py_flt;           /// filtered momentum py
-  std::vector<float> m_pz_flt;           /// filtered momentum pz
-  std::vector<float> m_eta_flt;          /// filtered momentum eta
-  std::vector<float> m_pT_flt;           /// filtered momentum pT
-  std::vector<float> m_chi2;             /// chisq from filtering
-
-  int m_nSmoothed{0};                    /// number of states with smoothed parameter
-  std::vector<bool> m_smt;               /// smoothed status
-  std::vector<float> m_eLOC0_smt;        /// smoothed parameter eLOC0
-  std::vector<float> m_eLOC1_smt;        /// smoothed parameter eLOC1
-  std::vector<float> m_ePHI_smt;         /// smoothed parameter ePHI
-  std::vector<float> m_eTHETA_smt;       /// smoothed parameter eTHETA
-  std::vector<float> m_eQOP_smt;         /// smoothed parameter eQOP
-  std::vector<float> m_eT_smt;           /// smoothed parameter eT
-  std::vector<float> m_res_eLOC0_smt;    /// smoothed parameter eLOC0 residual
-  std::vector<float> m_res_eLOC1_smt;    /// smoothed parameter eLOC1 residual
-  std::vector<float> m_err_eLOC0_smt;    /// smoothed parameter eLOC0 error
-  std::vector<float> m_err_eLOC1_smt;    /// smoothed parameter eLOC1 error
-  std::vector<float> m_err_ePHI_smt;     /// smoothed parameter ePHI error
-  std::vector<float> m_err_eTHETA_smt;   /// smoothed parameter eTHETA error
-  std::vector<float> m_err_eQOP_smt;     /// smoothed parameter eQOP error
-  std::vector<float> m_err_eT_smt;       /// smoothed parameter eT error
-  std::vector<float> m_pull_eLOC0_smt;   /// smoothed parameter eLOC0 pull
-  std::vector<float> m_pull_eLOC1_smt;   /// smoothed parameter eLOC1 pull
-  std::vector<float> m_x_smt;            /// smoothed global x
-  std::vector<float> m_y_smt;            /// smoothed global y
-  std::vector<float> m_z_smt;            /// smoothed global z
-  std::vector<float> m_px_smt;           /// smoothed momentum px
-  std::vector<float> m_py_smt;           /// smoothed momentum py
-  std::vector<float> m_pz_smt;           /// smoothed momentum pz
-  std::vector<float> m_eta_smt;          /// smoothed momentum eta
-  std::vector<float> m_pT_smt;           /// smoothed momentum pT
-
+  SG::WriteHandleKey<TrackCollection> m_trackCollection { this, "FaserActsKFTrackCollection", "FaserActsKFTrackCollection", "Output track collection" };
 };
 
 #endif
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h
index 35baa239600d5f273dd91c2145eecfd8e1c8b52a..06924bed8e4adbe304697a4f71019d9134be72f8 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h
@@ -6,82 +6,88 @@
 
 #pragma once
 
-#include <unordered_map>
-#include <utility>
-
-// ACTS
 #include "Acts/EventData/MultiTrajectory.hpp"
 #include "Acts/EventData/TrackParameters.hpp"
-
-// PACKAGE
 #include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include <unordered_map>
+#include <utility>
 
-using IndexedParams = std::unordered_map<size_t, Acts::BoundTrackParameters>;
-
-struct FaserActsRecMultiTrajectory
-{
+/// Store reconstructed trajectories from track finding/fitting.
+///
+/// It contains a MultiTrajectory with a vector of entry indices for
+/// individual trajectories, and a map of fitted parameters indexed by the
+/// entry index. In case of track fitting, there is at most one trajectory
+/// in the MultiTrajectory; In case of track finding, there could be
+/// multiple trajectories in the MultiTrajectory.
+struct FaserActsRecMultiTrajectory {
  public:
-  FaserActsRecMultiTrajectory() = default;
+  /// (Reconstructed) trajectory with multiple states.
+  using MultiTrajectory = ::Acts::MultiTrajectory<IndexSourceLink>;
+  /// Fitted parameters identified by indices in the multi trajectory.
+  using IndexedParams = std::unordered_map<size_t, Acts::BoundTrackParameters>;
 
-  FaserActsRecMultiTrajectory(const Acts::MultiTrajectory<IndexSourceLink>& multiTraj,
-                     const std::vector<size_t>& tTips,
-                     const IndexedParams& parameters)
+  /// Default construct an empty object. Required for container compatibility
+  /// and to signal an error.
+  FaserActsRecMultiTrajectory() = default;
+  /// Construct from fitted multi trajectory and parameters.
+  ///
+  /// @param multiTraj The multi trajectory
+  /// @param tTips Tip indices that identify valid trajectories
+  /// @param parameters Fitted track parameters indexed by trajectory index
+  FaserActsRecMultiTrajectory(const MultiTrajectory& multiTraj,
+                              const std::vector<size_t>& tTips,
+                              const IndexedParams& parameters)
       : m_multiTrajectory(multiTraj),
         m_trackTips(tTips),
         m_trackParameters(parameters) {}
 
-  FaserActsRecMultiTrajectory(const FaserActsRecMultiTrajectory& rhs)
-      : m_multiTrajectory(rhs.m_multiTrajectory),
-        m_trackTips(rhs.m_trackTips),
-        m_trackParameters(rhs.m_trackParameters) {}
-
-  FaserActsRecMultiTrajectory(FaserActsRecMultiTrajectory&& rhs)
-      : m_multiTrajectory(std::move(rhs.m_multiTrajectory)),
-        m_trackTips(std::move(rhs.m_trackTips)),
-        m_trackParameters(std::move(rhs.m_trackParameters)) {}
-
-  ~FaserActsRecMultiTrajectory() = default;
-
-  FaserActsRecMultiTrajectory& operator=(const FaserActsRecMultiTrajectory& rhs) {
-    m_multiTrajectory = rhs.m_multiTrajectory;
-    m_trackTips = rhs.m_trackTips;
-    m_trackParameters = rhs.m_trackParameters;
-    return *this;
-  }
+  /// Return true if there exists no valid trajectory.
+  bool empty() const { return m_trackTips.empty(); }
 
-  FaserActsRecMultiTrajectory& operator=(FaserActsRecMultiTrajectory&& rhs) {
-    m_multiTrajectory = std::move(rhs.m_multiTrajectory);
-    m_trackTips = std::move(rhs.m_trackTips);
-    m_trackParameters = std::move(rhs.m_trackParameters);
-    return *this;
-  }
+  /// Access the underlying multi trajectory.
+  const MultiTrajectory& multiTrajectory() const { return m_multiTrajectory; }
 
-  bool hasTrajectory(const size_t& entryIndex) const {
-    return std::count(m_trackTips.begin(), m_trackTips.end(), entryIndex) > 0;
-  }
+  /// Access the tip indices that identify valid trajectories.
+  const std::vector<size_t>& tips() const { return m_trackTips; }
 
-  bool hasTrackParameters(const size_t& entryIndex) const {
-    return m_trackParameters.count(entryIndex) > 0;
+  /// Check if a trajectory exists for the given index.
+  ///
+  /// @param entryIndex The trajectory entry index
+  /// @return Whether there is trajectory with provided entry index
+  bool hasTrajectory(size_t entryIndex) const {
+    return (0 < std::count(m_trackTips.begin(), m_trackTips.end(), entryIndex));
   }
 
-  std::pair<std::vector<size_t>, Acts::MultiTrajectory<IndexSourceLink>>
-  trajectory() const {
-    return std::make_pair(m_trackTips, m_multiTrajectory);
+  /// Check if fitted track parameters exists for the given index.
+  ///
+  /// @param entryIndex The trajectory entry index
+  /// @return Whether having fitted track parameters or not
+  bool hasTrackParameters(size_t entryIndex) const {
+    return (0 < m_trackParameters.count(entryIndex));
   }
 
-  const Acts::BoundTrackParameters& trackParameters(const size_t& entryIndex) const {
+  /// Access the fitted track parameters for the given index.
+  ///
+  /// @param entryIndex The trajectory entry index
+  /// @return The fitted track parameters of the trajectory
+  const Acts::BoundTrackParameters& trackParameters(size_t entryIndex) const {
     auto it = m_trackParameters.find(entryIndex);
-    if (it != m_trackParameters.end()) {
-      return it->second;
-    } else {
+    if (it == m_trackParameters.end()) {
       throw std::runtime_error(
           "No fitted track parameters for trajectory with entry index = " +
           std::to_string(entryIndex));
     }
+    return it->second;
   }
 
- private:
-  Acts::MultiTrajectory<IndexSourceLink> m_multiTrajectory;
+private:
+  // The multiTrajectory
+  MultiTrajectory m_multiTrajectory;
+  // The entry indices of trajectories stored in multiTrajectory
   std::vector<size_t> m_trackTips = {};
+  // The fitted parameters at the provided surface for individual trajectories
   IndexedParams m_trackParameters = {};
 };
+
+/// Container for multiple trajectories.
+using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/GhostBusters.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/GhostBusters.h
new file mode 100644
index 0000000000000000000000000000000000000000..f8ac55d6cbb62c20ac2de7ce9c8b5741a2bfe8cb
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/GhostBusters.h
@@ -0,0 +1,75 @@
+#ifndef FASERACTSKALMANFILTER_GHOSTBUSTERS_H
+#define FASERACTSKALMANFILTER_GHOSTBUSTERS_H
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthHistogramming.h"
+#include "TrkTrack/TrackCollection.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+#include "FaserActsKalmanFilter/TrackClassification.h"
+
+
+class TTree;
+class FaserSCT_ID;
+
+class GhostBusters : public AthReentrantAlgorithm, AthHistogramming {
+public:
+  GhostBusters(const std::string &name, ISvcLocator *pSvcLocator);
+  virtual ~GhostBusters() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext &ctx) const override;
+  virtual StatusCode finalize() override;
+  const ServiceHandle <ITHistSvc> &histSvc() const;
+
+private:
+  struct Segment {
+  public:
+    Segment(const Trk::Track *track, const FaserSCT_ID *idHelper);
+    // Segment(const Trk::Track *track, const TrackerSimDataCollection &simDataCollection,
+    //         const FaserSCT_ID *idHelper);
+    inline int station() const { return m_station; }
+    inline Amg::Vector3D position() const { return m_position; }
+    inline double x() const { return m_position.x(); }
+    inline double y() const { return m_position.y(); }
+    inline double z() const { return m_position.z(); }
+    inline double chi2() const { return m_chi2; }
+    inline size_t size() const { return m_size; }
+    inline bool isGhost() const { return m_isGhost; }
+    // inline size_t majorityHits() const { return m_particleHitCounts.empty() ? -1 : m_particleHitCounts.front().hitCount; }
+    inline const Trk::Track *track() const { return m_track; }
+    inline void setGhost() { m_isGhost = true; }
+   private:
+    // std::vector<ParticleHitCount> m_particleHitCounts {};
+    size_t m_size;
+    Amg::Vector3D m_position;
+    int m_station;
+    double m_chi2;
+    bool m_isGhost = false;
+    const Trk::Track *m_track;
+  };
+
+  ServiceHandle <ITHistSvc> m_histSvc;
+  SG::WriteHandleKey<TrackCollection> m_outputTrackCollection { this, "OutputCollection", "Segments", "Output track collection name" };
+  SG::ReadHandleKey<TrackCollection> m_trackCollection { this, "TrackCollection", "SegmentFit", "Input track collection name" };
+  // SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey { this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+  DoubleProperty m_xTolerance {this, "xTolerance", 0.5};
+  DoubleProperty m_yTolerance {this, "yTolerance", 0.25};
+
+  const FaserSCT_ID *m_idHelper;
+
+  mutable TTree* m_tree;
+  mutable unsigned int m_event_number;
+  mutable double m_x;
+  mutable double m_y;
+  mutable double m_z;
+  mutable double m_chi2;
+  mutable unsigned int m_station;
+  // mutable unsigned int m_majorityHits;
+  mutable unsigned int m_size;
+  mutable bool m_isGhost;
+};
+
+inline const ServiceHandle <ITHistSvc> &GhostBusters::histSvc() const {
+  return m_histSvc;
+}
+
+#endif  // FASERACTSKALMANFILTER_GHOSTBUSTERS_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackFinderTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackFinderTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..6fbc80c422cb3d83122a7c9ec9524da527221d15
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackFinderTool.h
@@ -0,0 +1,27 @@
+#ifndef FASERACTSKALMANFILTER_ITRACKFINDERTOOL_H
+#define FASERACTSKALMANFILTER_ITRACKFINDERTOOL_H
+
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/IAlgTool.h"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "FaserActsKalmanFilter/IdentifierLink.h"
+#include "FaserActsKalmanFilter/Measurement.h"
+#include "Acts/EventData/TrackParameters.hpp"
+
+class ITrackFinderTool : virtual public IAlgTool {
+public:
+  DeclareInterfaceID(ITrackFinderTool, 1, 0);
+
+  // TODO use Acts::BoundTrackParameters instead?
+  virtual StatusCode run() = 0;
+  virtual const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const = 0;
+  virtual const std::shared_ptr<const Acts::Surface> initialSurface() const = 0;
+  virtual const std::shared_ptr<std::vector<std::vector<IndexSourceLink>>> sourceLinks() const = 0;
+  virtual const std::shared_ptr<std::vector<IdentifierLink>> idLinks() const = 0;
+  virtual const std::shared_ptr<std::vector<std::vector<Measurement>>> measurements() const = 0;
+  virtual const std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>> spacePoints() const = 0;
+  virtual const std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>> clusters() const = 0;
+};
+
+#endif  // FASERACTSKALMANFILTER_ITRACKFINDERTOOL_H
\ No newline at end of file
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackSeedTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackSeedTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..0b664f78ec6e60f1d75f504a343221970d0b938d
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ITrackSeedTool.h
@@ -0,0 +1,29 @@
+#ifndef FASERACTSKALMANFILTER_ITRACKSEEDTOOL_H
+#define FASERACTSKALMANFILTER_ITRACKSEEDTOOL_H
+
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/IAlgTool.h"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "FaserActsKalmanFilter/IdentifierLink.h"
+#include "FaserActsKalmanFilter/Measurement.h"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+
+class ITrackSeedTool : virtual public IAlgTool {
+public:
+  DeclareInterfaceID(ITrackSeedTool, 1, 0);
+
+  virtual StatusCode run() = 0;
+  virtual const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const = 0;
+  virtual const std::shared_ptr<const Acts::Surface> initialSurface() const = 0;
+  virtual const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const = 0;
+  virtual const std::shared_ptr<IdentifierLink> idLinks() const = 0;
+  virtual const std::shared_ptr<std::vector<Measurement>> measurements() const = 0;
+  virtual const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters() const = 0;
+  virtual const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> seedClusters() const = 0;
+  virtual const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> spacePoints() const = 0;
+  virtual double targetZPosition() const = 0;
+};
+
+#endif  // FASERACTSKALMANFILTER_ITRACKSEEDTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IdentifierLink.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IdentifierLink.h
new file mode 100644
index 0000000000000000000000000000000000000000..5d236afec358b683ee5812889656d353a62d4924
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IdentifierLink.h
@@ -0,0 +1,10 @@
+#ifndef FASERACTSKALMANFILTER_IDENTIFIERLINK_H
+#define FASERACTSKALMANFILTER_IDENTIFIERLINK_H
+
+#include "Identifier/Identifier.h"
+
+using Index = uint32_t;
+
+using IdentifierLink = std::map<Index, Identifier>;
+
+#endif  // FASERACTSKALMANFILTER_IDENTIFIERLINK_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IndexSourceLink.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IndexSourceLink.h
index 99d101519030fca1d4fd7904bc7549b22a39a6c2..79973020943627dc32655fecb3a24437bb0c1284 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IndexSourceLink.h
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/IndexSourceLink.h
@@ -15,6 +15,7 @@
 #include <boost/container/flat_map.hpp>
 
 #include "FaserActsKalmanFilter/FaserActsGeometryContainers.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
 
 /// Index type to reference elements in a container.
 ///
@@ -34,10 +35,10 @@ using Index = uint32_t;
 class IndexSourceLink final {
 public:
     /// Construct from geometry identifier and index.
-    constexpr IndexSourceLink(Acts::GeometryIdentifier gid, Index idx)
-            : m_geometryId(gid), m_index(idx) {}
+  IndexSourceLink(Acts::GeometryIdentifier gid, Index idx, const Tracker::FaserSCT_Cluster* hit)
+  : m_geometryId(gid), m_index(idx), m_hit(hit) {}
 
-    // Construct an invalid source link. Must be default constructible to
+  // Construct an invalid source link. Must be default constructible to
     /// satisfy SourceLinkConcept.
     IndexSourceLink() = default;
     IndexSourceLink(const IndexSourceLink&) = default;
@@ -49,10 +50,13 @@ public:
     constexpr Acts::GeometryIdentifier geometryId() const { return m_geometryId; }
     /// Access the index.
     constexpr Index index() const { return m_index; }
+    /// Access the Tracker::FaserSCT_Cluster hit
+    constexpr const Tracker::FaserSCT_Cluster* hit() const { return m_hit; }
 
 private:
     Acts::GeometryIdentifier m_geometryId;
     Index m_index;
+    const Tracker::FaserSCT_Cluster* m_hit;
 
     friend constexpr bool operator==(const IndexSourceLink& lhs,
                                      const IndexSourceLink& rhs) {
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/KalmanFitterTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/KalmanFitterTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..8bf92e91580d7d1839fe2ebfaf78bdaba73295a3
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/KalmanFitterTool.h
@@ -0,0 +1,72 @@
+#ifndef FASERACTSKALMANFILTER_KALMANFITTERTOOL_H
+#define FASERACTSKALMANFILTER_KALMANFITTERTOOL_H
+
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/TrackFitting/KalmanFitter.hpp"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "FaserActsKalmanFilter/Measurement.h"
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include "MagFieldConditions/FaserFieldCacheCondObj.h"
+#include "FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h"
+#include "FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h"
+#include "TrkTrack/Track.h"
+
+
+class FaserSCT_ID;
+
+class KalmanFitterTool : virtual public AthAlgTool {
+public:
+  KalmanFitterTool(const std::string &type, const std::string &name, const IInterface *parent);
+  virtual ~KalmanFitterTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+
+  using TrackParameters = Acts::BoundTrackParameters;
+  using IndexedParams = std::unordered_map<size_t, TrackParameters>;
+  using TrackFitterOptions =
+      Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder, Acts::VoidReverseFilteringLogic>;
+  using TrackFitterResult = Acts::Result<Acts::KalmanFitterResult<IndexSourceLink>>;
+  class TrackFitterFunction {
+  public:
+    virtual ~TrackFitterFunction() = default;
+    virtual TrackFitterResult operator()(const std::vector<IndexSourceLink>&,
+                                         const TrackParameters&,
+                                         const TrackFitterOptions&) const = 0;
+  };
+  static std::shared_ptr<TrackFitterFunction> makeTrackFitterFunction(
+      std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry);
+
+  virtual Acts::MagneticFieldContext getMagneticFieldContext(const EventContext& ctx) const;
+  std::unique_ptr<Trk::Track> fit(const EventContext &ctx, const Acts::GeometryContext &gctx, const Trk::Track &inputTrack,
+                        std::vector<FaserActsRecMultiTrajectory> & /*trajectories*/,
+                        const Acts::BoundVector& inputVector = Acts::BoundVector::Zero(), bool isMC=false, double origin=0) const;
+
+private:
+  const FaserSCT_ID* m_idHelper {nullptr};
+  std::tuple<std::vector<IndexSourceLink>, std::vector<Measurement>>
+  getMeasurementsFromTrack(const Trk::Track &track) const;
+  // Acts::BoundTrackParameters getParametersFromTrack(const Acts::BoundVector& params, const Trk::TrackParameters *inputParameters) const;
+  Acts::BoundTrackParameters getParametersFromTrack(const Trk::TrackParameters *inputParameters, const Acts::BoundVector& inputVector, double origin) const;
+  std::shared_ptr<TrackFitterFunction> m_fit;
+  std::unique_ptr<const Acts::Logger> m_logger;
+  Gaudi::Property<std::string> m_actsLogging {this, "ActsLogging", "VERBOSE"};
+  Gaudi::Property<std::size_t> m_minMeasurements {this, "MinMeasurements", 12, "minimum number of measurements of the input track"};
+  Gaudi::Property<double> m_seedCovarianceScale {this, "SeedCovarianceScale", 100, "scale covariance from initial track"};
+  Gaudi::Property<bool> m_isMC {this, "isMC", false};
+  Gaudi::Property<bool> m_summaryWriter {this, "SummaryWriter", false};
+  Gaudi::Property<bool> m_statesWriter {this, "StatesWriter", false};
+  Gaudi::Property<bool> m_noDiagnostics {this, "noDiagnostics", true, "Set ACTS logging level to INFO and do not run performance writer, states writer or summary writer"};
+
+  SG::ReadCondHandleKey<FaserFieldCacheCondObj> m_fieldCondObjInputKey {this, "FaserFieldCacheCondObj", "fieldCondObj", "Name of the Magnetic Field conditions object key"};
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  ToolHandle<RootTrajectoryStatesWriterTool> m_trajectoryStatesWriterTool {this, "RootTrajectoryStatesWriterTool", "RootTrajectoryStatesWriterTool"};
+  ToolHandle<RootTrajectorySummaryWriterTool> m_trajectorySummaryWriterTool {this, "RootTrajectorySummaryWriterTool", "RootTrajectorySummaryWriterTool"};
+  std::unique_ptr<Trk::Track> makeTrack(const Acts::GeometryContext &gctx, TrackFitterResult& fitResult) const;
+  const Trk::TrackParameters* ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const;
+};
+
+#endif //FASERACTSKALMANFILTER_KALMANFITTERTOOL_H
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/LinearFit.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/LinearFit.h
new file mode 100644
index 0000000000000000000000000000000000000000..f367110a4b7a05cb9bc610628995cfa6df53ddb0
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/LinearFit.h
@@ -0,0 +1,26 @@
+#ifndef FASERACTSKALMANFILTER_LINEARFIT_H
+#define FASERACTSKALMANFILTER_LINEARFIT_H
+
+#include "Acts/Definitions/Algebra.hpp"
+
+namespace LinearFit {
+
+using Vector2 = Acts::Vector2;
+
+std::pair <Vector2, Vector2 > linearFit(const std::vector<Vector2> &points) {
+  size_t nPoints = points.size();
+  Eigen::Matrix< Vector2::Scalar, Eigen::Dynamic, Eigen::Dynamic > centers(nPoints, 2);
+  for (size_t i = 0; i < nPoints; ++i) centers.row(i) = points[i];
+
+  Vector2 origin = centers.colwise().mean();
+  Eigen::MatrixXd centered = centers.rowwise() - origin.transpose();
+  Eigen::MatrixXd cov = centered.adjoint() * centered;
+  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eig(cov);
+  Vector2 axis = eig.eigenvectors().col(1).normalized();
+
+  return std::make_pair(origin, axis);
+}
+
+}  // namespace LinearFit
+
+#endif  // FASERACTSKALMANFILTER_LINEARFIT_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MultiTrackFinderTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MultiTrackFinderTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..56e5db2666157c330e91258776b3a6b34af44278
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MultiTrackFinderTool.h
@@ -0,0 +1,232 @@
+#ifndef FASERACTSKALMANFILTER_MULTITRACKFINDERTOOL_H
+#define FASERACTSKALMANFILTER_MULTITRACKFINDERTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/EventContext.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+#include "StoreGate/ReadHandleKey.h"
+#include <string>
+#include <vector>
+
+#include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackFinderTool.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrkTrack/TrackCollection.h"
+
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+
+class MultiTrackFinderTool : public extends<AthAlgTool, ITrackFinderTool> {
+public:
+  MultiTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~MultiTrackFinderTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  virtual const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const override;
+  virtual const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<IndexSourceLink>>> sourceLinks() const override;
+  virtual const std::shared_ptr<std::vector<IdentifierLink>> idLinks() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<Measurement>>> measurements() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>> spacePoints() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>> clusters() const override;
+
+  struct Tracklet {
+  public:
+    Tracklet(const std::vector<Identifier>& ids,
+             const std::vector<Acts::GeometryIdentifier>& geoIds,
+             const std::vector<double>& positions,
+             const Amg::Vector3D& fitPosition,
+             const std::vector<const Tracker::FaserSCT_Cluster*>& clusters)
+        : m_ids(ids), m_geoIds(geoIds), m_positions(positions), m_fitPosition(fitPosition), m_clusters(clusters) {};
+
+    std::vector<Identifier> ids() const { return m_ids; };
+    std::vector<Acts::GeometryIdentifier> geoIds() const { return m_geoIds; }
+    std::vector<double> clusterPositions() const { return m_positions; }
+    const std::vector<const Tracker::FaserSCT_Cluster*>& clusters() const { return  m_clusters; }
+    Amg::Vector3D position() const { return  m_fitPosition; }
+
+  private:
+    std::vector<Identifier> m_ids;
+    std::vector<Acts::GeometryIdentifier> m_geoIds;
+    std::vector<double> m_positions;
+    Amg::Vector3D m_fitPosition;
+    std::vector<const Tracker::FaserSCT_Cluster*> m_clusters;
+  };
+
+  struct ProtoTrack {
+  public:
+    ProtoTrack(const MultiTrackFinderTool::Tracklet& t1,
+               const MultiTrackFinderTool::Tracklet& t2,
+               const MultiTrackFinderTool::Tracklet& t3)
+        : m_t1(t1), m_t2(t2), m_t3(t3) {}
+
+    Acts::CurvilinearTrackParameters initialTrackParameters(
+        double covLoc0, double covLoc1, double covPhi, double covTheta, double covQOverP, double covTime) const {
+      Acts::Vector3 dir = m_t2.position() - m_t1.position();
+      Acts::Vector3 pos = m_t1.position() - m_t1.position().z()/dir.z() * dir;
+      Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+      auto [abs_momentum, charge] = momentum({{1, m_t1.position()}, {2, m_t2.position()}, {3, m_t3.position()}});
+
+      Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+      cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = covLoc0;
+      cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = covLoc1;
+      cov(Acts::eBoundPhi, Acts::eBoundPhi) = covPhi;
+      cov(Acts::eBoundTheta, Acts::eBoundTheta) = covTheta;
+      cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = covQOverP;
+      cov(Acts::eBoundTime, Acts::eBoundTime) = covTime;
+
+      Acts::CurvilinearTrackParameters params =
+          Acts::CurvilinearTrackParameters(pos4, dir, abs_momentum, charge, cov);
+      return params;
+    }
+
+     std::tuple<std::vector<Measurement>, std::vector<IndexSourceLink>, std::map<Index, Identifier>, std::vector<const Tracker::FaserSCT_Cluster*>> run() const {
+       const int kSize = 1;
+       using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+       std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0};
+       std::vector<IndexSourceLink> sourceLinks;
+       std::map<Index, Identifier> idLinks;
+       std::vector<Measurement> measurements;
+       std::vector<const Tracker::FaserSCT_Cluster*> clusters;
+       clusters.reserve(m_t1.clusters().size() + m_t2.clusters().size() + m_t3.clusters().size());
+       clusters.insert(clusters.end(), m_t1.clusters().begin(), m_t1.clusters().end());
+       clusters.insert(clusters.end(), m_t2.clusters().begin(), m_t2.clusters().end());
+       clusters.insert(clusters.end(), m_t3.clusters().begin(), m_t3.clusters().end());
+
+       for (const MultiTrackFinderTool::Tracklet& tracklet : {m_t1, m_t2, m_t3}) {
+         // FIXME check that ids, geoIds and positions have the same size
+         auto ids = tracklet.ids();
+         auto geoIds = tracklet.geoIds();
+         auto positions = tracklet.clusterPositions();
+         for (size_t i = 0; i < tracklet.ids().size(); ++i) {
+           idLinks[measurements.size()] = ids[i];
+           IndexSourceLink sourceLink(geoIds[i], measurements.size());
+           Eigen::Matrix<double, 1, 1> clusterPos {positions[i]};
+           Eigen::Matrix<double, 1, 1> clusterCov {0.04 * 0.04,};
+           ThisMeasurement meas(sourceLink, Indices, clusterPos, clusterCov);
+           sourceLinks.push_back(sourceLink);
+           measurements.emplace_back(std::move(meas));
+         }
+       }
+       return std::make_tuple(measurements, sourceLinks, idLinks, clusters);
+    }
+
+    double chi2() const {
+      return calc_chi2({m_t1.position(), m_t2.position(), m_t3.position()});
+    }
+
+  private:
+    Tracklet m_t1, m_t2, m_t3;
+
+    static std::pair<double, double> momentum(const std::map<int, Amg::Vector3D>& pos, double B=0.57) {
+      Acts::Vector3 vec_l = pos.at(3) - pos.at(1);
+      double abs_l = std::sqrt(vec_l.y() * vec_l.y() + vec_l.z() * vec_l.z());
+      double t = (pos.at(2).z() - pos.at(1).z()) / (pos.at(3).z() - pos.at(1).z());
+      Acts::Vector3 vec_m = pos.at(1) + t * vec_l;
+      Acts::Vector3 vec_s = pos.at(2) - vec_m;
+      double abs_s = std::sqrt(vec_s.y() * vec_s.y() + vec_s.z() * vec_s.z());
+      double p_yz = 0.3 * abs_l * abs_l * B / (8 * abs_s * 1000);
+      double charge = vec_s.y() < 0 ? 1 : -1;
+      return std::make_pair(p_yz, charge);
+    }
+
+    static std::pair<Acts::Vector3, Acts::Vector3> linear_fit(const std::vector<Acts::Vector3>& hits) {
+      size_t n_hits = hits.size();
+      Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> centers(n_hits, 3);
+      for (size_t i = 0; i < n_hits; ++i) centers.row(i) = hits[i];
+      Acts::Vector3 origin = centers.colwise().mean();
+      Eigen::MatrixXd centered = centers.rowwise() - origin.transpose();
+      Eigen::MatrixXd cov = centered.adjoint() * centered;
+      Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eig(cov);
+      Acts::Vector3 axis = eig.eigenvectors().col(2).normalized();
+      return std::make_pair(origin, axis);
+    }
+
+    double calc_chi2(const std::vector<Acts::Vector3>& hits) const {
+      auto [origin, axis] = linear_fit(hits);
+      double chi2 = 0;
+      for (const Acts::Vector3& hit : hits) {
+        Acts::Vector3 exp = origin + (hit.z() - origin.z()) * axis/axis.z();
+        chi2 += (exp.x() - hit.x()) * (exp.x() - hit.x()) + (exp.y() - hit.y()) * (exp.y() - hit.y());
+      }
+      return chi2;
+    }
+  };
+
+  struct sort_chi2 {
+    inline bool operator() (const ProtoTrack& track1, const ProtoTrack& track2) {
+      return (track1.chi2() < track2.chi2());
+    }
+  };
+
+private:
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<std::vector<IndexSourceLink>>> m_sourceLinks {};
+  std::shared_ptr<std::vector<IdentifierLink>> m_idLinks {};
+  std::shared_ptr<std::vector<std::vector<Measurement>>> m_measurements {};
+  std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>> m_spacePoints {};
+  std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>> m_clusters {};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {
+      this, "TrackCollection", "SegmentFit", "Input track collection name" };
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+};
+
+inline const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>
+MultiTrackFinderTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+MultiTrackFinderTool::initialSurface() const {
+  return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<IndexSourceLink>>>
+MultiTrackFinderTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<std::vector<IdentifierLink>>
+MultiTrackFinderTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<Measurement>>>
+MultiTrackFinderTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>>
+MultiTrackFinderTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>>
+MultiTrackFinderTool::clusters() const {
+  return m_clusters;
+}
+
+#endif // FASERACTSKALMANFILTER_MULTITRACKFINDERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MyAmbiguitySolver.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MyAmbiguitySolver.h
new file mode 100644
index 0000000000000000000000000000000000000000..47257befa2fcbd70da95d1ee88bd41d0fdabeb2a
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MyAmbiguitySolver.h
@@ -0,0 +1,80 @@
+#ifndef FASERACTSKALMANFILTER_AMBIGUITYSOLVER_H
+#define FASERACTSKALMANFILTER_AMBIGUITYSOLVER_H
+
+#include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+
+using CombinatorialKalmanFilterResult = Acts::CombinatorialKalmanFilterResult<IndexSourceLink>;
+using TrackFitterResult = Acts::Result<CombinatorialKalmanFilterResult>;
+using TrackFinderResult = std::vector<TrackFitterResult>;
+
+
+size_t numberMeasurements(const CombinatorialKalmanFilterResult& ckfResult) {
+  auto traj = FaserActsRecMultiTrajectory(ckfResult.fittedStates, ckfResult.lastMeasurementIndices, ckfResult.fittedParameters);
+  const auto& mj = traj.multiTrajectory();
+  const auto& trackTips = traj.tips();
+  size_t maxMeasurements = 0;
+  for (const auto& trackTip : trackTips) {
+    auto trajState = Acts::MultiTrajectoryHelpers::trajectoryState(mj, trackTip);
+    size_t nMeasurements = trajState.nMeasurements;
+    if (nMeasurements > maxMeasurements) {
+      maxMeasurements = nMeasurements;
+    }
+    std::cout << "# measurements: " << trajState.nMeasurements << std::endl;
+  }
+  return maxMeasurements;
+}
+
+int countSharedHits(const CombinatorialKalmanFilterResult& result1, const CombinatorialKalmanFilterResult& result2) {
+  int count = 0;
+  std::vector<size_t> hitIndices {};
+
+  for (auto measIndex : result1.lastMeasurementIndices) {
+    result1.fittedStates.visitBackwards(measIndex, [&](const auto& state) {
+      if (not state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag))
+        return;
+      size_t hitIndex = state.uncalibrated().index();
+      hitIndices.emplace_back(hitIndex);
+    });
+  }
+
+  for (auto measIndex : result2.lastMeasurementIndices) {
+    result2.fittedStates.visitBackwards(measIndex, [&](const auto& state) {
+      if (not state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag))
+        return;
+      size_t hitIndex = state.uncalibrated().index();
+      if (std::find(hitIndices.begin(), hitIndices.end(), hitIndex) != hitIndices.end()) {
+        count += 1;
+      }
+    });
+  }
+  return count;
+}
+
+
+std::pair<int, int> solveAmbiguity(TrackFinderResult& results, size_t minMeasurements = 13) {
+  std::map<std::pair<size_t, size_t>, size_t> trackPairs {};
+  for (size_t i = 0; i < results.size(); ++i) {
+    // if (not results.at(i).ok()) continue;
+    // if (numberMeasurements(results.at(i).value()) < minMeasurements) continue;
+    for (size_t j = i+1; j < results.size(); ++j) {
+      // if (not results.at(j).ok()) continue;
+      // if (numberMeasurements(results.at(j).value()) < minMeasurements) continue;
+      int n = countSharedHits(results.at(i).value(), results.at(j).value());
+      trackPairs[std::make_pair(i, j)] = n;
+    }
+  }
+
+  std::pair<size_t, size_t> bestTrackPair;
+  size_t minSharedHits = std::numeric_limits<size_t>::max();
+  for (const auto& trackPair : trackPairs) {
+    if (trackPair.second < minSharedHits) {
+      minSharedHits = trackPair.second;
+      bestTrackPair = trackPair.first;
+    }
+  }
+
+  return bestTrackPair;
+}
+
+#endif //FASERACTSKALMANFILTER_AMBIGUITYSOLVER_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MyTrackSeedTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MyTrackSeedTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..abc4e987434c536b37a20862bef87fc5fe8cbbac
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/MyTrackSeedTool.h
@@ -0,0 +1,123 @@
+#ifndef FASERACTSKALMANFILTER_MYTRACKSEEDTOOL_H
+#define FASERACTSKALMANFILTER_MYTRACKSEEDTOOL_H
+
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include "TrkTrack/TrackCollection.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+class FaserSCT_ID;
+namespace TrackerDD { class SCT_DetectorManager; }
+
+
+class MyTrackSeedTool : public extends<AthAlgTool, ITrackSeedTool> {
+public:
+  MyTrackSeedTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~MyTrackSeedTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const override;
+  const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override;
+  const std::shared_ptr<IdentifierLink> idLinks() const override;
+  const std::shared_ptr<std::vector<Measurement>> measurements() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters() const override;
+  const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> seedClusters() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> spacePoints() const override;
+  double targetZPosition() const override;
+
+private:
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> m_clusters {};
+  std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> m_seedClusters {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> m_spacePoints {};
+  double m_targetZPosition {0.};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {
+      this, "TrackCollection", "SegmentFit", "Input track collection name" };
+  SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainerKey {
+      this, "ClusterContainer", "SCT_ClusterContainer"};
+  SG::ReadHandleKey<FaserSCT_SpacePointContainer> m_spacePointContainerKey {
+      this, "SpacePoints", "SCT_SpacePointContainer"};
+
+  // position resolution of a cluster
+  Gaudi::Property<double> m_std_cluster {this, "std_cluster", 0.04};
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+  Gaudi::Property<double> m_origin {this, "origin", 0, "z position of the reference surface"};
+
+  static Acts::CurvilinearTrackParameters get_params(const Amg::Vector3D& p1, const Amg::Vector3D& p2, const Acts::BoundSymMatrix& cov, double origin);
+  // static std::pair<double, double> momentum(const std::map<int, Amg::Vector3D>& pos, double B=0.57);
+};
+
+inline const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>
+MyTrackSeedTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+MyTrackSeedTool::initialSurface() const {
+  return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<IndexSourceLink>>
+MyTrackSeedTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+MyTrackSeedTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<Measurement>>
+MyTrackSeedTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>>
+MyTrackSeedTool::clusters() const {
+  return m_clusters;
+}
+
+inline const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>>
+MyTrackSeedTool::seedClusters() const {
+  return m_seedClusters;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>>
+MyTrackSeedTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline double MyTrackSeedTool::targetZPosition() const {
+  return m_targetZPosition;
+}
+
+#endif  // FASERACTSKALMANFILTER_MYTRACKSEEDTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/PerformanceWriterTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/PerformanceWriterTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..198e4515988226e71e76c07b1a812ca00e64c641
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/PerformanceWriterTool.h
@@ -0,0 +1,51 @@
+#ifndef FASERACTSKALMANFILTER_PERFORMANCEWRITER_H
+#define FASERACTSKALMANFILTER_PERFORMANCEWRITER_H
+
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "FaserActsKalmanFilter/ResPlotTool.h"
+#include "FaserActsKalmanFilter/EffPlotTool.h"
+#include "FaserActsKalmanFilter/SummaryPlotTool.h"
+#include "FaserActsGeometryInterfaces/IFaserActsExtrapolationTool.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+#include "GeneratorObjects/McEventCollection.h"
+#include "Acts/Geometry/GeometryContext.hpp"
+class TFile;
+struct FaserActsRecMultiTrajectory;
+using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
+
+class PerformanceWriterTool : public AthAlgTool {
+public:
+  PerformanceWriterTool(const std::string& type, const std::string& name, const IInterface* parent);
+  ~PerformanceWriterTool() override =  default;
+
+  StatusCode initialize() override;
+  StatusCode finalize() override;
+
+  StatusCode write(const Acts::GeometryContext& geoContext, const TrajectoriesContainer& trajectories);
+
+private:
+  std::unique_ptr<const Acts::BoundTrackParameters> extrapolateToReferenceSurface(
+      const EventContext& ctx, const HepMC::GenParticle* particle) const;
+  SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey {
+      this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+  SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey {
+      this, "McEventCollection", "BeamTruthEvent"};
+  ToolHandle<IFaserActsExtrapolationTool> m_extrapolationTool {
+      this, "ExtrapolationTool", "FaserActsExtrapolationTool"};
+  Gaudi::Property<bool> m_noDiagnostics {this, "noDiagnostics", true, "Set ACTS logging level to INFO and do not run performance writer, states writer or summary writer"};
+  Gaudi::Property<std::string> m_filePath{this, "FilePath", "performance_ckf.root"};
+  TFile* m_outputFile{nullptr};
+
+  /// Plot tool for residuals and pulls.
+  ResPlotTool m_resPlotTool;
+  ResPlotTool::ResPlotCache m_resPlotCache;
+  /// Plot tool for efficiency
+  EffPlotTool m_effPlotTool;
+  EffPlotTool::EffPlotCache m_effPlotCache;
+  /// Plot tool for track hit info
+  SummaryPlotTool m_summaryPlotTool;
+  SummaryPlotTool::SummaryPlotCache m_summaryPlotCache;
+};
+
+#endif  // FASERACTSKALMANFILTER_PERFORMANCEWRITER_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/PlotHelpers.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/PlotHelpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..c3e8a1caf22d1debbea709c8fbbfe3c8a1b695c2
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/PlotHelpers.h
@@ -0,0 +1,113 @@
+#ifndef FASERACTSKALMANFILTER_PLOTHELPERS_H
+#define FASERACTSKALMANFILTER_PLOTHELPERS_H
+
+#include "TEfficiency.h"
+#include "TFitResult.h"
+#include "TFitResultPtr.h"
+#include "TH1F.h"
+#include "TH2F.h"
+#include "TProfile.h"
+#include <string>
+
+namespace PlotHelpers {
+
+struct Binning {
+  Binning() = default;
+
+  Binning(std::string bTitle, int bins, float bMin, float bMax)
+      : title(bTitle), nBins(bins), min(bMin), max(bMax) {};
+
+  std::string title;  ///< title to be displayed
+  int nBins;          ///< number of bins
+  float min;          ///< minimum value
+  float max;          ///< maximum value
+};
+
+
+/// @brief book a 1D histogram
+/// @param histName the name of histogram
+/// @param histTitle the title of histogram
+/// @param varBinning the binning info of variable
+/// @return histogram pointer
+TH1F *bookHisto(const char *histName, const char *histTitle,
+                const Binning &varBinning);
+
+/// @brief book a 2D histogram
+/// @param histName the name of histogram
+/// @param histTitle the title of histogram
+/// @param varXBinning the binning info of variable at x axis
+/// @param varYBinning the binning info of variable at y axis
+/// @return histogram pointer
+TH2F* bookHisto(const char* histName, const char* histTitle,
+                const Binning& varXBinning, const Binning& varYBinning);
+
+/// @brief fill a 1D histogram
+/// @param hist histogram to fill
+/// @param value value to fill
+/// @param weight weight to fill
+void fillHisto(TH1F* hist, float value, float weight = 1.0);
+
+/// @brief fill a 2D histogram
+/// @param hist histogram to fill
+/// @param xValue x value to fill
+/// @param yValue y value to fill
+/// @param weight weight to fill
+void fillHisto(TH2F* hist, float xValue, float yValue, float weight = 1.0);
+
+/// @brief extract details, i.e. mean and width of a 1D histogram and fill
+/// them into histograms
+/// @param inputHist histogram to investigate
+/// @param j  which bin number of meanHist and widthHist to fill
+/// @param meanHist histogram to fill the mean value of inputHist
+/// @param widthHist  histogram to fill the width value of inputHist
+///
+void anaHisto(TH1D* inputHist, int j, TH1F* meanHist, TH1F* widthHist);
+
+/// @brief book a 1D efficiency plot
+/// @param effName the name of plot
+/// @param effTitle the title of plot
+/// @param varBinning the binning info of variable
+/// @return TEfficiency pointer
+TEfficiency* bookEff(const char* effName, const char* effTitle, const Binning& varBinning);
+
+/// @brief book a 2D efficiency plot
+/// @param effName the name of plot
+/// @param effTitle the title of plot
+/// @param varXBinning the binning info of variable at x axis
+/// @param varYBinning the binning info of variable at y axis
+/// @return TEfficiency pointer
+TEfficiency* bookEff(const char* effName, const char* effTitle,
+                     const Binning& varXBinning, const Binning& varYBinning);
+
+/// @brief fill a 1D efficiency plot
+/// @param efficiency plot to fill
+/// @param value value to fill
+/// @param status bool to denote passed or not
+void fillEff(TEfficiency* efficiency, float value, bool status);
+
+/// @brief fill a 2D efficiency plot
+/// @param efficiency plot to fill
+/// @param xValue x value to fill
+/// @param yValue y value to fill
+/// @param status bool to denote passed or not
+void fillEff(TEfficiency* efficiency, float xValue, float yValue, bool status);
+
+/// @brief book a TProfile plot
+/// @param profName the name of plot
+/// @param profTitle the title of plot
+/// @param varXBinning the binning info of variable at x axis
+/// @param varYBinning the binning info of variable at y axis
+/// @return TProfile pointer
+TProfile* bookProf(const char* profName, const char* profTitle,
+                   const Binning& varXBinning, const Binning& varYBinning);
+
+/// @brief fill a TProfile plot
+/// @param profile plot to fill
+/// @param xValue  xvalue to fill
+/// @param yValue  yvalue to fill
+/// @param weight weight to fill
+void fillProf(TProfile* profile, float xValue, float yValue, float weight = 1.0);
+
+}  // namespace PlotHelpers
+
+#endif  // FASERACTSKALMANFILTER_PLOTHELPERS_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ProtoTrackWriterTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ProtoTrackWriterTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..a71ddb56ab7b70af4930fc5cd1369010a5e7446b
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ProtoTrackWriterTool.h
@@ -0,0 +1,61 @@
+#ifndef FASERACTSKALMANFILTER_PROTOTRACKWRITERTOOL_H
+#define FASERACTSKALMANFILTER_PROTOTRACKWRITERTOOL_H
+
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+#include "GaudiKernel/ToolHandle.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "Acts/EventData/TrackParameters.hpp"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackFinderTool.h"
+#include "FaserActsKalmanFilter/Measurement.h"
+
+class FaserSCT_ID;
+class TFile;
+class TTree;
+
+class ProtoTrackWriterTool : public AthAlgTool {
+public:
+  ProtoTrackWriterTool(const std::string& type, const std::string& name,
+                       const IInterface* parent);
+  virtual ~ProtoTrackWriterTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  StatusCode write(std::shared_ptr<const Acts::CurvilinearTrackParameters> protoTrackParameters,
+                   std::shared_ptr<std::vector<Measurement>> measurements,
+                   const Acts::GeometryContext& ctx) const;
+
+private:
+  const FaserSCT_ID* m_idHelper{nullptr};
+
+  ToolHandle<ITrackFinderTool> m_trackFinderTool {this, "TrackFinderTool", "TruthTrackFinderTool"};
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  Gaudi::Property<std::string> m_filePath {this, "File", "ProtoTracks.root", "Root file"};
+  TFile* m_file;
+  TTree* m_params;
+  TTree* m_meas;
+
+  mutable int m_run_number;
+  mutable int m_event_number;
+  mutable double m_x;
+  mutable double m_y;
+  mutable double m_z;
+  mutable double m_px;
+  mutable double m_py;
+  mutable double m_pz;
+  mutable int m_station;
+  mutable int m_layer;
+  mutable int m_phi;
+  mutable int m_eta;
+  mutable int m_side;
+  mutable double m_meas_eLOC0;
+  mutable double m_meas_eLOC1;
+};
+
+#endif // FASERACTSKALMANFILTER_PROTOTRACKWRITERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ResPlotTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ResPlotTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..0b65984bed7e7de4c7975fdf9cc67248c253dab7
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ResPlotTool.h
@@ -0,0 +1,87 @@
+#ifndef FASERACTSKALMANFILTER_RESPLOTTOOL_H
+#define FASERACTSKALMANFILTER_RESPLOTTOOL_H
+
+#include "FaserActsKalmanFilter/PlotHelpers.h"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "HepMC/GenParticle.h"
+#include "TH1F.h"
+#include "TH2F.h"
+#include <map>
+#include <string>
+#include <vector>
+
+class ResPlotTool {
+public:
+  std::vector<std::string> m_paramNames = {"x0", "y0", "phi", "theta", "qop", "t"};
+  std::map<std::string, PlotHelpers::Binning> m_varBinning {
+      {"Eta", PlotHelpers::Binning("#eta", 40, 4, 12)},
+      {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 20)},
+      {"Pull", PlotHelpers::Binning("pull", 100, -5, 5)},
+      {"Residual_x0", PlotHelpers::Binning("r_{x0} [mm]", 100, -0.1, 0.1)},
+      {"Residual_y0", PlotHelpers::Binning("r_{y0} [mm]", 100, -4, 4)},
+      {"Residual_phi", PlotHelpers::Binning("r_{#phi} [rad]", 100, -0.3, 0.3)},
+      {"Residual_theta", PlotHelpers::Binning("r_{#theta} [rad]", 100, -0.002, 0.002)},
+      {"Residual_qop", PlotHelpers::Binning("r_{q/p} [c/GeV]", 100, -0.002, 0.002)},
+      {"Residual_t", PlotHelpers::Binning("r_{t} [s]", 100, -1000, 1000)},
+  };
+
+  /// @brief Nested Cache struct
+  struct ResPlotCache {
+    std::map<std::string, TH1F*> res;               ///< Residual distribution
+    std::map<std::string, TH2F*> res_vs_eta;        ///< Residual vs eta scatter plot
+    std::map<std::string, TH1F*> resMean_vs_eta;    ///< Residual mean vs eta distribution
+    std::map<std::string, TH1F*> resWidth_vs_eta;   ///< Residual width vs eta distribution
+    std::map<std::string, TH2F*> res_vs_pT;         ///< Residual vs pT scatter plot
+    std::map<std::string, TH1F*> resMean_vs_pT;     ///< Residual mean vs pT distribution
+    std::map<std::string, TH1F*> resWidth_vs_pT;    ///< Residual width vs pT distribution
+
+    std::map<std::string, TH1F*> pull;              ///< Pull distribution
+    std::map<std::string, TH2F*> pull_vs_eta;       ///< Pull vs eta scatter plot
+    std::map<std::string, TH1F*> pullMean_vs_eta;   ///< Pull mean vs eta distribution
+    std::map<std::string, TH1F*> pullWidth_vs_eta;  ///< Pull width vs eta distribution
+    std::map<std::string, TH2F*> pull_vs_pT;        ///< Pull vs pT scatter plot
+    std::map<std::string, TH1F*> pullMean_vs_pT;    ///< Pull mean vs pT distribution
+    std::map<std::string, TH1F*> pullWidth_vs_pT;   ///< Pull width vs pT distribution
+  };
+
+  /// Constructor
+  ///
+  ResPlotTool() = default;
+
+  /// @brief book the histograms
+  ///
+  /// @param resPlotCache the cache for residual/pull histograms
+  void book(ResPlotCache& resPlotCache) const;
+
+  /// @brief fill the histograms
+  ///
+  /// @param resPlotCache the cache for residual/pull histograms
+  /// @param gctx the geometry context
+  /// @param truthParticle the truth particle
+  /// @param fittedParamters the fitted parameters at perigee surface
+  void fill(ResPlotCache& resPlotCache, const Acts::GeometryContext& gctx,
+            std::unique_ptr<const Acts::BoundTrackParameters> truthParameters,
+            const Acts::BoundTrackParameters& fittedParameters) const;
+
+  /// @brief extract the details of the residual/pull plots and fill details
+  ///
+  /// into separate histograms
+  /// @param resPlotCache the cache object for residual/pull histograms
+  void refinement(ResPlotCache& resPlotCache) const;
+
+  /// @brief write the histograms to output file
+  ///
+  /// @param resPlotCache the cache object for residual/pull histograms
+  void write(const ResPlotCache& resPlotCache) const;
+
+  /// @brief delele the histograms
+  ///
+  /// @param resPlotCache the cache object for residual/pull histograms
+  void clear(ResPlotCache& resPlotCache) const;
+
+private:
+  const double m_MeV2GeV = 0.001;
+};
+
+#endif // FASERACTSKALMANFILTER_RESPLOTTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..6f0885d33ec1200edbd28761352a36840fd11e47
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h
@@ -0,0 +1,130 @@
+#ifndef FASERACTSKALMANFILTER_ROOTTRAJECTORYSTATESWRITERTOOL_H
+#define FASERACTSKALMANFILTER_ROOTTRAJECTORYSTATESWRITERTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+#include "GeneratorObjects/McEventCollection.h"
+#include <array>
+#include <string>
+#include <vector>
+
+
+class FaserSCT_ID;
+namespace  TrackerDD {
+class SCT_DetectorManager;
+}
+class TFile;
+class TTree;
+struct FaserActsRecMultiTrajectory;
+using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
+
+class RootTrajectoryStatesWriterTool : public AthAlgTool {
+public:
+  RootTrajectoryStatesWriterTool(const std::string& type, const std::string& name, const IInterface* parent);
+  ~RootTrajectoryStatesWriterTool() override = default;
+
+  StatusCode initialize() override;
+  StatusCode finalize() override;
+
+  StatusCode write(const Acts::GeometryContext& gctx, const TrajectoriesContainer& trajectories, bool isMC) const;
+
+private:
+  SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey {this, "McEventCollection", "TruthEvent"};
+  SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey {this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+  SG::ReadHandleKey <FaserSiHitCollection> m_faserSiHitKey {this, "FaserSiHitCollection", "SCT_Hits"};
+
+  const double m_MeV2GeV = 0.001;
+
+  const FaserSCT_ID* m_idHelper{nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detMgr {nullptr};
+  Gaudi::Property<bool> m_noDiagnostics {this, "noDiagnostics", true, "Set ACTS logging level to INFO and do not run performance writer, states writer or summary writer"};
+  Gaudi::Property<std::string> m_filePath {this, "FilePath", "track_states_ckf.root", "Output root file"};
+  Gaudi::Property<std::string> m_treeName {this, "TreeName", "tree", "Tree name"};
+  Gaudi::Property<bool> m_mc {this, "MC", false};
+  TFile* m_outputFile;
+  TTree* m_outputTree;
+
+  mutable uint32_t m_eventNr{0};         ///< the event number
+  mutable uint32_t m_multiTrajNr{0};     ///< the multi-trajectory number
+  mutable unsigned int m_subTrajNr{0};   ///< the multi-trajectory sub-trajectory number
+
+  mutable std::vector<float> m_t_x;  ///< Global truth hit position x
+  mutable std::vector<float> m_t_y;  ///< Global truth hit position y
+  mutable std::vector<float> m_t_z;  ///< Global truth hit position z
+  mutable std::vector<float> m_t_dx;  ///< Truth particle direction x at global hit position
+  mutable std::vector<float> m_t_dy;  ///< Truth particle direction y at global hit position
+  mutable std::vector<float> m_t_dz;  ///< Truth particle direction z at global hit position
+
+  mutable std::vector<float> m_t_eLOC0;   ///< truth parameter eBoundLoc0
+  mutable std::vector<float> m_t_eLOC1;   ///< truth parameter eBoundLoc1
+  mutable std::vector<float> m_t_ePHI;    ///< truth parameter ePHI
+  mutable std::vector<float> m_t_eTHETA;  ///< truth parameter eTHETA
+  mutable std::vector<float> m_t_eQOP;    ///< truth parameter eQOP
+  mutable std::vector<float> m_t_eT;      ///< truth parameter eT
+
+  mutable unsigned int m_nStates{0};        ///< number of all states
+  mutable unsigned int m_nMeasurements{0};  ///< number of states with measurements
+  mutable std::vector<int> m_volumeID;      ///< volume identifier
+  mutable std::vector<int> m_layerID;       ///< layer identifier
+  mutable std::vector<int> m_moduleID;      ///< surface identifier
+  mutable std::vector<int> m_station;       ///< station
+  mutable std::vector<int> m_layer;         ///< layer
+  mutable std::vector<int> m_phi_module;    ///< phi module (rows)
+  mutable std::vector<int> m_eta_module;    ///< eta module (columns)
+  mutable std::vector<int> m_side;          ///< module side
+  mutable std::vector<float> m_pathLength;  ///< path length
+  mutable std::vector<float> m_lx_hit;      ///< uncalibrated measurement local x
+  mutable std::vector<float> m_ly_hit;      ///< uncalibrated measurement local y
+  mutable std::vector<float> m_x_hit;       ///< uncalibrated measurement global x
+  mutable std::vector<float> m_y_hit;       ///< uncalibrated measurement global y
+  mutable std::vector<float> m_z_hit;       ///< uncalibrated measurement global z
+  mutable std::vector<float> m_res_x_hit;   ///< hit residual x
+  mutable std::vector<float> m_res_y_hit;   ///< hit residual y
+  mutable std::vector<float> m_err_x_hit;   ///< hit err x
+  mutable std::vector<float> m_err_y_hit;   ///< hit err y
+  mutable std::vector<float> m_pull_x_hit;  ///< hit pull x
+  mutable std::vector<float> m_pull_y_hit;  ///< hit pull y
+  mutable std::vector<int> m_dim_hit;       ///< dimension of measurement
+
+  mutable std::array<int, 3> m_nParams;  ///< number of states which have filtered/predicted/smoothed parameters
+  mutable std::array<std::vector<bool>, 3> m_hasParams;  ///< status of the filtered/predicted/smoothed parameters
+  mutable std::array<std::vector<float>, 3> m_eLOC0;  ///< predicted/filtered/smoothed parameter eLOC0
+  mutable std::array<std::vector<float>, 3> m_eLOC1;  ///< predicted/filtered/smoothed parameter eLOC1
+  mutable std::array<std::vector<float>, 3> m_ePHI;  ///< predicted/filtered/smoothed parameter ePHI
+  mutable std::array<std::vector<float>, 3> m_eTHETA;  ///< predicted/filtered/smoothed parameter eTHETA
+  mutable std::array<std::vector<float>, 3> m_eQOP;  ///< predicted/filtered/smoothed parameter eQOP
+  mutable std::array<std::vector<float>, 3> m_eT;  ///< predicted/filtered/smoothed parameter eT
+  mutable std::array<std::vector<float>, 3> m_res_eLOC0;  ///< predicted/filtered/smoothed parameter eLOC0 residual
+  mutable std::array<std::vector<float>, 3> m_res_eLOC1;  ///< predicted/filtered/smoothed parameter eLOC1 residual
+  mutable std::array<std::vector<float>, 3> m_res_ePHI;  ///< predicted/filtered/smoothed parameter ePHI residual
+  mutable std::array<std::vector<float>, 3> m_res_eTHETA;  ///< predicted/filtered/smoothed parameter eTHETA residual
+  mutable std::array<std::vector<float>, 3> m_res_eQOP;  ///< predicted/filtered/smoothed parameter eQOP residual
+  mutable std::array<std::vector<float>, 3> m_res_eT;  ///< predicted/filtered/smoothed parameter eT residual
+  mutable std::array<std::vector<float>, 3> m_err_eLOC0;  ///< predicted/filtered/smoothed parameter eLOC0 error
+  mutable std::array<std::vector<float>, 3> m_err_eLOC1;  ///< predicted/filtered/smoothed parameter eLOC1 error
+  mutable std::array<std::vector<float>, 3> m_err_ePHI;  ///< predicted/filtered/smoothed parameter ePHI error
+  mutable std::array<std::vector<float>, 3> m_err_eTHETA;  ///< predicted/filtered/smoothed parameter eTHETA error
+  mutable std::array<std::vector<float>, 3> m_err_eQOP;  ///< predicted/filtered/smoothed parameter eQOP error
+  mutable std::array<std::vector<float>, 3> m_err_eT;  ///< predicted/filtered/smoothed parameter eT error
+  mutable std::array<std::vector<float>, 3> m_pull_eLOC0;  ///< predicted/filtered/smoothed parameter eLOC0 pull
+  mutable std::array<std::vector<float>, 3> m_pull_eLOC1;  ///< predicted/filtered/smoothed parameter eLOC1 pull
+  mutable std::array<std::vector<float>, 3> m_pull_ePHI;  ///< predicted/filtered/smoothed parameter ePHI pull
+  mutable std::array<std::vector<float>, 3> m_pull_eTHETA;  ///< predicted/filtered/smoothed parameter eTHETA pull
+  mutable std::array<std::vector<float>, 3> m_pull_eQOP;  ///< predicted/filtered/smoothed parameter eQOP pull
+  mutable std::array<std::vector<float>, 3> m_pull_eT;  ///< predicted/filtered/smoothed parameter eT pull
+  mutable std::array<std::vector<float>, 3> m_x;  ///< predicted/filtered/smoothed parameter global x
+  mutable std::array<std::vector<float>, 3> m_y;  ///< predicted/filtered/smoothed parameter global y
+  mutable std::array<std::vector<float>, 3> m_z;  ///< predicted/filtered/smoothed parameter global z
+  mutable std::array<std::vector<float>, 3> m_px;  ///< predicted/filtered/smoothed parameter px
+  mutable std::array<std::vector<float>, 3> m_py;  ///< predicted/filtered/smoothed parameter py
+  mutable std::array<std::vector<float>, 3> m_pz;  ///< predicted/filtered/smoothed parameter pz
+  mutable std::array<std::vector<float>, 3> m_eta;  ///< predicted/filtered/smoothed parameter eta
+  mutable std::array<std::vector<float>, 3> m_pT;  ///< predicted/filtered/smoothed parameter pT
+
+  mutable  std::vector<float> m_chi2;  ///< chisq from filtering
+};
+
+#endif  // FASERACTSKALMANFILTER_ROOTTRAJECTORYSTATESWRITERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..90cf6cf84ca8a95b41397bd00dea30d85135967f
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h
@@ -0,0 +1,118 @@
+#ifndef FASERACTSKALMANFILTER_ROOTTRAJECTORYSUMMARYWRITERTOOL_H
+#define FASERACTSKALMANFILTER_ROOTTRAJECTORYSUMMARYWRITERTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "FaserActsKalmanFilter/IdentifierLink.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+#include "GeneratorObjects/McEventCollection.h"
+#include "FaserActsGeometryInterfaces/IFaserActsExtrapolationTool.h"
+#include <array>
+#include <string>
+#include <vector>
+
+class FaserSCT_ID;
+namespace  TrackerDD {
+class SCT_DetectorManager;
+}
+class TFile;
+class TTree;
+struct FaserActsRecMultiTrajectory;
+using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
+
+class RootTrajectorySummaryWriterTool : public AthAlgTool {
+public:
+  RootTrajectorySummaryWriterTool(const std::string& type, const std::string& name, const IInterface* parent);
+  ~RootTrajectorySummaryWriterTool() override = default;
+  StatusCode write( const Acts::GeometryContext& geoContext, const TrajectoriesContainer& trajectories, bool isMC) const;
+  StatusCode initialize() override;
+  StatusCode finalize() override;
+
+private:
+  std::unique_ptr<const Acts::BoundTrackParameters> extrapolateToReferenceSurface(
+      const EventContext& ctx, const HepMC::GenParticle* particle) const;
+  const FaserSCT_ID* m_idHelper {nullptr};
+  SG::ReadHandleKey<TrackerSimDataCollection> m_simDataCollectionKey {
+    this, "TrackerSimDataCollection", "SCT_SDO_Map"};
+  SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey {
+    this, "McEventCollection", "TruthEvent"};
+  ToolHandle<IFaserActsExtrapolationTool> m_extrapolationTool {
+      this, "ExtrapolationTool", "FaserActsExtrapolationTool"};
+
+  Gaudi::Property<bool> m_noDiagnostics {this, "noDiagnostics", true, "Set ACTS logging level to INFO and do not run performance writer, states writer or summary writer"};
+  Gaudi::Property<std::string> m_filePath{this, "FilePath", "track_summary_ckf.root", "Output root file"};
+  Gaudi::Property<std::string> m_treeName{this, "TreeName", "tree", "Tree name"};
+
+  const double m_MeV2GeV = 0.001;
+
+  TFile* m_outputFile;
+  TTree* m_outputTree;
+  mutable uint32_t m_eventNr{0};         ///< The event number
+  mutable std::vector<uint32_t> m_multiTrajNr;  ///< The multi-trajectory numbers in event
+  mutable std::vector<unsigned int> m_subTrajNr;  ///< The multi-trajectory sub-trajectory number in event
+
+  mutable std::vector<unsigned int> m_nStates;        ///< The number of states
+  mutable std::vector<unsigned int> m_nMeasurements;  ///< The number of measurements
+  mutable std::vector<unsigned int> m_nOutliers;      ///< The number of outliers
+  mutable std::vector<unsigned int> m_nHoles;         ///< The number of holes
+  mutable std::vector<unsigned int> m_nSharedHits;    ///< The number of shared hits
+  mutable std::vector<float> m_chi2Sum;               ///< The total chi2
+  mutable std::vector<unsigned int> m_NDF;  ///< The number of ndf of the measurements+outliers
+  mutable std::vector<std::vector<double>> m_measurementChi2;  ///< The chi2 on all measurement states
+  mutable std::vector<std::vector<double>> m_outlierChi2;  ///< The chi2 on all outlier states
+  // FIXME replace volume, layer, ... with station, layer, ...
+  mutable std::vector<std::vector<double>> m_measurementVolume;  ///< The volume id of the measurements
+  mutable std::vector<std::vector<double>> m_measurementLayer;  ///< The layer id of the measurements
+  mutable std::vector<std::vector<double>> m_outlierVolume;  ///< The volume id of the outliers
+  mutable std::vector<std::vector<double>> m_outlierLayer;  ///< The layer id of the outliers
+
+  // The majority truth particle info
+  mutable std::vector<unsigned int> m_nMajorityHits;  ///< The number of hits from majority particle
+  mutable std::vector<uint64_t> m_majorityParticleId;      ///< The particle Id of the majority particle
+  mutable std::vector<int> m_t_charge;   ///< Charge of majority particle
+  mutable std::vector<float> m_t_time;   ///< Time of majority particle
+  mutable std::vector<float> m_t_vx;     ///< Vertex x positions of majority particle
+  mutable std::vector<float> m_t_vy;     ///< Vertex y positions of majority particle
+  mutable std::vector<float> m_t_vz;     ///< Vertex z positions of majority particle
+  mutable std::vector<float> m_t_px;     ///< Initial momenta px of majority particle
+  mutable std::vector<float> m_t_py;     ///< Initial momenta py of majority particle
+  mutable std::vector<float> m_t_pz;     ///< Initial momenta pz of majority particle
+  mutable std::vector<float> m_t_theta;  ///< In * m_MeV2GeVitial momenta theta of majority particle
+  mutable std::vector<float> m_t_phi;    ///< Initial momenta phi of majority particle
+  mutable std::vector<float> m_t_p;      ///< Initial abs momenta of majority particle
+  mutable std::vector<float> m_t_pT;     ///< Initial momenta pT of majority particle
+  mutable std::vector<float> m_t_eta;    ///< Initial momenta eta of majority particle
+
+  mutable std::vector<bool> m_hasFittedParams;  ///< If the track has fitted parameter
+  // The fitted parameters
+  mutable std::vector<float> m_eLOC0_fit;   ///< Fitted parameters eBoundLoc0 of track
+  mutable std::vector<float> m_eLOC1_fit;   ///< Fitted parameters eBoundLoc1 of track
+  mutable std::vector<float> m_ePHI_fit;    ///< Fitted parameters ePHI of track
+  mutable std::vector<float> m_eTHETA_fit;  ///< Fitted parameters eTHETA of track
+  mutable std::vector<float> m_eQOP_fit;    ///< Fitted parameters eQOP of track
+  mutable std::vector<float> m_eT_fit;      ///< Fitted parameters eT of track
+  // The error of fitted parameters
+  mutable std::vector<float> m_err_eLOC0_fit;   ///< Fitted parameters eLOC err of track
+  mutable std::vector<float> m_err_eLOC1_fit;   ///< Fitted parameters eBoundLoc1 err of track
+  mutable std::vector<float> m_err_ePHI_fit;    ///< Fitted parameters ePHI err of track
+  mutable std::vector<float> m_err_eTHETA_fit;  ///< Fitted parameters eTHETA err of track
+  mutable std::vector<float> m_err_eQOP_fit;    ///< Fitted parameters eQOP err of track
+  mutable std::vector<float> m_err_eT_fit;      ///< Fitted parameters eT err of track
+  // The residual of fitted parameters
+  mutable std::vector<float> m_res_eLOC0_fit;   ///< Fitted parameters eLOC res of track
+  mutable std::vector<float> m_res_eLOC1_fit;   ///< Fitted parameters eBoundLoc1 res of track
+  mutable std::vector<float> m_res_ePHI_fit;    ///< Fitted parameters ePHI res of track
+  mutable std::vector<float> m_res_eTHETA_fit;  ///< Fitted parameters eTHETA res of track
+  mutable std::vector<float> m_res_eQOP_fit;    ///< Fitted parameters eQOP res of track
+  mutable std::vector<float> m_res_eT_fit;      ///< Fitted parameters eT res of track
+  // The pull of fitted parameters
+  mutable std::vector<float> m_pull_eLOC0_fit;    ///< Fitted parameters eLOC pull of track
+  mutable std::vector<float> m_pull_eLOC1_fit;    ///< Fitted parameters eBoundLoc1 pull of track
+  mutable std::vector<float> m_pull_ePHI_fit;    ///< Fitted parameters ePHI pull of track
+  mutable std::vector<float> m_pull_eTHETA_fit;  ///< Fitted parameters eTHETA pull of track
+  mutable std::vector<float> m_pull_eQOP_fit;    ///< Fitted parameters eQOP pull of track
+  mutable std::vector<float> m_pull_eT_fit;      ///< Fitted parameters eT pull of track
+};
+
+#endif  // FASERACTSKALMANFILTER_ROOTTRAJECTORYSUMMARYWRITERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SeedingAlg.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SeedingAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..49fd1c901ab2f0159cd033401ddde589b61262e6
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SeedingAlg.h
@@ -0,0 +1,23 @@
+#ifndef FASERACTSKALMANFILTER_SEEDINGALG_H
+#define FASERACTSKALMANFILTER_SEEDINGALG_H
+
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaBaseComps/AthAlgorithm.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include <boost/dynamic_bitset.hpp>
+
+
+class SeedingAlg : public AthAlgorithm {
+public:
+  SeedingAlg(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~SeedingAlg() = default;
+
+  StatusCode initialize() override;
+  StatusCode execute() override;
+  StatusCode finalize() override;
+
+private:
+  ToolHandle<ITrackSeedTool> m_trackSeedTool {this, "TrackSeed", "CircleFitTrackSeedTool"};
+};
+
+#endif // FASERACTSKALMANFILTER_SEEDINGALG_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SegmentFitClusterTrackFinderTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SegmentFitClusterTrackFinderTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..6bf7ba6984300ae218a39015786d99d034922e64
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SegmentFitClusterTrackFinderTool.h
@@ -0,0 +1,106 @@
+#ifndef FASERACTSKALMANFILTER_SEGMENTFITCLUSTERTRACKFINDERTOOL_H
+#define FASERACTSKALMANFILTER_SEGMENTFITCLUSTERTRACKFINDERTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/EventContext.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+#include "StoreGate/ReadHandleKey.h"
+#include <string>
+#include <vector>
+
+#include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackFinderTool.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrkTrack/TrackCollection.h"
+
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+
+class SegmentFitClusterTrackFinderTool : public extends<AthAlgTool, ITrackFinderTool> {
+public:
+  SegmentFitClusterTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~SegmentFitClusterTrackFinderTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  virtual const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const override;
+  virtual const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<IndexSourceLink>>> sourceLinks() const override;
+  virtual const std::shared_ptr<std::vector<IdentifierLink>> idLinks() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<Measurement>>> measurements() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>> spacePoints() const override;
+  virtual const std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>> clusters() const override;
+
+private:
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<std::vector<IndexSourceLink>>> m_sourceLinks {};
+  std::shared_ptr<std::vector<IdentifierLink>> m_idLinks {};
+  std::shared_ptr<std::vector<std::vector<Measurement>>> m_measurements {};
+  std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>> m_spacePoints {};
+  std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>> m_clusters {};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+  static std::pair<double, double> momentum(const std::map<int, Amg::Vector3D>& pos, double B=0.57);
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {
+      this, "TrackCollection", "SegmentFit", "Input track collection name" };
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_sigmaCluster {this, "sigmaCluster", 0.04};
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+};
+
+
+inline const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>
+SegmentFitClusterTrackFinderTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+SegmentFitClusterTrackFinderTool::initialSurface() const {
+  return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<IndexSourceLink>>>
+SegmentFitClusterTrackFinderTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<std::vector<IdentifierLink>>
+SegmentFitClusterTrackFinderTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<Measurement>>>
+SegmentFitClusterTrackFinderTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>>
+SegmentFitClusterTrackFinderTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline const std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>>
+SegmentFitClusterTrackFinderTool::clusters() const {
+  return m_clusters;
+}
+
+#endif // FASERACTSKALMANFILTER_SEGMENTFITCLUSTERTRACKFINDERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SegmentFitTrackFinderTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SegmentFitTrackFinderTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f555be285fc792c42db3b8903f9e208e4fa7902
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SegmentFitTrackFinderTool.h
@@ -0,0 +1,98 @@
+#ifndef FASERACTSKALMANFILTER_SEGMENTFITTRACKFINDERTOOL_H
+#define FASERACTSKALMANFILTER_SEGMENTFITTRACKFINDERTOOL_H
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/EventContext.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+#include "StoreGate/ReadHandleKey.h"
+#include <string>
+#include <vector>
+
+#include "TrackerSpacePoint/FaserSCT_SpacePointContainer.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackFinderTool.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrkTrack/TrackCollection.h"
+
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+
+class SegmentFitTrackFinderTool : public extends<AthAlgTool, ITrackFinderTool> {
+public:
+  SegmentFitTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~SegmentFitTrackFinderTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  virtual const std::shared_ptr<const Acts::CurvilinearTrackParameters> initialTrackParameters() const override;
+  virtual const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  virtual const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override;
+  virtual const std::shared_ptr<IdentifierLink> idLinks() const override;
+  virtual const std::shared_ptr<std::vector<Measurement>> measurements() const override;
+  virtual const std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>> spacePoints() const override;
+
+private:
+  std::shared_ptr<const Acts::CurvilinearTrackParameters> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>> m_spacePoints {};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+  static double momentum(const std::map<int, Amg::Vector3D>& pos, double B=0.57) ;
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {
+      this, "TrackCollection", "SegmentFit", "Input track collection name" };
+  SG::ReadHandleKey<FaserSCT_SpacePointContainer> m_spacePointContainerKey {
+      this, "SpacePoints", "SCT_SpacePointContainer", "Space point container"};
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+};
+
+
+inline const std::shared_ptr<const Acts::CurvilinearTrackParameters>
+    SegmentFitTrackFinderTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+    SegmentFitTrackFinderTool::initialSurface() const {
+return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<IndexSourceLink>>
+   SegmentFitTrackFinderTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+SegmentFitTrackFinderTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<Measurement>>
+    SegmentFitTrackFinderTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>>
+    SegmentFitTrackFinderTool::spacePoints() const {
+  return m_spacePoints;
+}
+#endif // FASERACTSKALMANFILTER_SEGMENTFITTRACKFINDERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SummaryPlotTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SummaryPlotTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..ea54ffc0f424409ce16887c8824ce271f9a342c2
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/SummaryPlotTool.h
@@ -0,0 +1,67 @@
+#ifndef FASERACTSKALMANFILTER_SUMMARYPLOTTOOL_H
+#define FASERACTSKALMANFILTER_SUMMARYPLOTTOOL_H
+
+#include "FaserActsKalmanFilter/PlotHelpers.h"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "TProfile.h"
+#include <map>
+#include <string>
+
+class SummaryPlotTool {
+public:
+  std::map<std::string, PlotHelpers::Binning> m_varBinning {
+      {"Eta", PlotHelpers::Binning("#eta", 40, 4, 12)},
+      {"Phi", PlotHelpers::Binning("#phi", 100, -3.15, 3.15)},
+      {"Pt", PlotHelpers::Binning("pT [GeV/c]", 40, 0, 20)},
+      {"Num", PlotHelpers::Binning("N", 30, -0.5, 29.5)}
+  };
+
+  /// @brief Nested Cache struct
+  struct SummaryPlotCache {
+    TProfile* nStates_vs_eta;        ///< Number of total states vs eta
+    TProfile* nMeasurements_vs_eta;  ///< Number of non-outlier measurements vs eta
+    TProfile* nHoles_vs_eta;         ///< Number of holes vs eta
+    TProfile* nOutliers_vs_eta;      ///< Number of outliers vs eta
+    TProfile* nSharedHits_vs_eta;    ///< Number of Shared Hits vs eta
+    TProfile* nStates_vs_pt;         ///< Number of total states vs pt
+    TProfile* nMeasurements_vs_pt;   ///< Number of non-outlier measurements vs pt
+    TProfile* nHoles_vs_pt;          ///< Number of holes vs pt
+    TProfile* nOutliers_vs_pt;       ///< Number of outliers vs pt
+    TProfile* nSharedHits_vs_pt;     ///< Number of Shared Hits vs pt
+  };
+
+  /// Constructor
+  ///
+  SummaryPlotTool() = default;
+
+  /// @brief book the track info plots
+  ///
+  /// @param trackSummaryPlotCache the cache for track info plots
+  void book(SummaryPlotCache& trackSummaryPlotCache) const;
+
+  /// @brief fill reco track info w.r.t. fitted track parameters
+  ///
+  /// @param trackSummaryPlotCache cache object for track info plots
+  /// @param fittedParameters fitted track parameters of this track
+  /// @param nStates number of track states
+  /// @param nMeasurements number of measurements
+  /// @param nOutliers number of outliers
+  /// @param nHoles number of holes
+  /// @param nSharedHits number of shared hits
+  void fill(SummaryPlotCache& trackSummaryPlotCache,
+            const Acts::BoundTrackParameters& fittedParameters, size_t nStates,
+            size_t nMeasurements, size_t nOutliers, size_t nHoles,
+            size_t nSharedHits) const;
+
+  /// @brief write the track info plots to file
+  ///
+  /// @param trackSummaryPlotCache cache object for track info plots
+  void write(const SummaryPlotCache& trackSummaryPlotCache) const;
+
+  /// @brief delete the track info plots
+  ///
+  /// @param trackSummaryPlotCache cache object for track info plots
+  void clear(SummaryPlotCache& trackSummaryPlotCache) const;
+};
+
+#endif // FASERACTSKALMANFILTER_SUMMARYPLOTTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ThreeStationTrackSeedTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ThreeStationTrackSeedTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..22cbf9af8420d5100635bbc0f6a00f760e111097
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/ThreeStationTrackSeedTool.h
@@ -0,0 +1,134 @@
+#ifndef FASERACTSKALMANFILTER_THREESTATIONTRACKSEEDTOOL_H
+#define FASERACTSKALMANFILTER_THREESTATIONTRACKSEEDTOOL_H
+
+#include "TrackerPrepRawData/FaserSCT_ClusterContainer.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackSeedTool.h"
+#include "TrkTrack/TrackCollection.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+class FaserSCT_ID;
+namespace TrackerDD { class SCT_DetectorManager; }
+
+class ThreeStationTrackSeedTool : public extends<AthAlgTool, ITrackSeedTool> {
+public:
+  ThreeStationTrackSeedTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~ThreeStationTrackSeedTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters() const override;
+  const std::shared_ptr<const Acts::Surface> initialSurface() const override;
+  const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override;
+  const std::shared_ptr<IdentifierLink> idLinks() const override;
+  const std::shared_ptr<std::vector<Measurement>> measurements() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters() const override;
+  const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> seedClusters() const override;
+  const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> spacePoints() const override;
+  double targetZPosition() const override;
+
+private:
+
+  struct Tracklet {
+   public:
+    Tracklet(const Amg::Vector3D& position, const std::vector<const Tracker::FaserSCT_Cluster*>& clusters)
+      : m_position(position), m_clusters(clusters) {}
+    inline const Amg::Vector3D position() const { return m_position; }
+    inline const std::vector<const Tracker::FaserSCT_Cluster*> clusters() const { return m_clusters; }
+    inline int size() const { return m_clusters.size(); }
+  private:
+    const Amg::Vector3D m_position;
+    const std::vector<const Tracker::FaserSCT_Cluster*> m_clusters;
+  };
+
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> m_clusters {};
+  std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> m_seedClusters {};
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> m_spacePoints {};
+  double m_targetZPosition {0.};
+
+  const FaserSCT_ID* m_idHelper {nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager {nullptr};
+
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+  SG::ReadHandleKey<TrackCollection> m_trackCollection {
+      this, "TrackCollection", "SegmentFit", "Input track collection name" };
+  SG::ReadHandleKey<Tracker::FaserSCT_ClusterContainer> m_clusterContainerKey {
+      this, "ClusterContainer", "SCT_ClusterContainer"};
+
+  // position resolution of a cluster
+  Gaudi::Property<double> m_std_cluster {this, "std_cluster", 0.04};
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+
+  Gaudi::Property<double> m_origin {this, "origin", 0, "z position of the reference surface"};
+
+  static Acts::CurvilinearTrackParameters get_params(
+      const Amg::Vector3D& position_st1, const Amg::Vector3D& position_st2, const Amg::Vector3D& position_st3, const Acts::BoundSymMatrix& cov, double origin);
+  static std::pair<double, double> momentum(const std::map<int, Amg::Vector3D>& pos, double B=0.57);
+};
+
+inline const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>
+ThreeStationTrackSeedTool::initialTrackParameters() const {
+  return m_initialTrackParameters;
+}
+
+inline const std::shared_ptr<const Acts::Surface>
+ThreeStationTrackSeedTool::initialSurface() const {
+  return m_initialSurface;
+}
+
+inline const std::shared_ptr<std::vector<IndexSourceLink>>
+ThreeStationTrackSeedTool::sourceLinks() const {
+  return m_sourceLinks;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+ThreeStationTrackSeedTool::idLinks() const {
+  return m_idLinks;
+}
+
+inline const std::shared_ptr<std::vector<Measurement>>
+ThreeStationTrackSeedTool::measurements() const {
+  return m_measurements;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>>
+ThreeStationTrackSeedTool::clusters() const {
+  return m_clusters;
+}
+
+inline const std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>>
+ThreeStationTrackSeedTool::seedClusters() const {
+  return m_seedClusters;
+}
+
+inline const std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>>
+ThreeStationTrackSeedTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline double ThreeStationTrackSeedTool::targetZPosition() const {
+  return m_targetZPosition;
+}
+
+#endif  // FASERACTSKALMANFILTER_THREESTATIONTRACKSEEDTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackClassification.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackClassification.h
new file mode 100644
index 0000000000000000000000000000000000000000..2ca594c7dfd08c322c08ae193239fa8cafbc5ae9
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackClassification.h
@@ -0,0 +1,23 @@
+#ifndef FASERACTSKALMANFILTER_TRACKCLASSIFICATION_H
+#define FASERACTSKALMANFILTER_TRACKCLASSIFICATION_H
+
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+
+struct ParticleHitCount {
+  int particleId;
+  size_t hitCount;
+};
+
+/// Identify all particles that contribute to a trajectory.
+void identifyContributingParticles(
+    const TrackerSimDataCollection& simDataCollection,
+    const FaserActsRecMultiTrajectory& trajectories, size_t tip,
+    std::vector<ParticleHitCount>& particleHitCounts);
+
+void identifyContributingParticles(
+    const TrackerSimDataCollection& simDataCollection,
+    const std::vector<const Tracker::FaserSCT_Cluster*> clusters,
+    std::vector<ParticleHitCount>& particleHitCounts);
+
+#endif  // FASERACTSKALMANFILTER_TRACKCLASSIFICATION_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackSeedWriterTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackSeedWriterTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..e078acc19edd584ce2f514a4a3985bbaf7cc326c
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackSeedWriterTool.h
@@ -0,0 +1,50 @@
+#ifndef FASERACTSKALMANFILTER_TRACKSEEDWRITERTOOL_H
+#define FASERACTSKALMANFILTER_TRACKSEEDWRITERTOOL_H
+
+
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "Acts/EventData/TrackParameters.hpp"
+
+class TFile;
+class TTree;
+
+class TrackSeedWriterTool : public  AthAlgTool {
+ public:
+  TrackSeedWriterTool(const std::string& type,
+                      const std::string& name, const IInterface* parent);
+  virtual ~TrackSeedWriterTool() = default;
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  void writeout(const Acts::GeometryContext& gctx,
+                const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>& initialParameters) const;
+
+private:
+  void initializeTree();
+  void clearVariables() const;
+  Gaudi::Property<std::string> m_filePath{this, "FilePath", "TrackSeeds.root", ""};
+  Gaudi::Property<std::string> m_treeName{this, "TreeName", "tree", ""};
+  TFile* m_file;
+  TTree* m_tree;
+
+  mutable int m_runNumber;
+  mutable int m_eventNumber;
+  mutable std::vector<float> m_eLOC0;
+  mutable std::vector<float> m_eLOC1;
+  mutable std::vector<float> m_ePHI;
+  mutable std::vector<float> m_eTHETA;
+  mutable std::vector<float> m_eQOP;
+  mutable std::vector<float> m_err_eLOC0;
+  mutable std::vector<float> m_err_eLOC1;
+  mutable std::vector<float> m_err_ePHI;
+  mutable std::vector<float> m_err_eTHETA;
+  mutable std::vector<float> m_err_eQOP;
+  mutable std::vector<float> m_x;
+  mutable std::vector<float> m_y;
+  mutable std::vector<float> m_z;
+  mutable std::vector<float> m_px;
+  mutable std::vector<float> m_py;
+  mutable std::vector<float> m_pz;
+};
+
+
+#endif // FASERACTSKALMANFILTER_TRACKSEEDWRITERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackSelection.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackSelection.h
new file mode 100644
index 0000000000000000000000000000000000000000..858bb15c82b1e1d58ce8d92698e17aacac0b9baf
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrackSelection.h
@@ -0,0 +1,20 @@
+#ifndef FASERACTSKALMANFILTER_TRACKSELECTION_H
+#define FASERACTSKALMANFILTER_TRACKSELECTION_H
+
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include "TrackerSimData/TrackerSimDataCollection.h"
+#include "Acts/TrackFinding/CombinatorialKalmanFilter.hpp"
+
+using TrackFitterResult =
+Acts::Result<Acts::CombinatorialKalmanFilterResult<IndexSourceLink>>;
+using TrackFinderResult = std::vector<TrackFitterResult>;
+
+struct TrackQuality {
+  Acts::CombinatorialKalmanFilterResult<IndexSourceLink> track;
+  size_t nMeasurements;
+  double chi2;
+};
+
+void selectTracks(TrackFinderResult& results, std::vector<TrackQuality>& trackQuality);
+
+#endif  // FASERACTSKALMANFILTER_TRACKSELECTION_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrajectoryWriterTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrajectoryWriterTool.h
index 659cea7fa08321bb43a38fcc598a7054b119262c..0643ad9ce973f65453e72063701befaa3b2789ec 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrajectoryWriterTool.h
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TrajectoryWriterTool.h
@@ -7,7 +7,7 @@
 
 class TFile;
 class TTree;
-class FaserActsRecMultiTrajectory;
+struct FaserActsRecMultiTrajectory;
 using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
 
 class TrajectoryWriterTool : public AthAlgTool {
@@ -21,7 +21,7 @@ class TrajectoryWriterTool : public AthAlgTool {
   virtual StatusCode finalize() override;
 
   void writeout(TrajectoriesContainer trajectories,
-      Acts::GeometryContext geoContext,std::vector<Acts::CurvilinearTrackParameters> traj ) const;
+      Acts::GeometryContext geoContext, std::vector<Acts::CurvilinearTrackParameters> traj ) const;
   
   void clearVariables() const;
 
@@ -60,6 +60,10 @@ class TrajectoryWriterTool : public AthAlgTool {
   mutable std::vector<float> m_x_hit;
   mutable std::vector<float> m_y_hit;
   mutable std::vector<float> m_z_hit;
+  mutable std::vector<float> m_meas_eLOC0;
+  mutable std::vector<float> m_meas_eLOC1;
+  mutable std::vector<float> m_meas_cov_eLOC0;
+  mutable std::vector<float> m_meas_cov_eLOC1;
   mutable std::vector<float> m_res_x_hit;
   mutable std::vector<float> m_res_y_hit;
   mutable std::vector<float> m_err_x_hit;
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TruthSeededTrackFinderTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TruthSeededTrackFinderTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..79425aa17c64a68e4c517170587a6fc84ac973c3
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TruthSeededTrackFinderTool.h
@@ -0,0 +1,113 @@
+#ifndef FASERACTSKALMANFILTER_TRUTHSEEDEDTRACKFINDERTOOL_H
+#define FASERACTSKALMANFILTER_TRUTHSEEDEDTRACKFINDERTOOL_H
+
+#include "Acts/Definitions/Units.hpp"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackFinderTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/EventContext.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+#include "GeneratorObjects/McEventCollection.h"
+#include "StoreGate/ReadHandleKey.h"
+#include "TrackerSpacePoint/SpacePointForSeedCollection.h"
+#include <string>
+#include <vector>
+
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+using namespace Acts::UnitLiterals;
+
+class TruthSeededTrackFinderTool : public extends<AthAlgTool, ITrackFinderTool> {
+public:
+  TruthSeededTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~TruthSeededTrackFinderTool() = default;
+
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  virtual const std::shared_ptr<const Acts::CurvilinearTrackParameters> initialTrackParameters() const override {
+    return m_initialTrackParameters;
+  }
+
+  virtual const std::shared_ptr<const Acts::Surface> initialSurface() const override {
+    return m_initialSurface;
+  }
+
+  virtual const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override {
+    return m_sourceLinks;
+  }
+
+  virtual const std::shared_ptr<IdentifierLink> idLinks() const override;
+
+  virtual const std::shared_ptr<std::vector<Measurement>> measurements() const override {
+    return m_measurements;
+  }
+
+  virtual const std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>> spacePoints() const override;
+
+private:
+  std::shared_ptr<const Acts::CurvilinearTrackParameters> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>> m_spacePoints {};
+
+  const FaserSCT_ID* m_idHelper{nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager{nullptr};
+
+  static std::vector<Acts::Vector3> map2vector(const std::map<int, Acts::Vector3>& positions, int station);
+  static Acts::Vector3 average(const std::vector<Acts::Vector3>& spacePoints);
+  static std::pair<Acts::Vector3, Acts::Vector3> linear_fit(const std::vector<Acts::Vector3>& position);
+  static std::pair<double, double> momentum2(const std::map<int, Acts::Vector3>& hits, double B=0.57) ;
+
+  SG::ReadHandleKey<SpacePointForSeedCollection>  m_spacePointCollectionKey {
+    this, "FaserSpacePointsSeedsName", "Seeds_SpacePointContainer"};
+  SG::ReadHandleKey<McEventCollection> m_mcEventCollectionKey {this, "McEventCollection", "BeamTruthEvent"};
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+      this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+
+  Gaudi::Property<bool> m_useTrueInitialParameters {this, "useTrueInitialParameters", true};
+
+  // covariance matrix of measurement
+  Gaudi::Property<double> m_covMeas00 {this, "covMeas00", 0.0004};
+  Gaudi::Property<double> m_covMeas01 {this, "covMeas01", 0.01};
+  Gaudi::Property<double> m_covMeas10 {this, "covMeas10", 0.01};
+  Gaudi::Property<double> m_covMeas11 {this, "covMeas11", 0.64};
+
+  // smearing of initial parameters
+  Gaudi::Property<double> m_sigmaLoc0 {this, "sigmaLoc0", 0};
+  Gaudi::Property<double> m_sigmaLoc1 {this, "sigmaLoc1", 0};
+  Gaudi::Property<double> m_sigmaPhi {this, "sigmaPhi", 0};
+  Gaudi::Property<double> m_sigmaTheta {this, "sigmaTheta", 0};
+  Gaudi::Property<double> m_sigmaP {this, "sigmaP", 0};
+  Gaudi::Property<double> m_sigmaTime {this, "sigmaTime", 0};
+
+  // covariance parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+};
+
+inline const std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>>
+TruthSeededTrackFinderTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+TruthSeededTrackFinderTool::idLinks() const {
+  return m_idLinks;
+}
+
+
+#endif // FASERACTSKALMANFILTER_TRUTHSEEDEDTRACKFINDERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TruthTrackFinderTool.h b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TruthTrackFinderTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..3bfe35b68013a3ef323a43c245e046b7248be094
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/FaserActsKalmanFilter/TruthTrackFinderTool.h
@@ -0,0 +1,109 @@
+#ifndef FASERACTSKALMANFILTER_TRUTHTRACKFINDERTOOL_H
+#define FASERACTSKALMANFILTER_TRUTHTRACKFINDERTOOL_H
+
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "FaserActsGeometryInterfaces/IFaserActsTrackingGeometryTool.h"
+#include "FaserActsKalmanFilter/ITrackFinderTool.h"
+#include "Gaudi/Property.h"
+#include "GaudiKernel/EventContext.h"
+#include "GaudiKernel/IInterface.h"
+#include "GaudiKernel/StatusCode.h"
+#include "StoreGate/ReadHandleKey.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include <string>
+#include <vector>
+
+class FaserSCT_ID;
+namespace TrackerDD {
+class SCT_DetectorManager;
+}
+
+
+class TruthTrackFinderTool : public extends<AthAlgTool, ITrackFinderTool> {
+public:
+  TruthTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent);
+  virtual ~TruthTrackFinderTool() = default;
+
+  virtual StatusCode initialize() override;
+  virtual StatusCode finalize() override;
+  virtual StatusCode run() override;
+
+  virtual const std::shared_ptr<const Acts::CurvilinearTrackParameters> initialTrackParameters() const override {
+    return m_initialTrackParameters;
+  }
+
+  virtual const std::shared_ptr<const Acts::Surface> initialSurface() const override {
+    return m_initialSurface;
+  }
+
+  virtual const std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks() const override {
+    return m_sourceLinks;
+  }
+
+  virtual const std::shared_ptr<IdentifierLink> idLinks() const override;
+
+  virtual const std::shared_ptr<std::vector<Measurement>> measurements() const override {
+    return m_measurements;
+  }
+
+  virtual const std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>> spacePoints() const override;
+
+private:
+  std::shared_ptr<const Acts::CurvilinearTrackParameters> m_initialTrackParameters;
+  std::shared_ptr<const Acts::Surface> m_initialSurface;
+  std::shared_ptr<std::vector<IndexSourceLink>> m_sourceLinks {};
+  std::shared_ptr<IdentifierLink> m_idLinks {};
+  std::shared_ptr<std::vector<Measurement>> m_measurements {};
+  std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>> m_spacePoints {};
+
+  const FaserSCT_ID* m_idHelper{nullptr};
+  const TrackerDD::SCT_DetectorManager* m_detManager{nullptr};
+
+  SG::ReadHandleKey<FaserSiHitCollection> m_siHitCollectionKey {
+    this, "FaserSiHitCollection", "SCT_Hits"};
+  ToolHandle<IFaserActsTrackingGeometryTool> m_trackingGeometryTool {
+    this, "TrackingGeometryTool", "FaserActsTrackingGeometryTool"};
+
+  // smearing of measurements
+  Gaudi::Property<double> m_sigma0 {this, "sigmaMeasLoc0", 0};
+  Gaudi::Property<double> m_sigma1 {this, "sigmaMeasLoc1", 0};
+
+  // covariance of the measurements
+  Gaudi::Property<double> m_covMeasLoc0 {this, "covMeasLoc0", 0.01};
+  Gaudi::Property<double> m_covMeasLoc1 {this, "covMeasLoc1", 0.01};
+
+  // smearing of initial parameters
+  Gaudi::Property<double> m_sigmaLoc0 {this, "sigmaLoc0", 0};
+  Gaudi::Property<double> m_sigmaLoc1 {this, "sigmaLoc1", 0};
+  Gaudi::Property<double> m_sigmaPhi {this, "sigmaPhi", 0};
+  Gaudi::Property<double> m_sigmaTheta {this, "sigmaTheta", 0};
+  Gaudi::Property<double> m_sigmaP {this, "sigmaP", 0};
+  Gaudi::Property<double> m_sigmaTime {this, "sigmaTime", 0};
+
+  // covariance of the initial parameters
+  Gaudi::Property<double> m_covLoc0 {this, "covLoc0", 1};
+  Gaudi::Property<double> m_covLoc1 {this, "covLoc1", 1};
+  Gaudi::Property<double> m_covPhi {this, "covPhi", 1};
+  Gaudi::Property<double> m_covTheta {this, "covTheta", 1};
+  Gaudi::Property<double> m_covQOverP {this, "covQOverP", 1};
+  Gaudi::Property<double> m_covTime {this, "covTime", 1};
+
+  // TODO get parameters of first layer from tracker geometry
+  Gaudi::Property<int> m_first_station {this, "first_station", 1, "first station for which the initial track parameters are calculated"};
+  Gaudi::Property<int> m_first_layer {this, "first_layer", 0, "first layer for which the initial track parameters are calculated"};
+  Gaudi::Property<int> m_first_side {this, "first_side", 1, "first side for which the initial track parameters are calculated"};
+};
+
+inline const std::shared_ptr<std::vector<Tracker::FaserSCT_SpacePoint>>
+    TruthTrackFinderTool::spacePoints() const {
+  return m_spacePoints;
+}
+
+inline const std::shared_ptr<IdentifierLink>
+TruthTrackFinderTool::idLinks() const {
+  return m_idLinks;
+}
+
+
+#endif // FASERACTSKALMANFILTER_TRUTHTRACKFINDERTOOL_H
diff --git a/Tracking/Acts/FaserActsKalmanFilter/python/CKF2Config.py b/Tracking/Acts/FaserActsKalmanFilter/python/CKF2Config.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea2dc0cb9eb08c309bf0453216230fe0b2074b4f
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/python/CKF2Config.py
@@ -0,0 +1,125 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
+from FaserActsGeometry.ActsGeometryConfig import ActsTrackingGeometrySvcCfg
+
+
+def FaserActsAlignmentCondAlgCfg(flags, **kwargs):
+    acc = ComponentAccumulator()
+    acc.addCondAlgo(CompFactory.FaserActsAlignmentCondAlg(name="FaserActsAlignmentCondAlg", **kwargs))
+    return acc
+
+
+def CKF2_OutputCfg(flags):
+    acc = ComponentAccumulator()
+    itemList = ["xAOD::EventInfo#*",
+                "xAOD::EventAuxInfo#*",
+                "TrackCollection#*",
+                ]
+    acc.merge(OutputStreamCfg(flags, "ESD", itemList))
+    ostream = acc.getEventAlgo("OutputStreamESD")
+    ostream.TakeItemsFromInput = True
+    return acc
+
+
+def CKF2Cfg(flags, **kwargs):
+    # acc = ComponentAccumulator()
+    acc = FaserSCT_GeometryCfg(flags)
+    acc.merge(MagneticFieldSvcCfg(flags))
+    # acc.merge(FaserActsAlignmentCondAlgCfg(flags))
+    acts_tracking_geometry_svc = ActsTrackingGeometrySvcCfg(flags)
+    acc.merge(acts_tracking_geometry_svc )
+
+    # track_seed_tool = CompFactory.ClusterTrackSeedTool()
+    # track_seed_tool = CompFactory.ActsTrackSeedTool()
+    # track_seed_tool = CompFactory.MyTrackSeedTool()
+    # track_seed_tool = CompFactory.ThreeStationTrackSeedTool()
+    track_seed_tool = CompFactory.CircleFitTrackSeedTool()
+    sigma_loc0 = 1.9e-2
+    sigma_loc1 = 9e-1
+    sigma_phi = 3.3e-2
+    sigma_theta = 2.9e-4
+    p = 1000
+    sigma_p = 0.1 * p
+    sigma_qop = sigma_p / (p * p)
+    # initial_variance_inflation = [1000, 1000, 100, 100, 10000]
+    initial_variance_inflation = [100, 100, 100, 100, 1000]
+    track_seed_tool.covLoc0 = initial_variance_inflation[0] * sigma_loc1 * sigma_loc1
+    track_seed_tool.covLoc1 = initial_variance_inflation[1] * sigma_loc0 * sigma_loc0
+    track_seed_tool.covPhi = initial_variance_inflation[2] * sigma_phi * sigma_phi
+    track_seed_tool.covTheta = initial_variance_inflation[3] * sigma_theta * sigma_theta
+    track_seed_tool.covQOverP = initial_variance_inflation[4] * sigma_qop * sigma_qop
+    track_seed_tool.std_cluster = 0.0231
+    track_seed_tool.TrackCollection = "Segments"
+
+    trajectory_states_writer_tool = CompFactory.RootTrajectoryStatesWriterTool()
+    trajectory_states_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_states_writer_tool1 = CompFactory.RootTrajectoryStatesWriterTool()
+    trajectory_states_writer_tool1.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_states_writer_tool1.FilePath = "track_states_ckf1.root"
+    trajectory_states_writer_tool2 = CompFactory.RootTrajectoryStatesWriterTool()
+    trajectory_states_writer_tool2.FilePath = "track_states_ckf2.root"
+    trajectory_states_writer_tool2.noDiagnostics = kwargs["noDiagnostics"]
+
+    trajectory_summary_writer_tool = CompFactory.RootTrajectorySummaryWriterTool(**kwargs)
+    trajectory_summary_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_summary_writer_tool1 = CompFactory.RootTrajectorySummaryWriterTool()
+    trajectory_summary_writer_tool1.FilePath = "track_summary_ckf1.root"
+    trajectory_summary_writer_tool1.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_summary_writer_tool2 = CompFactory.RootTrajectorySummaryWriterTool(**kwargs)
+    trajectory_summary_writer_tool2.FilePath = "track_summary_ckf2.root"
+    trajectory_summary_writer_tool2.noDiagnostics = kwargs["noDiagnostics"]
+
+    actsExtrapolationTool = CompFactory.FaserActsExtrapolationTool("FaserActsExtrapolationTool")
+    actsExtrapolationTool.MaxSteps = 1000
+    actsExtrapolationTool.TrackingGeometryTool = CompFactory.FaserActsTrackingGeometryTool("TrackingGeometryTool")
+
+    trajectory_performance_writer_tool = CompFactory.PerformanceWriterTool("PerformanceWriterTool", **kwargs)
+    trajectory_performance_writer_tool.ExtrapolationTool = actsExtrapolationTool
+    trajectory_performance_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+
+
+    ckf = CompFactory.CKF2(**kwargs)
+    kalman_fitter1 = CompFactory.KalmanFitterTool(name="fitterTool1", **kwargs)
+    kalman_fitter1.noDiagnostics = kwargs["noDiagnostics"]
+    kalman_fitter1.ActsLogging = "INFO"
+    kalman_fitter1.SummaryWriter = True
+    kalman_fitter1.StatesWriter = False
+    kalman_fitter1.SeedCovarianceScale = 10
+    kalman_fitter1.isMC = flags.Input.isMC
+    kalman_fitter1.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool1
+    kalman_fitter1.RootTrajectorySummaryWriterTool = trajectory_summary_writer_tool1
+    ckf.KalmanFitterTool1 = kalman_fitter1
+
+    kalman_fitter2 = CompFactory.KalmanFitterTool(name="fitterTool2", **kwargs)
+    kalman_fitter2.noDiagnostics = kwargs["noDiagnostics"]
+    kalman_fitter2.ActsLogging = "INFO"
+    kalman_fitter2.SummaryWriter = True
+    kalman_fitter2.StatesWriter = False
+    kalman_fitter2.SeedCovarianceScale = 10
+    kalman_fitter2.isMC = flags.Input.isMC
+    kalman_fitter2.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool2
+    kalman_fitter2.RootTrajectorySummaryWriterTool = trajectory_summary_writer_tool2
+    ckf.KalmanFitterTool2 = kalman_fitter2
+
+    ckf.TrackSeed = track_seed_tool
+    ckf.ActsLogging = "INFO"
+    ckf.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool
+    ckf.RootTrajectorySummaryWriterTool = trajectory_summary_writer_tool
+    ckf.PerformanceWriterTool = trajectory_performance_writer_tool
+    ckf.isMC = flags.Input.isMC
+    ckf.SummaryWriter = True
+    ckf.StatesWriter = False
+    ckf.PerformanceWriter = False
+
+    ckf.nMax = 10
+    ckf.chi2Max = 25
+    acc.addEventAlgo(ckf)
+    # acc.merge(CKF2_OutputCfg(flags))
+    return acc
diff --git a/Tracking/Acts/FaserActsKalmanFilter/python/CombinatorialKalmanFilterConfig.py b/Tracking/Acts/FaserActsKalmanFilter/python/CombinatorialKalmanFilterConfig.py
index 46c01fd06268dddf34867f76d358e4ab10f961e3..fac9e7ec1713f9e387da5746ea5c9fd98519202e 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/python/CombinatorialKalmanFilterConfig.py
+++ b/Tracking/Acts/FaserActsKalmanFilter/python/CombinatorialKalmanFilterConfig.py
@@ -1,43 +1,83 @@
 # Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
 
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
 from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
 from AthenaConfiguration.ComponentFactory import CompFactory
 from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
+from FaserActsGeometry.ActsGeometryConfig import ActsTrackingGeometrySvcCfg
 
 
-def CombinatorialKalmanFilter_OutputCfg(flags):
+def FaserActsAlignmentCondAlgCfg(flags, **kwargs):
     acc = ComponentAccumulator()
-    acc.merge(OutputStreamCfg(flags, "ESD"))
-    ostream = acc.getEventAlgo("OutputStreamESD")
-    ostream.TakeItemsFromInput = True
+    acc.addCondAlgo(CompFactory.FaserActsAlignmentCondAlg(name="FaserActsAlignmentCondAlg", **kwargs))
     return acc
 
-def CombinatorialKalmanFilter_OutputAODCfg(flags):
+
+def CombinatorialKalmanFilter_OutputCfg(flags):
     acc = ComponentAccumulator()
     itemList = ["xAOD::EventInfo#*",
                 "xAOD::EventAuxInfo#*",
-                "FaserSCT_RDO_Container#*",
-                "xAOD::FaserTriggerData#*",
-                "xAOD::FaserTriggerDataAux#*",
-                "ScintWaveformContainer#*",
                 "TrackCollection#*",
-                "xAOD::WaveformHitContainer#*",
-                "xAOD::WaveformHitAuxContainer#*",
-                "xAOD::WaveformClock#*",
-                "xAOD::WaveformClockAuxInfo#*"
                 ]
-    acc.merge(OutputStreamCfg(flags, "AOD",itemList))
-    ostream = acc.getEventAlgo("OutputStreamAOD")
+    acc.merge(OutputStreamCfg(flags, "ESD", itemList))
+    ostream = acc.getEventAlgo("OutputStreamESD")
     ostream.TakeItemsFromInput = True
     return acc
 
 
-
 def CombinatorialKalmanFilterCfg(flags, **kwargs):
-    acc = ComponentAccumulator()
-    kwargs.setdefault("SpacePointsSCTName", "SCT_SpacePointContainer")
-    combinatorialKalmanFilterAlg = CompFactory.CombinatorialKalmanFilterAlg(**kwargs)
-    acc.addEventAlgo(combinatorialKalmanFilterAlg)
+    # acc = ComponentAccumulator()
+    acc = FaserSCT_GeometryCfg(flags)
+    acc.merge(MagneticFieldSvcCfg(flags))
+    # acc.merge(FaserActsAlignmentCondAlgCfg(flags))
+    acts_tracking_geometry_svc = ActsTrackingGeometrySvcCfg(flags)
+    acc.merge(acts_tracking_geometry_svc )
+
+    # track_seed_tool = CompFactory.ClusterTrackSeedTool()
+    track_seed_tool = CompFactory.ThreeStationTrackSeedTool()
+    # track_seed_tool = CompFactory.ActsTrackSeedTool()
+    # track_seed_tool = CompFactory.MyTrackSeedTool()
+    sigma_loc0 = 1.9e-2
+    sigma_loc1 = 9e-1
+    sigma_phi = 3.3e-2
+    sigma_theta = 2.9e-4
+    p = 1000
+    sigma_p = 0.1 * p
+    sigma_qop = sigma_p / (p * p)
+    initial_variance_inflation = [100, 100, 100, 100, 1000]
+    track_seed_tool.covLoc0 = initial_variance_inflation[0] * sigma_loc0 * sigma_loc0
+    track_seed_tool.covLoc1 = initial_variance_inflation[1] * sigma_loc1 * sigma_loc1
+    track_seed_tool.covPhi = initial_variance_inflation[2] * sigma_phi * sigma_phi
+    track_seed_tool.covTheta = initial_variance_inflation[3] * sigma_theta * sigma_theta
+    track_seed_tool.covQOverP = initial_variance_inflation[4] * sigma_qop * sigma_qop
+    track_seed_tool.std_cluster = 0.023
+    track_seed_tool.origin = 0
+
+    trajectory_states_writer_tool = CompFactory.RootTrajectoryStatesWriterTool()
+    trajectory_states_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_states_writer_tool.MC = True
+
+    trajectory_summary_writer_tool = CompFactory.RootTrajectorySummaryWriterTool()
+    trajectory_summary_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+
+    actsExtrapolationTool = CompFactory.FaserActsExtrapolationTool("FaserActsExtrapolationTool")
+    actsExtrapolationTool.MaxSteps = 1000
+    actsExtrapolationTool.TrackingGeometryTool = CompFactory.FaserActsTrackingGeometryTool("TrackingGeometryTool")
+    performance_writer_tool = CompFactory.PerformanceWriterTool("PerformanceWriterTool")
+    performance_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+    performance_writer_tool.ExtrapolationTool = actsExtrapolationTool
+
+    ckf = CompFactory.CombinatorialKalmanFilterAlg(**kwargs)
+    ckf.TrackSeed = track_seed_tool
+    ckf.ActsLogging = "INFO"
+    ckf.noDiagnostics = kwargs["noDiagnostics"]
+    ckf.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool
+    ckf.RootTrajectorySummaryWriterTool = trajectory_summary_writer_tool
+    ckf.PerformanceWriterTool = performance_writer_tool
+
+    ckf.nMax = 10
+    ckf.chi2Max = 25
+    acc.addEventAlgo(ckf)
     acc.merge(CombinatorialKalmanFilter_OutputCfg(flags))
-    acc.merge(CombinatorialKalmanFilter_OutputAODCfg(flags))
     return acc
diff --git a/Tracking/Acts/FaserActsKalmanFilter/python/FaserActsKalmanFilterConfig.py b/Tracking/Acts/FaserActsKalmanFilter/python/FaserActsKalmanFilterConfig.py
index 6d6167c051b868d6032355e5bfd58d2a0f8296bd..a2ad7705968c54771f72345dfb56c82e9f03a8c4 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/python/FaserActsKalmanFilterConfig.py
+++ b/Tracking/Acts/FaserActsKalmanFilter/python/FaserActsKalmanFilterConfig.py
@@ -1,50 +1,162 @@
-# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
 
 from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
 from AthenaConfiguration.ComponentFactory import CompFactory
 from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
-from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
+# from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
 
-FaserActsKalmanFilterAlg,FaserActsTrackingGeometrySvc, FaserActsTrackingGeometryTool, FaserActsExtrapolationTool,FaserActsAlignmentCondAlg, THistSvc = CompFactory.getComps("FaserActsKalmanFilterAlg","FaserActsTrackingGeometrySvc", "FaserActsTrackingGeometryTool", "FaserActsExtrapolationTool","FaserActsAlignmentCondAlg", "THistSvc")
+# FaserActsKalmanFilterAlg,FaserActsTrackingGeometrySvc, FaserActsTrackingGeometryTool, FaserActsExtrapolationTool,FaserActsAlignmentCondAlg, THistSvc = CompFactory.getComps("FaserActsKalmanFilterAlg","FaserActsTrackingGeometrySvc", "FaserActsTrackingGeometryTool", "FaserActsExtrapolationTool","FaserActsAlignmentCondAlg", "THistSvc")
+#
+# def FaserActsTrackingGeometrySvcCfg(flags, **kwargs):
+#     acc = ComponentAccumulator()
+#     acc.addService(FaserActsTrackingGeometrySvc(name = "FaserActsTrackingGeometrySvc", **kwargs))
+#     return acc
+#
+# def FaserActsAlignmentCondAlgCfg(flags, **kwargs):
+#     acc = ComponentAccumulator()
+#     acc.addCondAlgo(CompFactory.FaserActsAlignmentCondAlg(name = "FaserActsAlignmentCondAlg", **kwargs))
+#     #acc.addCondAlgo(CompFactory.NominalAlignmentCondAlg(name = "NominalAlignmentCondAlg", **kwargs))
+#     return acc
+#
+# def FaserActsKalmanFilterCfg(flags, **kwargs):
+#     acc = ComponentAccumulator()
+#     # Initialize field service
+#     acc.merge(MagneticFieldSvcCfg(flags))
+#
+#     acc.merge(FaserActsTrackingGeometrySvcCfg(flags, **kwargs))
+#     acc.merge(FaserActsAlignmentCondAlgCfg(flags, **kwargs))
+#
+#     kwargs.setdefault("FaserSpacePointsSeedsName", "Seeds_SpacePointContainer")
+#     actsKalmanFilterAlg = FaserActsKalmanFilterAlg(**kwargs)
+#     #actsKalmanFilterAlg.TrackingGeometryTool = FaserActsTrackingGeometryTool("TrackingGeometryTool")
+#     actsExtrapolationTool=FaserActsExtrapolationTool("FaserActsExtrapolationTool")
+#     actsExtrapolationTool.FieldMode="FASER"
+#     #actsExtrapolationTool.FieldMode="Constant"
+#     actsExtrapolationTool.TrackingGeometryTool=FaserActsTrackingGeometryTool("TrackingGeometryTool")
+#     actsKalmanFilterAlg.ExtrapolationTool=actsExtrapolationTool
+#     acc.addEventAlgo(actsKalmanFilterAlg)
+#     histSvc= THistSvc()
+#     histSvc.Output += [ "KalmanTracks DATAFILE='KalmanTracks.root' OPT='RECREATE'" ]
+#     acc.addService(histSvc)
+#     acc.merge(FaserActsKalmanFilter_OutputCfg(flags))
+#     return  acc
+#
+# def FaserActsKalmanFilter_OutputCfg(flags):
+#     """Return ComponentAccumulator with Output for SCT. Not standalone."""
+#     acc = ComponentAccumulator()
+#     acc.merge(OutputStreamCfg(flags, "ESD"))
+#     ostream = acc.getEventAlgo("OutputStreamESD")
+#     ostream.TakeItemsFromInput = True
+#     return acc
 
-def FaserActsTrackingGeometrySvcCfg(flags, **kwargs):
+def FaserActsKalmanFilter_OutputCfg(flags):
     acc = ComponentAccumulator()
-    acc.addService(FaserActsTrackingGeometrySvc(name = "FaserActsTrackingGeometrySvc", **kwargs))
-    return acc 
+    itemList = ["xAOD::EventInfo#*",
+                "xAOD::EventAuxInfo#*",
+                "TrackCollection#*",
+                ]
+    acc.merge(OutputStreamCfg(flags, "ESD", itemList))
+    ostream = acc.getEventAlgo("OutputStreamESD")
+    ostream.TakeItemsFromInput = True
+    return acc
 
-def FaserActsAlignmentCondAlgCfg(flags, **kwargs):
+def FaserActsKalmanFilter_OutputAODCfg(flags):
     acc = ComponentAccumulator()
-    acc.addCondAlgo(CompFactory.FaserActsAlignmentCondAlg(name = "FaserActsAlignmentCondAlg", **kwargs))
-    #acc.addCondAlgo(CompFactory.NominalAlignmentCondAlg(name = "NominalAlignmentCondAlg", **kwargs))
+    itemList = ["xAOD::EventInfo#*",
+                "xAOD::EventAuxInfo#*",
+                "FaserSCT_RDO_Container#*",
+                "xAOD::FaserTriggerData#*",
+                "xAOD::FaserTriggerDataAux#*",
+                "ScintWaveformContainer#*",
+                "TrackCollection#*",
+                "xAOD::WaveformHitContainer#*",
+                "xAOD::WaveformHitAuxContainer#*",
+                "xAOD::WaveformClock#*",
+                "xAOD::WaveformClockAuxInfo#*"
+                ]
+    acc.merge(OutputStreamCfg(flags, "AOD",itemList))
+    ostream = acc.getEventAlgo("OutputStreamAOD")
+    ostream.TakeItemsFromInput = True
     return acc
 
+
 def FaserActsKalmanFilterCfg(flags, **kwargs):
     acc = ComponentAccumulator()
-    # Initialize field service
-    acc.merge(MagneticFieldSvcCfg(flags))
-
-    acc.merge(FaserActsTrackingGeometrySvcCfg(flags, **kwargs))
-    acc.merge(FaserActsAlignmentCondAlgCfg(flags, **kwargs))
-    
-    kwargs.setdefault("FaserSpacePointsSeedsName", "Seeds_SpacePointContainer")
-    actsKalmanFilterAlg = FaserActsKalmanFilterAlg(**kwargs)
-    #actsKalmanFilterAlg.TrackingGeometryTool = FaserActsTrackingGeometryTool("TrackingGeometryTool")
-    actsExtrapolationTool=FaserActsExtrapolationTool("FaserActsExtrapolationTool")
-    actsExtrapolationTool.FieldMode="FASER"
-    #actsExtrapolationTool.FieldMode="Constant"
-    actsExtrapolationTool.TrackingGeometryTool=FaserActsTrackingGeometryTool("TrackingGeometryTool")
-    actsKalmanFilterAlg.ExtrapolationTool=actsExtrapolationTool
-    acc.addEventAlgo(actsKalmanFilterAlg)
-    histSvc= THistSvc()
-    histSvc.Output += [ "KalmanTracks DATAFILE='KalmanTracks.root' OPT='RECREATE'" ]
-    acc.addService(histSvc)
-    acc.merge(FaserActsKalmanFilter_OutputCfg(flags))
-    return  acc
+    track_finder_tool = CompFactory.MultiTrackFinderTool()
+    # track_finder_tool = CompFactory.SegmentFitClusterTrackFinderTool()
+    # track_finder_tool = CompFactory.SegmentFitTrackFinderTool()
+    sigma_loc0 = 1.9e-2
+    sigma_loc1 = 9e-1
+    sigma_phi = 3.3e-2
+    sigma_theta = 2.9e-4
+    # sigma_p_rel = 0.1
+    p = 1
+    sigma_qop = 0.1 * p / (p * p)
+    initial_variance_inflation = [100, 100, 1000, 1000, 1000]
+    track_finder_tool.covLoc0 = initial_variance_inflation[0] * sigma_loc1 * sigma_loc1
+    track_finder_tool.covLoc1 = initial_variance_inflation[1] * sigma_loc0 * sigma_loc0
+    track_finder_tool.covPhi = initial_variance_inflation[2] * sigma_phi * sigma_phi
+    track_finder_tool.covTheta = initial_variance_inflation[3] * sigma_theta * sigma_theta
+    track_finder_tool.covQOverP = initial_variance_inflation[4] * sigma_qop * sigma_qop
+    # track_finder_tool.sigmaCluster = 0.04
+    # track_finder_tool.covLoc0 = 1e-1
+    # track_finder_tool.covLoc1 = 1e-1
+    # track_finder_tool.covPhi = 1e-1
+    # track_finder_tool.covTheta = 1e-1
+    # track_finder_tool.covQOverP = 1e-1
 
-def FaserActsKalmanFilter_OutputCfg(flags):
-    """Return ComponentAccumulator with Output for SCT. Not standalone."""
-    acc = ComponentAccumulator()
-    acc.merge(OutputStreamCfg(flags, "ESD"))
-    ostream = acc.getEventAlgo("OutputStreamESD")
-    ostream.TakeItemsFromInput = True
+    # track_finder_tool = CompFactory.TruthSeededTrackFinderTool()
+    # track_finder_tool.useTrueInitialParameters = False
+    # track_finder_tool.covMeas00 = 0.0004
+    # track_finder_tool.covMeas01 = 0.01
+    # track_finder_tool.covMeas10 = 0.01
+    # track_finder_tool.covMeas11 = 0.64
+    # track_finder_tool.covLoc0 = 1e-4
+    # track_finder_tool.covLoc1 = 1e-4
+    # track_finder_tool.covPhi = 1e-4
+    # track_finder_tool.covTheta = 1e-4
+    # track_finder_tool.covQOverP = 1e-4
+
+    # track_finder_tool.covLoc0 = 1e-1
+    # track_finder_tool.covLoc1 = 1e-1
+    # track_finder_tool.covPhi = 1e-1
+    # track_finder_tool.covTheta = 1e-1
+    # track_finder_tool.covQOverP = 1e-1
+
+    # track_finder_tool.sigmaLoc0 = 0.02
+    # track_finder_tool.sigmaLoc1 = 0.8
+    # track_finder_tool.sigmaPhi = 2 * pi/180
+    # track_finder_tool.sigmaTheta = 2 * pi/180
+    # track_finder_tool.sigmaP = 0.1
+
+    # track_finder_tool = CompFactory.TruthTrackFinderTool()
+    # track_finder_tool.first_side = 1
+    # track_finder_tool.sigmaMeasLoc0 = 0.02
+    # track_finder_tool.sigmaMeasLoc1 = 0.8
+    # track_finder_tool.covMeasLoc0 = 0.02 * 0.02 * 10
+    # track_finder_tool.covMeasLoc1 = 0.8 * 0.8 * 10
+    # track_finder_tool.sigmaLoc0 = 0
+    # track_finder_tool.sigmaLoc1 = 0
+    # track_finder_tool.sigmaPhi = 0
+    # track_finder_tool.sigmaTheta = 0
+    # track_finder_tool.sigmaP = 0
+    # track_finder_tool.covLoc0 = 1e-3
+    # track_finder_tool.covLoc1 = 1e-3
+    # track_finder_tool.covPhi = 1e-3
+    # track_finder_tool.covTheta = 1e-3
+    # track_finder_tool.covQOverP = 1e-3
+    trajectory_writer_tool = CompFactory.TrajectoryWriterTool()
+    trajectory_writer_tool.FilePath = "KalmanFilterTrajectories.root"
+    # trajectory_states_writer_tool = CompFactory.RootTrajectoryStatesWriterTool()
+    # trajectory_states_writer_tool.FilePath = "TrajectoryStates.root"
+    kalman_filter = CompFactory.FaserActsKalmanFilterAlg(**kwargs)
+    # kalman_filter.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool
+    kalman_filter.OutputTool = trajectory_writer_tool
+    kalman_filter.TrackFinderTool = track_finder_tool
+    kalman_filter.ActsLogging = "VERBOSE"
+    acc.addEventAlgo(kalman_filter)
+    acc.merge(FaserActsKalmanFilter_OutputCfg(flags))
+    # acc.merge(FaserActsKalmanFilter_OutputAODCfg(flags))
     return acc
diff --git a/Tracking/Acts/FaserActsKalmanFilter/python/GhostBustersConfig.py b/Tracking/Acts/FaserActsKalmanFilter/python/GhostBustersConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebe56d179393fcf3f51cb8df510271fbf5a85ebe
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/python/GhostBustersConfig.py
@@ -0,0 +1,35 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+from FaserActsGeometry.ActsGeometryConfig import ActsTrackingGeometrySvcCfg
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+
+
+def GhostBusters_OutputCfg(flags):
+    acc = ComponentAccumulator()
+    itemList = ["xAOD::EventInfo#*",
+                "xAOD::EventAuxInfo#*",
+                "TrackCollection#*",
+                ]
+    acc.merge(OutputStreamCfg(flags, "ESD", itemList))
+    ostream = acc.getEventAlgo("OutputStreamESD")
+    ostream.TakeItemsFromInput = True
+    return acc
+
+
+def GhostBustersCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    acts_tracking_geometry_svc = ActsTrackingGeometrySvcCfg(flags)
+    acc.merge(acts_tracking_geometry_svc )
+    acc.addEventAlgo(CompFactory.GhostBusters(**kwargs))
+    acc.merge(GhostBusters_OutputCfg(flags))
+
+    # thistSvc = CompFactory.THistSvc()
+    # thistSvc.Output += ["HIST2 DATAFILE='GhostBusters.root' OPT='RECREATE'"]
+    # acc.addService(thistSvc)
+
+    return acc
diff --git a/Tracking/Acts/FaserActsKalmanFilter/python/SeedingConfig.py b/Tracking/Acts/FaserActsKalmanFilter/python/SeedingConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6a84e5b48f6886a0935e6665f4827db55eba958
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/python/SeedingConfig.py
@@ -0,0 +1,16 @@
+"""
+    Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+"""
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+from FaserActsGeometry.ActsGeometryConfig import ActsTrackingGeometrySvcCfg
+
+
+def SeedingCfg(flags, **kwargs):
+    acc = FaserSCT_GeometryCfg(flags)
+    acts_tracking_geometry_svc = ActsTrackingGeometrySvcCfg(flags)
+    acc.merge(acts_tracking_geometry_svc )
+    seedingAlg = CompFactory.SeedingAlg(**kwargs)
+    acc.addEventAlgo(seedingAlg)
+    return acc
diff --git a/Tracking/Acts/FaserActsKalmanFilter/python/TI12CKF2Config.py b/Tracking/Acts/FaserActsKalmanFilter/python/TI12CKF2Config.py
new file mode 100644
index 0000000000000000000000000000000000000000..748900ed86612abebc50a0f64557223c922d97c8
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/python/TI12CKF2Config.py
@@ -0,0 +1,125 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS and FASER collaborations
+
+from FaserSCT_GeoModel.FaserSCT_GeoModelConfig import FaserSCT_GeometryCfg
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg
+from FaserActsGeometry.ActsGeometryConfig import ActsTrackingGeometrySvcCfg
+
+
+def FaserActsAlignmentCondAlgCfg(flags, **kwargs):
+    acc = ComponentAccumulator()
+    acc.addCondAlgo(CompFactory.FaserActsAlignmentCondAlg(name="FaserActsAlignmentCondAlg", **kwargs))
+    return acc
+
+
+def CKF2_OutputCfg(flags):
+    acc = ComponentAccumulator()
+    itemList = ["xAOD::EventInfo#*",
+                "xAOD::EventAuxInfo#*",
+                "TrackCollection#*",
+                ]
+    acc.merge(OutputStreamCfg(flags, "ESD", itemList))
+    ostream = acc.getEventAlgo("OutputStreamESD")
+    ostream.TakeItemsFromInput = True
+    return acc
+
+
+def TI12CKF2Cfg(flags, **kwargs):
+    # acc = ComponentAccumulator()
+    acc = FaserSCT_GeometryCfg(flags)
+    acc.merge(MagneticFieldSvcCfg(flags))
+    # acc.merge(FaserActsAlignmentCondAlgCfg(flags))
+    acts_tracking_geometry_svc = ActsTrackingGeometrySvcCfg(flags)
+    acc.merge(acts_tracking_geometry_svc )
+
+    # track_seed_tool = CompFactory.ClusterTrackSeedTool()
+    # track_seed_tool = CompFactory.ActsTrackSeedTool()
+    # track_seed_tool = CompFactory.MyTrackSeedTool()
+    # track_seed_tool = CompFactory.ThreeStationTrackSeedTool()
+    track_seed_tool = CompFactory.CircleFitTrackSeedTool()
+    sigma_loc0 = 1.9e-2
+    sigma_loc1 = 9e-1
+    sigma_phi = 3.3e-2
+    sigma_theta = 2.9e-4
+    p = 1000
+    sigma_p = 0.1 * p
+    sigma_qop = sigma_p / (p * p)
+    # initial_variance_inflation = [1000, 1000, 100, 100, 10000]
+    initial_variance_inflation = [100, 100, 100, 100, 1000]
+    track_seed_tool.covLoc0 = initial_variance_inflation[0] * sigma_loc1 * sigma_loc1
+    track_seed_tool.covLoc1 = initial_variance_inflation[1] * sigma_loc0 * sigma_loc0
+    track_seed_tool.covPhi = initial_variance_inflation[2] * sigma_phi * sigma_phi
+    track_seed_tool.covTheta = initial_variance_inflation[3] * sigma_theta * sigma_theta
+    track_seed_tool.covQOverP = initial_variance_inflation[4] * sigma_qop * sigma_qop
+    track_seed_tool.std_cluster = 0.0231
+    origin = -1900
+    track_seed_tool.origin = origin
+    track_seed_tool.TrackCollection = "Segments"
+
+    trajectory_states_writer_tool = CompFactory.RootTrajectoryStatesWriterTool()
+    trajectory_states_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_states_writer_tool1 = CompFactory.RootTrajectoryStatesWriterTool()
+    trajectory_states_writer_tool1.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_states_writer_tool1.FilePath = "track_states_ckf1.root"
+    trajectory_states_writer_tool2 = CompFactory.RootTrajectoryStatesWriterTool()
+    trajectory_states_writer_tool2.FilePath = "track_states_ckf2.root"
+    trajectory_states_writer_tool2.noDiagnostics = kwargs["noDiagnostics"]
+
+    trajectory_summary_writer_tool = CompFactory.RootTrajectorySummaryWriterTool(**kwargs)
+    trajectory_summary_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_summary_writer_tool1 = CompFactory.RootTrajectorySummaryWriterTool()
+    trajectory_summary_writer_tool1.FilePath = "track_summary_ckf1.root"
+    trajectory_summary_writer_tool1.noDiagnostics = kwargs["noDiagnostics"]
+    trajectory_summary_writer_tool2 = CompFactory.RootTrajectorySummaryWriterTool(**kwargs)
+    trajectory_summary_writer_tool2.FilePath = "track_summary_ckf2.root"
+    trajectory_summary_writer_tool2.noDiagnostics = kwargs["noDiagnostics"]
+
+    actsExtrapolationTool = CompFactory.FaserActsExtrapolationTool("FaserActsExtrapolationTool")
+    actsExtrapolationTool.MaxSteps = 1000
+    actsExtrapolationTool.TrackingGeometryTool = CompFactory.FaserActsTrackingGeometryTool("TrackingGeometryTool")
+
+    # trajectory_performance_writer_tool = CompFactory.PerformanceWriterTool("PerformanceWriterTool", **kwargs)
+    # trajectory_performance_writer_tool.ExtrapolationTool = actsExtrapolationTool
+    # trajectory_performance_writer_tool.noDiagnostics = kwargs["noDiagnostics"]
+
+
+    ckf = CompFactory.CKF2(**kwargs)
+    kalman_fitter1 = CompFactory.KalmanFitterTool(name="fitterTool1", **kwargs)
+    kalman_fitter1.noDiagnostics = kwargs["noDiagnostics"]
+    kalman_fitter1.ActsLogging = "INFO"
+    kalman_fitter1.SummaryWriter = True
+    kalman_fitter1.StatesWriter = True
+    kalman_fitter1.SeedCovarianceScale = 10
+    kalman_fitter1.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool1
+    kalman_fitter1.RootTrajectorySummaryWriterTool = trajectory_summary_writer_tool1
+    kalman_fitter1.origin = origin
+    ckf.KalmanFitterTool1 = kalman_fitter1
+
+    kalman_fitter2 = CompFactory.KalmanFitterTool(name="fitterTool2", **kwargs)
+    kalman_fitter2.noDiagnostics = kwargs["noDiagnostics"]
+    kalman_fitter2.ActsLogging = "INFO"
+    kalman_fitter2.SummaryWriter = True
+    kalman_fitter2.StatesWriter = True
+    kalman_fitter2.SeedCovarianceScale = 1
+    kalman_fitter2.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool2
+    kalman_fitter2.RootTrajectorySummaryWriterTool = trajectory_summary_writer_tool2
+    kalman_fitter2.origin = origin
+    ckf.KalmanFitterTool2 = kalman_fitter2
+
+    ckf.TrackSeed = track_seed_tool
+    ckf.ActsLogging = "INFO"
+    ckf.RootTrajectoryStatesWriterTool = trajectory_states_writer_tool
+    ckf.RootTrajectorySummaryWriterTool = trajectory_summary_writer_tool
+    ckf.SummaryWriter = True
+    ckf.StatesWriter = True
+    ckf.PerformanceWriter = False
+    # ckf.PerformanceWriterTool = trajectory_performance_writer_tool
+    ckf.origin = origin
+
+    ckf.nMax = 10
+    ckf.chi2Max = 100000
+    acc.addEventAlgo(ckf)
+    acc.merge(CKF2_OutputCfg(flags))
+    return acc
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/ActsTrackSeedTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/ActsTrackSeedTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..013e242db7572fc4a92875cd8a6188ed45bc5171
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/ActsTrackSeedTool.cxx
@@ -0,0 +1,147 @@
+#include "FaserActsKalmanFilter/ActsTrackSeedTool.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "Identifier/Identifier.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "boost/container/small_vector.hpp"
+#include "Acts/Seeding/EstimateTrackParamsFromSeed.hpp"
+
+using namespace Acts::UnitLiterals;
+
+
+ActsTrackSeedTool::ActsTrackSeedTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode ActsTrackSeedTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackCollection.initialize());
+  ATH_CHECK(m_clusterContainerKey.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ActsTrackSeedTool::run() {
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+  ATH_CHECK(trackCollection.isValid());
+
+  SG::ReadHandle<Tracker::FaserSCT_ClusterContainer> clusterContainer {m_clusterContainerKey};
+  ATH_CHECK(clusterContainer.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+  const FaserActsGeometryContext& gctx = m_trackingGeometryTool->getNominalGeometryContext();
+  Acts::GeometryContext geoctx = gctx.context();
+
+  const int kSize = 1;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0};
+  std::vector<IndexSourceLink> sourceLinks;
+  std::vector<Measurement> measurements;
+  std::map<Index, Identifier> identifierLinkMap;
+  std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+  for (const Tracker::FaserSCT_ClusterCollection* clusterCollection : *clusterContainer) {
+    for (const Tracker::FaserSCT_Cluster* cluster : *clusterCollection) {
+      Identifier id = cluster->detectorElement()->identify();
+      identifierLinkMap[measurements.size()] = id;
+      if (identifierMap->count(id) != 0) {
+        Acts::GeometryIdentifier geoId = identifierMap->at(id);
+        IndexSourceLink sourceLink(geoId, measurements.size(), cluster);
+        // create measurement
+        const auto& par = cluster->localPosition();
+        Eigen::Matrix<double, 1, 1> pos {par.x(),};
+        Eigen::Matrix<double, 1, 1> cov {m_std_cluster * m_std_cluster,};
+        ThisMeasurement meas(sourceLink, Indices, pos, cov);
+        sourceLinks.push_back(sourceLink);
+        measurements.emplace_back(std::move(meas));
+        clusters.push_back(cluster);
+      }
+    }
+  }
+
+  std::map<int, std::vector<Amg::Vector3D>> station_position_map;
+  for (const Trk::Track* track : *trackCollection) {
+    auto momentum = track->trackParameters()->front()->momentum();
+    ATH_MSG_DEBUG("track momentum: " << momentum.x() << ", " << momentum.y() << ", " << momentum.z());
+    for (const Trk::TrackStateOnSurface* trackState : *(track->trackStateOnSurfaces())) {
+      auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (trackState->measurementOnTrack());
+      if (clusterOnTrack) {
+        Identifier id = clusterOnTrack->identify();
+        int station = m_idHelper->station(id);
+        auto fitParameters = track->trackParameters()->front();
+        Amg::Vector3D fitPosition = fitParameters->position();
+        station_position_map[station].push_back(fitPosition);
+        break;
+      }
+    }
+  }
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  std::vector<Acts::CurvilinearTrackParameters> initParams {};
+  for (const Amg::Vector3D& pos1 : station_position_map[1]) {
+    for (const Amg::Vector3D& pos2 : station_position_map[2]) {
+      for (const Amg::Vector3D& pos3 : station_position_map[3]) {
+        initParams.push_back(get_params(geoctx, pos1, pos2, pos3, cov, m_origin));
+//        auto seed = initParams.back();
+//        auto seed_momentum = seed.momentum();
+      }
+    }
+  }
+
+  m_initialTrackParameters = std::make_shared<std::vector<Acts::CurvilinearTrackParameters>>(initParams);
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourceLinks);
+  m_idLinks = std::make_shared<IdentifierLink>(identifierLinkMap);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, m_origin}, Acts::Vector3{0, 0, -1});
+  m_clusters = std::make_shared<std::vector<const Tracker::FaserSCT_Cluster*>>(clusters);
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ActsTrackSeedTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+Acts::CurvilinearTrackParameters ActsTrackSeedTool::get_params(
+    const Acts::GeometryContext& gctx, const Amg::Vector3D& pos1,
+    const Amg::Vector3D& pos2, const Amg::Vector3D& pos3,
+    const Acts::BoundSymMatrix& cov, double /*origin*/) {
+  const auto surface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, pos1.z()}, Acts::Vector3{0, 0, -1});
+  boost::container::small_vector<const Amg::Vector3D*, 3> spacepoints {};
+  spacepoints.push_back(&pos1);
+  spacepoints.push_back(&pos2);
+  spacepoints.push_back(&pos3);
+  auto trackParams = Acts::CurvilinearTrackParameters(Acts::Vector4{0, 0, 0, 0}, Acts::Vector3{0, 0, 0}, 0, 0, cov);
+  auto optParams = Acts::estimateTrackParamsFromSeed(
+      gctx, spacepoints.begin(), spacepoints.end(), *surface, Acts::Vector3{0.57_T, 0, 0}, 0.1_T);
+  if (not optParams.has_value()) {
+    std::cout << "Estimation of track parameters failed." << std::endl;
+  } else {
+    const auto& params = optParams.value();
+    double charge = std::copysign(1, params[Acts::eBoundQOverP]);
+    Acts::Vector4 pos {params.x(), params.y(), params.z(), params.w()};
+    Acts::Vector3 dir{std::sin(params[Acts::eBoundTheta]) * std::cos(params[Acts::eBoundPhi]),
+                      std::sin(params[Acts::eBoundTheta]) * std::sin(params[Acts::eBoundPhi]),
+                      std::cos(params[Acts::eBoundTheta])};
+    double abs_momentum = std::abs(1/params[Acts::eBoundQOverP]);
+    trackParams = Acts::CurvilinearTrackParameters(pos, dir, charge, abs_momentum, cov);
+  }
+  return trackParams;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/CKF2.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/CKF2.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..e75632427e36b77d968cf9cb961268ac5342a481
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/CKF2.cxx
@@ -0,0 +1,565 @@
+#include "FaserActsKalmanFilter/CKF2.h"
+
+#include "StoreGate/ReadHandle.h"
+#include "StoreGate/ReadCondHandleKey.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePointCollection.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrkPrepRawData/PrepRawData.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrkRIO_OnTrack/RIO_OnTrack.h"
+#include "TrkSurfaces/Surface.h"
+#include "Identifier/Identifier.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "FaserActsKalmanFilter/Measurement.h"
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include "Acts/Surfaces/PerigeeSurface.hpp"
+#include "Acts/MagneticField/MagneticFieldContext.hpp"
+#include "FaserActsKalmanFilter/TrackSelection.h"
+#include <algorithm>
+
+#include "FaserActsGeometry/FASERMagneticFieldWrapper.h"
+
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
+#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
+
+
+#include "Acts/EventData/Measurement.hpp"
+
+size_t CKF2::TrajectoryInfo::nClusters {0};
+
+using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
+//std::array<Acts::BoundIndices, 2> indices = {Acts::eBoundLoc0, Acts::eBoundLoc1};
+
+
+CKF2::CKF2(
+    const std::string& name, ISvcLocator* pSvcLocator)
+    : AthAlgorithm(name, pSvcLocator) {}
+
+
+StatusCode CKF2::initialize() {
+  ATH_CHECK(m_fieldCondObjInputKey.initialize());
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackSeedTool.retrieve());
+  ATH_CHECK(m_kalmanFitterTool1.retrieve());
+  ATH_CHECK(m_kalmanFitterTool2.retrieve());
+  //  ATH_CHECK(m_trackCollection.initialize());
+  if (m_performanceWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_performanceWriterTool.retrieve());
+  }
+  if (m_statesWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectoryStatesWriterTool.retrieve());
+  }
+  if (m_summaryWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectorySummaryWriterTool.retrieve());
+  }
+  ATH_CHECK(detStore()->retrieve(m_idHelper,"FaserSCT_ID"));
+  m_fit = makeTrackFinderFunction(m_trackingGeometryTool->trackingGeometry(),
+                                  m_resolvePassive, m_resolveMaterial, m_resolveSensitive);
+  m_kf = makeTrackFitterFunction(m_trackingGeometryTool->trackingGeometry());
+  // FIXME fix Acts logging level
+  if (m_actsLogging == "VERBOSE") {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::VERBOSE);
+  } else if (m_actsLogging == "DEBUG") {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::DEBUG);
+  } else {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::INFO);
+  }
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode CKF2::execute() {
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+  m_numberOfEvents++;
+
+  ATH_CHECK(m_trackCollection.initialize());
+  SG::WriteHandle<TrackCollection> trackContainer{m_trackCollection,ctx};
+  std::unique_ptr<TrackCollection> outputTracks = std::make_unique<TrackCollection>();
+
+  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
+      = m_trackingGeometryTool->trackingGeometry();
+
+  const FaserActsGeometryContext& faserActsGeometryContext = m_trackingGeometryTool->getNominalGeometryContext();
+  auto gctx = faserActsGeometryContext.context();
+  Acts::MagneticFieldContext magFieldContext = getMagneticFieldContext(ctx);
+  Acts::CalibrationContext calibContext;
+
+  CHECK(m_trackSeedTool->run());
+  std::shared_ptr<const Acts::Surface> initialSurface =
+      m_trackSeedTool->initialSurface();
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialParameters =
+      m_trackSeedTool->initialTrackParameters();
+  std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks =
+      m_trackSeedTool->sourceLinks();
+  double origin = m_trackSeedTool->targetZPosition();
+
+  std::shared_ptr<std::vector<Measurement>> measurements = m_trackSeedTool->measurements();
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters = m_trackSeedTool->clusters();
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_SpacePoint*>> spacePoints = m_trackSeedTool->spacePoints();
+  std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> seedClusters = m_trackSeedTool->seedClusters();
+
+  TrajectoryInfo::nClusters = sourceLinks->size();
+  TrajectoriesContainer trajectories;
+  trajectories.reserve(initialParameters->size());
+
+  Acts::PropagatorPlainOptions pOptions;
+  pOptions.maxSteps = m_maxSteps;
+
+  Acts::MeasurementSelector::Config measurementSelectorCfg = {
+      {Acts::GeometryIdentifier(), {m_chi2Max, m_nMax}},
+  };
+
+  Acts::RotationMatrix3 rotation = Acts::RotationMatrix3::Identity();
+  rotation.col(0) = Acts::Vector3(0, 0, -1);
+  rotation.col(1) = Acts::Vector3(0, 1, 0);
+  rotation.col(2) = Acts::Vector3(1, 0, 0);
+  Acts::Translation3 trans(0., 0., origin);
+  Acts::Transform3 trafo(rotation * trans);
+  initialSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(trafo);
+
+  // Set the CombinatorialKalmanFilter options
+  CKF2::TrackFinderOptions options(
+      gctx, magFieldContext, calibContext,
+      IndexSourceLinkAccessor(), MeasurementCalibrator(*measurements),
+      Acts::MeasurementSelector(measurementSelectorCfg),
+      Acts::LoggerWrapper{*m_logger}, pOptions, &(*initialSurface));
+
+  // Perform the track finding for all initial parameters
+  m_numberOfTrackSeeds += initialParameters->size();
+  ATH_MSG_DEBUG("Invoke track finding with " << initialParameters->size() << " seeds.");
+  IndexSourceLinkContainer tmp;
+  for (const auto& sl : *sourceLinks) {
+    tmp.emplace_hint(tmp.end(), sl);
+  }
+
+  for (const auto& init : *initialParameters) {
+    ATH_MSG_DEBUG("  position: " << init.position(gctx).transpose());
+    ATH_MSG_DEBUG("  momentum: " << init.momentum().transpose());
+    ATH_MSG_DEBUG("  charge:   " << init.charge());
+  }
+
+  auto results = (*m_fit)(tmp, *initialParameters, options);
+
+  // results contain a MultiTrajectory for each track seed with a trajectory of each branch of the CKF.
+  // To simplify the ambiguity solving a list of MultiTrajectories is created, each containing only a single track.
+  std::list<TrajectoryInfo> allTrajectories;
+  for (auto &result : results) {
+    if (not result.ok()) {
+      continue;
+    }
+    CKFResult ckfResult = result.value();
+    for (size_t trackTip : ckfResult.lastMeasurementIndices) {
+      allTrajectories.emplace_back(TrajectoryInfo(FaserActsRecMultiTrajectory(
+          ckfResult.fittedStates, {trackTip}, {{trackTip, ckfResult.fittedParameters.at(trackTip)}})));
+    }
+  }
+  m_numberOfFittedTracks += allTrajectories.size();
+
+  // the list of MultiTrajectories is sorted by the number of measurements using the chi2 value as a tie-breaker
+  allTrajectories.sort([](const TrajectoryInfo &left, const TrajectoryInfo &right) {
+    if (left.nMeasurements > right.nMeasurements) return true;
+    if (left.nMeasurements < right.nMeasurements) return false;
+    if (left.chi2 < right.chi2) return true;
+    else return false;
+  });
+
+  // select all tracks with at least 13 heats and with 6 or less shared hits, starting from the best track
+  // TODO use Gaudi parameters for the number of hits and shared hits
+  // TODO allow shared hits only in the first station?
+  std::vector<FaserActsRecMultiTrajectory> selectedTrajectories {};
+  while (not allTrajectories.empty()) {
+    TrajectoryInfo selected = allTrajectories.front();
+    selectedTrajectories.push_back(selected.trajectory);
+    allTrajectories.remove_if([&](const TrajectoryInfo &p) {
+      return (p.nMeasurements <= 12) || ((p.clusterSet & selected.clusterSet).count() > 6);
+    });
+  }
+
+  for (const FaserActsRecMultiTrajectory &traj : selectedTrajectories) {
+    const auto params = traj.trackParameters(traj.tips().front());
+    ATH_MSG_DEBUG("Fitted parameters");
+    ATH_MSG_DEBUG("  params:   " << params.parameters().transpose());
+    ATH_MSG_DEBUG("  position: " << params.position(gctx).transpose());
+    ATH_MSG_DEBUG("  momentum: " << params.momentum().transpose());
+    ATH_MSG_DEBUG("  charge:   " << params.charge());
+    // double charge = params.charge();
+    std::unique_ptr<Trk::Track> track = makeTrack(gctx, traj);
+    if (track) {
+      // outputTracks->push_back(std::move(track));
+      std::unique_ptr<Trk::Track> track2 = m_kalmanFitterTool1->fit(ctx, gctx, *track, trajectories, Acts::BoundVector::Zero(), m_isMC, origin);
+      if (track2) {
+        std::unique_ptr<Trk::Track> track3 = m_kalmanFitterTool2->fit(ctx, gctx, *track2, trajectories, Acts::BoundVector::Zero(), m_isMC, origin);
+        outputTracks->push_back(std::move(track3));
+        m_numberOfSelectedTracks++;
+      }
+    }
+  }
+
+  // run the performance writer
+  if (m_statesWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectoryStatesWriterTool->write(gctx, selectedTrajectories, m_isMC));
+  }
+  if (m_summaryWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectorySummaryWriterTool->write(gctx, selectedTrajectories, m_isMC));
+  }
+  if  (m_performanceWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_performanceWriterTool->write(gctx, selectedTrajectories));
+  }
+  ATH_CHECK(trackContainer.record(std::move(outputTracks)));
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode CKF2::finalize() {
+  ATH_MSG_INFO("CombinatorialKalmanFilterAlg::finalize()");
+  ATH_MSG_INFO(m_numberOfEvents << " events processed.");
+  ATH_MSG_INFO(m_numberOfTrackSeeds << " seeds.");
+  ATH_MSG_INFO(m_numberOfFittedTracks << " fitted tracks.");
+  ATH_MSG_INFO(m_numberOfSelectedTracks << " selected and re-fitted tracks.");
+  return StatusCode::SUCCESS;
+}
+
+
+Acts::MagneticFieldContext CKF2::getMagneticFieldContext(const EventContext& ctx) const {
+  SG::ReadCondHandle<FaserFieldCacheCondObj> readHandle{m_fieldCondObjInputKey, ctx};
+  if (!readHandle.isValid()) {
+    std::stringstream msg;
+    msg << "Failed to retrieve magnetic field condition data " << m_fieldCondObjInputKey.key() << ".";
+    throw std::runtime_error(msg.str());
+  }
+  const FaserFieldCacheCondObj* fieldCondObj{*readHandle};
+  return Acts::MagneticFieldContext(fieldCondObj);
+}
+
+
+std::unique_ptr<Trk::Track>
+CKF2::makeTrack(const Acts::GeometryContext &geoCtx, const FaserActsRecMultiTrajectory &traj) const {
+  using ConstTrackStateProxy =
+      Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+  std::unique_ptr<Trk::Track> newtrack = nullptr;
+  //Get the fit output object
+  DataVector<const Trk::TrackStateOnSurface>* finalTrajectory = new DataVector<const Trk::TrackStateOnSurface>{};
+  std::vector<std::unique_ptr<const Acts::BoundTrackParameters>> actsSmoothedParam;
+  // Loop over all the output state to create track state
+  traj.multiTrajectory().visitBackwards(traj.tips().front(), [&](const ConstTrackStateProxy& state) {
+    auto flag = state.typeFlags();
+    if (state.referenceSurface().associatedDetectorElement() != nullptr) {
+      // We need to determine the type of state
+      std::bitset<Trk::TrackStateOnSurface::NumberOfTrackStateOnSurfaceTypes> typePattern;
+      const Trk::TrackParameters *parm;
+
+      // State is a hole (no associated measurement), use predicted para meters
+      if (flag[Acts::TrackStateFlag::HoleFlag] == true) {
+        const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                   state.predicted(),
+                                                   state.predictedCovariance());
+        parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+        // auto boundaryCheck = m_boundaryCheckTool->boundaryCheck(*p arm);
+        typePattern.set(Trk::TrackStateOnSurface::Hole);
+      }
+        // The state was tagged as an outlier, use filtered parameters
+      else if (flag[Acts::TrackStateFlag::OutlierFlag] == true) {
+        const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                   state.filtered(), state.filteredCovariance());
+        parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+        typePattern.set(Trk::TrackStateOnSurface::Outlier);
+      }
+        // The state is a measurement state, use smoothed parameters
+      else {
+        const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                   state.smoothed(), state.smoothedCovariance());
+        actsSmoothedParam.push_back(std::make_unique<const Acts::BoundTrackParameters>(Acts::BoundTrackParameters(actsParam)));
+        //  const auto& psurface=actsParam.referenceSurface();
+        Acts::Vector2 local(actsParam.parameters()[Acts::eBoundLoc0], actsParam.parameters()[Acts::eBoundLoc1]);
+        //  const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(actsParam.parameters()[Acts::eBoundPhi], actsParam.parameters()[Acts::eBoundTheta]);
+        //  auto pos=actsParam.position(tgContext);
+        parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+        typePattern.set(Trk::TrackStateOnSurface::Measurement);
+      }
+      Tracker::FaserSCT_ClusterOnTrack* measState = nullptr;
+      if (state.hasUncalibrated()) {
+        const Tracker::FaserSCT_Cluster* fitCluster = state.uncalibrated().hit();
+        if (fitCluster->detectorElement() != nullptr) {
+          measState = new Tracker::FaserSCT_ClusterOnTrack{
+              fitCluster,
+              Trk::LocalParameters{
+                  Trk::DefinedParameter{fitCluster->localPosition()[0], Trk::loc1},
+                  Trk::DefinedParameter{fitCluster->localPosition()[1], Trk::loc2}
+              },
+              fitCluster->localCovariance(),
+              m_idHelper->wafer_hash(fitCluster->detectorElement()->identify())
+          };
+        }
+      }
+      double nDoF = state.calibratedSize();
+      const Trk::FitQualityOnSurface *quality = new Trk::FitQualityOnSurface(state.chi2(), nDoF);
+      const Trk::TrackStateOnSurface *perState = new Trk::TrackStateOnSurface(measState, parm, quality, nullptr, typePattern);
+      // If a state was succesfully created add it to the trajectory
+      if (perState) {
+        finalTrajectory->insert(finalTrajectory->begin(), perState);
+      }
+    }
+    return;
+  });
+
+  // Create the track using the states
+  const Trk::TrackInfo newInfo(Trk::TrackInfo::TrackFitter::KalmanFitter, Trk::ParticleHypothesis::muon);
+  // Trk::FitQuality* q = nullptr;
+  // newInfo.setTrackFitter(Trk::TrackInfo::TrackFitter::KalmanFitter     ); //Mark the fitter as KalmanFitter
+  newtrack = std::make_unique<Trk::Track>(newInfo, std::move(*finalTrajectory), nullptr);
+  return newtrack;
+}
+
+
+std::unique_ptr<Trk::Track>
+CKF2::makeTrack(Acts::GeometryContext& geoCtx, TrackFitterResult& fitResult) const {
+  using ConstTrackStateProxy =
+  Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+  std::unique_ptr<Trk::Track> newtrack = nullptr;
+  //Get the fit output object
+  const auto& fitOutput = fitResult.value();
+  if (fitOutput.fittedParameters.size() > 0) {
+    DataVector<const Trk::TrackStateOnSurface>* finalTrajectory = new DataVector<const Trk::TrackStateOnSurface>{};
+    std::vector<std::unique_ptr<const Acts::BoundTrackParameters>> actsSmoothedParam;
+    // Loop over all the output state to create track state
+    fitOutput.fittedStates.visitBackwards(fitOutput.lastMeasurementIndices.front(), [&](const ConstTrackStateProxy& state) {
+      auto flag = state.typeFlags();
+      if (state.referenceSurface().associatedDetectorElement() != nullptr) {
+        // We need to determine the type of state
+        std::bitset<Trk::TrackStateOnSurface::NumberOfTrackStateOnSurfaceTypes> typePattern;
+        const Trk::TrackParameters *parm;
+
+        // State is a hole (no associated measurement), use predicted para meters
+        if (flag[Acts::TrackStateFlag::HoleFlag] == true) {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.predicted(),
+                                                     state.predictedCovariance());
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          // auto boundaryCheck = m_boundaryCheckTool->boundaryCheck(*p arm);
+          typePattern.set(Trk::TrackStateOnSurface::Hole);
+        }
+          // The state was tagged as an outlier, use filtered parameters
+        else if (flag[Acts::TrackStateFlag::OutlierFlag] == true) {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.filtered(), state.filteredCovariance());
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          typePattern.set(Trk::TrackStateOnSurface::Outlier);
+        }
+          // The state is a measurement state, use smoothed parameters
+        else {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.smoothed(), state.smoothedCovariance());
+          actsSmoothedParam.push_back(std::make_unique<const Acts::BoundTrackParameters>(Acts::BoundTrackParameters(actsParam)));
+          //  const auto& psurface=actsParam.referenceSurface();
+          Acts::Vector2 local(actsParam.parameters()[Acts::eBoundLoc0], actsParam.parameters()[Acts::eBoundLoc1]);
+          //  const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(actsParam.parameters()[Acts::eBoundPhi], actsParam.parameters()[Acts::eBoundTheta]);
+          //  auto pos=actsParam.position(tgContext);
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          typePattern.set(Trk::TrackStateOnSurface::Measurement);
+        }
+        Tracker::FaserSCT_ClusterOnTrack* measState = nullptr;
+        if (state.hasUncalibrated()) {
+          const Tracker::FaserSCT_Cluster* fitCluster = state.uncalibrated().hit();
+          if (fitCluster->detectorElement() != nullptr) {
+            measState = new Tracker::FaserSCT_ClusterOnTrack{
+                fitCluster,
+                Trk::LocalParameters{
+                    Trk::DefinedParameter{fitCluster->localPosition()[0], Trk::loc1},
+                    Trk::DefinedParameter{fitCluster->localPosition()[1], Trk::loc2}
+                },
+                fitCluster->localCovariance(),
+                m_idHelper->wafer_hash(fitCluster->detectorElement()->identify())
+            };
+          }
+        }
+        double nDoF = state.calibratedSize();
+        const Trk::FitQualityOnSurface *quality = new Trk::FitQualityOnSurface(state.chi2(), nDoF);
+        const Trk::TrackStateOnSurface *perState = new Trk::TrackStateOnSurface(measState, parm, quality, nullptr, typePattern);
+        // If a state was succesfully created add it to the trajectory
+        if (perState) {
+          finalTrajectory->insert(finalTrajectory->begin(), perState);
+        }
+      }
+      return;
+    });
+
+    // Create the track using the states
+    const Trk::TrackInfo newInfo(Trk::TrackInfo::TrackFitter::KalmanFitter, Trk::ParticleHypothesis::muon);
+    // Trk::FitQuality* q = nullptr;
+    // newInfo.setTrackFitter(Trk::TrackInfo::TrackFitter::KalmanFitter     ); //Mark the fitter as KalmanFitter
+    newtrack = std::make_unique<Trk::Track>(newInfo, std::move(*finalTrajectory), nullptr);
+  }
+  return newtrack;
+}
+
+const Trk::TrackParameters*
+CKF2::ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const      {
+  using namespace Acts::UnitLiterals;
+  std::optional<AmgSymMatrix(5)> cov = std::nullopt;
+  if (actsParameter.covariance()){
+    AmgSymMatrix(5) newcov(actsParameter.covariance()->topLeftCorner(5, 5));
+    // Convert the covariance matrix to GeV
+    for(int i=0; i < newcov.rows(); i++){
+      newcov(i, 4) = newcov(i, 4)*1_MeV;
+    }
+    for(int i=0; i < newcov.cols(); i++){
+      newcov(4, i) = newcov(4, i)*1_MeV;
+    }
+    cov =  std::optional<AmgSymMatrix(5)>(newcov);
+  }
+  const Amg::Vector3D& pos=actsParameter.position(gctx);
+  double tphi=actsParameter.get<Acts::eBoundPhi>();
+  double ttheta=actsParameter.get<Acts::eBoundTheta>();
+  double tqOverP=actsParameter.get<Acts::eBoundQOverP>()*1_MeV;
+  double p = std::abs(1. / tqOverP);
+  Amg::Vector3D tmom(p * std::cos(tphi) * std::sin(ttheta), p * std::sin(tphi) * std::sin(ttheta), p * std::cos(ttheta));
+  const Trk::CurvilinearParameters * curv = new Trk::CurvilinearParameters(pos,tmom,tqOverP>0, cov);
+  return curv;
+}
+
+void CKF2::computeSharedHits(std::vector<IndexSourceLink>* sourceLinks, TrackFinderResult& results) const {
+  // Compute shared hits from all the reconstructed tracks
+  // Compute nSharedhits and Update ckf results
+  // hit index -> list of multi traj indexes [traj, meas]
+  static_assert(Acts::SourceLinkConcept<IndexSourceLink>,
+                "Source link does not fulfill SourceLinkConcept");
+
+  std::vector<std::size_t> firstTrackOnTheHit(
+      sourceLinks->size(), std::numeric_limits<std::size_t>::max());
+  std::vector<std::size_t> firstStateOnTheHit(
+      sourceLinks->size(), std::numeric_limits<std::size_t>::max());
+
+  for (unsigned int iresult = 0; iresult < results.size(); iresult++) {
+    if (not results.at(iresult).ok()) {
+      continue;
+    }
+
+    auto& ckfResult = results.at(iresult).value();
+    auto& measIndexes = ckfResult.lastMeasurementIndices;
+
+    for (auto measIndex : measIndexes) {
+      ckfResult.fittedStates.visitBackwards(measIndex, [&](const auto& state) {
+        if (not state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag))
+          return;
+
+        std::size_t hitIndex = state.uncalibrated().index();
+
+        // Check if hit not already used
+        if (firstTrackOnTheHit.at(hitIndex) ==
+            std::numeric_limits<std::size_t>::max()) {
+          firstTrackOnTheHit.at(hitIndex) = iresult;
+          firstStateOnTheHit.at(hitIndex) = state.index();
+          return;
+        }
+
+        // if already used, control if first track state has been marked
+        // as shared
+        int indexFirstTrack = firstTrackOnTheHit.at(hitIndex);
+        int indexFirstState = firstStateOnTheHit.at(hitIndex);
+        if (not results.at(indexFirstTrack).value().fittedStates.getTrackState(indexFirstState).typeFlags().test(Acts::TrackStateFlag::SharedHitFlag))
+          results.at(indexFirstTrack).value().fittedStates.getTrackState(indexFirstState).typeFlags().set(Acts::TrackStateFlag::SharedHitFlag);
+
+        // Decorate this track
+        results.at(iresult).value().fittedStates.getTrackState(state.index()).typeFlags().set(Acts::TrackStateFlag::SharedHitFlag);
+      });
+    }
+  }
+}
+
+
+namespace {
+
+using Updater = Acts::GainMatrixUpdater;
+using Smoother = Acts::GainMatrixSmoother;
+
+using Stepper = Acts::EigenStepper<>;
+using Navigator = Acts::Navigator;
+using Propagator = Acts::Propagator<Stepper, Navigator>;
+using CKF = Acts::CombinatorialKalmanFilter<Propagator, Updater, Smoother>;
+
+using TrackParametersContainer = std::vector<CKF2::TrackParameters>;
+
+struct TrackFinderFunctionImpl
+    : public CKF2::TrackFinderFunction {
+  CKF trackFinder;
+
+  TrackFinderFunctionImpl(CKF&& f) : trackFinder(std::move(f)) {}
+
+  CKF2::TrackFinderResult operator()(
+      const IndexSourceLinkContainer& sourcelinks,
+      const TrackParametersContainer& initialParameters,
+      const CKF2::TrackFinderOptions& options)
+  const override {
+    return trackFinder.findTracks(sourcelinks, initialParameters, options);
+  };
+};
+
+}  // namespace
+
+std::shared_ptr<CKF2::TrackFinderFunction>
+CKF2::makeTrackFinderFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    bool resolvePassive, bool resolveMaterial, bool resolveSensitive) {
+  auto magneticField = std::make_shared<FASERMagneticFieldWrapper>();
+  Stepper stepper(std::move(magneticField));
+  Navigator::Config cfg{trackingGeometry};
+  cfg.resolvePassive = resolvePassive;
+  cfg.resolveMaterial = resolveMaterial;
+  cfg.resolveSensitive = resolveSensitive;
+  Navigator navigator(cfg);
+  Propagator propagator(std::move(stepper), std::move(navigator));
+  CKF trackFinder(std::move(propagator));
+
+  // build the track finder functions. owns the track finder object.
+  return std::make_shared<TrackFinderFunctionImpl>(std::move(trackFinder));
+}
+
+
+namespace {
+
+using Updater = Acts::GainMatrixUpdater;
+using Smoother = Acts::GainMatrixSmoother;
+using Stepper = Acts::EigenStepper<>;
+using Propagator = Acts::Propagator<Stepper, Acts::Navigator>;
+using Fitter = Acts::KalmanFitter<Propagator, Updater, Smoother>;
+
+struct TrackFitterFunctionImpl
+    : public CKF2::TrackFitterFunction {
+  Fitter trackFitter;
+
+  TrackFitterFunctionImpl(Fitter &&f) : trackFitter(std::move(f)) {}
+
+  CKF2::KFResult operator()(
+      const std::vector<IndexSourceLink> &sourceLinks,
+      const Acts::BoundTrackParameters &initialParameters,
+      const CKF2::TrackFitterOptions &options)
+  const override {
+    return trackFitter.fit(sourceLinks, initialParameters, options);
+  };
+};
+
+}  // namespace
+
+
+std::shared_ptr<CKF2::TrackFitterFunction>
+CKF2::makeTrackFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry) {
+  auto magneticField = std::make_shared<FASERMagneticFieldWrapper>();
+  auto stepper = Stepper(std::move(magneticField));
+  Acts::Navigator::Config cfg{trackingGeometry};
+  cfg.resolvePassive = false;
+  cfg.resolveMaterial = true;
+  cfg.resolveSensitive = true;
+  Acts::Navigator navigator(cfg);
+  Propagator propagator(std::move(stepper), std::move(navigator));
+  Fitter trackFitter(std::move(propagator));
+  return std::make_shared<TrackFitterFunctionImpl>(std::move(trackFitter));
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/CircleFit.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/CircleFit.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9f1fab11cf10edac1a497c7af498e737b5bd84c6
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/CircleFit.cxx
@@ -0,0 +1,169 @@
+#include "FaserActsKalmanFilter/CircleFit.h"
+
+namespace CircleFit {
+
+CircleData::CircleData(const std::vector<const Tracker::FaserSCT_SpacePoint *> &spacePoints) {
+  for (auto sp: spacePoints) {
+    m_x.push_back(sp->globalPosition().z());
+    m_y.push_back(sp->globalPosition().y());
+  }
+  m_size = spacePoints.size();
+}
+
+CircleData::CircleData(const std::vector<Amg::Vector3D> &spacePoints) {
+  for (auto sp: spacePoints) {
+    m_x.push_back(sp.z());
+    m_y.push_back(sp.y());
+  }
+  m_size = spacePoints.size();
+}
+
+double CircleData::meanX() const {
+  double mean = 0;
+  for (double i: m_x) mean += i;
+  return mean / m_size;
+}
+
+double CircleData::meanY() const {
+  double mean = 0;
+  for (double i: m_y) mean += i;
+  return mean / m_size;
+}
+
+double CircleData::x(int i) const {
+  return m_x[i];
+}
+
+double CircleData::y(int i) const {
+  return m_y[i];
+}
+
+int CircleData::size() const {
+  return m_size;
+}
+
+
+double sigma(const CircleData &data, const Circle &circle) {
+  double sum=0.,dx,dy;
+  for (int i=0; i<data.size(); i++) {
+    dx = data.x(i) - circle.cx;
+    dy = data.y(i) - circle.cy;
+    sum += (sqrt(dx*dx+dy*dy) - circle.r) * (sqrt(dx*dx+dy*dy) - circle.r);
+  }
+  return sqrt(sum/data.size());
+}
+
+
+Circle circleFit(const CircleData &data)
+/*
+      Circle fit to a given set of data points (in 2D)
+
+      This is an algebraic fit, due to Taubin, based on the journal article
+
+      G. Taubin, "Estimation Of Planar Curves, Surfaces And Nonplanar
+                  Space Curves Defined By Implicit Equations, With
+                  Applications To Edge And Range Image Segmentation",
+                  IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991)
+
+      Input:  data     - the class of data (contains the given points):
+
+	      data.size()   - the number of data points
+	      data.X[] - the array of X-coordinates
+	      data.Y[] - the array of Y-coordinates
+
+     Output:
+               circle - parameters of the fitting circle:
+
+	       circle.a - the X-coordinate of the center of the fitting circle
+	       circle.b - the Y-coordinate of the center of the fitting circle
+ 	       circle.r - the radius of the fitting circle
+ 	       circle.s - the root mean square error (the estimate of sigma)
+ 	       circle.j - the total number of iterations
+
+     The method is based on the minimization of the function
+
+                  sum [(x-a)^2 + (y-b)^2 - R^2]^2
+              F = -------------------------------
+                      sum [(x-a)^2 + (y-b)^2]
+
+     This method is more balanced than the simple Kasa fit.
+
+     It works well whether data points are sampled along an entire circle or
+     along a small arc.
+
+     It still has a small bias and its statistical accuracy is slightly
+     lower than that of the geometric fit (minimizing geometric distances),
+     but slightly higher than that of the very similar Pratt fit.
+     Besides, the Taubin fit is slightly simpler than the Pratt fit
+
+     It provides a very good initial guess for a subsequent geometric fit.
+
+       Nikolai Chernov  (September 2012)
+
+*/
+{
+  int i, iter, IterMAX = 999;
+
+  double Xi, Yi, Zi;
+  double Mz, Mxy, Mxx, Myy, Mxz, Myz, Mzz, Cov_xy, Var_z;
+  double A0, A1, A2, A22, A3, A33;
+  double Dy, xnew, x, ynew, y;
+  double DET, Xcenter, Ycenter;
+
+  Circle circle;
+
+  Mxx = Myy = Mxy = Mxz = Myz = Mzz = 0.;
+  for (i = 0; i < data.size(); i++) {
+    Xi = data.x(i) - data.meanX();
+    Yi = data.y(i) - data.meanY();
+    Zi = Xi * Xi + Yi * Yi;
+
+    Mxy += Xi * Yi;
+    Mxx += Xi * Xi;
+    Myy += Yi * Yi;
+    Mxz += Xi * Zi;
+    Myz += Yi * Zi;
+    Mzz += Zi * Zi;
+  }
+  Mxx /= data.size();
+  Myy /= data.size();
+  Mxy /= data.size();
+  Mxz /= data.size();
+  Myz /= data.size();
+  Mzz /= data.size();
+
+  Mz = Mxx + Myy;
+  Cov_xy = Mxx * Myy - Mxy * Mxy;
+  Var_z = Mzz - Mz * Mz;
+  A3 = 4 * Mz;
+  A2 = -3 * Mz * Mz - Mzz;
+  A1 = Var_z * Mz + 4 * Cov_xy * Mz - Mxz * Mxz - Myz * Myz;
+  A0 = Mxz * (Mxz * Myy - Myz * Mxy) + Myz * (Myz * Mxx - Mxz * Mxy) - Var_z * Cov_xy;
+  A22 = A2 + A2;
+  A33 = A3 + A3 + A3;
+
+  for (x = 0., y = A0, iter = 0; iter < IterMAX; iter++) {
+    Dy = A1 + x * (A22 + A33 * x);
+    xnew = x - y / Dy;
+    if ((xnew == x) || (!std::isfinite(xnew))) break;
+    ynew = A0 + xnew * (A1 + xnew * (A2 + xnew * A3));
+    if (abs(ynew) >= abs(y)) break;
+    x = xnew;
+    y = ynew;
+  }
+
+  DET = x * x - x * Mz + Cov_xy;
+  Xcenter = (Mxz * (Myy - x) - Myz * Mxy) / DET / 2;
+  Ycenter = (Myz * (Mxx - x) - Mxz * Mxy) / DET / 2;
+
+  circle.cx = Xcenter + data.meanX();
+  circle.cy = Ycenter + data.meanY();
+  circle.r = sqrt(Xcenter * Xcenter + Ycenter * Ycenter + Mz);
+  circle.s = sigma(data, circle);
+  circle.i = 0;
+  circle.j = iter;
+
+  return circle;
+}
+
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/CircleFitTrackSeedTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/CircleFitTrackSeedTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..18499e4964e49ebf4ef041a870125bdae2d2d694
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/CircleFitTrackSeedTool.cxx
@@ -0,0 +1,346 @@
+#include "FaserActsKalmanFilter/CircleFitTrackSeedTool.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "Identifier/Identifier.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "FaserActsKalmanFilter/CircleFit.h"
+#include "FaserActsKalmanFilter/LinearFit.h"
+#include "FaserActsKalmanFilter/TrackClassification.h"
+#include <array>
+#include <algorithm>
+
+
+std::map<Identifier,Index> CircleFitTrackSeedTool::s_indexMap {};
+std::map<Identifier, const Tracker::FaserSCT_SpacePoint*> CircleFitTrackSeedTool::s_spacePointMap {};
+
+CircleFitTrackSeedTool::CircleFitTrackSeedTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode CircleFitTrackSeedTool::initialize() {
+      ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+      ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+      ATH_CHECK(m_trackingGeometryTool.retrieve());
+      ATH_CHECK(m_trackCollection.initialize());
+      ATH_CHECK(m_clusterContainerKey.initialize());
+      ATH_CHECK(m_spacePointContainerKey.initialize());
+  // ATH_CHECK(m_simDataCollectionKey.initialize());
+  // ATH_CHECK(m_mcEventCollectionKey.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode CircleFitTrackSeedTool::run() {
+  SG::ReadHandle<Tracker::FaserSCT_ClusterContainer> clusterContainer {m_clusterContainerKey};
+      ATH_CHECK(clusterContainer.isValid());
+
+  SG::ReadHandle<FaserSCT_SpacePointContainer> spacePointContainer {m_spacePointContainerKey};
+      ATH_CHECK(spacePointContainer.isValid());
+
+  // SG::ReadHandle<TrackerSimDataCollection> simData {m_simDataCollectionKey};
+  // ATH_CHECK(simData.isValid());
+
+  // SG::ReadHandle<McEventCollection> mcEvents {m_mcEventCollectionKey};
+  // ATH_CHECK(mcEvents.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+  // std::map<int, const HepMC::GenParticle*> particles {};
+  // for (const HepMC::GenParticle* particle : mcEvents->front()->particle_range()) {
+  //   particles[particle->barcode()] = particle;
+  // }
+
+  // std::map<Identifier, const Tracker::FaserSCT_SpacePoint*> s_spacePointMap {};
+  std::vector<const Tracker::FaserSCT_SpacePoint*> spacePoints {};
+  for (const FaserSCT_SpacePointCollection* spacePointCollection : *spacePointContainer) {
+    for (const Tracker::FaserSCT_SpacePoint *spacePoint: *spacePointCollection) {
+      spacePoints.push_back(spacePoint);
+      Identifier id1 = spacePoint->cluster1()->identify();
+      CircleFitTrackSeedTool::s_spacePointMap[id1] = spacePoint;
+      Identifier id2 = spacePoint->cluster2()->identify();
+      CircleFitTrackSeedTool::s_spacePointMap[id2] = spacePoint;
+    }
+  }
+
+
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, 1>;
+  std::array<Acts::BoundIndices, 1> Indices = {Acts::eBoundLoc0};
+  std::vector<IndexSourceLink> sourceLinks {};
+  std::vector<Measurement> measurements {};
+  std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+  for (const Tracker::FaserSCT_ClusterCollection* clusterCollection : *clusterContainer) {
+    for (const Tracker::FaserSCT_Cluster* cluster : *clusterCollection) {
+      clusters.push_back(cluster);
+      Identifier id = cluster->detectorElement()->identify();
+      CircleFitTrackSeedTool::s_indexMap[cluster->identify()] = measurements.size();
+      if (identifierMap->count(id) != 0) {
+        Acts::GeometryIdentifier geoId = identifierMap->at(id);
+        IndexSourceLink sourceLink(geoId, measurements.size(), cluster);
+        // create measurement
+        const auto& par = cluster->localPosition();
+        Eigen::Matrix<double, 1, 1> pos {par.x(),};
+        Eigen::Matrix<double, 1, 1> cov {0.0231 * 0.0231,};
+        ThisMeasurement meas(sourceLink, Indices, pos, cov);
+        sourceLinks.push_back(sourceLink);
+        measurements.emplace_back(std::move(meas));
+      }
+    }
+  }
+
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+      ATH_CHECK(trackCollection.isValid());
+
+  std::array<std::vector<Segment>, 4> segments {};
+  for (const Trk::Track* track : *trackCollection) {
+    auto s = Segment(track, m_idHelper);
+    segments[s.station].push_back(s);
+  }
+
+  std::vector<Segment> combination {};
+  std::vector<Seed> seeds {};
+  // create seeds from four stations
+  go(segments, combination, seeds, 0, 4);
+  if (seeds.size() < 2) {
+    // create seeds from three stations
+    go(segments, combination, seeds, 0, 3);
+  }
+  // create seeds from two stations
+  if (seeds.size() < 2) {
+    go(segments, combination, seeds, 0, 2);
+  }
+  if (seeds.size() < 2) {
+    go(segments, combination, seeds, 0, 1);
+  }
+
+  std::list<Seed> allSeeds;
+  for (const Seed &seed : seeds) allSeeds.push_back(seed);
+
+  /*
+  allSeeds.sort([](const Seed &left, const Seed &right) {
+    if (left.size > right.size) return true;
+    if (left.size < right.size) return false;
+    if (left.chi2 < right.chi2) return true;
+    else return false;
+  });
+
+  std::vector<Seed> selectedSeeds {};
+  while (not allSeeds.empty()) {
+    Seed selected = allSeeds.front();
+    selectedSeeds.push_back(selected);
+    allSeeds.remove_if([&](const Seed &p) {
+      return (p.size <= 12) || ((p.clusterSet & selected.clusterSet).count() > 6);
+    });
+  }
+  */
+
+  std::vector<Seed> selectedSeeds {};
+  for (const Seed &seed : allSeeds) {
+    selectedSeeds.push_back(seed);
+  }
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  auto minSeed = std::min_element(
+      selectedSeeds.begin(), selectedSeeds.end(), [](const Seed &lhs, const Seed &rhs) {
+        return lhs.minZ < rhs.minZ;
+      });
+  double origin = !selectedSeeds.empty() ? minSeed->minZ : 0;
+  m_targetZPosition = origin;
+  std::vector<Acts::CurvilinearTrackParameters> initParams {};
+  for (const Seed &seed : selectedSeeds) {
+    initParams.push_back(seed.get_params(origin, cov));
+  }
+
+  m_initialTrackParameters = std::make_shared<std::vector<Acts::CurvilinearTrackParameters>>(initParams);
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourceLinks);
+  // m_idLinks = std::make_shared<IdentifierLink>(identifierLinkMap);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, origin}, Acts::Vector3{0, 0, -1});
+  m_clusters = std::make_shared<std::vector<const Tracker::FaserSCT_Cluster*>>(clusters);
+  m_spacePoints = std::make_shared<std::vector<const Tracker::FaserSCT_SpacePoint*>>(spacePoints);
+
+  /*
+  std::cout.precision(17);
+  for (auto &seed : selectedSeeds) {
+    std::cout << "np.array([";
+    for (const Acts::Vector3 &pos : seed.fakePositions) {
+      std::cout << "[" << pos.x() << ", " << pos.y() << ", " << pos.z() << "], ";
+    }
+    std::cout << "])" << std::endl;
+    std::cout << "chi2: " << seed.chi2 << ", momentum: " << seed.momentum << ", charge: " << seed.charge << std::endl;
+    std::cout << "fit = np.array([" << seed.c1 << ", " << seed.c0 << ", " << seed.cy << ", " << seed.cx << ", " << seed.r << "])" << std::endl;
+
+    std::vector<ParticleHitCount> particleHitCounts;
+    identifyContributingParticles(*simData, seed.clusters, particleHitCounts);
+    auto ip = particles.find(particleHitCounts.front().particleId);
+    if (ip != particles.end()) {
+      const HepMC::GenParticle* particle = ip->second;
+      HepMC::FourVector momentum = particle->momentum();
+      std::cout << "true momentum: " << momentum.rho() * 0.001 << std::endl;
+    }
+    for (const ParticleHitCount &hitCount : particleHitCounts) {
+      std::cout << hitCount.particleId << " : " << hitCount.hitCount << std::endl;
+    }
+  }
+  */
+
+  s_indexMap.clear();
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode CircleFitTrackSeedTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+void CircleFitTrackSeedTool::go(const std::array<std::vector<Segment>, 4> &v,
+                                std::vector<Segment> &combination,
+                                std::vector<Seed> &seeds,
+                                int offset, int k) {
+  if (k == 0) {
+    seeds.push_back(Seed(combination));
+    return;
+  }
+  for (std::size_t i = offset; i < v.size() + 1 - k; ++i) {
+    for (const auto& ve : v[i]) {
+      combination.push_back(ve);
+      go(v, combination, seeds, i+1, k-1);
+      combination.pop_back();
+    }
+  }
+}
+
+CircleFitTrackSeedTool::Segment::Segment(const Trk::Track* track, const FaserSCT_ID *idHelper) :
+    clusterSet(CircleFitTrackSeedTool::s_indexMap.size()) {
+  for (const Trk::TrackStateOnSurface* trackState : *(track->trackStateOnSurfaces())) {
+    auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (trackState->measurementOnTrack());
+    if (clusterOnTrack) {
+      const Tracker::FaserSCT_Cluster* cluster = clusterOnTrack->prepRawData();
+      Identifier id = cluster->identify();
+      clusters.push_back(cluster);
+      if (CircleFitTrackSeedTool::s_spacePointMap.count(id) > 0) {
+        const Tracker::FaserSCT_SpacePoint *sp = CircleFitTrackSeedTool::s_spacePointMap.at(cluster->identify());
+        if (std::find(spacePoints.begin(), spacePoints.end(), sp) == spacePoints.end()) {
+          spacePoints.push_back(sp);
+        }
+      }
+      station = idHelper->station(id);
+      clusterSet.set(CircleFitTrackSeedTool::s_indexMap.at(id));
+      auto fitParameters = track->trackParameters()->front();
+      position = fitParameters->position();
+      momentum = fitParameters->momentum();
+    }
+  }
+  fakePositions.push_back(position);
+  fakePositions.push_back(position - 30 * momentum.normalized());
+  fakePositions.push_back(position + 30 * momentum.normalized());
+}
+
+CircleFitTrackSeedTool::Seed::Seed(const std::vector<Segment> &segments) :
+    clusterSet(CircleFitTrackSeedTool::s_indexMap.size()) {
+  for (const Segment &seg : segments) {
+    clusters.insert(clusters.end(), seg.clusters.begin(), seg.clusters.end());
+    spacePoints.insert(spacePoints.end(), seg.spacePoints.begin(), seg.spacePoints.end());
+    positions.push_back(seg.position);
+    // TODO use reconstruct space points instead of fake positions
+    fakePositions.insert(fakePositions.end(), seg.fakePositions.begin(), seg.fakePositions.end());
+    for (size_t i = 0; i < seg.clusterSet.size(); ++i) {
+      if (seg.clusterSet[i]) clusterSet.set(i);
+    }
+  }
+
+  auto minCluster = std::min_element(
+      clusters.begin(), clusters.end(), [](const Tracker::FaserSCT_Cluster *lhs, const Tracker::FaserSCT_Cluster *rhs) {
+        return lhs->globalPosition().z() < rhs->globalPosition().z();
+      } );
+  minZ = (*minCluster)->globalPosition().z();
+
+  if (segments.size() > 1) {
+    direction = positions[1] - positions[0];
+  } else {
+    direction = segments[0].momentum;
+  }
+
+  std::vector<Acts::Vector2> pointsZX {};
+  for (const Acts::Vector3 &pos : fakePositions) {
+    pointsZX.push_back({pos.z(), pos.x()});
+  }
+  linearFit(pointsZX);
+
+  if (segments.size() > 1) {
+    fakeFit();
+  } else {
+    momentum = 9999999.;
+    charge = 1;
+  }
+
+  getChi2();
+  size = clusters.size();
+}
+
+
+void CircleFitTrackSeedTool::Seed::fit() {
+  CircleFit::CircleData circleData(positions);
+  CircleFit::Circle circle = CircleFit::circleFit(circleData);
+  momentum = circle.r > 0 ? circle.r * 0.001 * 0.3 * 0.55 : 9999999.;
+  charge = circle.cy < 0 ? -1 : 1;
+}
+
+void CircleFitTrackSeedTool::Seed::fakeFit(double B) {
+  CircleFit::CircleData circleData(fakePositions);
+  CircleFit::Circle circle = CircleFit::circleFit(circleData);
+  cx = circle.cx;
+  cy = circle.cy;
+  r = circle.r;
+  momentum = r * 0.3 * B * m_MeV2GeV;
+  charge = circle.cy > 0 ? 1 : -1;
+}
+
+void CircleFitTrackSeedTool::Seed::linearFit(const std::vector<Acts::Vector2> &points) {
+  auto [origin, dir] = LinearFit::linearFit(points);
+  c1 = dir[1]/dir[0];
+  c0 = origin[1] - origin[0] * c1;
+}
+
+
+double CircleFitTrackSeedTool::Seed::getY(double z) {
+  double sqt = std::sqrt(-cx*cx + 2*cx*z + r*r - z*z);
+  return abs(cy - sqt) < abs(cy + sqt) ? cy - sqt : cy + sqt;
+}
+
+
+double CircleFitTrackSeedTool::Seed::getX(double z) {
+  return c0 + z * c1;
+}
+
+void CircleFitTrackSeedTool::Seed::getChi2() {
+  chi2 = 0;
+  for (const Acts::Vector3 &pos : fakePositions) {
+    m_dy = pos.y() - getY(pos.z());
+    chi2 += (m_dy * m_dy) / (m_sigma_y * m_sigma_y);
+  }
+
+  for (const Acts::Vector3 &pos : positions) {
+    m_dx = pos.x() - getX(pos.x());
+    chi2 += (m_dx * m_dx) / (m_sigma_x * m_sigma_x);
+  }
+}
+
+Acts::CurvilinearTrackParameters CircleFitTrackSeedTool::Seed::get_params(double origin, Acts::BoundSymMatrix cov) const {
+  Acts::Vector3 pos = positions[0] - (positions[0].z() - origin)/direction.z() * direction;
+  Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+  return Acts::CurvilinearTrackParameters(pos4, direction.normalized(), momentum, charge, cov);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/ClusterTrackSeedTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/ClusterTrackSeedTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..8d78c9f0eaa1088675806b0a9aee1472ce3396ff
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/ClusterTrackSeedTool.cxx
@@ -0,0 +1,129 @@
+#include "FaserActsKalmanFilter/ClusterTrackSeedTool.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "Identifier/Identifier.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+
+
+// FIXME make this a method of ClusterTrackSeedTool
+std::pair<double, double> momentum(const std::map<int, Amg::Vector3D>& pos, double B=0.57) {
+  Acts::Vector3 vec_l = pos.at(3) - pos.at(1);
+  double abs_l = std::sqrt(vec_l.y() * vec_l.y() + vec_l.z() * vec_l.z());
+  double t = (pos.at(2).z() - pos.at(1).z()) / (pos.at(3).z() - pos.at(1).z());
+  Acts::Vector3 vec_m = pos.at(1) + t * vec_l;
+  Acts::Vector3 vec_s = pos.at(2) - vec_m;
+  double abs_s = std::sqrt(vec_s.y() * vec_s.y() + vec_s.z() * vec_s.z());
+  double p_yz = 0.3 * abs_l * abs_l * B / (8 * abs_s * 1000);
+  double charge = vec_s.y() < 0 ? 1 : -1;
+  return std::make_pair(p_yz, charge);
+}
+
+
+ClusterTrackSeedTool::ClusterTrackSeedTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode ClusterTrackSeedTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackCollection.initialize());
+  ATH_CHECK(m_clusterContainerKey.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ClusterTrackSeedTool::run() {
+  // create track seeds for multiple tracks
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+  ATH_CHECK(trackCollection.isValid());
+
+  SG::ReadHandle<Tracker::FaserSCT_ClusterContainer> clusterContainer {m_clusterContainerKey};
+  ATH_CHECK(clusterContainer.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+
+  const int kSize = 1;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0};
+  std::vector<IndexSourceLink> sourceLinks;
+  std::vector<Measurement> measurements;
+  std::map<Index, Identifier> identifierLinkMap;
+  std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+  for (const Tracker::FaserSCT_ClusterCollection* clusterCollection : *clusterContainer) {
+    for (const Tracker::FaserSCT_Cluster* cluster : *clusterCollection) {
+      Identifier id = cluster->detectorElement()->identify();
+      identifierLinkMap[measurements.size()] = id;
+      Acts::GeometryIdentifier geoId = identifierMap->at(id);
+      IndexSourceLink sourceLink(geoId, measurements.size());
+      // create measurement
+      const auto& par = cluster->localPosition();
+      Eigen::Matrix<double, 1, 1> pos {par.x(),};
+      Eigen::Matrix<double, 1, 1> cov {m_std_cluster * m_std_cluster,};
+      ThisMeasurement meas(sourceLink, Indices, pos, cov);
+      sourceLinks.push_back(sourceLink);
+      measurements.emplace_back(std::move(meas));
+      clusters.push_back(cluster);
+    }
+  }
+
+
+  std::map<int, std::vector<Amg::Vector3D>> station_position_map;
+  for (const Trk::Track* track : *trackCollection) {
+    for (const Trk::TrackStateOnSurface* trackState : *(track->trackStateOnSurfaces())) {
+      auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (trackState->measurementOnTrack());
+      if (clusterOnTrack) {
+        Identifier id = clusterOnTrack->identify();
+        int station = m_idHelper->station(id);
+        auto fitParameters = track->trackParameters()->front();
+        Amg::Vector3D fitPosition = fitParameters->position();
+        station_position_map[station].push_back(fitPosition);
+        break;
+      }
+    }
+  }
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  std::vector<Acts::CurvilinearTrackParameters> initParams {};
+  for (const Amg::Vector3D& pos1 : station_position_map[1]) {
+    for (const Amg::Vector3D& pos2 : station_position_map[2]) {
+      initParams.push_back(get_params(pos1, pos2, cov, m_origin));
+    }
+  }
+
+  m_initialTrackParameters = std::make_shared<std::vector<Acts::CurvilinearTrackParameters>>(initParams);
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourceLinks);
+  m_idLinks = std::make_shared<IdentifierLink>(identifierLinkMap);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, m_origin}, Acts::Vector3{0, 0, 1});
+  m_clusters = std::make_shared<std::vector<const Tracker::FaserSCT_Cluster*>>(clusters);
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ClusterTrackSeedTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+Acts::CurvilinearTrackParameters ClusterTrackSeedTool::get_params(
+    const Amg::Vector3D& position_st1, const Amg::Vector3D& position_st2, const Acts::BoundSymMatrix& cov, double origin) {
+  Acts::Vector3 dir = position_st2 - position_st1;
+  Acts::Vector3 pos = position_st1 - (position_st1.z() - origin)/dir.z() * dir;
+  Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+  return Acts::CurvilinearTrackParameters(pos4, dir, 100000000, 1, cov);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/CombinatorialKalmanFilterAlg.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/CombinatorialKalmanFilterAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..71afcc4431fabb6064af9601d31dd783de497a4f
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/CombinatorialKalmanFilterAlg.cxx
@@ -0,0 +1,359 @@
+#include "FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h"
+
+#include "StoreGate/ReadHandle.h"
+#include "StoreGate/ReadCondHandleKey.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePointCollection.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrkPrepRawData/PrepRawData.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrkRIO_OnTrack/RIO_OnTrack.h"
+#include "TrkSurfaces/Surface.h"
+#include "Identifier/Identifier.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/EventData/TrackParameters.hpp"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "FaserActsKalmanFilter/Measurement.h"
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include "Acts/Surfaces/PerigeeSurface.hpp"
+#include "Acts/MagneticField/MagneticFieldContext.hpp"
+#include "FaserActsKalmanFilter/TrackSelection.h"
+#include <algorithm>
+
+
+size_t CombinatorialKalmanFilterAlg::TrajectoryInfo::nClusters {0};
+
+using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
+std::array<Acts::BoundIndices, 2> indices = {Acts::eBoundLoc0, Acts::eBoundLoc1};
+
+
+CombinatorialKalmanFilterAlg::CombinatorialKalmanFilterAlg(
+    const std::string& name, ISvcLocator* pSvcLocator)
+    : AthAlgorithm(name, pSvcLocator) {}
+
+
+StatusCode CombinatorialKalmanFilterAlg::initialize() {
+  ATH_CHECK(m_fieldCondObjInputKey.initialize());
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackSeedTool.retrieve());
+  //  ATH_CHECK(m_trackCollection.initialize());
+  if (m_performanceWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_performanceWriterTool.retrieve());
+  }
+  if (m_statesWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectoryStatesWriterTool.retrieve());
+  }
+  if (m_summaryWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectorySummaryWriterTool.retrieve());
+  }
+  ATH_CHECK(detStore()->retrieve(m_idHelper,"FaserSCT_ID"));
+  m_fit = makeTrackFinderFunction(m_trackingGeometryTool->trackingGeometry(),
+                                  m_resolvePassive, m_resolveMaterial, m_resolveSensitive);
+  if (m_actsLogging == "VERBOSE" && !m_noDiagnostics) {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::VERBOSE);
+  } else if (m_actsLogging == "DEBUG" && !m_noDiagnostics) {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::DEBUG);
+  } else {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::INFO);
+  }
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode CombinatorialKalmanFilterAlg::execute() {
+
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+  m_numberOfEvents++;
+
+  ATH_CHECK(m_trackCollection.initialize());
+  SG::WriteHandle<TrackCollection> trackContainer{m_trackCollection,ctx};
+  std::unique_ptr<TrackCollection> outputTracks = std::make_unique<TrackCollection>();
+
+  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
+      = m_trackingGeometryTool->trackingGeometry();
+
+  const FaserActsGeometryContext& gctx = m_trackingGeometryTool->getNominalGeometryContext();
+  auto geoctx = gctx.context();
+  Acts::MagneticFieldContext magFieldContext = getMagneticFieldContext(ctx);
+  Acts::CalibrationContext calibContext;
+
+  CHECK(m_trackSeedTool->run());
+  std::shared_ptr<const Acts::Surface> initialSurface =
+      m_trackSeedTool->initialSurface();
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialParameters =
+      m_trackSeedTool->initialTrackParameters();
+  std::shared_ptr<std::vector<IndexSourceLink>> sourceLinks =
+      m_trackSeedTool->sourceLinks();
+  std::shared_ptr<IdentifierLink> idLinks = m_trackSeedTool->idLinks();
+  std::shared_ptr<std::vector<Measurement>> measurements = m_trackSeedTool->measurements();
+  std::shared_ptr<std::vector<const Tracker::FaserSCT_Cluster*>> clusters = m_trackSeedTool->clusters();
+  std::shared_ptr<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>> seedClusters = m_trackSeedTool->seedClusters();
+
+  TrajectoryInfo::nClusters = sourceLinks->size();
+
+  TrajectoriesContainer trajectories;
+  trajectories.reserve(initialParameters->size());
+
+  Acts::PropagatorPlainOptions pOptions;
+  pOptions.maxSteps = m_maxSteps;
+
+  Acts::MeasurementSelector::Config measurementSelectorCfg = {
+    {Acts::GeometryIdentifier(), {m_chi2Max, m_nMax}},
+  };
+
+  Acts::RotationMatrix3 rotation = Acts::RotationMatrix3::Identity();
+  rotation.col(0) = Acts::Vector3(0, 0, -1);
+  rotation.col(1) = Acts::Vector3(0, 1, 0);
+  rotation.col(2) = Acts::Vector3(1, 0, 0);
+  Acts::Translation3 trans(0., 0., 0.);
+  Acts::Transform3 trafo(rotation * trans);
+  initialSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(trafo);
+
+  // Set the CombinatorialKalmanFilter options
+  CombinatorialKalmanFilterAlg::TrackFinderOptions options(
+      geoctx, magFieldContext, calibContext,
+      IndexSourceLinkAccessor(), MeasurementCalibrator(*measurements),
+      Acts::MeasurementSelector(measurementSelectorCfg),
+      Acts::LoggerWrapper{*m_logger}, pOptions, &(*initialSurface));
+
+  m_numberOfTrackSeeds += initialParameters->size();
+
+  // Perform the track finding for all initial parameters
+  ATH_MSG_DEBUG("Invoke track finding with " << initialParameters->size() << " seeds.");
+  IndexSourceLinkContainer tmp;
+  for (const auto& sl : *sourceLinks) {
+    tmp.emplace_hint(tmp.end(), sl);
+  }
+  auto results = (*m_fit)(tmp, *initialParameters, options);
+
+  // results contains a MultiTrajectory for each track seed with a trajectory of each branch of the CKF.
+  // To simplify the ambiguity solving a list of MultiTrajectories is created, each containing only a single track.
+  std::list<TrajectoryInfo> allTrajectories;
+  for (auto &result : results) {
+    if (not result.ok()) {
+      continue;
+    }
+    CKFResult ckfResult = result.value();
+    for (size_t trackTip : ckfResult.lastMeasurementIndices) {
+      allTrajectories.emplace_back(TrajectoryInfo(FaserActsRecMultiTrajectory(
+          ckfResult.fittedStates, {trackTip}, {{trackTip, ckfResult.fittedParameters.at(trackTip)}})));
+    }
+  }
+
+  // the list of MultiTrajectories is sorted by the number of measurements using the chi2 value as a tie-breaker
+  allTrajectories.sort([](const TrajectoryInfo &left, const TrajectoryInfo &right) {
+    if (left.nMeasurements > right.nMeasurements) return true;
+    if (left.nMeasurements < right.nMeasurements) return false;
+    if (left.chi2 < right.chi2) return true;
+    else return false;
+  });
+
+  // select all tracks with at least 13 heats and with 6 or less shared hits, starting from the best track
+  // TODO use Gaudi parameters for the number of hits and shared hits
+  // TODO allow shared hits only in the first station?
+  std::vector<FaserActsRecMultiTrajectory> selectedTrajectories {};
+  while (not allTrajectories.empty()) {
+    TrajectoryInfo selected = allTrajectories.front();
+    selectedTrajectories.push_back(selected.trajectory);
+    allTrajectories.remove_if([&](const TrajectoryInfo &p) {
+      return (p.nMeasurements <= 12) || ((p.clusterSet & selected.clusterSet).count() > 6);
+    });
+  }
+
+  // create Trk::Tracks from the trajectories
+  for (const FaserActsRecMultiTrajectory &traj : selectedTrajectories) {
+    std::unique_ptr<Trk::Track> track = makeTrack(geoctx, traj);
+    if (track) {
+      outputTracks->push_back(std::move(track));
+    }
+  }
+
+  // run the performance writer
+  if (m_statesWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectoryStatesWriterTool->write(geoctx, trajectories, m_isMC));
+  }
+  if (m_summaryWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_trajectorySummaryWriterTool->write(geoctx, trajectories, m_isMC));
+  }
+  if  (m_performanceWriter && !m_noDiagnostics) {
+    ATH_CHECK(m_performanceWriterTool->write(geoctx, trajectories));
+  }
+  ATH_CHECK(trackContainer.record(std::move(outputTracks)));
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode CombinatorialKalmanFilterAlg::finalize() {
+  ATH_MSG_INFO("CombinatorialKalmanFilterAlg::finalize()");
+  ATH_MSG_INFO(m_numberOfEvents << " events processed.");
+  ATH_MSG_INFO(m_numberOfTrackSeeds << " seeds.");
+  ATH_MSG_INFO(m_numberOfFittedTracks << " fitted tracks.");
+  ATH_MSG_INFO(m_numberOfSelectedTracks << " good fitted tracks.");
+  return StatusCode::SUCCESS;
+}
+
+
+Acts::MagneticFieldContext CombinatorialKalmanFilterAlg::getMagneticFieldContext(const EventContext& ctx) const {
+  SG::ReadCondHandle<FaserFieldCacheCondObj> readHandle{m_fieldCondObjInputKey, ctx};
+  if (!readHandle.isValid()) {
+    std::stringstream msg;
+    msg << "Failed to retrieve magnetic field condition data " << m_fieldCondObjInputKey.key() << ".";
+    throw std::runtime_error(msg.str());
+  }
+  const FaserFieldCacheCondObj* fieldCondObj{*readHandle};
+  return Acts::MagneticFieldContext(fieldCondObj);
+}
+
+std::unique_ptr<Trk::Track>
+CombinatorialKalmanFilterAlg::makeTrack(const Acts::GeometryContext &geoCtx, const FaserActsRecMultiTrajectory &traj) const {
+  using ConstTrackStateProxy =
+      Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+  std::unique_ptr<Trk::Track> newtrack = nullptr;
+  //Get the fit output object
+  DataVector<const Trk::TrackStateOnSurface>* finalTrajectory = new DataVector<const Trk::TrackStateOnSurface>{};
+  std::vector<std::unique_ptr<const Acts::BoundTrackParameters>> actsSmoothedParam;
+  // Loop over all the output state to create track state
+  traj.multiTrajectory().visitBackwards(traj.tips().front(), [&](const ConstTrackStateProxy& state) {
+    auto flag = state.typeFlags();
+    if (state.referenceSurface().associatedDetectorElement() != nullptr) {
+      // We need to determine the type of state
+      std::bitset<Trk::TrackStateOnSurface::NumberOfTrackStateOnSurfaceTypes> typePattern;
+      const Trk::TrackParameters *parm;
+
+      // State is a hole (no associated measurement), use predicted para meters
+      if (flag[Acts::TrackStateFlag::HoleFlag] == true) {
+        const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                   state.predicted(),
+                                                   state.predictedCovariance());
+        parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+        // auto boundaryCheck = m_boundaryCheckTool->boundaryCheck(*p arm);
+        typePattern.set(Trk::TrackStateOnSurface::Hole);
+      }
+        // The state was tagged as an outlier, use filtered parameters
+      else if (flag[Acts::TrackStateFlag::OutlierFlag] == true) {
+        const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                   state.filtered(), state.filteredCovariance());
+        parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+        typePattern.set(Trk::TrackStateOnSurface::Outlier);
+      }
+        // The state is a measurement state, use smoothed parameters
+      else {
+        const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                   state.smoothed(), state.smoothedCovariance());
+        actsSmoothedParam.push_back(std::make_unique<const Acts::BoundTrackParameters>(Acts::BoundTrackParameters(actsParam)));
+        //  const auto& psurface=actsParam.referenceSurface();
+        Acts::Vector2 local(actsParam.parameters()[Acts::eBoundLoc0], actsParam.parameters()[Acts::eBoundLoc1]);
+        //  const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(actsParam.parameters()[Acts::eBoundPhi], actsParam.parameters()[Acts::eBoundTheta]);
+        //  auto pos=actsParam.position(tgContext);
+        parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+        typePattern.set(Trk::TrackStateOnSurface::Measurement);
+      }
+      Tracker::FaserSCT_ClusterOnTrack* measState = nullptr;
+      if (state.hasUncalibrated()) {
+        const Tracker::FaserSCT_Cluster* fitCluster = state.uncalibrated().hit();
+        if (fitCluster->detectorElement() != nullptr) {
+          measState = new Tracker::FaserSCT_ClusterOnTrack{
+              fitCluster,
+              Trk::LocalParameters{
+                  Trk::DefinedParameter{fitCluster->localPosition()[0], Trk::loc1},
+                  Trk::DefinedParameter{fitCluster->localPosition()[1], Trk::loc2}
+              },
+              fitCluster->localCovariance(),
+              m_idHelper->wafer_hash(fitCluster->detectorElement()->identify())
+          };
+        }
+      }
+      double nDoF = state.calibratedSize();
+      const Trk::FitQualityOnSurface *quality = new Trk::FitQualityOnSurface(state.chi2(), nDoF);
+      const Trk::TrackStateOnSurface *perState = new Trk::TrackStateOnSurface(measState, parm, quality, nullptr, typePattern);
+      // If a state was succesfully created add it to the trajectory
+      if (perState) {
+        finalTrajectory->insert(finalTrajectory->begin(), perState);
+      }
+    }
+    return;
+  });
+
+  // Create the track using the states
+  const Trk::TrackInfo newInfo(Trk::TrackInfo::TrackFitter::KalmanFitter, Trk::ParticleHypothesis::muon);
+  // Trk::FitQuality* q = nullptr;
+  // newInfo.setTrackFitter(Trk::TrackInfo::TrackFitter::KalmanFitter     ); //Mark the fitter as KalmanFitter
+  newtrack = std::make_unique<Trk::Track>(newInfo, std::move(*finalTrajectory), nullptr);
+  return newtrack;
+}
+
+const Trk::TrackParameters*
+CombinatorialKalmanFilterAlg::ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const      {
+  using namespace Acts::UnitLiterals;
+  std::optional<AmgSymMatrix(5)> cov = std::nullopt;
+  if (actsParameter.covariance()){
+    AmgSymMatrix(5) newcov(actsParameter.covariance()->topLeftCorner(5, 5));
+    // Convert the covariance matrix to GeV
+    for(int i=0; i < newcov.rows(); i++){
+      newcov(i, 4) = newcov(i, 4)*1_MeV;
+    }
+    for(int i=0; i < newcov.cols(); i++){
+      newcov(4, i) = newcov(4, i)*1_MeV;
+    }
+    cov =  std::optional<AmgSymMatrix(5)>(newcov);
+  }
+  const Amg::Vector3D& pos=actsParameter.position(gctx);
+  double tphi=actsParameter.get<Acts::eBoundPhi>();
+  double ttheta=actsParameter.get<Acts::eBoundTheta>();
+  double tqOverP=actsParameter.get<Acts::eBoundQOverP>()*1_MeV;
+  double p = std::abs(1. / tqOverP);
+  Amg::Vector3D tmom(p * std::cos(tphi) * std::sin(ttheta), p * std::sin(tphi) * std::sin(ttheta), p * std::cos(ttheta));
+  const Trk::CurvilinearParameters * curv = new Trk::CurvilinearParameters(pos,tmom,tqOverP>0, cov);
+  return curv;
+}
+
+void CombinatorialKalmanFilterAlg::computeSharedHits(std::vector<IndexSourceLink>* sourceLinks, TrackFinderResult& results) const {
+  // Compute shared hits from all the reconstructed tracks
+  // Compute nSharedhits and Update ckf results
+  // hit index -> list of multi traj indexes [traj, meas]
+  static_assert(Acts::SourceLinkConcept<IndexSourceLink>,
+                "Source link does not fulfill SourceLinkConcept");
+
+  std::vector<std::size_t> firstTrackOnTheHit(
+      sourceLinks->size(), std::numeric_limits<std::size_t>::max());
+  std::vector<std::size_t> firstStateOnTheHit(
+      sourceLinks->size(), std::numeric_limits<std::size_t>::max());
+
+  for (unsigned int iresult = 0; iresult < results.size(); iresult++) {
+    if (not results.at(iresult).ok()) {
+      continue;
+    }
+
+    auto& ckfResult = results.at(iresult).value();
+    auto& measIndexes = ckfResult.lastMeasurementIndices;
+
+    for (auto measIndex : measIndexes) {
+      ckfResult.fittedStates.visitBackwards(measIndex, [&](const auto& state) {
+        if (not state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag))
+          return;
+
+        std::size_t hitIndex = state.uncalibrated().index();
+
+        // Check if hit not already used
+        if (firstTrackOnTheHit.at(hitIndex) ==
+            std::numeric_limits<std::size_t>::max()) {
+          firstTrackOnTheHit.at(hitIndex) = iresult;
+          firstStateOnTheHit.at(hitIndex) = state.index();
+          return;
+        }
+
+        // if already used, control if first track state has been marked
+        // as shared
+        int indexFirstTrack = firstTrackOnTheHit.at(hitIndex);
+        int indexFirstState = firstStateOnTheHit.at(hitIndex);
+        if (not results.at(indexFirstTrack).value().fittedStates.getTrackState(indexFirstState).typeFlags().test(Acts::TrackStateFlag::SharedHitFlag))
+          results.at(indexFirstTrack).value().fittedStates.getTrackState(indexFirstState).typeFlags().set(Acts::TrackStateFlag::SharedHitFlag);
+
+        // Decorate this track
+        results.at(iresult).value().fittedStates.getTrackState(state.index()).typeFlags().set(Acts::TrackStateFlag::SharedHitFlag);
+      });
+    }
+  }
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/CombinatorialKalmbanFilterAlg.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/CombinatorialKalmbanFilterAlg.cxx
deleted file mode 100644
index f5c547e61418ca7d5d0e4eb8b1cbdb9651e9316b..0000000000000000000000000000000000000000
--- a/Tracking/Acts/FaserActsKalmanFilter/src/CombinatorialKalmbanFilterAlg.cxx
+++ /dev/null
@@ -1,449 +0,0 @@
-#include "FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h"
-
-#include "StoreGate/ReadHandle.h"
-#include "StoreGate/ReadCondHandleKey.h"
-#include "TrackerSpacePoint/FaserSCT_SpacePointCollection.h"
-#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
-#include "TrackerIdentifier/FaserSCT_ID.h"
-#include "TrkPrepRawData/PrepRawData.h"
-#include "TrackerPrepRawData/FaserSCT_Cluster.h"
-#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
-#include "TrkRIO_OnTrack/RIO_OnTrack.h"
-#include "TrkSurfaces/Surface.h"
-#include "Identifier/Identifier.h"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/EventData/TrackParameters.hpp"
-#include "FaserActsKalmanFilter/IndexSourceLink.h"
-#include "FaserActsKalmanFilter/Measurement.h"
-#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
-#include "Acts/Surfaces/PerigeeSurface.hpp"
-#include "Acts/MagneticField/MagneticFieldContext.hpp"
-
-using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
-using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, 2>;
-using TrajectoriesContainer = std::vector<FaserActsRecMultiTrajectory>;
-std::array<Acts::BoundIndices, 2> indices = {Acts::eBoundLoc0, Acts::eBoundLoc1};
-
-
-CombinatorialKalmanFilterAlg::CombinatorialKalmanFilterAlg(
-    const std::string& name, ISvcLocator* pSvcLocator)
-    : AthReentrantAlgorithm(name, pSvcLocator) {}
-
-
-StatusCode CombinatorialKalmanFilterAlg::initialize() {
-  ATH_MSG_INFO("CombinatorialKalmanFilterAlg::initialize");
-  m_nevents=0;
-  m_ntracks=0;
-  m_nseeds=0;
-  m_nsp10=0;
-  m_nsp11=0;
-  m_ncom=0;
-  m_nsp1=0;
-  m_nsp2=0;
-  m_nsp3=0;
-
-  ATH_CHECK(m_trackingGeometryTool.retrieve());
-  ATH_CHECK(m_initialParameterTool.retrieve());
-  ATH_CHECK(m_trajectoryWriterTool.retrieve());
-  ATH_CHECK(detStore()->retrieve(m_idHelper,"FaserSCT_ID"));
-
-  ATH_CHECK( m_fieldCondObjInputKey.initialize() );
-
-  ATH_CHECK(m_trackCollection.initialize()); 
-
-  if (m_SpacePointContainerKey.key().empty()) {
-    ATH_MSG_FATAL("empty space point container key");
-    return StatusCode::FAILURE;
-  }
-  ATH_CHECK(m_SpacePointContainerKey.initialize());
-  ATH_CHECK(m_Sct_clcontainerKey.initialize() );
-
-  return StatusCode::SUCCESS;
-}
-
-
-StatusCode CombinatorialKalmanFilterAlg::execute(const EventContext& ctx) const {
-  ATH_MSG_DEBUG("CombinatorialKalmanFilterAlg::execute");
-  m_nevents++;
-
-  SG::ReadHandle<FaserSCT_SpacePointContainer> spcontainer(m_SpacePointContainerKey, ctx);
-  if (!spcontainer.isValid()) {
-    ATH_MSG_FATAL( "Could not find the data object "<< spcontainer.name());
-    return StatusCode::FAILURE;
-  }
-  SG::ReadHandle<Tracker::FaserSCT_ClusterContainer> sct_clcontainer( m_Sct_clcontainerKey, ctx );
-if (!sct_clcontainer.isValid()){
-msg(MSG:: FATAL) << "Could not find the data object "<< sct_clcontainer.name() << " !" << endmsg;
-return StatusCode::RECOVERABLE;
-}
-
-
-   //make TrackCollection                                                  
-     SG::WriteHandle<TrackCollection> trackContainer{m_trackCollection,ctx};
-    std::unique_ptr<TrackCollection> outputTracks = std::make_unique<TrackCollection>();
-
-  const std::shared_ptr<IdentifierMap> identifierMap
-      = m_trackingGeometryTool->getIdentifierMap();
-
-      /*
-  // Create measurement and source link containers
-  IndexSourceLinkContainer sourceLinks;
-  MeasurementContainer measurements;
-  std::vector<Identifier> sp_ids;
-  std::vector<Tracker::FaserSCT_Cluster> sps;
-
-  std::vector<Acts::Vector3> pos1;
-  std::vector<Acts::Vector3> pos2;
-  std::vector<Acts::Vector3> pos3;
-  pos1.clear();pos2.clear();pos3.clear();
-  Tracker::FaserSCT_ClusterContainer::const_iterator coll_it = sct_clcontainer->begin();
-  Tracker::FaserSCT_ClusterContainer::const_iterator coll_itend = sct_clcontainer->end();
-  for (; coll_it != coll_itend; ++coll_it) {
-    const Tracker::FaserSCT_ClusterCollection* spcollection = *coll_it;
-    Tracker::FaserSCT_ClusterCollection::const_iterator sp_it = spcollection->begin();
-    Tracker::FaserSCT_ClusterCollection::const_iterator sp_end = spcollection->end();
-    for (; sp_it != sp_end; ++sp_it) {
-      const Tracker::FaserSCT_Cluster* sp = *sp_it;
-      Identifier id = sp->detectorElement()->identify();
-      //Identifier id = sp->associatedSurface().associatedDetectorElementIdentifier();
-      Acts::GeometryIdentifier geoId = identifierMap->at(id);
-      IndexSourceLink sourceLink(geoId, measurements.size());
-      sourceLinks.emplace_hint(sourceLinks.end(), std::move(sourceLink));
-      ThisMeasurement meas(sourceLink, indices, sp->localPosition(), sp->localCovariance());
-      //ThisMeasurement meas(sourceLink, indices, sp->localParameters(), sp->localCovariance());
-      measurements.emplace_back(std::move(meas));
-
-      if(m_idHelper->station(sp->identify())==1)pos1.push_back(Acts::Vector3(sp->globalPosition().x(),sp->globalPosition().y(),sp->globalPosition().z()));
-      if(m_idHelper->station(sp->identify())==2)pos2.push_back(Acts::Vector3(sp->globalPosition().x(),sp->globalPosition().y(),sp->globalPosition().z()));
-      if(m_idHelper->station(sp->identify())==3)pos3.push_back(Acts::Vector3(sp->globalPosition().x(),sp->globalPosition().y(),sp->globalPosition().z()));
-      sp_ids.push_back(sp->identify());
-      sps.push_back(*sp);
-      }
-      }
-*/
-  // Create measurement and source link containers
-  IndexSourceLinkContainer sourceLinks;
-  MeasurementContainer measurements;
-  std::vector<Identifier> sp_ids;
-  std::vector<Tracker::FaserSCT_SpacePoint> sps;
-
-//  std::vector<Acts::Vector3> pos1;
-//  std::vector<Acts::Vector3> pos2;
-//  std::vector<Acts::Vector3> pos3;
-//  pos1.clear();pos2.clear();pos3.clear();
-  std::vector<int> layer1;
-  layer1.clear();
-  FaserSCT_SpacePointContainer::const_iterator coll_it = spcontainer->begin();
-  FaserSCT_SpacePointContainer::const_iterator coll_itend = spcontainer->end();
-  for (; coll_it != coll_itend; ++coll_it) {
-    const FaserSCT_SpacePointCollection* spcollection = *coll_it;
-    FaserSCT_SpacePointCollection::const_iterator sp_it = spcollection->begin();
-    FaserSCT_SpacePointCollection::const_iterator sp_end = spcollection->end();
-    for (; sp_it != sp_end; ++sp_it) {
-      const Tracker::FaserSCT_SpacePoint* sp = *sp_it;
-      Identifier id = sp->associatedSurface().associatedDetectorElementIdentifier();
-      Acts::GeometryIdentifier geoId = identifierMap->at(id);
-      IndexSourceLink sourceLink(geoId, measurements.size());
-      sourceLinks.emplace_hint(sourceLinks.end(), std::move(sourceLink));
-      ThisMeasurement meas(sourceLink, indices, sp->localParameters(), sp->localCovariance());
-      measurements.emplace_back(std::move(meas));
-
-//      if(m_idHelper->station(sp->clusterList().first->identify())==1) {
-//	      pos1.push_back(Acts::Vector3(sp->globalPosition().x(),sp->globalPosition().y(),sp->globalPosition().z()));
-//	      layer1.push_back(m_idHelper->layer(sp->clusterList().first->identify()));
-//      }
-//      if(m_idHelper->station(sp->clusterList().first->identify())==2)pos2.push_back(Acts::Vector3(sp->globalPosition().x(),sp->globalPosition().y(),sp->globalPosition().z()));
-//      if(m_idHelper->station(sp->clusterList().first->identify())==3)pos3.push_back(Acts::Vector3(sp->globalPosition().x(),sp->globalPosition().y(),sp->globalPosition().z()));
-      sp_ids.push_back(sp->clusterList().first->identify());
-      sps.push_back(*sp);
-      }
-      }
-
-      // Get initial parameters
-      // FIXME: Get initial parameters from clusterFitter or SeedFinder not MC!
-        std::vector<Acts::CurvilinearTrackParameters> initialParameters;
-        auto initialParameter = m_initialParameterTool->getInitialParameters(sp_ids);
-        initialParameters.push_back(initialParameter);
-//      if(pos1.size()<1||pos2.size()<1||pos3.size()<1) return StatusCode::SUCCESS;
-//      std::vector<Acts::CurvilinearTrackParameters> initialParameters;
-//      initialParameters.clear();
-//      Acts::Vector3 pos1a(0,0,0);
-//      Acts::Vector3 pos2a(0,0,0);
-//      Acts::Vector3 pos3a(0,0,0);
-//      for(long unsigned int i1=0;i1<pos1.size();i1++)pos1a+=pos1[i1];
-//      for(long unsigned int i1=0;i1<pos2.size();i1++)pos2a+=pos2[i1];
-//      for(long unsigned int i1=0;i1<pos3.size();i1++)pos3a+=pos3[i1];
-//      pos1a/=pos1.size();
-//      pos2a/=pos2.size();
-//      pos3a/=pos3.size();
-//      m_nsp1+=pos1.size();
-//      m_nsp2+=pos2.size();
-//      m_nsp3+=pos3.size();
-//      for(int i1=0;i1<pos1.size();i1++){
-//      for(int i2=0;i2<pos2.size();i2++){
-//      for(int i3=0;i3<pos3.size();i3++){
-//      auto initialParameter=m_initialParameterTool->getInitialParameters(pos1[i1],pos2[i2],pos3[i3]);
-//      initialParameters.push_back(initialParameter);
-//      }
-//      }
-//      }
-      /*
-      if(pos1.size()>0&&pos2.size()>0&&pos3.size()>0) {
-      auto initialParameter=m_initialParameterTool->getInitialParameters(pos1a,pos2a,pos3a);
-       if(initialParameter.momentum().z()>0){
-	  m_nseeds++;
-      initialParameters.push_back(initialParameter);
-      */
- // for one stations
-//      if(pos1.size()>2) {
-//      std::vector<Acts::Vector3> pos10;
-//      std::vector<Acts::Vector3> pos11;
-//      std::vector<Acts::Vector3> pos12;
-//      pos10.clear();pos11.clear();pos12.clear();
-//	for(std::size_t ipos=0;ipos<pos1.size();ipos++){
-//	  if(layer1[ipos]==0)pos10.push_back(pos1[ipos]);
-//	  if(layer1[ipos]==1)pos11.push_back(pos1[ipos]);
-//	  if(layer1[ipos]==2)pos12.push_back(pos1[ipos]);
-//	}
-//	if(pos10.size()>0&&pos11.size()>0&&pos12.size()>0){
-//      auto initialParameter=m_initialParameterTool->getInitialParameters_1station(pos10,pos11,pos12);
-//      initialParameters.insert(initialParameters.end(),initialParameter.begin(),initialParameter.end());
-      /*
-       //for two stations
-      if(pos1.size()>1&&pos2.size()>0) {
-      Acts::Vector3 pos10a(0,0,0);
-      Acts::Vector3 pos11a(0,0,0);
-      int n10a=0,n11a=0;
-	for(int ipos=0;ipos<pos1.size();ipos++){
-	  if(layer1[ipos]==0){pos10a+=pos1[ipos];n10a++;}
-	  if(layer1[ipos]>0){pos11a+=pos1[ipos];n11a++;}
-	}
-	m_nsp10+=n10a;
-	m_nsp11+=n11a;
-	if(n10a>0&&n11a>0){
-	  m_ncom++;
-	  pos10a/=n10a;
-	  pos11a/=n11a;
-	  Acts::Vector3 dir1=(pos11a-pos10a).normalized();
-      auto initialParameter=m_initialParameterTool->getInitialParameters_2stations(pos1a,pos2a,dir1);
-       if(initialParameter.momentum().z()>0){
-	  m_nseeds++;
-      initialParameters.push_back(initialParameter);
-       }
-       */
-
-  // Prepare the output data with MultiTrajectory
-  TrajectoriesContainer trajectories;
-  trajectories.reserve(initialParameters.size());
-
-  // Construct a perigee surface as the target surface
-  auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(
-      Acts::Vector3{0., 0., 0.});
-
-  Acts::PropagatorPlainOptions pOptions;
-  pOptions.maxSteps = 10000;
-  /*
-    Acts::DirectNavigator     navigator;
-     std::unique_ptr<ActsExtrapolationDetail::VariantPropagator> varProp;
-      Acts::Vector3 constantFieldVector = Acts::Vector3(0,0,0.55);
-	  auto bField = std::make_shared<Acts::ConstantBField>(constantFieldVector);
-        auto stepper = Acts::EigenStepper<>(std::move(bField));
-	auto propagator = Acts::Propagator<decltype(stepper), Acts::DirectNavigator>(std::move(stepper),
-	std::move(navigator));
-	varProp = std::make_unique<VariantPropagator>(propagator);
-	*/
-
-  Acts::GeometryContext geoContext = m_trackingGeometryTool->getNominalGeometryContext().context();
-  Acts::MagneticFieldContext magFieldContext = getMagneticFieldContext(ctx);
-  Acts::CalibrationContext calibContext;
-  double chi2Max = 15;
-  size_t nMax = 10;
-  Acts::MeasurementSelector::Config measurementSelectorCfg = {
-    {Acts::GeometryIdentifier(), {chi2Max, nMax}},
-  };
-  std::unique_ptr<const Acts::Logger> logger
-      = Acts::getDefaultLogger("CombinatorialKalmanFilter", Acts::Logging::INFO);
-
-  // Set the CombinatorialKalmanFilter options
-  CombinatorialKalmanFilterAlg::TrackFinderOptions options(
-      geoContext, magFieldContext, calibContext,
-      IndexSourceLinkAccessor(), MeasurementCalibrator(measurements),
-      Acts::MeasurementSelector(measurementSelectorCfg),
-      //Acts::LoggerWrapper{*logger}, varProp, &(*pSurface));
-      Acts::LoggerWrapper{*logger}, pOptions, &(*pSurface));
-
-  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
-      = m_trackingGeometryTool->trackingGeometry();
-
-  // Get track finder function
-  auto trackFinderFunction = makeTrackFinderFunction(trackingGeometry);
-
-  // Perform the track finding for all initial parameters
-  ATH_MSG_DEBUG("Invoke track finding with " << initialParameters.size()
-                                          << " seeds.");
-  auto results = trackFinderFunction(sourceLinks, initialParameters, options);
-  // Loop over the track finding results for all initial parameters
-  for (std::size_t iseed = 0; iseed < initialParameters.size(); ++iseed) {
-    // The result for this seed
-    auto& result = results[iseed];
-    if (result.ok()) {
-      // Get the track finding output object
-      const auto& trackFindingOutput = result.value();
-      std::unique_ptr<Trk::Track> track = makeTrack(geoContext, result,sps);
-      m_ntracks++;
-      if(track!=nullptr) {
-        outputTracks->push_back(std::move(track));
-      } else {
-        ATH_MSG_DEBUG("No Trk::Track is created" );
-      }
-      // Create a Trajectories result struct
-      trajectories.emplace_back(std::move(trackFindingOutput.fittedStates),
-                                std::move(trackFindingOutput.lastMeasurementIndices),
-                                std::move(trackFindingOutput.fittedParameters));
-    } else {
-      ATH_MSG_WARNING("Track finding failed for seed " << iseed << " with error"
-                                                       << result.error());
-      // Track finding failed. Add an empty result so the output container has
-      // the same number of entries as the input.
-      trajectories.push_back(FaserActsRecMultiTrajectory());
-    }
-  }
-
-  m_trajectoryWriterTool->writeout(trajectories, geoContext,initialParameters);
-//  }
-//      }
-
-  if(outputTracks->size()>0) {
-    ATH_MSG_DEBUG("Found " << outputTracks->size() << " tracks");
-  } else {
-    ATH_MSG_WARNING("No track is found");
-  }
-
-  ATH_CHECK(trackContainer.record(std::move(outputTracks)));
-  return StatusCode::SUCCESS;
-}
-
-
-StatusCode CombinatorialKalmanFilterAlg::finalize() {
-  ATH_MSG_INFO("CombinatorialKalmanFilterAlg::finalize");
-  ATH_MSG_INFO("Summary info");
-  ATH_MSG_INFO("In total, "<<m_nevents<<" events, and "<<m_nseeds<<" seeds, and "<<m_ntracks<<" tracks");
-  ATH_MSG_INFO("In total, "<<m_nsp1<<" , "<<m_nsp2<<" , "<<m_nsp3<<" ,"<<m_nsp10<<" , "<<m_nsp11<<" , "<<m_ncom);
-
-  return StatusCode::SUCCESS;
-}
-
-
-Acts::MagneticFieldContext CombinatorialKalmanFilterAlg::getMagneticFieldContext(const EventContext& ctx) const {
-  SG::ReadCondHandle<FaserFieldCacheCondObj> readHandle{m_fieldCondObjInputKey, ctx};
-  if (!readHandle.isValid()) {
-    std::stringstream msg;
-    msg << "Failed to retrieve magnetic field condition data " << m_fieldCondObjInputKey.key() << ".";
-    throw std::runtime_error(msg.str());
-  }
-  const FaserFieldCacheCondObj* fieldCondObj{*readHandle};
-
-  return Acts::MagneticFieldContext(fieldCondObj);
-}
-
-std::unique_ptr<Trk::Track>
-CombinatorialKalmanFilterAlg::makeTrack(Acts::GeometryContext& tgContext, FitterResult& fitResult, std::vector<Tracker::FaserSCT_SpacePoint> sps) const {
-  std::unique_ptr<Trk::Track> newtrack = nullptr;
-  //Get the fit output object
-  const auto& fitOutput = fitResult.value();
-  if (fitOutput.fittedParameters.size()>0) {
-    DataVector<const Trk::TrackStateOnSurface>* finalTrajectory = new DataVector<const Trk::TrackStateOnSurface>{};
-    std::vector<std::unique_ptr<const Acts::BoundTrackParameters>> actsSmoothedParam;
-    ATH_MSG_DEBUG("makeTrack : trackTip "<<fitOutput.lastMeasurementIndices.size());
-    // Loop over all the output state to create track state
-    fitOutput.fittedStates.visitBackwards(fitOutput.lastMeasurementIndices.front(), [&](const auto &state) {
-      auto flag = state.typeFlags();
-      if (state.referenceSurface().associatedDetectorElement() != nullptr) {
-        //	const auto* actsElement = dynamic_cast<const FaserActsDetectorElement*>(state.referenceSurface().associatedDetectorElement());
-        //	if (actsElement != nullptr ){
-        // We need to determine the type of state
-        std::bitset<Trk::TrackStateOnSurface::NumberOfTrackStateOnSurfaceTypes> typePattern;
-        const Trk::TrackParameters *parm;
-
-        // State is a hole (no associated measurement), use predicted para meters
-        if (flag[Acts::TrackStateFlag::HoleFlag] == true) {
-          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
-          state.predicted(),
-          state.predictedCovariance());
-          parm = ConvertActsTrackParameterToATLAS(actsParam, tgContext);
-          // auto boundaryCheck = m_boundaryCheckTool->boundaryCheck(*p arm);
-          typePattern.set(Trk::TrackStateOnSurface::Hole);
-        }
-        // The state was tagged as an outlier, use filtered parameters
-        else if (flag[Acts::TrackStateFlag::OutlierFlag] == true) {
-          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
-                                                     state.filtered(), state.filteredCovariance());
-          parm = ConvertActsTrackParameterToATLAS(actsParam, tgContext);
-          typePattern.set(Trk::TrackStateOnSurface::Outlier);
-        }
-        // The state is a measurement state, use smoothed parameters
-        else {
-          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
-                                                     state.smoothed(), state.smoothedCovariance());
-          actsSmoothedParam.push_back(std::make_unique<const Acts::BoundTrackParameters>(Acts::BoundTrackParameters(actsParam)));
-          //  const auto& psurface=actsParam.referenceSurface();
-          Acts::Vector2 local(actsParam.parameters()[Acts::eBoundLoc0], actsParam.parameters()[Acts::eBoundLoc1]);
-          //  const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(actsParam.parameters()[Acts::eBoundPhi], actsParam.parameters()[Acts::eBoundTheta]);
-          //  auto pos=actsParam.position(tgContext);
-          parm = ConvertActsTrackParameterToATLAS(actsParam, tgContext);
-          typePattern.set(Trk::TrackStateOnSurface::Measurement);
-        }
-        Tracker::FaserSCT_ClusterOnTrack* measState = nullptr;
-        if (state.hasUncalibrated()) {
-          auto sp= sps.at(state.uncalibrated().index());
-          //const Tracker::FaserSCT_Cluster* fitCluster=&sp;
-          const Tracker::FaserSCT_Cluster* fitCluster=sp.clusterList().first;
-          if(fitCluster !=nullptr) {
-            measState = new Tracker::FaserSCT_ClusterOnTrack{ fitCluster, Trk::LocalParameters { Trk::DefinedParameter { fitCluster->localPosition()[0], Trk::loc1 }, Trk::DefinedParameter { fitCluster->localPosition()[1], Trk::loc2 } }, fitCluster->localCovariance(), m_idHelper->wafer_hash(fitCluster->detectorElement()->identify())};
-          }
-        }
-        double nDoF = state.calibratedSize();
-        const Trk::FitQualityOnSurface *quality = new Trk::FitQualityOnSurface(state.chi2(), nDoF);
-        const Trk::TrackStateOnSurface *perState = new Trk::TrackStateOnSurface(measState, parm, quality, nullptr, typePattern);
-        // If a state was succesfully created add it to the trajectory
-        if (perState) {
-          finalTrajectory->insert(finalTrajectory->begin(), perState);
-        }
-      }
-      return;
-    });
-
-    // Create the track using the states
-    const Trk::TrackInfo newInfo(Trk::TrackInfo::TrackFitter::KalmanFitter, Trk::ParticleHypothesis::muon);
-    // Trk::FitQuality* q = nullptr;
-    // newInfo.setTrackFitter(Trk::TrackInfo::TrackFitter::KalmanFitter     ); //Mark the fitter as KalmanFitter
-    newtrack = std::make_unique<Trk::Track>(newInfo, std::move(*finalTrajectory), nullptr); 
-  }
-  return newtrack;
-}
-
-const Trk::TrackParameters*
-CombinatorialKalmanFilterAlg ::ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const      {
-  using namespace Acts::UnitLiterals;
-  std::optional<AmgSymMatrix(5)> cov = std::nullopt;
-  if (actsParameter.covariance()){
-    AmgSymMatrix(5) newcov(actsParameter.covariance()->topLeftCorner(5, 5));
-    // Convert the covariance matrix to GeV
-    for(int i=0; i < newcov.rows(); i++){
-      newcov(i, 4) = newcov(i, 4)*1_MeV;
-    }
-    for(int i=0; i < newcov.cols(); i++){
-      newcov(4, i) = newcov(4, i)*1_MeV;
-    }
-    cov =  std::optional<AmgSymMatrix(5)>(newcov);
-  }
-  const Amg::Vector3D& pos=actsParameter.position(gctx);
-  double tphi=actsParameter.get<Acts::eBoundPhi>();
-  double ttheta=actsParameter.get<Acts::eBoundTheta>();
-  double tqOverP=actsParameter.get<Acts::eBoundQOverP>()*1_MeV;
-  double p = std::abs(1. / tqOverP);
-  Amg::Vector3D tmom(p * std::cos(tphi) * std::sin(ttheta), p * std::sin(tphi) * std::sin(ttheta), p * std::cos(ttheta));
-  const Trk::CurvilinearParameters * curv = new Trk::CurvilinearParameters(pos,tmom,tqOverP>0, cov);
-  return curv;  
-} 
-
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/EffPlotTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/EffPlotTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..514fe5407c1585e1a0884a60f2dc5d039ebe92ae
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/EffPlotTool.cxx
@@ -0,0 +1,40 @@
+#include "FaserActsKalmanFilter/EffPlotTool.h"
+#include "Acts/Utilities/Helpers.hpp"
+
+void EffPlotTool::book(EffPlotTool::EffPlotCache &effPlotCache) const {
+  PlotHelpers::Binning bPhi = m_varBinning.at("Phi");
+  PlotHelpers::Binning bEta = m_varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_varBinning.at("Pt");
+  // efficiency vs pT
+  effPlotCache.trackEff_vs_pT = PlotHelpers::bookEff(
+      "trackeff_vs_pT", "Tracking efficiency;Truth pT [GeV/c];Efficiency", bPt);
+  // efficiency vs eta
+  effPlotCache.trackEff_vs_eta = PlotHelpers::bookEff(
+      "trackeff_vs_eta", "Tracking efficiency;Truth #eta;Efficiency", bEta);
+  // efficiency vs phi
+  effPlotCache.trackEff_vs_phi = PlotHelpers::bookEff(
+      "trackeff_vs_phi", "Tracking efficiency;Truth #phi;Efficiency", bPhi);
+}
+
+void EffPlotTool::fill(EffPlotTool::EffPlotCache &effPlotCache,
+                       const HepMC::GenParticle* truthParticle, bool status) const {
+  const auto t_phi = truthParticle->momentum().phi();
+  const auto t_eta = truthParticle->momentum().eta();
+  const auto t_pT = truthParticle->momentum().perp() * m_MeV2GeV;
+
+  PlotHelpers::fillEff(effPlotCache.trackEff_vs_pT, t_pT, status);
+  PlotHelpers::fillEff(effPlotCache.trackEff_vs_eta, t_eta, status);
+  PlotHelpers::fillEff(effPlotCache.trackEff_vs_phi, t_phi, status);
+}
+
+void EffPlotTool::write(const EffPlotTool::EffPlotCache &effPlotCache) const {
+  effPlotCache.trackEff_vs_pT->Write();
+  effPlotCache.trackEff_vs_eta->Write();
+  effPlotCache.trackEff_vs_phi->Write();
+}
+
+void EffPlotTool::clear(EffPlotTool::EffPlotCache &effPlotCache) const {
+  delete effPlotCache.trackEff_vs_pT;
+  delete effPlotCache.trackEff_vs_eta;
+  delete effPlotCache.trackEff_vs_phi;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/FaserActsKalmanFilterAlg.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/FaserActsKalmanFilterAlg.cxx
index c9427a0f8f9940f6d181e4f4775bcb9a8e2ae527..673524123467db8461bb25da2157bfa00eb6755a 100755
--- a/Tracking/Acts/FaserActsKalmanFilter/src/FaserActsKalmanFilterAlg.cxx
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/FaserActsKalmanFilterAlg.cxx
@@ -7,7 +7,7 @@
 // ATHENA
 #include "GaudiKernel/EventContext.h"
 #include "GaudiKernel/ISvcLocator.h"
-#include "GaudiKernel/PhysicalConstants.h"
+//#include "GaudiKernel/PhysicalConstants.h"
 #include "TrackerReadoutGeometry/SCT_DetectorManager.h"
 #include "TrackerReadoutGeometry/SiDetectorDesign.h"
 #include "TrackerReadoutGeometry/SiLocalPosition.h"
@@ -18,14 +18,8 @@
 #include "FaserDetDescr/FaserDetectorID.h"
 
 // ACTS
-#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
-#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
 #include "Acts/Geometry/GeometryIdentifier.hpp"
-#include "Acts/MagneticField/ConstantBField.hpp"
-#include "Acts/MagneticField/InterpolatedBFieldMap.hpp"
-#include "Acts/MagneticField/SharedBField.hpp"
 #include "Acts/MagneticField/MagneticFieldContext.hpp"
-#include "Acts/Propagator/EigenStepper.hpp"
 #include "Acts/Propagator/Navigator.hpp"
 #include "Acts/Propagator/Propagator.hpp"
 #include "Acts/Surfaces/Surface.hpp"
@@ -43,7 +37,6 @@
 #include "Acts/EventData/TrackParameters.hpp"
 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
 #include "Acts/EventData/Measurement.hpp"
-#include "Acts/Geometry/GeometryIdentifier.hpp"
 
 
 // PACKAGE
@@ -52,6 +45,9 @@
 #include "FaserActsGeometry/FaserActsGeometryContext.h"
 #include "FaserActsGeometry/IFaserActsPropStepRootWriterSvc.h"
 #include "FaserActsGeometry/FaserActsDetectorElement.h"
+#include "FaserActsKalmanFilter/IdentifierLink.h"
+
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
 
 //ROOT
 #include <TTree.h>
@@ -65,7 +61,6 @@
 #include <string>
 #include <fstream>
 #include <cmath>
-#include <random>
 
 using TrajectoryContainer = std::vector<FaserActsRecMultiTrajectory>;
 
@@ -75,476 +70,99 @@ using Acts::VectorHelpers::perp;
 using Acts::VectorHelpers::phi;
 using Acts::VectorHelpers::theta;
 using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, 2>;
-using Updater = Acts::GainMatrixUpdater;
-using Smoother = Acts::GainMatrixSmoother;
-using Stepper = Acts::EigenStepper<FASERMagneticFieldWrapper>;
-using Propagator = Acts::Propagator<Stepper, Acts::DirectNavigator>;
-using Fitter = Acts::KalmanFitter<Propagator, Updater, Smoother>;
-
-namespace ActsExtrapolationDetail {
-  using VariantPropagatorBase = boost::variant<
-    Acts::Propagator<Acts::EigenStepper<>, Acts::DirectNavigator>,
-    Acts::Propagator<Acts::EigenStepper<>, Acts::DirectNavigator>
-  >;
+using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
 
-  class VariantPropagator : public VariantPropagatorBase
-  {
-  public:
-    using VariantPropagatorBase::VariantPropagatorBase;
-  };
+FaserActsKalmanFilterAlg::FaserActsKalmanFilterAlg(const std::string& name, ISvcLocator* pSvcLocator) :
+   AthAlgorithm(name, pSvcLocator)  {}
 
-}
-
-using ActsExtrapolationDetail::VariantPropagator;
-
-FaserActsKalmanFilterAlg::FaserActsKalmanFilterAlg(const std::string& name, ISvcLocator* pSvcLocator)
-    : AthAlgorithm(name, pSvcLocator) , m_thistSvc("THistSvc", name)
-{
-}
-
-// FixMe the Identifier to GeometryIdentfier mapping should not be hardcoded.
-int FaserActsKalmanFilterAlg::getGeometryIdentifierVolume(int station)
-{
-  switch(station)
-  {
-    case 1:
-      return 2;
-    case 2:
-      return 3;
-    case 3:
-      return 4;
-    default:
-      ATH_MSG_ERROR("Received unexpected station. Check detector geometry");
-      return 0;
-  }
-}
-
-int FaserActsKalmanFilterAlg::getGeometryIdentifierLayer(int layer)
-{
-  switch(layer)
-  {
-    case 0:
-      return 2;
-    case 1:
-      return 4;
-    case 2:
-      return 6;
-    default:
-      ATH_MSG_ERROR("Received unexpected layer. Check detector geometry");
-      return 0;
-  }
-}
-
-int FaserActsKalmanFilterAlg::getGeometryIdentifierSensitive(int row, int column)
-{
-  if (row == 0 && column == -1)
-    return 2;
-  else if (row == 0 && column == 1)
-    return 4;
-  else if (row == 1 && column == -1)
-    return 5;
-  else if (row == 1 && column == 1)
-    return 7;
-  else if (row == 2 && column == -1)
-    return 10;
-  else if (row == 2 && column == 1)
-    return 12;
-  else if (row == 3 && column == -1)
-    return 13;
-  else if (row ==3 && column == 1)
-    return 15;
-  else
-  {
-    ATH_MSG_ERROR("Received unexpected row or column. Check detector geometry");
-    return 0;
-  }
-}
-
-Acts::GeometryIdentifier FaserActsKalmanFilterAlg::getGeometryIdentifier(const Identifier id)
-{
-  Acts::GeometryIdentifier geoId = Acts::GeometryIdentifier();
-  geoId.setVolume(getGeometryIdentifierVolume(m_idHelper->station(id)));
-  geoId.setLayer(getGeometryIdentifierLayer(m_idHelper->layer(id)));
-  geoId.setSensitive(getGeometryIdentifierSensitive(m_idHelper->phi_module(id), m_idHelper->eta_module(id)));
-  return geoId;
-}
 
 StatusCode FaserActsKalmanFilterAlg::initialize() {
-
-  ATH_MSG_DEBUG(name() << "::" << __FUNCTION__);
-
-  ATH_MSG_INFO("Initializing ACTS kalman filter");
-
-      ATH_CHECK( m_fieldCondObjInputKey.initialize() );
-
-      ATH_CHECK( m_extrapolationTool.retrieve() );
-
-  if ( m_seed_spcollectionKey.key().empty()){
-    ATH_MSG_FATAL( "SCTs selected and no name set for SCT clusters");
-    return StatusCode::FAILURE;
+  ATH_CHECK(m_fieldCondObjInputKey.initialize());
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackFinderTool.retrieve());
+  ATH_CHECK(m_trajectoryWriterTool.retrieve());
+  ATH_CHECK(m_trajectoryStatesWriterTool.retrieve());
+//  ATH_CHECK(m_protoTrackWriterTool.retrieve());
+  ATH_CHECK(m_trackCollection.initialize());
+  ATH_CHECK(detStore()->retrieve(m_idHelper,"FaserSCT_ID"));
+  m_fit = makeTrackFitterFunction(m_trackingGeometryTool->trackingGeometry());
+  if (m_actsLogging == "VERBOSE") {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::VERBOSE);
+  } else if (m_actsLogging == "DEBUG") {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::DEBUG);
+  } else {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::INFO);
   }
-
-      ATH_CHECK( m_seed_spcollectionKey.initialize() );
-
-      ATH_CHECK( m_mcEventKey.initialize() );
-
-      ATH_CHECK( m_sctMap.initialize());
-
-      ATH_CHECK(detStore()->retrieve(m_idHelper,"FaserSCT_ID"));
-
-      ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
-
-      ATH_CHECK(m_thistSvc.retrieve());
-
-  m_trackTree = new TTree("tracks", "");
-
-      ATH_CHECK(m_thistSvc->regTree("/KalmanTracks/tracks", m_trackTree));
-
-  if (m_trackTree) {
-    initializeTree();
-  }
-  else {
-    ATH_MSG_ERROR("No tree found!");
-  }
-
-  ATH_MSG_INFO("ACTS kalman filter successfully initialized");
   return StatusCode::SUCCESS;
 }
 
-//StatusCode FaserActsKalmanFilterAlg::execute(const EventContext& ctx) const
-StatusCode FaserActsKalmanFilterAlg::execute()
-{
 
-  ATH_MSG_VERBOSE(name() << "::" << __FUNCTION__);
+//StatusCode FaserActsKalmanFilterAlg::execute(const EventContext& ctx) const {
+StatusCode FaserActsKalmanFilterAlg::execute() {
+  const EventContext& ctx = Gaudi::Hive::currentContext();
 
-  m_eventNr++;
+  //make TrackCollection
+  ATH_CHECK(m_trackCollection.initialize());
+  SG::WriteHandle<TrackCollection> trackContainer{m_trackCollection,ctx};
+  std::unique_ptr<TrackCollection> outputTracks = std::make_unique<TrackCollection>();
 
-  //SG::ReadHandle<SpacePointForSeedCollection> seed_spcollection( m_seed_spcollectionKey, ctx );
-  SG::ReadHandle<SpacePointForSeedCollection> seed_spcollection( m_seed_spcollectionKey );
-  if (!seed_spcollection.isValid()){
-    msg(MSG:: FATAL) << "Could not find the data object "<< seed_spcollection.name() << " !" << endmsg;
-    return StatusCode::RECOVERABLE;
-  }
+  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
+      = m_trackingGeometryTool->trackingGeometry();
 
-  const FaserActsGeometryContext& gctx
-      = m_extrapolationTool->trackingGeometryTool()->getNominalGeometryContext();
+  const FaserActsGeometryContext& gctx = m_trackingGeometryTool->getNominalGeometryContext();
   auto geoctx = gctx.context();
-  Acts::MagneticFieldContext magctx = getMagneticFieldContext();
+  Acts::MagneticFieldContext magctx = getMagneticFieldContext(ctx);
   Acts::CalibrationContext calctx;
 
-  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
-      = m_extrapolationTool->trackingGeometryTool()->trackingGeometry();
-
-  std::shared_ptr<const Acts::Surface> pSurface;
-
-  // Get SiDetectorElements
-  const TrackerDD::SiDetectorElementCollection* elCollection{m_detManager->getDetectorElementCollection()};
-  if (elCollection == nullptr) {
-    ATH_MSG_FATAL("Null pointer is returned by getDetectorElementCollection()");
-    return StatusCode::FAILURE;
-  }
-
+  CHECK(m_trackFinderTool->run());
+  std::shared_ptr<const Acts::Surface> initialSurface =
+      m_trackFinderTool->initialSurface();
+  std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>> initialTrackParameters =
+      m_trackFinderTool->initialTrackParameters();
+  std::shared_ptr<std::vector<std::vector<IndexSourceLink>>> sourceLinks =
+      m_trackFinderTool->sourceLinks();
+  std::shared_ptr<std::vector<IdentifierLink>> idLinks = m_trackFinderTool->idLinks();
+  std::shared_ptr<std::vector<std::vector<Measurement>>> measurements =
+      m_trackFinderTool->measurements();
+  std::shared_ptr<std::vector<std::vector<Tracker::FaserSCT_SpacePoint>>> spacePoints =
+      m_trackFinderTool->spacePoints();
+  std::shared_ptr<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>> clusters =
+      m_trackFinderTool->clusters();
 
-  static const TrackerDD::SCT_DetectorManager     *s_sct;
-  if(detStore()->retrieve(s_sct,"SCT").isFailure()) s_sct = 0;
-  int N_1_0=0, N_1_1=0, N_1_2=0, N_2_0=0, N_2_1=0, N_2_2=0;
-  Acts::Vector3 pos1_0(0., 0., 0.);
-  Acts::Vector3 pos1_1(0., 0., 0.);
-  Acts::Vector3 pos1_2(0., 0., 0.);
-  Acts::Vector3 pos2_0(0., 0., 0.);
-  Acts::Vector3 pos2_1(0., 0., 0.);
-  Acts::Vector3 pos2_2(0., 0., 0.);
-  HepMC::FourVector truthmom;
-  HepMC::FourVector pv;
+//  ATH_CHECK(m_protoTrackWriterTool->write(initialTrackParameters, measurements, geoctx));
 
-  // create source links and measurements
-  std::vector<IndexSourceLink> sourceLinks;
-  std::vector<const Acts::Surface*> surfSequence;
-  MeasurementContainer measurements;
+  int n_trackSeeds = initialTrackParameters->size();
 
-  SpacePointForSeedCollection::const_iterator it = seed_spcollection->begin();
-  SpacePointForSeedCollection::const_iterator itend = seed_spcollection->end();
-  for (; it != itend; ++it){
-    const Tracker::FaserSCT_SpacePoint *sp = (&(**it))->SpacePoint();
+  TrajectoryContainer trajectories;
+  trajectories.reserve(1);
 
-    const Identifier id = sp->clusterList().first->identify();
-    const TrackerDD::SiDetectorElement* siSpElement = m_detManager->getDetectorElement(id);
-    auto spElement = static_cast<const FaserActsDetectorElement>(siSpElement);
-    // Acts::GeometryIdentifier geoId = getGeometryIdentifier(id);
-    Acts::GeometryIdentifier geoId;
+  for (int i = 0; i < n_trackSeeds; ++i) {
 
-    const Acts::TrackingVolume* tVolume = trackingGeometry->highestTrackingVolume();
-    if (tVolume->confinedVolumes()) {
-      for (auto volume : tVolume->confinedVolumes()->arrayObjects()) {
-        if (volume->confinedLayers()) {
-          for (const auto& layer : volume->confinedLayers()->arrayObjects()) {
-            if (layer->layerType() == Acts::navigation) continue;
-            for (auto surface : layer->surfaceArray()->surfaces()) {
-              if (surface) {
-                const Acts::DetectorElementBase *detElement = surface->associatedDetectorElement();
-                const auto *faserDetElement = dynamic_cast<const FaserActsDetectorElement*>(detElement);
-                auto* tmp = const_cast<FaserActsDetectorElement*>(faserDetElement);
-                if (*tmp == spElement) {
-                  geoId = surface->geometryId();
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-
-    const Acts::Surface* surfacePtr = trackingGeometry->findSurface(geoId);
-    surfSequence.push_back(surfacePtr);
-    if (not surfacePtr) {
-      ATH_MSG_ERROR("Could not find surface " << geoId);
-      return StatusCode::FAILURE;
-    }
-
-    Index spIdx = measurements.size();
-    IndexSourceLink sourceLink(geoId, spIdx);
-
-
-    auto par = sp->localParameters();
-    auto cov = sp->localCovariance();
-    std::array<Acts::BoundIndices, 2> indices = {Acts::eBoundLoc0, Acts::eBoundLoc1};
-    ThisMeasurement meas(sourceLink, indices, par, cov);
-    sourceLinks.push_back(std::move(sourceLink));
-    measurements.emplace_back(std::move(meas));
-
-
-    // Get the truth position and momentum
-    SG::ReadHandle<McEventCollection> h_mcEvents(m_mcEventKey);
-    SG::ReadHandle<TrackerSimDataCollection> h_collectionMap(m_sctMap);
-    const auto& simdata = h_collectionMap->find(id)->second;
-    const auto& deposits = simdata.getdeposits();
-    for( const auto& depositPair : deposits)
-    {
-      if (depositPair.first->pdg_id() == -13) {
-        pv = depositPair.first->production_vertex()->position();
-        truthmom = depositPair.first->momentum();
-      }
-    }
-
-    // Get the measurements
-    Amg::Vector3D gloPos=sp->globalPosition();
-    int station = m_idHelper->station(id);
-    int plane = m_idHelper->layer(id);
-    if (station==1 && plane==0) {
-      N_1_0++;
-      pos1_0 = Acts::Vector3(gloPos.x(), gloPos.y(), gloPos.z());
-
-      // Construct a plane surface as the target surface
-      const TrackerDD::SiDetectorDesign &design = siSpElement->design();
-      double hlX = design.width()/2. * 1_mm;
-      double hlY = design.length()/2. * 1_mm;
-      auto rectangleBounds = std::make_shared<const Acts::RectangleBounds>(hlX, hlY);
-      Amg::Transform3D g2l = siSpElement->getMaterialGeom()->getDefAbsoluteTransform()
-                             * Amg::CLHEPTransformToEigen(siSpElement->recoToHitTransform());
-      pSurface = Acts::Surface::makeShared<Acts::PlaneSurface>( g2l, rectangleBounds );
-    }
-    if (station==1 && plane==1) {
-      N_1_1++;
-      pos1_1 = Acts::Vector3(gloPos.x(), gloPos.y(), gloPos.z());
-    }
-    if (station==1 && plane==2) {
-      N_1_2++;
-      pos1_2 = Acts::Vector3(gloPos.x(), gloPos.y(), gloPos.z());
-    }
-    if (station==2 && plane==0) {
-      N_2_0++;
-      pos2_0 = Acts::Vector3(gloPos.x(), gloPos.y(), gloPos.z());
-    }
-    if (station==2 && plane==1) {
-      N_2_1++;
-      pos2_1 = Acts::Vector3(gloPos.x(), gloPos.y(), gloPos.z());
-    }
-    if (station==2 && plane==2) {
-      N_2_2++;
-      pos2_2 = Acts::Vector3(gloPos.x(), gloPos.y(), gloPos.z());
-    }
-  }
-
-  for (auto sl : sourceLinks)
-  {
-    std::cout << "??volume=" << sl.geometryId().volume() << std::endl;
-    std::cout << "??layer=" << sl.geometryId().layer() << std::endl;
-    std::cout << "??sensitive=" << sl.geometryId().sensitive() << std::endl;
-  }
-
-  // Calculate the initial track parameters
-  if ( (N_1_0==1) && (N_1_1==1) && (N_1_2==1) && (N_2_0==1) && (N_2_1==1) && (N_2_2==1)) {
-    std::cout<<"!!!!!!!!!!!  pos1_0 = ("<<pos1_0.x()<<", "<<pos1_0.y()<<", "<<pos1_0.z()<<") "<<std::endl;
-    std::cout<<"!!!!!!!!!!!  pos1_1 = ("<<pos1_1.x()<<", "<<pos1_1.y()<<", "<<pos1_1.z()<<") "<<std::endl;
-    std::cout<<"!!!!!!!!!!!  pos1_2 = ("<<pos1_2.x()<<", "<<pos1_2.y()<<", "<<pos1_2.z()<<") "<<std::endl;
-    std::cout<<"!!!!!!!!!!!  pos2_0 = ("<<pos2_0.x()<<", "<<pos2_0.y()<<", "<<pos2_0.z()<<") "<<std::endl;
-    std::cout<<"!!!!!!!!!!!  pos2_1 = ("<<pos2_1.x()<<", "<<pos2_1.y()<<", "<<pos2_1.z()<<") "<<std::endl;
-    std::cout<<"!!!!!!!!!!!  pos2_2 = ("<<pos2_2.x()<<", "<<pos2_2.y()<<", "<<pos2_2.z()<<") "<<std::endl;
-    //@FIXME: change the hard codes in future
-    double charge = 1;
-    double B = 0.55;
-    //const Acts::Vector3 pos = pos1_0;
-    const Acts::Vector3 pos(pos1_0.x(), pos1_0.y(), pos1_0.z()-1);
-    Acts::Vector3 d1 = pos1_2 - pos1_0;
-    Acts::Vector3 d2 = pos2_2 - pos2_0;
-    // the direction of momentum in the first station
-    Acts::Vector3 direct1 = d1.normalized();
-    // the direction of momentum in the second station
-    Acts::Vector3 direct2 = d2.normalized();
-    // the vector pointing from the center of circle to the particle at layer 2 in Y-Z plane
-    double R1_z = charge * direct1.y() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
-    // double R1_y = -charge * direct1.z() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
-    // the vector pointing from the center of circle to the particle at layer 3 in Y-Z plane
-    double R2_z = charge * direct2.y() / std::sqrt(direct2.y()*direct2.y() + direct2.z()*direct2.z());
-    // double R2_y = -charge * direct2.z() / std::sqrt(direct2.y()*direct2.y() + direct2.z()*direct2.z());
-    // the norm of radius
-    double R = (pos2_0.z() - pos1_2.z()) / (R2_z - R1_z);
-    // the norm of momentum in Y-Z plane
-    double p_yz = 0.3*B*R / 1000.0;  // R(mm), p(GeV), B(T)
-    double p_z = p_yz * direct1.z() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
-    double p_y = p_yz * direct1.y() / std::sqrt(direct1.y()*direct1.y() + direct1.z()*direct1.z());
-    double p_x = direct1.x() * p_z / direct1.z();
-    // total momentum at the layer 0
-    const Acts::Vector3 mom(p_x, p_y, p_z);
-    double p = mom.norm();
-    std::cout<<"!!!!!!!!!!!  InitTrack momentum on layer 0: ( "<<mom.x()*1000<<",  "<<mom.y()*1000<<",  "<<mom.z()*1000<<",  "<<p*1000<<")  "<<std::endl;
-    // build the track covariance matrix using the smearing sigmas
-    double sigmaU = 200_um;
-    double sigmaV = 200_um;
-    double sigmaPhi = 1_degree;
-    double sigmaTheta = 1_degree;
-    double sigmaQOverP = 0.01*p / (p*p);
-    double sigmaT0 = 1_ns;
-    Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
-    cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = sigmaU * sigmaU;
-    cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = sigmaV * sigmaV;
-    cov(Acts::eBoundPhi, Acts::eBoundPhi) = sigmaPhi * sigmaPhi;
-    cov(Acts::eBoundTheta, Acts::eBoundTheta) = sigmaTheta * sigmaTheta;
-    cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = sigmaQOverP * sigmaQOverP;
-    cov(Acts::eBoundTime, Acts::eBoundTime) = sigmaT0 * sigmaT0;
-    double time =0;
-    //Acts::CurvilinearTrackParameters InitTrackParam(std::make_optional(std::move(cov)), pos, mom, charge, time); // calculated initial parameters
-
-    // Smearing truth parameters as initial parameters
-    Acts::Vector3 pPos(pv.x(), pv.y(), pv.z());
-    Acts::Vector3 pMom(truthmom.x()/1000., truthmom.y()/1000., truthmom.z()/1000.);
-    std::random_device rd;
-    std::default_random_engine rng {rd()};
-    std::normal_distribution<> norm; // mu: 0 sigma: 1
-    Acts::Vector3 deltaPos(sigmaU*norm(rng), sigmaU*norm(rng), sigmaU*norm(rng));
-    auto theta = Acts::VectorHelpers::theta(pMom.normalized());
-    auto phi = Acts::VectorHelpers::phi(pMom.normalized());
-    auto angles = Acts::detail::normalizePhiTheta(phi + sigmaPhi*norm(rng), theta + sigmaTheta*norm(rng));
-    Acts::Vector3 dir(std::sin(angles.second) * std::cos(angles.first),
-                      std::sin(angles.second) * std::sin(angles.first),
-                      std::cos(angles.second));
-    const Acts::Vector3 deltaMom = ( pMom.norm()*(1 + 0.01*norm(rng)) ) * dir - pMom;
-    std::cout << "deltaPos: " << deltaPos << std::endl;
-    std::cout << "deltaMom: " << deltaMom << std::endl;
-    const Acts::Vector4 posTime ((pPos+deltaPos).x(), (pPos+deltaPos).y(), (pPos+deltaPos).z(), time);
-    const Acts::Vector3 momentum = pMom+deltaMom;
-    const Acts::Vector3 momentum_dir = momentum.normalized();
-    double momentum_abs = momentum.norm();
-    Acts::CurvilinearTrackParameters InitTrackParam(posTime, momentum_dir, momentum_abs, charge, std::make_optional(std::move(cov)));
-
-    // the surface which the production point is bound to
-    Acts::Vector3 center(0, 0, pPos.z());
-    Acts::Vector3 normal(0, 0, 1);
-    std::shared_ptr<const Acts::Surface> initSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(center, normal);
-    // extrapolate the particle from production point to the first layer
-    const Acts::Vector4 truthPosTime (pPos.x(), pPos.y(), pPos.z(), time);
-//    const Acts::Vector3 truthMomentum_dir = pMom.normalized();
-//    double truthMomentum_abs = pMom.norm();
-
-    BoundVector params = BoundVector::Zero();
-    params[Acts::eBoundLoc0] = pPos.x();
-    params[Acts::eBoundLoc1] = pPos.y();
-    params[Acts::eBoundPhi] = Acts::VectorHelpers::phi(pMom.normalized());
-    params[Acts::eBoundTheta] = Acts::VectorHelpers::theta(pMom.normalized());
-    params[Acts::eBoundQOverP] = charge/p;
-    params[Acts::eBoundTime] = time;
-
-    Acts::BoundTrackParameters startParameters(initSurface, params, charge, std::nullopt);
-    auto truthParam = m_extrapolationTool->propagate(Gaudi::Hive::currentContext(), startParameters, *pSurface);
-    std::cout << "truth pos on 1st layer: " << truthParam->position(geoctx) << std::endl;
-    std::cout << "truth mom on 1st layer: " << truthParam->momentum() << std::endl;
-    std::cout << "truth parameters on 1st layer: " << truthParam->parameters() << std::endl;
-
-
-    // Call the Acts Kalman Filter
-    // Prepare the output data with MultiTrajectory
-    TrajectoryContainer trajectories;
-    trajectories.reserve(1);
-
-    // Construct a perigee surface as the target surface
-    //auto pSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(
-    //                Acts::Vector3{0., 0., 0.});
-
-    // Set the KalmanFitter options
-    std::unique_ptr<const Acts::Logger> logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::VERBOSE);
-    Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder> kfOptions(
+    Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder, Acts::VoidReverseFilteringLogic> kfOptions(
         geoctx,
         magctx,
         calctx,
-        MeasurementCalibrator(measurements),
+        MeasurementCalibrator(measurements->at(i)),
         Acts::VoidOutlierFinder(),
-        Acts::LoggerWrapper{*logger},
+        Acts::VoidReverseFilteringLogic(),
+        Acts::LoggerWrapper{*m_logger},
         Acts::PropagatorPlainOptions(),
-        &(*pSurface),
-        true, // scattering
-        true, // energy loss
-        false  // backward filtering
+        &(*initialSurface)
     );
+    kfOptions.multipleScattering = false;
+    kfOptions.energyLoss = false;
 
     ATH_MSG_DEBUG("Invoke fitter");
-
-    // Acts::Navigator     navigator(trackingGeometry);
-    Acts::DirectNavigator     navigator;
-    // navigator.resolvePassive   = false;
-    // navigator.resolveMaterial  = true;
-    // navigator.resolveSensitive = true;
-
-    std::unique_ptr<ActsExtrapolationDetail::VariantPropagator> varProp;
-
-    if (m_fieldMode == "FASER") {
-      ATH_MSG_INFO("Using FASER magnetic field service");
-      ATH_CHECK( m_fieldCondObjInputKey.initialize() );
-      auto bField = std::make_shared<FASERMagneticFieldWrapper>();
-      auto stepper = Acts::EigenStepper<>(std::move(bField));
-      auto propagator = Acts::Propagator<decltype(stepper), Acts::DirectNavigator>(std::move(stepper),
-                                                                                   std::move(navigator));
-      varProp = std::make_unique<VariantPropagator>(propagator);
-    }
-    else if (m_fieldMode == "Constant") {
-      if (m_constantFieldVector.value().size() != 3)
-      {
-        ATH_MSG_ERROR("Incorrect field vector size. Using empty field.");
-        return StatusCode::FAILURE; 
-      }
-
-      Acts::Vector3 constantFieldVector = Acts::Vector3(m_constantFieldVector[0], 
-                                                        m_constantFieldVector[1], 
-                                                        m_constantFieldVector[2]);
-
-      ATH_MSG_INFO("Using constant magnetic field: (Bx, By, Bz) = (" << m_constantFieldVector[0] << ", " 
-                                                                     << m_constantFieldVector[1] << ", " 
-                                                                     << m_constantFieldVector[2] << ")");
-      auto bField = std::make_shared<Acts::ConstantBField>(constantFieldVector);
-      auto stepper = Acts::EigenStepper<>(std::move(bField));
-      auto propagator = Acts::Propagator<decltype(stepper), Acts::DirectNavigator>(std::move(stepper),
-                                                                                   std::move(navigator));
-      varProp = std::make_unique<VariantPropagator>(propagator);
-    }
-
-    auto fit = makeFitterFunction(varProp.get());
-    auto result = fit(sourceLinks, InitTrackParam, kfOptions, surfSequence);
-
-    ATH_MSG_VERBOSE("Size of sourceLinks: " << sourceLinks.size());
+    auto result = (*m_fit)(sourceLinks->at(i), initialTrackParameters->at(i), kfOptions);
 
     int itrack = 0;
     if (result.ok()) {
       // Get the fit output object
       const auto& fitOutput = result.value();
+      std::unique_ptr<Trk::Track> track = makeTrack(geoctx, result, clusters->at(i));
+       if (track) {
+         outputTracks->push_back(std::move(track));
+       }
 
       // The track entry indices container. One element here.
       std::vector<size_t> trackTips;
@@ -568,752 +186,142 @@ StatusCode FaserActsKalmanFilterAlg::execute()
       trajectories.emplace_back(std::move(fitOutput.fittedStates),
                                 std::move(trackTips), std::move(indexedParams));
     } else {
-      ATH_MSG_WARNING("Fit failed for track " << itrack << " with error"
-                                              << result.error());
+      ATH_MSG_WARNING("Fit failed for track " << itrack << " with error" << result.error());
       // Fit failed, but still create a empty truth fit track
       trajectories.push_back(FaserActsRecMultiTrajectory());
     }
 
+  }
 
-    fillFitResult(geoctx, trajectories, *truthParam);
+//  std::vector<Acts::CurvilinearTrackParameters> initialTrackParametersVector {*initialTrackParameters};
+//  m_trajectoryWriterTool->writeout(trajectories, geoctx, initialTrackParametersVector);
+//  ATH_CHECK(m_trajectoryStatesWriterTool->write(trajectories, geoctx, *idLinks));
 
-  }
-  return StatusCode::SUCCESS;
-}
+  ATH_CHECK(trackContainer.record(std::move(outputTracks)));
 
-StatusCode FaserActsKalmanFilterAlg::finalize()
-{
   return StatusCode::SUCCESS;
 }
 
-namespace {
-
-  template <typename Fitter>
-  struct FitterFunctionImpl
-  {
-    Fitter fitter;
-
-    FitterFunctionImpl(Fitter&& f) : fitter(std::move(f)) {}
-
-    FaserActsKalmanFilterAlg::FitterResult
-    operator()(
-        const std::vector<IndexSourceLink>&    sourceLinks,
-        const Acts::CurvilinearTrackParameters&   initialParameters,
-        const Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder>& options,
-        const std::vector<const Acts::Surface*>& sSequence) const
-    {
-      return fitter.fit(sourceLinks, initialParameters, options, sSequence);
-    };
-  };
+StatusCode FaserActsKalmanFilterAlg::finalize() {
+  return StatusCode::SUCCESS;
 }
 
-FaserActsKalmanFilterAlg::FitterFunction
-FaserActsKalmanFilterAlg::makeFitterFunction(
-    ActsExtrapolationDetail::VariantPropagator* varProp)
-{
-
-  return boost::apply_visitor([&](const auto& propagator) -> FitterFunction {
-    using Updater  = Acts::GainMatrixUpdater;
-    using Smoother = Acts::GainMatrixSmoother;
-    using Fitter = Acts::KalmanFitter<typename std::decay_t<decltype(propagator)>, Updater, Smoother>;
-
-    Fitter     fitter(std::move(propagator));
-    // build the fitter functions. owns the fitter object.
-    return FitterFunctionImpl<Fitter>(std::move(fitter));
-  }, *varProp);
 
-}
-
-//Acts::MagneticFieldContext FaserActsKalmanFilterAlg::getMagneticFieldContext(const EventContext& ctx) const {
-//SG::ReadCondHandle<FaserFieldCacheCondObj> readHandle{m_fieldCondObjInputKey, ctx};
-Acts::MagneticFieldContext FaserActsKalmanFilterAlg::getMagneticFieldContext() const {
-  SG::ReadCondHandle<FaserFieldCacheCondObj> readHandle{m_fieldCondObjInputKey};
+Acts::MagneticFieldContext FaserActsKalmanFilterAlg::getMagneticFieldContext(const EventContext& ctx) const {
+  SG::ReadCondHandle<FaserFieldCacheCondObj> readHandle{m_fieldCondObjInputKey, ctx};
   if (!readHandle.isValid()) {
     std::stringstream msg;
     msg << "Failed to retrieve magnetic field condition data " << m_fieldCondObjInputKey.key() << ".";
     throw std::runtime_error(msg.str());
   }
   const FaserFieldCacheCondObj* fieldCondObj{*readHandle};
-
   return Acts::MagneticFieldContext(fieldCondObj);
 }
 
-void FaserActsKalmanFilterAlg::initializeTree()
-{
-  m_trackTree->Branch("event_nr", &m_eventNr);
-  m_trackTree->Branch("traj_nr", &m_trajNr);
-  m_trackTree->Branch("track_nr", &m_trackNr);
-  m_trackTree->Branch("t_barcode", &m_t_barcode, "t_barcode/l");
-  m_trackTree->Branch("t_charge", &m_t_charge);
-  m_trackTree->Branch("t_eT", &m_t_eT);
-  m_trackTree->Branch("t_eLOC0", &m_t_eLOC0);
-  m_trackTree->Branch("t_eLOC1", &m_t_eLOC1);
-  m_trackTree->Branch("t_x", &m_t_x);
-  m_trackTree->Branch("t_y", &m_t_y);
-  m_trackTree->Branch("t_z", &m_t_z);
-  m_trackTree->Branch("t_px", &m_t_px);
-  m_trackTree->Branch("t_py", &m_t_py);
-  m_trackTree->Branch("t_pz", &m_t_pz);
-  m_trackTree->Branch("t_eTHETA", &m_t_eTHETA);
-  m_trackTree->Branch("t_ePHI", &m_t_ePHI);
-  m_trackTree->Branch("t_eQOP", &m_t_eQOP);
-
-  m_trackTree->Branch("hasFittedParams", &m_hasFittedParams);
-  m_trackTree->Branch("chi2_fit", &m_chi2_fit);
-  m_trackTree->Branch("ndf_fit", &m_ndf_fit);
-  m_trackTree->Branch("eLOC0_fit", &m_eLOC0_fit);
-  m_trackTree->Branch("eLOC1_fit", &m_eLOC1_fit);
-  m_trackTree->Branch("ePHI_fit", &m_ePHI_fit);
-  m_trackTree->Branch("eTHETA_fit", &m_eTHETA_fit);
-  m_trackTree->Branch("eQOP_fit", &m_eQOP_fit);
-  m_trackTree->Branch("eT_fit", &m_eT_fit);
-  m_trackTree->Branch("charge_fit", &m_charge_fit);
-  m_trackTree->Branch("err_eLOC0_fit", &m_err_eLOC0_fit);
-  m_trackTree->Branch("err_eLOC1_fit", &m_err_eLOC1_fit);
-  m_trackTree->Branch("err_ePHI_fit", &m_err_ePHI_fit);
-  m_trackTree->Branch("err_eTHETA_fit", &m_err_eTHETA_fit);
-  m_trackTree->Branch("err_eQOP_fit", &m_err_eQOP_fit);
-  m_trackTree->Branch("err_eT_fit", &m_err_eT_fit);
-  m_trackTree->Branch("g_px_fit", &m_px_fit);
-  m_trackTree->Branch("g_py_fit", &m_py_fit);
-  m_trackTree->Branch("g_pz_fit", &m_pz_fit);
-  m_trackTree->Branch("g_x_fit" , &m_x_fit);
-  m_trackTree->Branch("g_y_fit" , &m_y_fit);
-  m_trackTree->Branch("g_z_fit" , &m_z_fit);
-
-  m_trackTree->Branch("nHoles", &m_nHoles);
-  m_trackTree->Branch("nOutliers", &m_nOutliers);
-  m_trackTree->Branch("nStates", &m_nStates);
-  m_trackTree->Branch("nMeasurements", &m_nMeasurements);
-  m_trackTree->Branch("volume_id", &m_volumeID);
-  m_trackTree->Branch("layer_id", &m_layerID);
-  m_trackTree->Branch("module_id", &m_moduleID);
-  m_trackTree->Branch("l_x_hit", &m_lx_hit);
-  m_trackTree->Branch("l_y_hit", &m_ly_hit);
-  m_trackTree->Branch("g_x_hit", &m_x_hit);
-  m_trackTree->Branch("g_y_hit", &m_y_hit);
-  m_trackTree->Branch("g_z_hit", &m_z_hit);
-  m_trackTree->Branch("res_x_hit", &m_res_x_hit);
-  m_trackTree->Branch("res_y_hit", &m_res_y_hit);
-  m_trackTree->Branch("err_x_hit", &m_err_x_hit);
-  m_trackTree->Branch("err_y_hit", &m_err_y_hit);
-  m_trackTree->Branch("pull_x_hit", &m_pull_x_hit);
-  m_trackTree->Branch("pull_y_hit", &m_pull_y_hit);
-  m_trackTree->Branch("dim_hit", &m_dim_hit);
 
-  m_trackTree->Branch("nPredicted", &m_nPredicted);
-  m_trackTree->Branch("predicted", &m_prt);
-  m_trackTree->Branch("eLOC0_prt", &m_eLOC0_prt);
-  m_trackTree->Branch("eLOC1_prt", &m_eLOC1_prt);
-  m_trackTree->Branch("ePHI_prt", &m_ePHI_prt);
-  m_trackTree->Branch("eTHETA_prt", &m_eTHETA_prt);
-  m_trackTree->Branch("eQOP_prt", &m_eQOP_prt);
-  m_trackTree->Branch("eT_prt", &m_eT_prt);
-  m_trackTree->Branch("res_eLOC0_prt", &m_res_eLOC0_prt);
-  m_trackTree->Branch("res_eLOC1_prt", &m_res_eLOC1_prt);
-  m_trackTree->Branch("err_eLOC0_prt", &m_err_eLOC0_prt);
-  m_trackTree->Branch("err_eLOC1_prt", &m_err_eLOC1_prt);
-  m_trackTree->Branch("err_ePHI_prt", &m_err_ePHI_prt);
-  m_trackTree->Branch("err_eTHETA_prt", &m_err_eTHETA_prt);
-  m_trackTree->Branch("err_eQOP_prt", &m_err_eQOP_prt);
-  m_trackTree->Branch("err_eT_prt", &m_err_eT_prt);
-  m_trackTree->Branch("pull_eLOC0_prt", &m_pull_eLOC0_prt);
-  m_trackTree->Branch("pull_eLOC1_prt", &m_pull_eLOC1_prt);
-  m_trackTree->Branch("g_x_prt", &m_x_prt);
-  m_trackTree->Branch("g_y_prt", &m_y_prt);
-  m_trackTree->Branch("g_z_prt", &m_z_prt);
-  m_trackTree->Branch("px_prt", &m_px_prt);
-  m_trackTree->Branch("py_prt", &m_py_prt);
-  m_trackTree->Branch("pz_prt", &m_pz_prt);
-  m_trackTree->Branch("eta_prt", &m_eta_prt);
-  m_trackTree->Branch("pT_prt", &m_pT_prt);
-
-  m_trackTree->Branch("nFiltered", &m_nFiltered);
-  m_trackTree->Branch("filtered", &m_flt);
-  m_trackTree->Branch("eLOC0_flt", &m_eLOC0_flt);
-  m_trackTree->Branch("eLOC1_flt", &m_eLOC1_flt);
-  m_trackTree->Branch("ePHI_flt", &m_ePHI_flt);
-  m_trackTree->Branch("eTHETA_flt", &m_eTHETA_flt);
-  m_trackTree->Branch("eQOP_flt", &m_eQOP_flt);
-  m_trackTree->Branch("eT_flt", &m_eT_flt);
-  m_trackTree->Branch("res_eLOC0_flt", &m_res_eLOC0_flt);
-  m_trackTree->Branch("res_eLOC1_flt", &m_res_eLOC1_flt);
-  m_trackTree->Branch("err_eLOC0_flt", &m_err_eLOC0_flt);
-  m_trackTree->Branch("err_eLOC1_flt", &m_err_eLOC1_flt);
-  m_trackTree->Branch("err_ePHI_flt", &m_err_ePHI_flt);
-  m_trackTree->Branch("err_eTHETA_flt", &m_err_eTHETA_flt);
-  m_trackTree->Branch("err_eQOP_flt", &m_err_eQOP_flt);
-  m_trackTree->Branch("err_eT_flt", &m_err_eT_flt);
-  m_trackTree->Branch("pull_eLOC0_flt", &m_pull_eLOC0_flt);
-  m_trackTree->Branch("pull_eLOC1_flt", &m_pull_eLOC1_flt);
-  m_trackTree->Branch("g_x_flt", &m_x_flt);
-  m_trackTree->Branch("g_y_flt", &m_y_flt);
-  m_trackTree->Branch("g_z_flt", &m_z_flt);
-  m_trackTree->Branch("px_flt", &m_px_flt);
-  m_trackTree->Branch("py_flt", &m_py_flt);
-  m_trackTree->Branch("pz_flt", &m_pz_flt);
-  m_trackTree->Branch("eta_flt", &m_eta_flt);
-  m_trackTree->Branch("pT_flt", &m_pT_flt);
-  m_trackTree->Branch("chi2", &m_chi2);
-
-  m_trackTree->Branch("nSmoothed", &m_nSmoothed);
-  m_trackTree->Branch("smoothed", &m_smt);
-  m_trackTree->Branch("eLOC0_smt", &m_eLOC0_smt);
-  m_trackTree->Branch("eLOC1_smt", &m_eLOC1_smt);
-  m_trackTree->Branch("ePHI_smt", &m_ePHI_smt);
-  m_trackTree->Branch("eTHETA_smt", &m_eTHETA_smt);
-  m_trackTree->Branch("eQOP_smt", &m_eQOP_smt);
-  m_trackTree->Branch("eT_smt", &m_eT_smt);
-  m_trackTree->Branch("res_eLOC0_smt", &m_res_eLOC0_smt);
-  m_trackTree->Branch("res_eLOC1_smt", &m_res_eLOC1_smt);
-  m_trackTree->Branch("err_eLOC0_smt", &m_err_eLOC0_smt);
-  m_trackTree->Branch("err_eLOC1_smt", &m_err_eLOC1_smt);
-  m_trackTree->Branch("err_ePHI_smt", &m_err_ePHI_smt);
-  m_trackTree->Branch("err_eTHETA_smt", &m_err_eTHETA_smt);
-  m_trackTree->Branch("err_eQOP_smt", &m_err_eQOP_smt);
-  m_trackTree->Branch("err_eT_smt", &m_err_eT_smt);
-  m_trackTree->Branch("pull_eLOC0_smt", &m_pull_eLOC0_smt);
-  m_trackTree->Branch("pull_eLOC1_smt", &m_pull_eLOC1_smt);
-  m_trackTree->Branch("g_x_smt", &m_x_smt);
-  m_trackTree->Branch("g_y_smt", &m_y_smt);
-  m_trackTree->Branch("g_z_smt", &m_z_smt);
-  m_trackTree->Branch("px_smt", &m_px_smt);
-  m_trackTree->Branch("py_smt", &m_py_smt);
-  m_trackTree->Branch("pz_smt", &m_pz_smt);
-  m_trackTree->Branch("eta_smt", &m_eta_smt);
-  m_trackTree->Branch("pT_smt", &m_pT_smt);
+std::unique_ptr<Trk::Track>
+FaserActsKalmanFilterAlg::makeTrack(Acts::GeometryContext& geoCtx, TrackFitterResult& fitResult, std::vector<const Tracker::FaserSCT_Cluster*> clusters) const {
+  using ConstTrackStateProxy =
+     Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+  std::unique_ptr<Trk::Track> newtrack = nullptr;
+  //Get the fit output object
+  const auto& fitOutput = fitResult.value();
+  if (fitOutput.fittedParameters) {
+    DataVector<const Trk::TrackStateOnSurface>* finalTrajectory = new DataVector<const Trk::TrackStateOnSurface>{};
+    std::vector<std::unique_ptr<const Acts::BoundTrackParameters>> actsSmoothedParam;
+    // Loop over all the output state to create track state
+    fitOutput.fittedStates.visitBackwards(fitOutput.lastMeasurementIndex, [&](const ConstTrackStateProxy& state) {
+      auto flag = state.typeFlags();
+      if (state.referenceSurface().associatedDetectorElement() != nullptr) {
+        // We need to determine the type of state
+        std::bitset<Trk::TrackStateOnSurface::NumberOfTrackStateOnSurfaceTypes> typePattern;
+        const Trk::TrackParameters *parm;
+
+        // State is a hole (no associated measurement), use predicted para meters
+        if (flag[Acts::TrackStateFlag::HoleFlag] == true) {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.predicted(),
+                                                     state.predictedCovariance());
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          // auto boundaryCheck = m_boundaryCheckTool->boundaryCheck(*p arm);
+          typePattern.set(Trk::TrackStateOnSurface::Hole);
+        }
+          // The state was tagged as an outlier, use filtered parameters
+        else if (flag[Acts::TrackStateFlag::OutlierFlag] == true) {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.filtered(), state.filteredCovariance());
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          typePattern.set(Trk::TrackStateOnSurface::Outlier);
+        }
+          // The state is a measurement state, use smoothed parameters
+        else {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.smoothed(), state.smoothedCovariance());
+          actsSmoothedParam.push_back(std::make_unique<const Acts::BoundTrackParameters>(Acts::BoundTrackParameters(actsParam)));
+          //  const auto& psurface=actsParam.referenceSurface();
+          Acts::Vector2 local(actsParam.parameters()[Acts::eBoundLoc0], actsParam.parameters()[Acts::eBoundLoc1]);
+          //  const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(actsParam.parameters()[Acts::eBoundPhi], actsParam.parameters()[Acts::eBoundTheta]);
+          //  auto pos=actsParam.position(tgContext);
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          typePattern.set(Trk::TrackStateOnSurface::Measurement);
+        }
+        Tracker::FaserSCT_ClusterOnTrack* measState = nullptr;
+        if (state.hasUncalibrated()) {
+          const Tracker::FaserSCT_Cluster* fitCluster = clusters.at(state.uncalibrated().index());
+          if (fitCluster->detectorElement() != nullptr) {
+            measState = new Tracker::FaserSCT_ClusterOnTrack{
+                fitCluster,
+                Trk::LocalParameters{
+                    Trk::DefinedParameter{fitCluster->localPosition()[0], Trk::loc1},
+                    Trk::DefinedParameter{fitCluster->localPosition()[1], Trk::loc2}
+                },
+                fitCluster->localCovariance(),
+                m_idHelper->wafer_hash(fitCluster->detectorElement()->identify())
+            };
+          }
+        }
+        double nDoF = state.calibratedSize();
+        const Trk::FitQualityOnSurface *quality = new Trk::FitQualityOnSurface(state.chi2(), nDoF);
+        const Trk::TrackStateOnSurface *perState = new Trk::TrackStateOnSurface(measState, parm, quality, nullptr, typePattern);
+        // If a state was succesfully created add it to the trajectory
+        if (perState) {
+          finalTrajectory->insert(finalTrajectory->begin(), perState);
+        }
+      }
+      return;
+    });
+
+    // Create the track using the states
+    const Trk::TrackInfo newInfo(Trk::TrackInfo::TrackFitter::KalmanFitter, Trk::ParticleHypothesis::muon);
+    // Trk::FitQuality* q = nullptr;
+    // newInfo.setTrackFitter(Trk::TrackInfo::TrackFitter::KalmanFitter     ); //Mark the fitter as KalmanFitter
+    newtrack = std::make_unique<Trk::Track>(newInfo, std::move(*finalTrajectory), nullptr);
+  }
+  return newtrack;
 }
 
-void FaserActsKalmanFilterAlg::fillFitResult(
-    const Acts::GeometryContext& geoctx,
-    const TrajectoryContainer& trajectories,
-    const Acts::BoundTrackParameters& truthParam
-)
-{
-  m_t_eLOC0 = truthParam.parameters()[Acts::eBoundLoc0];
-  m_t_eLOC1 = truthParam.parameters()[Acts::eBoundLoc1];
-  m_t_ePHI = truthParam.parameters()[Acts::eBoundPhi];
-  m_t_eTHETA = truthParam.parameters()[Acts::eBoundTheta];
-  m_t_eQOP = truthParam.parameters()[Acts::eBoundQOverP];
-  m_t_eT = truthParam.parameters()[Acts::eBoundTime];
-  m_t_x = truthParam.position(geoctx)(0);
-  m_t_y = truthParam.position(geoctx)(1);
-  m_t_z = truthParam.position(geoctx)(2);
-  m_t_px = truthParam.momentum()(0);
-  m_t_py = truthParam.momentum()(1);
-  m_t_pz = truthParam.momentum()(2);
-  std::cout<<"truth global position on the first layer = "<<m_t_x<<"  "<<m_t_y<<"  "<<m_t_z<<"  "<<std::endl;
-  std::cout<<"truth momentum on the first layer = "<<m_t_px<<"  "<<m_t_py<<"  "<<m_t_pz<<"  "<<std::endl;
-  std::cout<<"truth local parameters on the first layer = "<<m_t_eLOC0<<"  "<<m_t_eLOC1<<"  "<<m_t_ePHI<<"  "<<m_t_eTHETA<<"  "<<m_t_eQOP<<"  "<<std::endl;
-
-  // Loop over the trajectories
-  int iTraj = 0;
-  for (const auto& traj : trajectories) {
-    m_trajNr = iTraj;
-
-    // The trajectory entry indices and the multiTrajectory
-    const auto& [trackTips, mj] = traj.trajectory();
-    if (trackTips.empty()) {
-      ATH_MSG_WARNING("Empty multiTrajectory.");
-      continue;
+const Trk::TrackParameters*
+FaserActsKalmanFilterAlg ::ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const      {
+  using namespace Acts::UnitLiterals;
+  std::optional<AmgSymMatrix(5)> cov = std::nullopt;
+  if (actsParameter.covariance()){
+    AmgSymMatrix(5) newcov(actsParameter.covariance()->topLeftCorner(5, 5));
+    // Convert the covariance matrix to GeV
+    for(int i=0; i < newcov.rows(); i++){
+      newcov(i, 4) = newcov(i, 4)*1_MeV;
     }
-
-    // Get the entry index for the single trajectory
-    auto& trackTip = trackTips.front();
-    std::cout<<"trackTip = "<<trackTip<<std::endl;
-
-    // Collect the trajectory summary info
-    auto trajState =
-        Acts::MultiTrajectoryHelpers::trajectoryState(mj, trackTip);
-
-    m_nMeasurements = trajState.nMeasurements;
-    m_nStates = trajState.nStates;
-    m_nOutliers = trajState.nOutliers;
-    m_nHoles = trajState.nHoles;
-    m_chi2_fit = trajState.chi2Sum;
-    m_ndf_fit = trajState.NDF;
-    std::cout << "Track has " << trajState.nMeasurements
-              << " measurements and " << trajState.nHoles
-              << " holes and " << trajState.nOutliers
-              << " outliers and " << trajState.nStates
-              << " states " << std::endl;
-
-    /// If it has track parameters, fill the values
-    if (traj.hasTrackParameters(trackTip))
-    {
-      m_hasFittedParams = true;
-      const auto &boundParam = traj.trackParameters(trackTip);
-      const auto &parameter = boundParam.parameters();
-      const auto &covariance = *boundParam.covariance();
-      m_charge_fit = boundParam.charge();
-      m_eLOC0_fit = parameter[Acts::eBoundLoc0];
-      m_eLOC1_fit = parameter[Acts::eBoundLoc1];
-      m_ePHI_fit = parameter[Acts::eBoundPhi];
-      m_eTHETA_fit = parameter[Acts::eBoundTheta];
-      m_eQOP_fit = parameter[Acts::eBoundQOverP];
-      m_eT_fit = parameter[Acts::eBoundTime];
-      m_err_eLOC0_fit =
-          sqrt(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0));
-      m_err_eLOC1_fit =
-          sqrt(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1));
-      m_err_ePHI_fit = sqrt(covariance(Acts::eBoundPhi, Acts::eBoundPhi));
-      m_err_eTHETA_fit =
-          sqrt(covariance(Acts::eBoundTheta, Acts::eBoundTheta));
-      m_err_eQOP_fit = sqrt(covariance(Acts::eBoundQOverP, Acts::eBoundQOverP));
-      m_err_eT_fit = sqrt(covariance(Acts::eBoundTime, Acts::eBoundTime));
-
-      m_px_fit = boundParam.momentum()(0);
-      m_py_fit = boundParam.momentum()(1);
-      m_pz_fit = boundParam.momentum()(2);
-      m_x_fit  = boundParam.position(geoctx)(0);
-      m_y_fit  = boundParam.position(geoctx)(1);
-      m_z_fit  = boundParam.position(geoctx)(2);
+    for(int i=0; i < newcov.cols(); i++){
+      newcov(4, i) = newcov(4, i)*1_MeV;
     }
-
-    m_nPredicted = 0;
-    m_nFiltered = 0;
-    m_nSmoothed = 0;
-
-    mj.visitBackwards(trackTip, [&](const auto &state) {
-      /// Only fill the track states with non-outlier measurement
-      auto typeFlags = state.typeFlags();
-      if (not typeFlags.test(Acts::TrackStateFlag::MeasurementFlag))
-      {
-        return true;
-      }
-
-      const auto& surface = state.referenceSurface();
-
-      /// Get the geometry ID
-      auto geoID = state.referenceSurface().geometryId();
-      m_volumeID.push_back(geoID.volume());
-      m_layerID.push_back(geoID.layer());
-      m_moduleID.push_back(geoID.sensitive());
-
-      // expand the local measurements into the full bound space
-      Acts::BoundVector meas =
-          state.projector().transpose() * state.calibrated();
-
-      // extract local and global position
-      Acts::Vector2 local(meas[Acts::eBoundLoc0], meas[Acts::eBoundLoc1]);
-      Acts::Vector3 mom(1, 1, 1);
-      Acts::Vector3 global =
-          surface.localToGlobal(geoctx, local, mom);
-
-      // fill the measurement info
-      m_lx_hit.push_back(local[Acts::ePos0]);
-      m_ly_hit.push_back(local[Acts::ePos1]);
-      m_x_hit.push_back(global[Acts::ePos0]);
-      m_y_hit.push_back(global[Acts::ePos1]);
-      m_z_hit.push_back(global[Acts::ePos2]);
-
-      /// Get the predicted parameter for this state
-      bool predicted = false;
-      if (state.hasPredicted())
-      {
-        predicted = true;
-        m_nPredicted++;
-        Acts::BoundTrackParameters parameter(
-            state.referenceSurface().getSharedPtr(),
-            state.predicted(),
-            state.predictedCovariance());
-        auto covariance = state.predictedCovariance();
-
-        /// Local hit residual info
-        auto H = state.effectiveProjector();
-        auto resCov = state.effectiveCalibratedCovariance() +
-                      H * covariance * H.transpose();
-        auto residual = state.effectiveCalibrated() - H * state.predicted();
-
-        /// Predicted residual
-        m_res_eLOC0_prt.push_back(residual(Acts::eBoundLoc0));
-        m_res_eLOC1_prt.push_back(residual(Acts::eBoundLoc1));
-
-        /// Predicted parameter pulls
-        m_pull_eLOC0_prt.push_back(
-            residual(Acts::eBoundLoc0) /
-            sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_eLOC1_prt.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-
-
-        /// Predicted parameter
-        m_eLOC0_prt.push_back(parameter.parameters()[Acts::eBoundLoc0]);
-        m_eLOC1_prt.push_back(parameter.parameters()[Acts::eBoundLoc1]);
-        m_ePHI_prt.push_back(parameter.parameters()[Acts::eBoundPhi]);
-        m_eTHETA_prt.push_back(parameter.parameters()[Acts::eBoundTheta]);
-        m_eQOP_prt.push_back(parameter.parameters()[Acts::eBoundQOverP]);
-        m_eT_prt.push_back(parameter.parameters()[Acts::eBoundTime]);
-
-        /// Predicted parameter Uncertainties
-        m_err_eLOC0_prt.push_back(
-            sqrt(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_err_eLOC1_prt.push_back(
-            sqrt(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-        m_err_ePHI_prt.push_back(
-            sqrt(covariance(Acts::eBoundPhi, Acts::eBoundPhi)));
-        m_err_eTHETA_prt.push_back(
-            sqrt(covariance(Acts::eBoundTheta, Acts::eBoundTheta)));
-        m_err_eQOP_prt.push_back(
-            sqrt(covariance(Acts::eBoundQOverP, Acts::eBoundQOverP)));
-        m_err_eT_prt.push_back(
-            sqrt(covariance(Acts::eBoundTime, Acts::eBoundTime)));
-
-        m_x_prt.push_back(parameter.position(geoctx).x());
-        m_y_prt.push_back(parameter.position(geoctx).y());
-        m_z_prt.push_back(parameter.position(geoctx).z());
-        m_px_prt.push_back(parameter.momentum().x());
-        m_py_prt.push_back(parameter.momentum().y());
-        m_pz_prt.push_back(parameter.momentum().z());
-        m_pT_prt.push_back(parameter.transverseMomentum());
-        m_eta_prt.push_back(eta(parameter.position(geoctx)));
-      }
-      else
-      {
-        /// Push bad values if no predicted parameter
-        m_eLOC0_prt.push_back(-9999);
-        m_eLOC1_prt.push_back(-9999);
-        m_ePHI_prt.push_back(-9999);
-        m_eTHETA_prt.push_back(-9999);
-        m_eQOP_prt.push_back(-9999);
-        m_eT_prt.push_back(-9999);
-        m_res_eLOC0_prt.push_back(-9999);
-        m_res_eLOC1_prt.push_back(-9999);
-        m_err_eLOC0_prt.push_back(-9999);
-        m_err_eLOC1_prt.push_back(-9999);
-        m_err_ePHI_prt.push_back(-9999);
-        m_err_eTHETA_prt.push_back(-9999);
-        m_err_eQOP_prt.push_back(-9999);
-        m_err_eT_prt.push_back(-9999);
-        m_pull_eLOC0_prt.push_back(-9999);
-        m_pull_eLOC1_prt.push_back(-9999);
-        m_x_prt.push_back(-9999);
-        m_y_prt.push_back(-9999);
-        m_z_prt.push_back(-9999);
-        m_px_prt.push_back(-9999);
-        m_py_prt.push_back(-9999);
-        m_pz_prt.push_back(-9999);
-        m_pT_prt.push_back(-9999);
-        m_eta_prt.push_back(-9999);
-      }
-
-      bool filtered = false;
-      if (state.hasFiltered())
-      {
-        filtered = true;
-        m_nFiltered++;
-        Acts::BoundTrackParameters parameter(
-            state.referenceSurface().getSharedPtr(),
-            state.filtered(),
-            state.filteredCovariance());
-        auto covariance = state.filteredCovariance();
-
-        /// Local hit residual info
-        auto H = state.effectiveProjector();
-        auto resCov = state.effectiveCalibratedCovariance() +
-                      H * covariance * H.transpose();
-        auto residual = state.effectiveCalibrated() - H * state.filtered();
-
-        /// Filtered residual
-        m_res_eLOC0_flt.push_back(residual(Acts::eBoundLoc0));
-        m_res_eLOC1_flt.push_back(residual(Acts::eBoundLoc1));
-
-        /// Filtered parameter pulls
-        m_pull_eLOC0_flt.push_back(
-            residual(Acts::eBoundLoc0) /
-            sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_eLOC1_flt.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-
-        /// Filtered parameter
-        m_eLOC0_flt.push_back(parameter.parameters()[Acts::eBoundLoc0]);
-        m_eLOC1_flt.push_back(parameter.parameters()[Acts::eBoundLoc1]);
-        m_ePHI_flt.push_back(parameter.parameters()[Acts::eBoundPhi]);
-        m_eTHETA_flt.push_back(parameter.parameters()[Acts::eBoundTheta]);
-        m_eQOP_flt.push_back(parameter.parameters()[Acts::eBoundQOverP]);
-        m_eT_flt.push_back(parameter.parameters()[Acts::eBoundTime]);
-
-        /// Filtered parameter uncertainties
-        m_err_eLOC0_flt.push_back(
-            sqrt(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_err_eLOC1_flt.push_back(
-            sqrt(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-        m_err_ePHI_flt.push_back(
-            sqrt(covariance(Acts::eBoundPhi, Acts::eBoundPhi)));
-        m_err_eTHETA_flt.push_back(
-            sqrt(covariance(Acts::eBoundTheta, Acts::eBoundTheta)));
-        m_err_eQOP_flt.push_back(
-            sqrt(covariance(Acts::eBoundQOverP, Acts::eBoundQOverP)));
-        m_err_eT_flt.push_back(
-            sqrt(covariance(Acts::eBoundTime, Acts::eBoundTime)));
-
-        /// Other filtered parameter info
-        m_x_flt.push_back(parameter.position(geoctx).x());
-        m_y_flt.push_back(parameter.position(geoctx).y());
-        m_z_flt.push_back(parameter.position(geoctx).z());
-        m_px_flt.push_back(parameter.momentum().x());
-        m_py_flt.push_back(parameter.momentum().y());
-        m_pz_flt.push_back(parameter.momentum().z());
-        m_pT_flt.push_back(parameter.transverseMomentum());
-        m_eta_flt.push_back(eta(parameter.position(geoctx)));
-        m_chi2.push_back(state.chi2());
-
-      }
-      else
-      {
-        /// Push bad values if no filtered parameter
-        m_eLOC0_flt.push_back(-9999);
-        m_eLOC1_flt.push_back(-9999);
-        m_ePHI_flt.push_back(-9999);
-        m_eTHETA_flt.push_back(-9999);
-        m_eQOP_flt.push_back(-9999);
-        m_eT_flt.push_back(-9999);
-        m_res_eLOC0_flt.push_back(-9999);
-        m_res_eLOC1_flt.push_back(-9999);
-        m_err_eLOC0_flt.push_back(-9999);
-        m_err_eLOC1_flt.push_back(-9999);
-        m_err_ePHI_flt.push_back(-9999);
-        m_err_eTHETA_flt.push_back(-9999);
-        m_err_eQOP_flt.push_back(-9999);
-        m_err_eT_flt.push_back(-9999);
-        m_pull_eLOC0_flt.push_back(-9999);
-        m_pull_eLOC1_flt.push_back(-9999);
-        m_x_flt.push_back(-9999);
-        m_y_flt.push_back(-9999);
-        m_z_flt.push_back(-9999);
-        m_py_flt.push_back(-9999);
-        m_pz_flt.push_back(-9999);
-        m_pT_flt.push_back(-9999);
-        m_eta_flt.push_back(-9999);
-        m_chi2.push_back(-9999);
-      }
-
-      bool smoothed = false;
-      if (state.hasSmoothed())
-      {
-        smoothed = true;
-        m_nSmoothed++;
-        Acts::BoundTrackParameters parameter(
-            state.referenceSurface().getSharedPtr(),
-            state.smoothed(),
-            state.smoothedCovariance());
-        auto covariance = state.smoothedCovariance();
-
-        /// Local hit residual info
-        auto H = state.effectiveProjector();
-        auto resCov = state.effectiveCalibratedCovariance() +
-                      H * covariance * H.transpose();
-        auto residual = state.effectiveCalibrated() - H * state.smoothed();
-
-        m_res_x_hit.push_back(residual(Acts::eBoundLoc0));
-        m_res_y_hit.push_back(residual(Acts::eBoundLoc1));
-        m_err_x_hit.push_back(
-            sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_err_y_hit.push_back(
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-        m_pull_x_hit.push_back(
-            residual(Acts::eBoundLoc0) /
-            sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_y_hit.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-        m_dim_hit.push_back(state.calibratedSize());
-
-        /// Smoothed residual
-        m_res_eLOC0_smt.push_back(residual(Acts::eBoundLoc0));
-        m_res_eLOC1_smt.push_back(residual(Acts::eBoundLoc1));
-
-        /// Smoothed parameter pulls
-        m_pull_eLOC0_smt.push_back(
-            residual(Acts::eBoundLoc0) /
-            sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_eLOC1_smt.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-
-        /// Smoothed parameter
-        m_eLOC0_smt.push_back(parameter.parameters()[Acts::eBoundLoc0]);
-        m_eLOC1_smt.push_back(parameter.parameters()[Acts::eBoundLoc1]);
-        m_ePHI_smt.push_back(parameter.parameters()[Acts::eBoundPhi]);
-        m_eTHETA_smt.push_back(parameter.parameters()[Acts::eBoundTheta]);
-        m_eQOP_smt.push_back(parameter.parameters()[Acts::eBoundQOverP]);
-        m_eT_smt.push_back(parameter.parameters()[Acts::eBoundTime]);
-
-        /// Smoothed parameter uncertainties
-        m_err_eLOC0_smt.push_back(
-            sqrt(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_err_eLOC1_smt.push_back(
-            sqrt(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)));
-        m_err_ePHI_smt.push_back(
-            sqrt(covariance(Acts::eBoundPhi, Acts::eBoundPhi)));
-        m_err_eTHETA_smt.push_back(
-            sqrt(covariance(Acts::eBoundTheta, Acts::eBoundTheta)));
-        m_err_eQOP_smt.push_back(
-            sqrt(covariance(Acts::eBoundQOverP, Acts::eBoundQOverP)));
-        m_err_eT_smt.push_back(
-            sqrt(covariance(Acts::eBoundTime, Acts::eBoundTime)));
-
-        m_x_smt.push_back(parameter.position(geoctx).x());
-        m_y_smt.push_back(parameter.position(geoctx).y());
-        m_z_smt.push_back(parameter.position(geoctx).z());
-        m_px_smt.push_back(parameter.momentum().x());
-        m_py_smt.push_back(parameter.momentum().y());
-        m_pz_smt.push_back(parameter.momentum().z());
-        m_pT_smt.push_back(parameter.transverseMomentum());
-        m_eta_smt.push_back(eta(parameter.position(geoctx)));
-      }
-      else
-      {
-        /// Push bad values if no smoothed parameter
-        m_eLOC0_smt.push_back(-9999);
-        m_eLOC1_smt.push_back(-9999);
-        m_ePHI_smt.push_back(-9999);
-        m_eTHETA_smt.push_back(-9999);
-        m_eQOP_smt.push_back(-9999);
-        m_eT_smt.push_back(-9999);
-        m_res_eLOC0_smt.push_back(-9999);
-        m_res_eLOC1_smt.push_back(-9999);
-        m_err_eLOC0_smt.push_back(-9999);
-        m_err_eLOC1_smt.push_back(-9999);
-        m_err_ePHI_smt.push_back(-9999);
-        m_err_eTHETA_smt.push_back(-9999);
-        m_err_eQOP_smt.push_back(-9999);
-        m_err_eT_smt.push_back(-9999);
-        m_pull_eLOC0_smt.push_back(-9999);
-        m_pull_eLOC1_smt.push_back(-9999);
-        m_x_smt.push_back(-9999);
-        m_y_smt.push_back(-9999);
-        m_z_smt.push_back(-9999);
-        m_px_smt.push_back(-9999);
-        m_py_smt.push_back(-9999);
-        m_pz_smt.push_back(-9999);
-        m_pT_smt.push_back(-9999);
-        m_eta_smt.push_back(-9999);
-        m_res_x_hit.push_back(-9999);
-        m_res_y_hit.push_back(-9999);
-        m_err_x_hit.push_back(-9999);
-        m_err_y_hit.push_back(-9999);
-        m_pull_x_hit.push_back(-9999);
-        m_pull_y_hit.push_back(-9999);
-        m_dim_hit.push_back(-9999);
-      }
-
-      /// Save whether or not states had various KF steps
-      m_prt.push_back(predicted);
-      m_flt.push_back(filtered);
-      m_smt.push_back(smoothed);
-
-      return true;
-    }   /// Finish lambda function
-    );  /// Finish multi trajectory visitBackwards call
-
-    iTraj++;
-
-  }  // all trajectories
-
-  m_trackTree->Fill();
-
-  clearTrackVariables();
+    cov =  std::optional<AmgSymMatrix(5)>(newcov);
+  }
+  const Amg::Vector3D& pos=actsParameter.position(gctx);
+  double tphi=actsParameter.get<Acts::eBoundPhi>();
+  double ttheta=actsParameter.get<Acts::eBoundTheta>();
+  double tqOverP=actsParameter.get<Acts::eBoundQOverP>()*1_MeV;
+  double p = std::abs(1. / tqOverP);
+  Amg::Vector3D tmom(p * std::cos(tphi) * std::sin(ttheta), p * std::sin(tphi) * std::sin(ttheta), p * std::cos(ttheta));
+  const Trk::CurvilinearParameters * curv = new Trk::CurvilinearParameters(pos,tmom,tqOverP>0, cov);
+  return curv;
 }
 
-void FaserActsKalmanFilterAlg::clearTrackVariables()
-{
-  m_volumeID.clear();
-  m_layerID.clear();
-  m_moduleID.clear();
-  m_lx_hit.clear();
-  m_ly_hit.clear();
-  m_x_hit.clear();
-  m_y_hit.clear();
-  m_z_hit.clear();
-  m_res_x_hit.clear();
-  m_res_y_hit.clear();
-  m_err_x_hit.clear();
-  m_err_y_hit.clear();
-  m_pull_x_hit.clear();
-  m_pull_y_hit.clear();
-  m_dim_hit.clear();
-
-  m_prt.clear();
-  m_eLOC0_prt.clear();
-  m_eLOC1_prt.clear();
-  m_ePHI_prt.clear();
-  m_eTHETA_prt.clear();
-  m_eQOP_prt.clear();
-  m_eT_prt.clear();
-  m_res_eLOC0_prt.clear();
-  m_res_eLOC1_prt.clear();
-  m_err_eLOC0_prt.clear();
-  m_err_eLOC1_prt.clear();
-  m_err_ePHI_prt.clear();
-  m_err_eTHETA_prt.clear();
-  m_err_eQOP_prt.clear();
-  m_err_eT_prt.clear();
-  m_pull_eLOC0_prt.clear();
-  m_pull_eLOC1_prt.clear();
-  m_x_prt.clear();
-  m_y_prt.clear();
-  m_z_prt.clear();
-  m_px_prt.clear();
-  m_py_prt.clear();
-  m_pz_prt.clear();
-  m_eta_prt.clear();
-  m_pT_prt.clear();
-
-  m_flt.clear();
-  m_eLOC0_flt.clear();
-  m_eLOC1_flt.clear();
-  m_ePHI_flt.clear();
-  m_eTHETA_flt.clear();
-  m_eQOP_flt.clear();
-  m_eT_flt.clear();
-  m_res_eLOC0_flt.clear();
-  m_res_eLOC1_flt.clear();
-  m_err_eLOC0_flt.clear();
-  m_err_eLOC1_flt.clear();
-  m_err_ePHI_flt.clear();
-  m_err_eTHETA_flt.clear();
-  m_err_eQOP_flt.clear();
-  m_err_eT_flt.clear();
-  m_pull_eLOC0_flt.clear();
-  m_pull_eLOC1_flt.clear();
-  m_x_flt.clear();
-  m_y_flt.clear();
-  m_z_flt.clear();
-  m_px_flt.clear();
-  m_py_flt.clear();
-  m_pz_flt.clear();
-  m_eta_flt.clear();
-  m_pT_flt.clear();
-  m_chi2.clear();
-
-  m_smt.clear();
-  m_eLOC0_smt.clear();
-  m_eLOC1_smt.clear();
-  m_ePHI_smt.clear();
-  m_eTHETA_smt.clear();
-  m_eQOP_smt.clear();
-  m_eT_smt.clear();
-  m_res_eLOC0_smt.clear();
-  m_res_eLOC1_smt.clear();
-  m_err_eLOC0_smt.clear();
-  m_err_eLOC1_smt.clear();
-  m_err_ePHI_smt.clear();
-  m_err_eTHETA_smt.clear();
-  m_err_eQOP_smt.clear();
-  m_err_eT_smt.clear();
-  m_pull_eLOC0_smt.clear();
-  m_pull_eLOC1_smt.clear();
-  m_x_smt.clear();
-  m_y_smt.clear();
-  m_z_smt.clear();
-  m_px_smt.clear();
-  m_py_smt.clear();
-  m_pz_smt.clear();
-  m_eta_smt.clear();
-  m_pT_smt.clear();
-
-  return;
-}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/GhostBusters.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/GhostBusters.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..6cfb07d143b8b49652ad54ee768a1af2b0671fae
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/GhostBusters.cxx
@@ -0,0 +1,114 @@
+#include "FaserActsKalmanFilter/GhostBusters.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "Identifier/Identifier.h"
+
+
+GhostBusters::GhostBusters(const std::string &name, ISvcLocator *pSvcLocator)
+    : AthReentrantAlgorithm(name, pSvcLocator), AthHistogramming(name),
+      m_histSvc("THistSvc/THistSvc", name) {}
+
+
+StatusCode GhostBusters::initialize() {
+  ATH_CHECK(m_trackCollection.initialize());
+  // ATH_CHECK(m_simDataCollectionKey.initialize());
+  ATH_CHECK(m_outputTrackCollection.initialize());
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+
+  m_tree = new TTree("tree", "tree");
+  m_tree->Branch("event_number", &m_event_number, "event_number/I");
+  m_tree->Branch("station", &m_station, "station/I");
+  m_tree->Branch("x", &m_x, "x/D");
+  m_tree->Branch("y", &m_y, "y/D");
+  m_tree->Branch("z", &m_z, "z/D");
+  m_tree->Branch("chi2", &m_chi2, "chi2/D");
+  // m_tree->Branch("majorityHits", &m_majorityHits, "majorityHits/I");
+  m_tree->Branch("size", &m_size, "size/I");
+  m_tree->Branch("isGhost", &m_isGhost, "isGhost/B");
+  ATH_CHECK(histSvc()->regTree("/HIST2/tree", m_tree));
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode GhostBusters::execute(const EventContext &ctx) const {
+  m_event_number = ctx.eventID().event_number();
+
+  SG::WriteHandle outputTrackCollection {m_outputTrackCollection, ctx};
+  std::unique_ptr<TrackCollection> outputTracks = std::make_unique<TrackCollection>();
+
+  // SG::ReadHandle<TrackerSimDataCollection> simData {m_simDataCollectionKey, ctx};
+  // ATH_CHECK(simData.isValid());
+
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection, ctx};
+  ATH_CHECK(trackCollection.isValid());
+
+  std::map<int, std::vector<Segment>> segments {};
+  for (const Trk::Track* track : *trackCollection) {
+    auto segment = Segment(track, m_idHelper);
+    segments[segment.station()].push_back(segment);
+  }
+
+  for (const auto &station : segments) {
+    double nGoodSegments = 0;
+    std::vector<Segment> stationSegments = station.second;
+    std::sort(stationSegments.begin(), stationSegments.end(), [](const Segment &lhs, const Segment &rhs) { return lhs.y() > rhs.y(); });
+    double maxX = stationSegments.front().x();
+    double maxY = stationSegments.front().y();
+    double minX = stationSegments.back().x();
+    double minY = stationSegments.back().y();
+    double deltaY = maxY - minY;
+    double meanX = 0.5 * (minX + maxX);
+    double meanY = 0.5 * (minY + maxY);
+    double deltaX = deltaY / (2 * std::tan(0.02));
+    for (Segment &segment : stationSegments) {
+      bool isGhost = (segment.y() > meanY - m_yTolerance * deltaY) && (segment.y() < meanY + m_yTolerance * deltaY) && (
+          ((segment.x() > meanX - (1 + m_xTolerance) * deltaX) &&
+           (segment.x() < meanX - (1 - m_xTolerance) * deltaX)) ||
+          ((segment.x() > meanX + (1 - m_xTolerance) * deltaX) && (segment.x() < meanX + (1 + m_xTolerance) * deltaX)));
+      if (isGhost) segment.setGhost();
+      if (not isGhost && segment.size() >= 5) nGoodSegments++;
+    }
+    for (const Segment &segment : stationSegments) {
+      m_x = segment.x();
+      m_y = segment.y();
+      m_z = segment.z();
+      m_chi2 = segment.chi2();
+      m_station = segment.station();
+      m_size = segment.size();
+      // m_majorityHits = segment.majorityHits();
+      m_isGhost = segment.isGhost();
+      if (nGoodSegments >= 2 && segment.size() == 4) m_isGhost = true;
+      m_tree->Fill();
+      if (segment.isGhost()) continue;
+      if (nGoodSegments >= 2 && segment.size() == 4) continue;
+      outputTracks->push_back(new Trk::Track(*segment.track()));
+    }
+  }
+
+  ATH_CHECK(outputTrackCollection.record(std::move(outputTracks)));
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode GhostBusters::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+GhostBusters::Segment::Segment(const Trk::Track *track, const FaserSCT_ID *idHelper) {
+  m_track = track;
+  m_position = track->trackParameters()->front()->position();
+  m_chi2 = track->fitQuality()->chiSquared();
+  std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+  for (const Trk::TrackStateOnSurface* trackState : *(track->trackStateOnSurfaces())) {
+    auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (trackState->measurementOnTrack());
+    if (clusterOnTrack) {
+      clusters.emplace_back(clusterOnTrack->prepRawData());
+      m_station = idHelper->station(clusterOnTrack->identify());
+    }
+  }
+  m_size = clusters.size();
+  // identifyContributingParticles(simDataCollection, clusters, m_particleHitCounts);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/KalmanFitterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/KalmanFitterTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..52aae1d0f2a06e728df5f8f63767836c009cfa45
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/KalmanFitterTool.cxx
@@ -0,0 +1,354 @@
+#include "FaserActsKalmanFilter/KalmanFitterTool.h"
+
+#include "FaserActsGeometry/FASERMagneticFieldWrapper.h"
+
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
+#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+
+
+KalmanFitterTool::KalmanFitterTool(const std::string& type, const std::string& name, const IInterface* parent) :
+    AthAlgTool(type, name, parent) {}
+
+
+StatusCode KalmanFitterTool::initialize() {
+  ATH_CHECK(m_fieldCondObjInputKey.initialize());
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(detStore()->retrieve(m_idHelper,"FaserSCT_ID"));
+  if (m_statesWriter && !m_noDiagnostics) ATH_CHECK(m_trajectoryStatesWriterTool.retrieve());
+  if (m_summaryWriter && !m_noDiagnostics) ATH_CHECK(m_trajectorySummaryWriterTool.retrieve());
+  m_fit = makeTrackFitterFunction(m_trackingGeometryTool->trackingGeometry());
+  if (m_actsLogging == "VERBOSE" && !m_noDiagnostics) {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::VERBOSE);
+  } else if (m_actsLogging == "DEBUG" && !m_noDiagnostics) {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::DEBUG);
+  } else {
+    m_logger = Acts::getDefaultLogger("KalmanFitter", Acts::Logging::INFO);
+  }
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode KalmanFitterTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+std::unique_ptr<Trk::Track>
+KalmanFitterTool::fit(const EventContext &ctx, const Acts::GeometryContext &gctx, const Trk::Track &inputTrack,
+                      std::vector<FaserActsRecMultiTrajectory> & /*trajectories*/,
+                      const Acts::BoundVector& inputVector, bool isMC, double origin) const {
+  std::unique_ptr<Trk::Track> newTrack = nullptr;
+  std::vector<FaserActsRecMultiTrajectory> myTrajectories;
+
+  if (!inputTrack.measurementsOnTrack() || inputTrack.measurementsOnTrack()->size() < m_minMeasurements) {
+    ATH_MSG_DEBUG("Input track has no or too little measurements and cannot be fitted");
+    return nullptr;
+  }
+
+  if (!inputTrack.trackParameters() || inputTrack.trackParameters()->empty()) {
+    ATH_MSG_DEBUG("Input track has no track parameters and cannot be fitted");
+    return nullptr;
+  }
+
+
+  auto pSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, origin}, Acts::Vector3{0, 0, -1});
+
+  Acts::MagneticFieldContext mfContext = getMagneticFieldContext(ctx);
+  Acts::CalibrationContext calibContext = Acts::CalibrationContext();
+
+  auto [sourceLinks, measurements] = getMeasurementsFromTrack(inputTrack);
+  auto trackParameters = getParametersFromTrack(inputTrack.trackParameters()->front(), inputVector, origin);
+  ATH_MSG_DEBUG("trackParameters: " << trackParameters.parameters().transpose());
+  ATH_MSG_DEBUG("position: " << trackParameters.position(gctx).transpose());
+  ATH_MSG_DEBUG("momentum: " << trackParameters.momentum().transpose());
+  ATH_MSG_DEBUG("charge: " << trackParameters.charge());
+
+  Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder, Acts::VoidReverseFilteringLogic>
+      kfOptions(gctx, mfContext, calibContext, MeasurementCalibrator(measurements),
+                Acts::VoidOutlierFinder(), Acts::VoidReverseFilteringLogic(), Acts::LoggerWrapper{*m_logger},
+                Acts::PropagatorPlainOptions(), &(*pSurface));
+  kfOptions.multipleScattering = false;
+  kfOptions.energyLoss = false;
+
+  ATH_MSG_DEBUG("Invoke fitter");
+  auto result = (*m_fit)(sourceLinks, trackParameters, kfOptions);
+
+  if (result.ok()) {
+    const auto& fitOutput = result.value();
+    if (fitOutput.fittedParameters) {
+      const auto& params = fitOutput.fittedParameters.value();
+      ATH_MSG_DEBUG("ReFitted parameters");
+      ATH_MSG_DEBUG("  params:   " << params.parameters().transpose());
+      ATH_MSG_DEBUG("  position: " << params.position(gctx).transpose());
+      ATH_MSG_DEBUG("  momentum: " << params.momentum().transpose());
+      ATH_MSG_DEBUG("  charge:   " << params.charge());
+      using IndexedParams = std::unordered_map<size_t, Acts::BoundTrackParameters>;
+      IndexedParams indexedParams;
+      indexedParams.emplace(fitOutput.lastMeasurementIndex, std::move(params));
+      std::vector<size_t> trackTips;
+      trackTips.reserve(1);
+      trackTips.emplace_back(fitOutput.lastMeasurementIndex);
+      // trajectories.emplace_back(std::move(fitOutput.fittedStates),
+      //                           std::move(trackTips),
+      //                           std::move(indexedParams));
+      myTrajectories.emplace_back(std::move(fitOutput.fittedStates),
+                                  std::move(trackTips),
+                                  std::move(indexedParams));
+    } else {
+      ATH_MSG_DEBUG("No fitted parameters for track");
+    }
+    newTrack = makeTrack(gctx, result);
+  }
+
+  if (m_statesWriter && !m_noDiagnostics) {
+    StatusCode statusStatesWriterTool = m_trajectoryStatesWriterTool->write(gctx, myTrajectories, isMC);
+  }
+  if (m_summaryWriter && !m_noDiagnostics) {
+    StatusCode statusSummaryWriterTool = m_trajectorySummaryWriterTool->write(gctx, myTrajectories, isMC);
+  }
+
+  return newTrack;
+}
+
+
+namespace {
+
+using Updater = Acts::GainMatrixUpdater;
+using Smoother = Acts::GainMatrixSmoother;
+using Stepper = Acts::EigenStepper<>;
+using Propagator = Acts::Propagator<Stepper, Acts::Navigator>;
+using Fitter = Acts::KalmanFitter<Propagator, Updater, Smoother>;
+
+struct TrackFitterFunctionImpl
+    : public KalmanFitterTool::TrackFitterFunction {
+  Fitter trackFitter;
+
+  TrackFitterFunctionImpl(Fitter &&f) : trackFitter(std::move(f)) {}
+
+  KalmanFitterTool::TrackFitterResult operator()(
+      const std::vector<IndexSourceLink> &sourceLinks,
+      const KalmanFitterTool::TrackParameters &initialParameters,
+      const KalmanFitterTool::TrackFitterOptions &options)
+  const override {
+    return trackFitter.fit(sourceLinks, initialParameters, options);
+  };
+};
+
+}  // namespace
+
+
+std::shared_ptr<KalmanFitterTool::TrackFitterFunction>
+KalmanFitterTool::makeTrackFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry) {
+  auto magneticField = std::make_shared<FASERMagneticFieldWrapper>();
+  auto stepper = Stepper(std::move(magneticField));
+  Acts::Navigator::Config cfg{trackingGeometry};
+  cfg.resolvePassive = false;
+  cfg.resolveMaterial = true;
+  cfg.resolveSensitive = true;
+  Acts::Navigator navigator(cfg);
+  Propagator propagator(std::move(stepper), std::move(navigator));
+  Fitter trackFitter(std::move(propagator));
+  return std::make_shared<TrackFitterFunctionImpl>(std::move(trackFitter));
+}
+
+
+Acts::MagneticFieldContext KalmanFitterTool::getMagneticFieldContext(const EventContext& ctx) const {
+  SG::ReadCondHandle<FaserFieldCacheCondObj> readHandle{m_fieldCondObjInputKey, ctx};
+  if (!readHandle.isValid()) {
+    std::stringstream msg;
+    msg << "Failed to retrieve magnetic field condition data " << m_fieldCondObjInputKey.key() << ".";
+    throw std::runtime_error(msg.str());
+  }
+  const FaserFieldCacheCondObj* fieldCondObj{*readHandle};
+  return Acts::MagneticFieldContext(fieldCondObj);
+}
+
+
+std::tuple<std::vector<IndexSourceLink>, std::vector<Measurement>>
+KalmanFitterTool::getMeasurementsFromTrack(const Trk::Track &track) const {
+  const int kSize = 1;
+  std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0};
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+  std::vector<IndexSourceLink> sourceLinks;
+  std::vector<Measurement> measurements;
+  for (const Trk::MeasurementBase *meas : *track.measurementsOnTrack()) {
+    const auto* clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*>(meas);
+    const Tracker::FaserSCT_Cluster* cluster = clusterOnTrack->prepRawData();
+    if (clusterOnTrack) {
+      Identifier id = clusterOnTrack->identify();
+      Identifier waferId = m_idHelper->wafer_id(id);
+      if (identifierMap->count(waferId) != 0) {
+        Acts::GeometryIdentifier geoId = identifierMap->at(waferId);
+        IndexSourceLink sourceLink(geoId, measurements.size(), cluster);
+        Eigen::Matrix<double, 1, 1> pos {meas->localParameters()[Trk::locX],};
+        Eigen::Matrix<double, 1, 1> cov {0.08 * 0.08 / 12};
+        ThisMeasurement actsMeas(sourceLink, Indices, pos, cov);
+        sourceLinks.push_back(sourceLink);
+        measurements.emplace_back(std::move(actsMeas));
+      }
+    }
+  }
+  return std::make_tuple(sourceLinks, measurements);
+}
+
+
+Acts::BoundTrackParameters
+KalmanFitterTool::getParametersFromTrack(const Trk::TrackParameters *inputParameters,
+                                         const Acts::BoundVector& inputVector, double origin) const {
+  using namespace Acts::UnitLiterals;
+  std::shared_ptr<const Acts::Surface> pSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, origin}, Acts::Vector3{0, 0, -1});
+
+  auto atlasParam = inputParameters->parameters();
+  auto center = inputParameters->associatedSurface().center();
+  // Acts::BoundVector params {center[Trk::locY], center[Trk::locX],
+  //                           atlasParam[Trk::phi0], atlasParam[Trk::theta],
+  //                           charge / (inputParameters->momentum().norm() * 1_MeV),
+  //                           0.};
+  Acts::BoundVector params;
+  if (inputVector == Acts::BoundVector::Zero()) {
+    params(0.0) = center[Trk::locY];
+    params(1.0) = center[Trk::locX];
+    params(2.0) = atlasParam[Trk::phi0];
+    params(3.0) = atlasParam[Trk::theta];
+    // input charge is 1 for positively charged particles and 0 for negatively charged particles!
+    double charge = 2 * inputParameters->charge() - 1;
+    params(4.0) = charge / (inputParameters->momentum().norm() * 1_MeV);
+    params(5.0) = 0.;
+  } else {
+    params = inputVector;
+  }
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Identity();
+  cov.topLeftCorner(5, 5) = *inputParameters->covariance();
+
+  for(int i=0; i < cov.rows(); i++){
+    cov(i, 4) = cov(i, 4)/1_MeV;
+  }
+  for(int i=0; i < cov.cols(); i++){
+    cov(4, i) = cov(4, i)/1_MeV;
+  }
+  for (int i = 0; i < 6; ++i) {
+    cov(i,i) = m_seedCovarianceScale * cov(i,i);
+  }
+
+  return Acts::BoundTrackParameters(pSurface, params, inputParameters->charge(), cov);
+}
+
+
+std::unique_ptr<Trk::Track>
+KalmanFitterTool::makeTrack(const Acts::GeometryContext &geoCtx, TrackFitterResult& fitResult) const {
+  using ConstTrackStateProxy =
+      Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+  std::unique_ptr<Trk::Track> newtrack = nullptr;
+  //Get the fit output object
+  const auto& fitOutput = fitResult.value();
+  if (fitOutput.fittedParameters) {
+    DataVector<const Trk::TrackStateOnSurface>* finalTrajectory = new DataVector<const Trk::TrackStateOnSurface>{};
+    std::vector<std::unique_ptr<const Acts::BoundTrackParameters>> actsSmoothedParam;
+    // Loop over all the output state to create track state
+    fitOutput.fittedStates.visitBackwards(fitOutput.lastMeasurementIndex, [&](const ConstTrackStateProxy& state) {
+      auto flag = state.typeFlags();
+      if (state.referenceSurface().associatedDetectorElement() != nullptr) {
+        // We need to determine the type of state
+        std::bitset<Trk::TrackStateOnSurface::NumberOfTrackStateOnSurfaceTypes> typePattern;
+        const Trk::TrackParameters *parm;
+
+        // State is a hole (no associated measurement), use predicted para meters
+        if (flag[Acts::TrackStateFlag::HoleFlag] == true) {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.predicted(),
+                                                     state.predictedCovariance());
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          // auto boundaryCheck = m_boundaryCheckTool->boundaryCheck(*p arm);
+          typePattern.set(Trk::TrackStateOnSurface::Hole);
+        }
+          // The state was tagged as an outlier, use filtered parameters
+        else if (flag[Acts::TrackStateFlag::OutlierFlag] == true) {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.filtered(), state.filteredCovariance());
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          typePattern.set(Trk::TrackStateOnSurface::Outlier);
+        }
+          // The state is a measurement state, use smoothed parameters
+        else {
+          const Acts::BoundTrackParameters actsParam(state.referenceSurface().getSharedPtr(),
+                                                     state.smoothed(), state.smoothedCovariance());
+          actsSmoothedParam.push_back(std::make_unique<const Acts::BoundTrackParameters>(Acts::BoundTrackParameters(actsParam)));
+          //  const auto& psurface=actsParam.referenceSurface();
+          // Acts::Vector2 local(actsParam.parameters()[Acts::eBoundLoc0], actsParam.parameters()[Acts::eBoundLoc1]);
+          //  const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(actsParam.parameters()[Acts::eBoundPhi], actsParam.parameters()[Acts::eBoundTheta]);
+          //  auto pos=actsParam.position(tgContext);
+          parm = ConvertActsTrackParameterToATLAS(actsParam, geoCtx);
+          typePattern.set(Trk::TrackStateOnSurface::Measurement);
+        }
+        Tracker::FaserSCT_ClusterOnTrack* measState = nullptr;
+        if (state.hasUncalibrated()) {
+          const Tracker::FaserSCT_Cluster* fitCluster = state.uncalibrated().hit();
+          if (fitCluster->detectorElement() != nullptr) {
+            measState = new Tracker::FaserSCT_ClusterOnTrack{
+                fitCluster,
+                Trk::LocalParameters{
+                    Trk::DefinedParameter{fitCluster->localPosition()[0], Trk::loc1},
+                    Trk::DefinedParameter{fitCluster->localPosition()[1], Trk::loc2}
+                },
+                fitCluster->localCovariance(),
+                m_idHelper->wafer_hash(fitCluster->detectorElement()->identify())
+            };
+          }
+        }
+        double nDoF = state.calibratedSize();
+        const Trk::FitQualityOnSurface *quality = new Trk::FitQualityOnSurface(state.chi2(), nDoF);
+        const Trk::TrackStateOnSurface *perState = new Trk::TrackStateOnSurface(measState, parm, quality, nullptr, typePattern);
+        // If a state was successfully created add it to the trajectory
+        if (perState) {
+          finalTrajectory->insert(finalTrajectory->begin(), perState);
+        }
+      }
+      return;
+    });
+
+    // Create the track using the states
+    const Trk::TrackInfo newInfo(Trk::TrackInfo::TrackFitter::KalmanFitter, Trk::ParticleHypothesis::muon);
+    // Trk::FitQuality* q = nullptr;
+    // newInfo.setTrackFitter(Trk::TrackInfo::TrackFitter::KalmanFitter     ); //Mark the fitter as KalmanFitter
+    newtrack = std::make_unique<Trk::Track>(newInfo, std::move(*finalTrajectory), nullptr);
+  }
+  return newtrack;
+}
+
+
+const Trk::TrackParameters*
+KalmanFitterTool::ConvertActsTrackParameterToATLAS(const Acts::BoundTrackParameters &actsParameter, const Acts::GeometryContext& gctx) const {
+  using namespace Acts::UnitLiterals;
+  std::optional<AmgSymMatrix(5)> cov = std::nullopt;
+  if (actsParameter.covariance()){
+    AmgSymMatrix(5) newcov(actsParameter.covariance()->topLeftCorner(5, 5));
+    // Convert the covariance matrix to GeV
+    for(int i=0; i < newcov.rows(); i++){
+      newcov(i, 4) = newcov(i, 4)*1_MeV;
+    }
+    for(int i=0; i < newcov.cols(); i++){
+      newcov(4, i) = newcov(4, i)*1_MeV;
+    }
+    cov =  std::optional<AmgSymMatrix(5)>(newcov);
+  }
+  const Amg::Vector3D& pos=actsParameter.position(gctx);
+  double tphi=actsParameter.get<Acts::eBoundPhi>();
+  double ttheta=actsParameter.get<Acts::eBoundTheta>();
+  double tqOverP=actsParameter.get<Acts::eBoundQOverP>()*1_MeV;
+  double p = std::abs(1. / tqOverP);
+  Amg::Vector3D tmom(p * std::cos(tphi) * std::sin(ttheta), p * std::sin(tphi) * std::sin(ttheta), p * std::cos(ttheta));
+  const Trk::CurvilinearParameters * curv = new Trk::CurvilinearParameters(pos,tmom,tqOverP>0, cov);
+  return curv;
+}
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/MultiTrackFinderTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/MultiTrackFinderTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f10448452d2a9161dab5207963a514135475efce
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/MultiTrackFinderTool.cxx
@@ -0,0 +1,120 @@
+#include "FaserActsKalmanFilter/MultiTrackFinderTool.h"
+
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/Measurement.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "Identifier/Identifier.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrkTrack/Track.h"
+#include "TrkTrack/TrackStateOnSurface.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+//#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include <algorithm>
+#include <vector>
+
+
+MultiTrackFinderTool::MultiTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode MultiTrackFinderTool::initialize() {
+      ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+      ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+      ATH_CHECK(m_trackingGeometryTool.retrieve());
+      ATH_CHECK(m_trackCollection.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MultiTrackFinderTool::run() {
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+      ATH_CHECK(trackCollection.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+
+  std::map<int, std::vector<Tracklet>> tracklets;
+  for (const Trk::Track* track : *trackCollection) {
+    std::vector<Identifier> ids;
+    std::vector<Acts::GeometryIdentifier> geoIds;
+    std::vector<double> positions;
+    std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+    auto fitParameters = track->trackParameters()->front();
+    const Amg::Vector3D fitPosition = fitParameters->position();
+    const Amg::Vector3D fitMomentum = fitParameters->momentum();
+    ATH_MSG_DEBUG(fitPosition.x() << ", " << fitPosition.y() << fitPosition.z());
+    for (const Trk::TrackStateOnSurface* state : *(track->trackStateOnSurfaces())) {
+      auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> ( state->measurementOnTrack());
+      if (clusterOnTrack) {
+        const Tracker::FaserSCT_Cluster* cluster = clusterOnTrack->prepRawData();
+        clusters.push_back(cluster);
+        Identifier id = cluster->detectorElement()->identify();
+        ids.push_back(id);
+        Acts::GeometryIdentifier geoId = identifierMap->at(id);
+        geoIds.push_back(geoId);
+        const auto& par = cluster->localPosition();
+        positions.push_back(par.x());
+      }
+    }
+    auto tracklet = Tracklet(ids, geoIds, positions, fitPosition, clusters);
+    int station = m_idHelper->station(ids.front());
+    tracklets[station].push_back(tracklet);
+  }
+
+  // create all combinations of tracklets
+  std::vector<ProtoTrack> protoTracks {};
+  for (const Tracklet& t1 : tracklets[1]) {
+    for (const Tracklet& t2 : tracklets[2]) {
+      for (const Tracklet& t3 : tracklets[3]) {
+        protoTracks.push_back(ProtoTrack(t1, t2, t3));
+      }
+    }
+  }
+
+  // sort proto tracks by their chi2 value and select best two tracks
+  std::sort(protoTracks.begin(), protoTracks.end(), sort_chi2());
+
+  std::vector<std::vector<IndexSourceLink>> sourceLinkVector;
+  std::vector<std::vector<Measurement>> measurementVector;
+  std::vector<std::map<Index, Identifier>> idLinkVector;
+  std::vector<Acts::CurvilinearTrackParameters> paramVector;
+  std::vector<std::vector<const Tracker::FaserSCT_Cluster*>> clusterVector;
+  int n_protoTracks = std::min((int)protoTracks.size(), 2);
+  for (int i = 0; i < n_protoTracks; ++i) {
+    // FIXME check if protoTrack exists
+    ProtoTrack track = protoTracks[i];
+    auto [measurements, sourceLinks, idLinks, clusters] = track.run();
+    auto params = track.initialTrackParameters(m_covLoc0, m_covLoc1, m_covPhi, m_covTheta, m_covQOverP, m_covTime);
+    sourceLinkVector.push_back(sourceLinks);
+    measurementVector.push_back(measurements);
+    idLinkVector.push_back(idLinks);
+    paramVector.push_back(params);
+    clusterVector.push_back(clusters);
+  }
+
+  m_sourceLinks = std::make_shared<std::vector<std::vector<IndexSourceLink>>>(sourceLinkVector);
+  m_measurements = std::make_shared<std::vector<std::vector<Measurement>>>(measurementVector);
+  m_idLinks = std::make_shared<std::vector<std::map<Index, Identifier>>>(idLinkVector);
+  m_initialTrackParameters = std::make_shared<std::vector<Acts::CurvilinearTrackParameters>>(paramVector);
+  m_clusters = std::make_shared<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>>(clusterVector);
+
+  // create initial surface
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, 0}, Acts::Vector3{0, 0, 1});
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MultiTrackFinderTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/MyAmbiguitySolver.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/MyAmbiguitySolver.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..998269a83cf6228092dd3b31987696f929820e4e
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/MyAmbiguitySolver.cxx
@@ -0,0 +1,3 @@
+#include "FaserActsKalmanFilter/MyAmbiguitySolver.h"
+
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/MyTrackSeedTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/MyTrackSeedTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..c2648161b595230fc626544d4e9dd11fdf4118e5
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/MyTrackSeedTool.cxx
@@ -0,0 +1,143 @@
+#include "FaserActsKalmanFilter/MyTrackSeedTool.h"
+
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePointCollection.h"
+#include "Identifier/Identifier.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+
+
+MyTrackSeedTool::MyTrackSeedTool( const std::string& type, const std::string& name, const IInterface* parent) :
+    base_class(type, name, parent) {}
+
+
+StatusCode MyTrackSeedTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackCollection.initialize());
+  ATH_CHECK(m_clusterContainerKey.initialize());
+  ATH_CHECK(m_spacePointContainerKey.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MyTrackSeedTool::run() {
+
+  // create track seeds for multiple tracks
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+  ATH_CHECK(trackCollection.isValid());
+
+  SG::ReadHandle<Tracker::FaserSCT_ClusterContainer> clusterContainer {m_clusterContainerKey};
+  ATH_CHECK(clusterContainer.isValid());
+
+  SG::ReadHandle<FaserSCT_SpacePointContainer> spacePointContainer {m_spacePointContainerKey};
+  ATH_CHECK(spacePointContainer.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+
+  std::map<Identifier, const Tracker::FaserSCT_SpacePoint*> spacePointMap {};
+  for (const FaserSCT_SpacePointCollection* spacePointCollection : *spacePointContainer) {
+    for (const Tracker::FaserSCT_SpacePoint *spacePoint: *spacePointCollection) {
+      Identifier id1 = spacePoint->cluster1()->identify();
+      spacePointMap[id1] = spacePoint;
+      Identifier id2 = spacePoint->cluster2()->identify();
+      spacePointMap[id2] = spacePoint;
+    }
+  }
+
+  const int kSize = 1;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0};
+  std::vector<IndexSourceLink> sourceLinks;
+  std::vector<Measurement> measurements;
+  std::map<Index, Identifier> identifierLinkMap;
+  std::vector<const Tracker::FaserSCT_SpacePoint*> spacePoints {};
+  std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+  for (const Tracker::FaserSCT_ClusterCollection* clusterCollection : *clusterContainer) {
+    for (const Tracker::FaserSCT_Cluster* cluster : *clusterCollection) {
+      Identifier id = cluster->detectorElement()->identify();
+      identifierLinkMap[measurements.size()] = id;
+      if (identifierMap->count(id) != 0) {
+        Acts::GeometryIdentifier geoId = identifierMap->at(id);
+        IndexSourceLink sourceLink(geoId, measurements.size(), cluster);
+        // create measurement
+        const auto& par = cluster->localPosition();
+        Eigen::Matrix<double, 1, 1> pos {par.x(),};
+        Eigen::Matrix<double, 1, 1> cov {m_std_cluster * m_std_cluster,};
+        ThisMeasurement meas(sourceLink, Indices, pos, cov);
+        sourceLinks.push_back(sourceLink);
+        measurements.emplace_back(std::move(meas));
+        clusters.push_back(cluster);
+        if (spacePointMap.count(cluster->identify()) > 0) {
+          spacePoints.push_back(spacePointMap[cluster->identify()]);
+        } else {
+          spacePoints.push_back(nullptr);
+        }
+      }
+    }
+  }
+
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  std::map<int, std::vector<Amg::Vector3D>> stationHitMap {};
+  for (const Trk::Track* track : *trackCollection) {
+    const Amg::Vector3D position = track->trackParameters()->front()->position();
+    for (const Trk::TrackStateOnSurface* trackState : *(track->trackStateOnSurfaces())) {
+      auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (trackState->measurementOnTrack());
+      if (clusterOnTrack) {
+        int station = m_idHelper->station(clusterOnTrack->identify());
+        stationHitMap[station].push_back(position);
+        break;
+      }
+    }
+  }
+
+
+  std::vector<Acts::CurvilinearTrackParameters> initParams {};
+  for (size_t i = 0; i < stationHitMap.size(); ++i) {
+    for (size_t j = i+1; j < stationHitMap.size(); ++j) {
+      for (const auto &p1 : stationHitMap[i]) {
+        for (const auto &p2 : stationHitMap[j]) {
+          initParams.push_back(get_params(p1, p2, cov, m_origin));
+        }
+      }
+    }
+  }
+
+  m_initialTrackParameters = std::make_shared<std::vector<Acts::CurvilinearTrackParameters>>(initParams);
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourceLinks);
+  m_idLinks = std::make_shared<IdentifierLink>(identifierLinkMap);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, m_origin}, Acts::Vector3{0, 0, -1});
+  m_clusters = std::make_shared<std::vector<const Tracker::FaserSCT_Cluster*>>(clusters);
+  m_spacePoints = std::make_shared<std::vector<const Tracker::FaserSCT_SpacePoint*>>(spacePoints);
+  // m_seedClusters = std::make_shared<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>>({});
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode MyTrackSeedTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+Acts::CurvilinearTrackParameters MyTrackSeedTool::get_params(const Amg::Vector3D& p1, const Amg::Vector3D& p2, const Acts::BoundSymMatrix& cov, double origin) {
+  Acts::Vector3 dir = p2 - p1;
+  Acts::Vector3 pos = p1 - (p1.z() - origin)/dir.z() * dir;
+  Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+  return Acts::CurvilinearTrackParameters(pos4, dir, 10000000, 1, cov);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/PerformanceWriterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/PerformanceWriterTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..c545c551adeb640a985ab4999d0864ecd817fb7c
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/PerformanceWriterTool.cxx
@@ -0,0 +1,183 @@
+#include "FaserActsKalmanFilter/PerformanceWriterTool.h"
+#include "FaserActsKalmanFilter/TrackClassification.h"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/MultiTrajectoryHelpers.hpp"
+#include "TFile.h"
+
+
+PerformanceWriterTool::PerformanceWriterTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : AthAlgTool(type, name, parent) {}
+
+
+StatusCode PerformanceWriterTool::initialize() {
+    ATH_CHECK(m_extrapolationTool.retrieve());
+    ATH_CHECK(m_mcEventCollectionKey.initialize());
+    ATH_CHECK(m_simDataCollectionKey.initialize());
+
+    if (!m_noDiagnostics) {
+      std::string filePath = m_filePath;
+      m_outputFile = TFile::Open(filePath.c_str(), "RECREATE");
+      if (m_outputFile == nullptr) {
+        ATH_MSG_WARNING("Unable to open output file at " << m_filePath);
+        return StatusCode::RECOVERABLE;
+      }
+    }
+
+    // initialize the residual and efficiency plots tool
+    m_resPlotTool.book(m_resPlotCache);
+    m_effPlotTool.book(m_effPlotCache);
+    m_summaryPlotTool.book(m_summaryPlotCache);
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode PerformanceWriterTool::finalize() {
+  if (!m_noDiagnostics) {
+    // fill residual and pull details into additional hists
+    m_resPlotTool.refinement(m_resPlotCache);
+    if (m_outputFile) {
+      m_outputFile->cd();
+      m_resPlotTool.write(m_resPlotCache);
+      m_effPlotTool.write(m_effPlotCache);
+      m_summaryPlotTool.write(m_summaryPlotCache);
+      ATH_MSG_VERBOSE("Wrote performance plots to '" << m_outputFile->GetPath() << "'");
+    }
+
+    m_resPlotTool.clear(m_resPlotCache);
+    m_effPlotTool.clear(m_effPlotCache);
+    m_summaryPlotTool.clear(m_summaryPlotCache);
+    if (m_outputFile) {
+      m_outputFile->Close();
+    }
+  }
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode PerformanceWriterTool::write(const Acts::GeometryContext& geoContext, const TrajectoriesContainer& trajectories) {
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+
+  SG::ReadHandle<McEventCollection> mcEvents {m_mcEventCollectionKey, ctx};
+  ATH_CHECK(mcEvents.isValid());
+  if (mcEvents->size() != 1) {
+    ATH_MSG_ERROR("There should be exactly one event in the McEventCollection.");
+    return StatusCode::FAILURE;
+  }
+
+  std::map<int, const HepMC::GenParticle*> particles {};
+  for (const HepMC::GenParticle* particle : mcEvents->front()->particle_range()) {
+    particles[particle->barcode()] = particle;
+  }
+
+  SG::ReadHandle<TrackerSimDataCollection> simData {m_simDataCollectionKey, ctx};
+  ATH_CHECK(simData.isValid());
+
+  // Truth particles with corresponding reconstructed tracks
+  std::vector<int> reconParticleIds;
+  reconParticleIds.reserve(particles.size());
+  // For each particle within a track, how many hits did it contribute
+  std::vector<ParticleHitCount> particleHitCounts;
+
+
+  // Loop over all trajectories
+  for (size_t itraj = 0; itraj < trajectories.size(); ++itraj) {
+    const auto& traj = trajectories[itraj];
+
+    if (traj.empty()) {
+      ATH_MSG_WARNING("Empty trajectories object " << itraj);
+      continue;
+    }
+
+    // The trajectory entry indices and the multiTrajectory
+    const auto& trackTips = traj.tips();
+    const auto& mj = traj.multiTrajectory();
+
+    // Check the size of the trajectory entry indices. For track fitting, there
+    // should be at most one trajectory
+    if (trackTips.size() > 1) {
+      ATH_MSG_ERROR("Track fitting should not result in multiple trajectories.");
+      return StatusCode::FAILURE;
+    }
+    // Get the entry index for the single trajectory
+    auto trackTip = trackTips.front();
+
+    // Select reco track with fitted parameters
+    if (not traj.hasTrackParameters(trackTip)) {
+      ATH_MSG_WARNING("No fitted track parameters.");
+      continue;
+    }
+    const auto& fittedParameters = traj.trackParameters(trackTip);
+
+    // Get the majority truth particle for this trajectory
+    identifyContributingParticles(*simData, traj, trackTip, particleHitCounts);
+    if (particleHitCounts.empty()) {
+      ATH_MSG_WARNING("No truth particle associated with this trajectory.");
+      continue;
+    }
+    // Find the truth particle for the majority barcode
+    const auto ip = particles.find(particleHitCounts.front().particleId);
+    if (ip == particles.end()) {
+      ATH_MSG_WARNING("Majority particle not found in the particles collection.");
+      continue;
+    }
+
+    // Record this majority particle ID of this trajectory
+    reconParticleIds.push_back(ip->first);
+    const HepMC::GenParticle* truthParticle = ip->second;
+    std::unique_ptr<const Acts::BoundTrackParameters> truthParameters
+        = extrapolateToReferenceSurface(ctx, truthParticle);
+    // Fill the residual plots
+    if (truthParameters) {
+      m_resPlotTool.fill(m_resPlotCache, geoContext, std::move(truthParameters), fittedParameters);
+    } else {
+      ATH_MSG_WARNING("Can not extrapolate truth parameters to reference surface.");
+    }
+    // Collect the trajectory summary info
+    auto trajState = Acts::MultiTrajectoryHelpers::trajectoryState(mj, trackTip);
+    // Fill the trajectory summary info
+    m_summaryPlotTool.fill(m_summaryPlotCache, fittedParameters, trajState.nStates, trajState.nMeasurements,
+                           trajState.nOutliers, trajState.nHoles, trajState.nSharedHits);
+  }
+
+  // Fill the efficiency, defined as the ratio between number of tracks with fitted parameter and total truth tracks
+  // (assumes one truth partilce has one truth track)
+  for (const auto& particle : particles) {
+    bool isReconstructed = false;
+    // Find if the particle has been reconstructed
+    auto it = std::find(reconParticleIds.begin(), reconParticleIds.end(), particle.first);
+    if (it != reconParticleIds.end()) {
+      isReconstructed = true;
+    }
+    m_effPlotTool.fill(m_effPlotCache, particle.second, isReconstructed);
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+std::unique_ptr<const Acts::BoundTrackParameters> PerformanceWriterTool::extrapolateToReferenceSurface(
+    const EventContext& ctx, const HepMC::GenParticle* particle) const {
+  const HepMC::FourVector &vertex = particle->production_vertex()->position();
+  const HepMC::FourVector &momentum = particle->momentum();
+
+  // The coordinate system of the Acts::PlaneSurface is defined as
+  // T = Z = normal, U = X x T = -Y, V = T x U = x
+  auto startSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3(0, 0, vertex.z()), Acts::Vector3(0, 0, 1));
+  auto targetSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3(0, 0, 0), Acts::Vector3(0, 0, -1));
+  Acts::BoundVector params = Acts::BoundVector::Zero();
+  params[Acts::eBoundLoc0] = -vertex.y();
+  params[Acts::eBoundLoc1] = vertex.x();
+  params[Acts::eBoundPhi] = momentum.phi();
+  params[Acts::eBoundTheta] = momentum.theta();
+  // FIXME get charge of HepMC::GenParticle, the following does not work, e.g. for pions
+  double charge = particle->pdg_id() > 0 ? -1 : 1;
+  double MeV2GeV = 1e-3;
+  params[Acts::eBoundQOverP] = charge / (momentum.rho() * MeV2GeV);
+  params[Acts::eBoundTime] = vertex.t();
+  Acts::BoundTrackParameters startParameters(std::move(startSurface), params, charge);
+  std::unique_ptr<const Acts::BoundTrackParameters> targetParameters =
+      m_extrapolationTool->propagate(ctx, startParameters, *targetSurface);
+  return targetParameters;
+}
\ No newline at end of file
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/PlotHelpers.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/PlotHelpers.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0d50d843ea075ab3d3799dccdc6e2ba46cd74127
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/PlotHelpers.cxx
@@ -0,0 +1,92 @@
+#include "FaserActsKalmanFilter/PlotHelpers.h"
+#include <cassert>
+
+namespace PlotHelpers {
+
+TH1F* bookHisto(const char* histName, const char* histTitle,
+                const Binning& varBinning) {
+  TH1F* hist = new TH1F(histName, histTitle, varBinning.nBins, varBinning.min,
+                        varBinning.max);
+  hist->GetXaxis()->SetTitle(varBinning.title.c_str());
+  hist->GetYaxis()->SetTitle("Entries");
+  hist->Sumw2();
+  return hist;
+}
+
+TH2F* bookHisto(const char* histName, const char* histTitle,
+                const Binning& varXBinning, const Binning& varYBinning) {
+  TH2F* hist = new TH2F(histName, histTitle, varXBinning.nBins, varXBinning.min,
+                        varXBinning.max, varYBinning.nBins, varYBinning.min,
+                        varYBinning.max);
+  hist->GetXaxis()->SetTitle(varXBinning.title.c_str());
+  hist->GetYaxis()->SetTitle(varYBinning.title.c_str());
+  hist->Sumw2();
+  return hist;
+}
+
+void fillHisto(TH1F* hist, float value, float weight) {
+  assert(hist != nullptr);
+  hist->Fill(value, weight);
+}
+
+void fillHisto(TH2F* hist, float xValue, float yValue, float weight) {
+  assert(hist != nullptr);
+  hist->Fill(xValue, yValue, weight);
+}
+
+void anaHisto(TH1D* inputHist, int j, TH1F* meanHist, TH1F* widthHist) {
+  // evaluate mean and width via the Gauss fit
+  assert(inputHist != nullptr);
+  if (inputHist->GetEntries() > 0) {
+    TFitResultPtr r = inputHist->Fit("gaus", "QS0");
+    if (r.Get() and ((r->Status() % 1000) == 0)) {
+      // fill the mean and width into 'j'th bin of the meanHist and widthHist,
+      // respectively
+      meanHist->SetBinContent(j, r->Parameter(1));
+      meanHist->SetBinError(j, r->ParError(1));
+      widthHist->SetBinContent(j, r->Parameter(2));
+      widthHist->SetBinError(j, r->ParError(2));
+    }
+  }
+}
+
+TEfficiency* bookEff(const char* effName, const char* effTitle, const Binning& varBinning) {
+  TEfficiency* efficiency = new TEfficiency(effName, effTitle, varBinning.nBins,
+                                            varBinning.min, varBinning.max);
+  return efficiency;
+}
+
+TEfficiency* bookEff(const char* effName, const char* effTitle,
+                     const Binning& varXBinning, const Binning& varYBinning) {
+  TEfficiency* efficiency = new TEfficiency(
+      effName, effTitle, varXBinning.nBins, varXBinning.min, varXBinning.max,
+      varYBinning.nBins, varYBinning.min, varYBinning.max);
+  return efficiency;
+}
+
+void fillEff(TEfficiency* efficiency, float value, bool status) {
+  assert(efficiency != nullptr);
+  efficiency->Fill(status, value);
+}
+
+void fillEff(TEfficiency* efficiency, float xValue, float yValue, bool status) {
+  assert(efficiency != nullptr);
+  efficiency->Fill(status, xValue, yValue);
+}
+
+TProfile* bookProf(const char* profName, const char* profTitle,
+                   const Binning& varXBinning, const Binning& varYBinning) {
+  TProfile* prof =
+      new TProfile(profName, profTitle, varXBinning.nBins, varXBinning.min,
+                   varXBinning.max, varYBinning.min, varYBinning.max);
+  prof->GetXaxis()->SetTitle(varXBinning.title.c_str());
+  prof->GetYaxis()->SetTitle(varYBinning.title.c_str());
+  return prof;
+}
+
+void fillProf(TProfile* profile, float xValue, float yValue, float weight) {
+  assert(profile != nullptr);
+  profile->Fill(xValue, yValue, weight);
+}
+
+}  // namespace PlotHelpers
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/ProtoTrackWriterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/ProtoTrackWriterTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5ec0dfc1a1d430208b10c14a7c368ac89ac08ff3
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/ProtoTrackWriterTool.cxx
@@ -0,0 +1,108 @@
+#include "FaserActsKalmanFilter/ProtoTrackWriterTool.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Identifier/Identifier.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include <TFile.h>
+#include <TTree.h>
+
+
+ProtoTrackWriterTool::ProtoTrackWriterTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : AthAlgTool(type, name, parent) {}
+
+
+StatusCode ProtoTrackWriterTool::initialize() {
+
+  ATH_CHECK(m_trackFinderTool.retrieve());
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+
+  std::string filePath = m_filePath;
+  m_file = TFile::Open(filePath.c_str(), "RECREATE");
+  if (m_file == nullptr) {
+    ATH_MSG_ERROR("Unable to open output file at " << m_filePath);
+    return StatusCode::FAILURE;
+  }
+  m_file->cd();
+
+  m_params = new TTree("parameters", "parameters");
+  m_params->Branch("run_number", &m_run_number, "run_number/I");
+  m_params->Branch("event_number", &m_event_number, "event_number/I");
+  m_params->Branch("x", &m_x, "x/D");
+  m_params->Branch("y", &m_y, "y/D");
+  m_params->Branch("z", &m_z, "z/D");
+  m_params->Branch("px", &m_px, "px/D");
+  m_params->Branch("py", &m_py, "py/D");
+  m_params->Branch("pz", &m_pz, "pz/D");
+
+  m_meas = new TTree("measurements", "measurements");
+  m_meas->Branch("run_number", &m_run_number, "run_number/I");
+  m_meas->Branch("event_number", &m_event_number, "event_number/I");
+  m_meas->Branch("station", &m_station, "station/I");
+  m_meas->Branch("layer", &m_layer, "layer/I");
+  m_meas->Branch("phi", &m_phi, "phi/I");
+  m_meas->Branch("eta", &m_eta, "eta/I");
+  m_meas->Branch("side", &m_side, "side/I");
+  m_meas->Branch("meas_eLOC0", &m_meas_eLOC0, "meas_eLOC0/D");
+  m_meas->Branch("meas_eLOC1", &m_meas_eLOC1, "meas_eLOC1/D");
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ProtoTrackWriterTool::finalize() {
+  m_file->cd();
+  m_params->Write();
+  m_meas->Write();
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ProtoTrackWriterTool::write(
+    std::shared_ptr<const Acts::CurvilinearTrackParameters> protoTrackParameters,
+    std::shared_ptr<std::vector<Measurement>> measurements,
+    const Acts::GeometryContext& geoctx) const {
+
+  EventContext ctx = Gaudi::Hive::currentContext();
+  m_run_number = ctx.eventID().run_number();
+  m_event_number = ctx.eventID().event_number();
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+  // flip key-value pairs of identifier map
+  std::map<Acts::GeometryIdentifier, Identifier> geoIdentifierMap;
+  for (const std::pair<const Identifier, Acts::GeometryIdentifier>& identifierPair : *identifierMap) {
+    geoIdentifierMap[identifierPair.second] = identifierPair.first;
+  }
+
+  Acts::Vector3 position = protoTrackParameters->position(geoctx);
+  Acts::Vector3 direction = protoTrackParameters->momentum();
+  m_x = position.x();
+  m_y = position.y();
+  m_z = position.z();
+  m_px = direction.x();
+  m_py = direction.y();
+  m_pz = direction.z();
+  m_params->Fill();
+
+  for (const Measurement& variantMeasurement : *measurements) {
+    // auto meas = std::get<Acts::Measurement<IndexSourceLink, Acts::BoundIndices, 2>>(variantMeasurement);
+    std::visit([&](const auto& meas) {
+      Acts::GeometryIdentifier geoId = meas.sourceLink().geometryId();
+      Identifier id = geoIdentifierMap.at(geoId);
+      m_station = m_idHelper->station(id);
+      m_layer = m_idHelper->layer(id);
+      m_phi = m_idHelper->phi_module(id);
+      m_eta = m_idHelper->eta_module(id);
+      m_side = m_idHelper->side(id);
+      auto params = meas.parameters();
+      m_meas_eLOC0 = params[Acts::eBoundLoc0];
+      // write out as many variables as there are
+      // m_meas_eLOC1 = params[Acts::eBoundLoc1];
+      m_meas->Fill();
+    }, variantMeasurement);
+  };
+
+  return StatusCode::SUCCESS;
+}
\ No newline at end of file
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/ResPlotTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/ResPlotTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..47922a13e99abca49446668f9f8c180a86ebecdf
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/ResPlotTool.cxx
@@ -0,0 +1,201 @@
+#include "FaserActsKalmanFilter/ResPlotTool.h"
+#include "Acts/Utilities/Helpers.hpp"
+#include "HepMC/GenVertex.h"
+
+void ResPlotTool::book(ResPlotTool::ResPlotCache& resPlotCache) const {
+  PlotHelpers::Binning bEta = m_varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_varBinning.at("Pt");
+  PlotHelpers::Binning bPull = m_varBinning.at("Pull");
+  std::cout << "DEBUG: Initialize the histograms for residual and pull plots" << std::endl;
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_paramNames.at(parID);
+    std::string parResidual = "Residual_" + parName;
+    // Binning for residual is parameter dependent
+    PlotHelpers::Binning bResidual = m_varBinning.at(parResidual);
+
+
+    // residual distributions
+    resPlotCache.res[parName] = PlotHelpers::bookHisto(
+        Form("res_%s", parName.c_str()),
+        Form("Residual of %s", parName.c_str()), bResidual);
+    // residual vs eta scatter plots
+    resPlotCache.res_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("res_%s_vs_eta", parName.c_str()),
+        Form("Residual of %s vs eta", parName.c_str()), bEta, bResidual);
+    // residual mean in each eta bin
+    resPlotCache.resMean_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("resmean_%s_vs_eta", parName.c_str()),
+        Form("Residual mean of %s", parName.c_str()), bEta);
+    // residual width in each eta bin
+    resPlotCache.resWidth_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("reswidth_%s_vs_eta", parName.c_str()),
+        Form("Residual width of %s", parName.c_str()), bEta);
+    // residual vs pT scatter plots
+    resPlotCache.res_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("res_%s_vs_pT", parName.c_str()),
+        Form("Residual of %s vs pT", parName.c_str()), bPt, bResidual);
+    // residual mean in each pT bin
+    resPlotCache.resMean_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("resmean_%s_vs_pT", parName.c_str()),
+        Form("Residual mean of %s", parName.c_str()), bPt);
+    // residual width in each pT bin
+    resPlotCache.resWidth_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("reswidth_%s_vs_pT", parName.c_str()),
+        Form("Residual width of %s", parName.c_str()), bPt);
+
+    // pull distritutions
+    resPlotCache.pull[parName] = PlotHelpers::bookHisto(
+        Form("pull_%s", parName.c_str()),
+        Form("Pull of %s", parName.c_str()), bPull);
+    // pull vs eta scatter plots
+    resPlotCache.pull_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("pull_%s_vs_eta", parName.c_str()),
+        Form("Pull of %s vs eta", parName.c_str()), bEta, bPull);
+    // pull mean in each eta bin
+    resPlotCache.pullMean_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("pullmean_%s_vs_eta", parName.c_str()),
+        Form("Pull mean of %s", parName.c_str()), bEta);
+    // pull width in each eta bin
+    resPlotCache.pullWidth_vs_eta[parName] = PlotHelpers::bookHisto(
+        Form("pullwidth_%s_vs_eta", parName.c_str()),
+        Form("Pull width of %s", parName.c_str()), bEta);
+    // pull vs pT scatter plots
+    resPlotCache.pull_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("pull_%s_vs_pT", parName.c_str()),
+        Form("Pull of %s vs pT", parName.c_str()), bPt, bPull);
+    // pull mean in each pT bin
+    resPlotCache.pullMean_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("pullmean_%s_vs_pT", parName.c_str()),
+        Form("Pull mean of %s", parName.c_str()), bPt);
+    // pull width in each pT bin
+    resPlotCache.pullWidth_vs_pT[parName] = PlotHelpers::bookHisto(
+        Form("pullwidth_%s_vs_pT", parName.c_str()),
+        Form("Pull width of %s", parName.c_str()), bPt);
+    }
+}
+
+void ResPlotTool::write(const ResPlotCache &resPlotCache) const {
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_paramNames.at(parID);
+    if (resPlotCache.res.count(parName)) {
+      resPlotCache.res.at(parName)->Write();
+      resPlotCache.res_vs_eta.at(parName)->Write();
+      resPlotCache.resMean_vs_eta.at(parName)->Write();
+      resPlotCache.resWidth_vs_eta.at(parName)->Write();
+      resPlotCache.res_vs_pT.at(parName)->Write();
+      resPlotCache.resMean_vs_pT.at(parName)->Write();
+      resPlotCache.resWidth_vs_pT.at(parName)->Write();
+      resPlotCache.pull.at(parName)->Write();
+      resPlotCache.pull_vs_eta.at(parName)->Write();
+      resPlotCache.pullMean_vs_eta.at(parName)->Write();
+      resPlotCache.pullWidth_vs_eta.at(parName)->Write();
+      resPlotCache.pull_vs_pT.at(parName)->Write();
+      resPlotCache.pullMean_vs_pT.at(parName)->Write();
+      resPlotCache.pullWidth_vs_pT.at(parName)->Write();
+    }
+  }
+}
+
+void ResPlotTool::clear(ResPlotCache &resPlotCache) const {
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_paramNames.at(parID);
+    if (resPlotCache.res.count(parName)) {
+      delete resPlotCache.res.at(parName);
+      delete resPlotCache.res_vs_eta.at(parName);
+      delete resPlotCache.resMean_vs_eta.at(parName);
+      delete resPlotCache.resWidth_vs_eta.at(parName);
+      delete resPlotCache.res_vs_pT.at(parName);
+      delete resPlotCache.resMean_vs_pT.at(parName);
+      delete resPlotCache.resWidth_vs_pT.at(parName);
+      delete resPlotCache.pull.at(parName);
+      delete resPlotCache.pull_vs_eta.at(parName);
+      delete resPlotCache.pullMean_vs_eta.at(parName);
+      delete resPlotCache.pullWidth_vs_eta.at(parName);
+      delete resPlotCache.pull_vs_pT.at(parName);
+      delete resPlotCache.pullMean_vs_pT.at(parName);
+      delete resPlotCache.pullWidth_vs_pT.at(parName);
+    }
+  }
+}
+
+void ResPlotTool::fill(
+    ResPlotCache& resPlotCache, const Acts::GeometryContext& /*gctx*/,
+    std::unique_ptr<const Acts::BoundTrackParameters> truthParameters,
+    const Acts::BoundTrackParameters& fittedParameters) const {
+  using ParametersVector = Acts::BoundTrackParameters::ParametersVector;
+  using Acts::VectorHelpers::eta;
+  using Acts::VectorHelpers::perp;
+  // using Acts::VectorHelpers::phi;
+  // using Acts::VectorHelpers::theta;
+
+  // get the fitted parameter (at perigee surface) and its error
+  auto trackParameter = fittedParameters.parameters();
+
+  // get the truth position and momentum
+  ParametersVector truthParameter = truthParameters->parameters();
+
+  // get the truth eta and pT
+  const auto truthEta = eta(truthParameters->momentum().normalized());
+  const auto truthPt = truthParameters->absoluteMomentum() * perp(truthParameters->momentum().normalized());
+
+  // fill the histograms for residual and pull
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_paramNames.at(parID);
+    float residual = trackParameter[parID] - truthParameter[parID];
+    PlotHelpers::fillHisto(resPlotCache.res.at(parName), residual);
+    PlotHelpers::fillHisto(resPlotCache.res_vs_eta.at(parName), truthEta, residual);
+    PlotHelpers::fillHisto(resPlotCache.res_vs_pT.at(parName), truthPt, residual);
+
+    if (fittedParameters.covariance().has_value()) {
+      auto covariance = *fittedParameters.covariance();
+      if (covariance(parID, parID) > 0) {
+        float pull = residual / sqrt(covariance(parID, parID));
+        PlotHelpers::fillHisto(resPlotCache.pull[parName], pull);
+        PlotHelpers::fillHisto(resPlotCache.pull_vs_eta.at(parName), truthEta, pull);
+        PlotHelpers::fillHisto(resPlotCache.pull_vs_pT.at(parName), truthPt, pull);
+      } else {
+        std::cout << "WARNING: Fitted track parameter :" << parName << " has negative covariance = " << covariance(parID, parID) << std::endl;
+      }
+    } else {
+      std::cout << "WARNING: Fitted track parameter :" << parName << " has no covariance" << std::endl;
+    }
+  }
+}
+
+
+// get the mean and width of residual/pull in each eta/pT bin and fill them into histograms
+void ResPlotTool::refinement(ResPlotTool::ResPlotCache& resPlotCache) const {
+  PlotHelpers::Binning bEta = m_varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_varBinning.at("Pt");
+  for (unsigned int parID = 0; parID < Acts::eBoundSize; parID++) {
+    std::string parName = m_paramNames.at(parID);
+    // refine the plots vs eta
+    for (int j = 1; j <= bEta.nBins; j++) {
+      TH1D* temp_res = resPlotCache.res_vs_eta.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Residual_vs_eta_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_res, j,
+                            resPlotCache.resMean_vs_eta.at(parName),
+                            resPlotCache.resWidth_vs_eta.at(parName));
+
+      TH1D* temp_pull = resPlotCache.pull_vs_eta.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Pull_vs_eta_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_pull, j,
+                            resPlotCache.pullMean_vs_eta.at(parName),
+                            resPlotCache.pullWidth_vs_eta.at(parName));
+    }
+
+    // refine the plots vs pT
+    for (int j = 1; j <= bPt.nBins; j++) {
+      TH1D* temp_res = resPlotCache.res_vs_pT.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Residual_vs_pT_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_res, j, resPlotCache.resMean_vs_pT.at(parName),
+                            resPlotCache.resWidth_vs_pT.at(parName));
+
+      TH1D* temp_pull = resPlotCache.pull_vs_pT.at(parName)->ProjectionY(
+          Form("%s_projy_bin%d", "Pull_vs_pT_Histo", j), j, j);
+      PlotHelpers::anaHisto(temp_pull, j,
+                            resPlotCache.pullMean_vs_pT.at(parName),
+                            resPlotCache.pullWidth_vs_pT.at(parName));
+    }
+  }
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/RootTrajectoryStatesWriterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/RootTrajectoryStatesWriterTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5378fc080cb49628248d4a7eda2e9206d6c016ba
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/RootTrajectoryStatesWriterTool.cxx
@@ -0,0 +1,725 @@
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/MultiTrajectoryHelpers.hpp"
+#include "Acts/EventData/detail/TransformationBoundToFree.hpp"
+#include "Acts/Utilities/Helpers.hpp"
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "GeoPrimitives/CLHEPtoEigenConverter.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include "FaserActsKalmanFilter/TrackClassification.h"
+#include <TFile.h>
+#include <TTree.h>
+
+constexpr float NaNfloat = std::numeric_limits<float>::quiet_NaN();
+constexpr float NaNint = std::numeric_limits<int>::quiet_NaN();
+
+using Acts::VectorHelpers::eta;
+using Acts::VectorHelpers::perp;
+using Acts::VectorHelpers::phi;
+using Acts::VectorHelpers::theta;
+
+RootTrajectoryStatesWriterTool::RootTrajectoryStatesWriterTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : AthAlgTool(type, name, parent) {}
+
+StatusCode RootTrajectoryStatesWriterTool::initialize() {
+  ATH_CHECK(m_mcEventCollectionKey.initialize());
+  ATH_CHECK(m_simDataCollectionKey.initialize());
+  ATH_CHECK(m_faserSiHitKey.initialize());
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detMgr, "SCT"));
+
+  if (!m_noDiagnostics) {
+    std::string filePath = m_filePath;
+    std::string treeName = m_treeName;
+    m_outputFile = TFile::Open(filePath.c_str(), "RECREATE");
+    if (m_outputFile == nullptr) {
+      ATH_MSG_ERROR("Unable to open output file at " << m_filePath);
+      return StatusCode::FAILURE;
+    }
+    m_outputFile->cd();
+    m_outputTree = new TTree(treeName.c_str(), treeName.c_str());
+    if (m_outputTree == nullptr) {
+      ATH_MSG_ERROR("Unable to create TTree");
+      return StatusCode::FAILURE;
+    }
+
+    m_outputTree = new TTree("tree", "tree");
+
+    m_outputTree->Branch("event_nr", &m_eventNr);
+    m_outputTree->Branch("multiTraj_nr", &m_multiTrajNr);
+    m_outputTree->Branch("subTraj_nr", &m_subTrajNr);
+
+    m_outputTree->Branch("t_x", &m_t_x);
+    m_outputTree->Branch("t_y", &m_t_y);
+    m_outputTree->Branch("t_z", &m_t_z);
+    m_outputTree->Branch("t_dx", &m_t_dx);
+    m_outputTree->Branch("t_dy", &m_t_dy);
+    m_outputTree->Branch("t_dz", &m_t_dz);
+    m_outputTree->Branch("t_eLOC0", &m_t_eLOC0);
+    m_outputTree->Branch("t_eLOC1", &m_t_eLOC1);
+    m_outputTree->Branch("t_ePHI", &m_t_ePHI);
+    m_outputTree->Branch("t_eTHETA", &m_t_eTHETA);
+    m_outputTree->Branch("t_eQOP", &m_t_eQOP);
+    m_outputTree->Branch("t_eT", &m_t_eT);
+
+    m_outputTree->Branch("nStates", &m_nStates);
+    m_outputTree->Branch("nMeasurements", &m_nMeasurements);
+    m_outputTree->Branch("volume_id", &m_volumeID);
+    m_outputTree->Branch("layer_id", &m_layerID);
+    m_outputTree->Branch("module_id", &m_moduleID);
+    m_outputTree->Branch("station", &m_station);
+    m_outputTree->Branch("layer", &m_layer);
+    m_outputTree->Branch("phi_module", &m_phi_module);
+    m_outputTree->Branch("eta_module", &m_eta_module);
+    m_outputTree->Branch("side", &m_side);
+    m_outputTree->Branch("pathLength", &m_pathLength);
+    m_outputTree->Branch("l_x_hit", &m_lx_hit);
+    m_outputTree->Branch("l_y_hit", &m_ly_hit);
+    m_outputTree->Branch("g_x_hit", &m_x_hit);
+    m_outputTree->Branch("g_y_hit", &m_y_hit);
+    m_outputTree->Branch("g_z_hit", &m_z_hit);
+    m_outputTree->Branch("res_x_hit", &m_res_x_hit);
+    m_outputTree->Branch("res_y_hit", &m_res_y_hit);
+    m_outputTree->Branch("err_x_hit", &m_err_x_hit);
+    m_outputTree->Branch("err_y_hit", &m_err_y_hit);
+    m_outputTree->Branch("pull_x_hit", &m_pull_x_hit);
+    m_outputTree->Branch("pull_y_hit", &m_pull_y_hit);
+    m_outputTree->Branch("dim_hit", &m_dim_hit);
+
+    m_outputTree->Branch("nPredicted", &m_nParams[0]);
+    m_outputTree->Branch("predicted", &m_hasParams[0]);
+    m_outputTree->Branch("eLOC0_prt", &m_eLOC0[0]);
+    m_outputTree->Branch("eLOC1_prt", &m_eLOC1[0]);
+    m_outputTree->Branch("ePHI_prt", &m_ePHI[0]);
+    m_outputTree->Branch("eTHETA_prt", &m_eTHETA[0]);
+    m_outputTree->Branch("eQOP_prt", &m_eQOP[0]);
+    m_outputTree->Branch("eT_prt", &m_eT[0]);
+    m_outputTree->Branch("res_eLOC0_prt", &m_res_eLOC0[0]);
+    m_outputTree->Branch("res_eLOC1_prt", &m_res_eLOC1[0]);
+    m_outputTree->Branch("res_ePHI_prt", &m_res_ePHI[0]);
+    m_outputTree->Branch("res_eTHETA_prt", &m_res_eTHETA[0]);
+    m_outputTree->Branch("res_eQOP_prt", &m_res_eQOP[0]);
+    m_outputTree->Branch("res_eT_prt", &m_res_eT[0]);
+    m_outputTree->Branch("err_eLOC0_prt", &m_err_eLOC0[0]);
+    m_outputTree->Branch("err_eLOC1_prt", &m_err_eLOC1[0]);
+    m_outputTree->Branch("err_ePHI_prt", &m_err_ePHI[0]);
+    m_outputTree->Branch("err_eTHETA_prt", &m_err_eTHETA[0]);
+    m_outputTree->Branch("err_eQOP_prt", &m_err_eQOP[0]);
+    m_outputTree->Branch("err_eT_prt", &m_err_eT[0]);
+    m_outputTree->Branch("pull_eLOC0_prt", &m_pull_eLOC0[0]);
+    m_outputTree->Branch("pull_eLOC1_prt", &m_pull_eLOC1[0]);
+    m_outputTree->Branch("pull_ePHI_prt", &m_pull_ePHI[0]);
+    m_outputTree->Branch("pull_eTHETA_prt", &m_pull_eTHETA[0]);
+    m_outputTree->Branch("pull_eQOP_prt", &m_pull_eQOP[0]);
+    m_outputTree->Branch("pull_eT_prt", &m_pull_eT[0]);
+    m_outputTree->Branch("g_x_prt", &m_x[0]);
+    m_outputTree->Branch("g_y_prt", &m_y[0]);
+    m_outputTree->Branch("g_z_prt", &m_z[0]);
+    m_outputTree->Branch("px_prt", &m_px[0]);
+    m_outputTree->Branch("py_prt", &m_py[0]);
+    m_outputTree->Branch("pz_prt", &m_pz[0]);
+    m_outputTree->Branch("eta_prt", &m_eta[0]);
+    m_outputTree->Branch("pT_prt", &m_pT[0]);
+
+    m_outputTree->Branch("nFiltered", &m_nParams[1]);
+    m_outputTree->Branch("filtered", &m_hasParams[1]);
+    m_outputTree->Branch("eLOC0_flt", &m_eLOC0[1]);
+    m_outputTree->Branch("eLOC1_flt", &m_eLOC1[1]);
+    m_outputTree->Branch("ePHI_flt", &m_ePHI[1]);
+    m_outputTree->Branch("eTHETA_flt", &m_eTHETA[1]);
+    m_outputTree->Branch("eQOP_flt", &m_eQOP[1]);
+    m_outputTree->Branch("eT_flt", &m_eT[1]);
+    m_outputTree->Branch("res_eLOC0_flt", &m_res_eLOC0[1]);
+    m_outputTree->Branch("res_eLOC1_flt", &m_res_eLOC1[1]);
+    m_outputTree->Branch("res_ePHI_flt", &m_res_ePHI[1]);
+    m_outputTree->Branch("res_eTHETA_flt", &m_res_eTHETA[1]);
+    m_outputTree->Branch("res_eQOP_flt", &m_res_eQOP[1]);
+    m_outputTree->Branch("res_eT_flt", &m_res_eT[1]);
+    m_outputTree->Branch("err_eLOC0_flt", &m_err_eLOC0[1]);
+    m_outputTree->Branch("err_eLOC1_flt", &m_err_eLOC1[1]);
+    m_outputTree->Branch("err_ePHI_flt", &m_err_ePHI[1]);
+    m_outputTree->Branch("err_eTHETA_flt", &m_err_eTHETA[1]);
+    m_outputTree->Branch("err_eQOP_flt", &m_err_eQOP[1]);
+    m_outputTree->Branch("err_eT_flt", &m_err_eT[1]);
+    m_outputTree->Branch("pull_eLOC0_flt", &m_pull_eLOC0[1]);
+    m_outputTree->Branch("pull_eLOC1_flt", &m_pull_eLOC1[1]);
+    m_outputTree->Branch("pull_ePHI_flt", &m_pull_ePHI[1]);
+    m_outputTree->Branch("pull_eTHETA_flt", &m_pull_eTHETA[1]);
+    m_outputTree->Branch("pull_eQOP_flt", &m_pull_eQOP[1]);
+    m_outputTree->Branch("pull_eT_flt", &m_pull_eT[1]);
+    m_outputTree->Branch("g_x_flt", &m_x[1]);
+    m_outputTree->Branch("g_y_flt", &m_y[1]);
+    m_outputTree->Branch("g_z_flt", &m_z[1]);
+    m_outputTree->Branch("px_flt", &m_px[1]);
+    m_outputTree->Branch("py_flt", &m_py[1]);
+    m_outputTree->Branch("pz_flt", &m_pz[1]);
+    m_outputTree->Branch("eta_flt", &m_eta[1]);
+    m_outputTree->Branch("pT_flt", &m_pT[1]);
+
+    m_outputTree->Branch("nSmoothed", &m_nParams[2]);
+    m_outputTree->Branch("smoothed", &m_hasParams[2]);
+    m_outputTree->Branch("eLOC0_smt", &m_eLOC0[2]);
+    m_outputTree->Branch("eLOC1_smt", &m_eLOC1[2]);
+    m_outputTree->Branch("ePHI_smt", &m_ePHI[2]);
+    m_outputTree->Branch("eTHETA_smt", &m_eTHETA[2]);
+    m_outputTree->Branch("eQOP_smt", &m_eQOP[2]);
+    m_outputTree->Branch("eT_smt", &m_eT[2]);
+    m_outputTree->Branch("res_eLOC0_smt", &m_res_eLOC0[2]);
+    m_outputTree->Branch("res_eLOC1_smt", &m_res_eLOC1[2]);
+    m_outputTree->Branch("res_ePHI_smt", &m_res_ePHI[2]);
+    m_outputTree->Branch("res_eTHETA_smt", &m_res_eTHETA[2]);
+    m_outputTree->Branch("res_eQOP_smt", &m_res_eQOP[2]);
+    m_outputTree->Branch("res_eT_smt", &m_res_eT[2]);
+    m_outputTree->Branch("err_eLOC0_smt", &m_err_eLOC0[2]);
+    m_outputTree->Branch("err_eLOC1_smt", &m_err_eLOC1[2]);
+    m_outputTree->Branch("err_ePHI_smt", &m_err_ePHI[2]);
+    m_outputTree->Branch("err_eTHETA_smt", &m_err_eTHETA[2]);
+    m_outputTree->Branch("err_eQOP_smt", &m_err_eQOP[2]);
+    m_outputTree->Branch("err_eT_smt", &m_err_eT[2]);
+    m_outputTree->Branch("pull_eLOC0_smt", &m_pull_eLOC0[2]);
+    m_outputTree->Branch("pull_eLOC1_smt", &m_pull_eLOC1[2]);
+    m_outputTree->Branch("pull_ePHI_smt", &m_pull_ePHI[2]);
+    m_outputTree->Branch("pull_eTHETA_smt", &m_pull_eTHETA[2]);
+    m_outputTree->Branch("pull_eQOP_smt", &m_pull_eQOP[2]);
+    m_outputTree->Branch("pull_eT_smt", &m_pull_eT[2]);
+    m_outputTree->Branch("g_x_smt", &m_x[2]);
+    m_outputTree->Branch("g_y_smt", &m_y[2]);
+    m_outputTree->Branch("g_z_smt", &m_z[2]);
+    m_outputTree->Branch("px_smt", &m_px[2]);
+    m_outputTree->Branch("py_smt", &m_py[2]);
+    m_outputTree->Branch("pz_smt", &m_pz[2]);
+    m_outputTree->Branch("eta_smt", &m_eta[2]);
+    m_outputTree->Branch("pT_smt", &m_pT[2]);
+
+    m_outputTree->Branch("chi2", &m_chi2);
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode RootTrajectoryStatesWriterTool::finalize() {
+  if (!m_noDiagnostics) {
+    m_outputFile->cd();
+    m_outputTree->Write();
+    m_outputFile->Close();
+  }
+  return StatusCode::SUCCESS;
+}
+
+StatusCode RootTrajectoryStatesWriterTool::write(const Acts::GeometryContext& gctx, const TrajectoriesContainer& trajectories, bool isMC) const {
+
+  if (m_outputFile == nullptr)
+    return StatusCode::SUCCESS;
+
+  // Get the event number
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+  m_eventNr = ctx.eventID().event_number();
+
+  std::vector<ParticleHitCount> particleHitCounts;
+  std::map<int, const HepMC::GenParticle*> particles {};
+  std::map<std::pair<int, Identifier>, const FaserSiHit*> siHitMap;
+
+  std::shared_ptr<TrackerSimDataCollection> simData {nullptr};
+
+  if (isMC) {
+    SG::ReadHandle<McEventCollection> mcEvents {m_mcEventCollectionKey, ctx};
+        ATH_CHECK(mcEvents.isValid());
+    if (mcEvents->size() != 1) {
+      ATH_MSG_ERROR("There should be exactly one event in the McEventCollection.");
+      return StatusCode::FAILURE;
+    }
+    ATH_MSG_VERBOSE("Found " << mcEvents->front()->particles_size() << " particles.");
+    for (const HepMC::GenParticle* particle : mcEvents->front()->particle_range()) {
+      particles[particle->barcode()] = particle;
+    }
+
+    SG::ReadHandle<TrackerSimDataCollection> simDataHandle {m_simDataCollectionKey, ctx};
+        ATH_CHECK(simDataHandle.isValid());
+    simData = std::make_shared<TrackerSimDataCollection>(*simDataHandle);
+
+    SG::ReadHandle<FaserSiHitCollection> siHitCollection {m_faserSiHitKey, ctx};
+    ATH_CHECK(siHitCollection.isValid());
+    for (const FaserSiHit& hit : *siHitCollection) {
+      int barcode = hit.trackNumber();
+      Identifier id = m_idHelper->wafer_id(hit.getStation(), hit.getPlane(), hit.getRow(), hit.getModule(), hit.getSensor());
+      siHitMap[std::make_pair(barcode, id)] = &hit;
+    }
+  }
+
+
+  // Loop over the trajectories
+  for (size_t itraj = 0; itraj < trajectories.size(); ++itraj) {
+    const auto& traj = trajectories[itraj];
+
+    if (traj.empty()) {
+      ATH_MSG_WARNING("Empty trajectories object " << itraj);
+      continue;
+    }
+
+    // The trajectory index
+    m_multiTrajNr = itraj;
+
+    // The trajectory entry indices and the multiTrajectory
+    const auto& mj = traj.multiTrajectory();
+    const auto& trackTips = traj.tips();
+
+    // Loop over the entry indices for the subtrajectories
+    for (unsigned int isubtraj = 0; isubtraj < trackTips.size(); ++isubtraj) {
+      // The subtrajectory index
+      m_subTrajNr = isubtraj;
+      // The entry index for this subtrajectory
+      const auto& trackTip = trackTips[isubtraj];
+      // Collect the trajectory summary info
+      auto trajState = Acts::MultiTrajectoryHelpers::trajectoryState(mj, trackTip);
+      m_nMeasurements = trajState.nMeasurements;
+      m_nStates = trajState.nStates;
+
+      // Get the majority truth particle to this track
+      int barcode = NaNint;
+      int truthQ = NaNint;
+      float truthMomentum = NaNfloat;
+      float truthLOC0 = NaNfloat;
+      float truthLOC1 = NaNfloat;
+      float truthPHI = NaNfloat;
+      float truthTHETA = NaNfloat;
+      float truthQOP = NaNfloat;
+      float truthTIME = NaNfloat;
+
+      if (isMC) {
+        truthQ = 1;
+        truthMomentum = 1;
+        identifyContributingParticles(*simData, traj, trackTip, particleHitCounts);
+        if (not particleHitCounts.empty()) {
+          // Get the barcode of the majority truth particle
+          barcode = particleHitCounts.front().particleId;
+          // Find the truth particle via the barcode
+          auto ip = particles.find(barcode);
+          if (ip != particles.end()) {
+            const auto& particle = ip->second;
+            ATH_MSG_DEBUG("Find the truth particle with barcode = " << barcode);
+            // Get the truth particle charge
+            // FIXME find better way to access charge of simulated particle, this does not work for
+            // pions which have a positive pdg code (211) and positive charge
+            truthQ = particle->pdg_id() > 0 ? -1 : 1;
+            truthMomentum = particle->momentum().rho() * m_MeV2GeV;
+          } else {
+            ATH_MSG_WARNING("Truth particle with barcode = " << barcode << " not found!");
+          }
+        }
+      }
+
+      // Get the trackStates on the trajectory
+      m_nParams = {0, 0, 0};
+      using ConstTrackStateProxy =
+          Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+      mj.visitBackwards(trackTip, [&](const ConstTrackStateProxy& state) {
+        // we only fill the track states with non-outlier measurement
+        auto typeFlags = state.typeFlags();
+        if (not typeFlags.test(Acts::TrackStateFlag::MeasurementFlag)) {
+          return true;
+        }
+
+        const auto& surface = state.referenceSurface();
+
+        // get the truth hits corresponding to this trackState
+        Identifier id = state.uncalibrated().hit()->identify();
+        Identifier waferId = m_idHelper->wafer_id(id);
+        // if (siHitMap.count(std::make_pair(barcode, waferId)) == 0) {
+        //   ATH_MSG_WARNING("no FaserSiHit for hit with id " << id << " from particle " << barcode);
+        //   return true;
+        // }
+
+        if (isMC && siHitMap.count(std::make_pair(barcode, waferId)) != 0) {
+          const FaserSiHit* siHit = siHitMap.find(std::make_pair(barcode, waferId))->second;
+          HepGeom::Point3D localStartPos = siHit->localStartPosition();
+          HepGeom::Point3D localEndPos = siHit->localEndPosition();
+          HepGeom::Point3D<double> localPos = 0.5 * (localEndPos + localStartPos);
+          auto truthLocal = Acts::Vector2(localPos.y(), localPos.z());
+          const TrackerDD::SiDetectorElement* element = m_detMgr->getDetectorElement(id);
+          const HepGeom::Point3D<double> globalStartPosition =
+              Amg::EigenTransformToCLHEP(element->transformHit()) * localStartPos;
+          const HepGeom::Point3D<double> globalEndPosition =
+              Amg::EigenTransformToCLHEP(element->transformHit()) * localEndPos;
+          auto globalPosition = 0.5 * (globalStartPosition + globalEndPosition);
+          auto globalDirection = globalEndPosition - globalStartPosition;
+          auto truthUnitDir = Acts::Vector3(globalDirection.x(), globalDirection.y(), globalDirection.z()).normalized();
+          auto truthPos = Acts::Vector3(globalPosition.x() , globalPosition.y(), globalPosition.z());
+          // FIXME get truthQOP for each state
+
+          // fill the truth hit info
+          m_t_x.push_back(truthPos[Acts::ePos0]);
+          m_t_y.push_back(truthPos[Acts::ePos1]);
+          m_t_z.push_back(truthPos[Acts::ePos2]);
+          m_t_dx.push_back(truthUnitDir[Acts::eMom0]);
+          m_t_dy.push_back(truthUnitDir[Acts::eMom1]);
+          m_t_dz.push_back(truthUnitDir[Acts::eMom2]);
+
+          // get the truth track parameter at this track State
+          float truthLOC0 = truthLocal[Acts::ePos0];
+          float truthLOC1 = truthLocal[Acts::ePos1];
+          float truthTIME = siHit->meanTime();
+          float truthPHI = phi(truthUnitDir);
+          float truthTHETA = theta(truthUnitDir);
+
+          // fill the truth track parameter at this track State
+          m_t_eLOC0.push_back(truthLOC0);
+          m_t_eLOC1.push_back(truthLOC1);
+          m_t_ePHI.push_back(truthPHI);
+          m_t_eTHETA.push_back(truthTHETA);
+          m_t_eQOP.push_back(truthQOP);
+          m_t_eT.push_back(truthTIME);
+        } else {
+          m_t_x.push_back(NaNfloat);
+          m_t_y.push_back(NaNfloat);
+          m_t_z.push_back(NaNfloat);
+          m_t_dx.push_back(NaNfloat);
+          m_t_dy.push_back(NaNfloat);
+          m_t_dz.push_back(NaNfloat);
+          m_t_eLOC0.push_back(NaNfloat);
+          m_t_eLOC1.push_back(NaNfloat);
+          m_t_ePHI.push_back(NaNfloat);
+          m_t_eTHETA.push_back(NaNfloat);
+          m_t_eT.push_back(NaNfloat);
+        }
+
+        // get the geometry ID
+        auto geoID = surface.geometryId();
+        m_volumeID.push_back(geoID.volume());
+        m_layerID.push_back(geoID.layer());
+        m_moduleID.push_back(geoID.sensitive());
+
+        // get wafer information
+        m_station.push_back(m_idHelper->station(id));
+        m_layer.push_back(m_idHelper->layer(id));
+        m_phi_module.push_back(m_idHelper->phi_module(id));
+        m_eta_module.push_back(m_idHelper->eta_module(id));
+        m_side.push_back(m_idHelper->side(id));
+
+        // get the path length
+        m_pathLength.push_back(state.pathLength());
+
+        // expand the local measurements into the full bound space
+        Acts::BoundVector meas = state.projector().transpose() * state.calibrated();
+        // extract local and global position
+        Acts::Vector2 local(meas[Acts::eBoundLoc0], meas[Acts::eBoundLoc1]);
+        Acts::Vector3 mom(1, 1, 1);
+        Acts::Vector3 global = surface.localToGlobal(gctx, local, mom);
+
+        // fill the measurement info
+        m_lx_hit.push_back(local[Acts::ePos0]);
+        m_ly_hit.push_back(local[Acts::ePos1]);
+        m_x_hit.push_back(global[Acts::ePos0]);
+        m_y_hit.push_back(global[Acts::ePos1]);
+        m_z_hit.push_back(global[Acts::ePos2]);
+
+        // status of the fitted track parameters
+        std::array<bool, 3> hasParams = {false, false, false};
+        // optional fitted track parameters
+        std::optional<std::pair<Acts::BoundVector, Acts::BoundMatrix>>
+            trackParamsOpt = std::nullopt;
+        // lambda to get the fitted track parameters
+        auto getTrackParams = [&](unsigned int ipar) {
+          if (ipar == 0 && state.hasPredicted()) {
+            hasParams[0] = true;
+            m_nParams[0]++;
+            trackParamsOpt =
+                std::make_pair(state.predicted(), state.predictedCovariance());
+          } else if (ipar == 1 && state.hasFiltered()) {
+            hasParams[1] = true;
+            m_nParams[1]++;
+            trackParamsOpt =
+                std::make_pair(state.filtered(), state.filteredCovariance());
+          } else if (ipar == 2 && state.hasSmoothed()) {
+            hasParams[2] = true;
+            m_nParams[2]++;
+            trackParamsOpt =
+                std::make_pair(state.smoothed(), state.smoothedCovariance());
+          }
+        };
+
+        // fill the fitted track parameters
+        for (unsigned int ipar = 0; ipar < 3; ++ipar) {
+          // get the fitted track parameters
+          getTrackParams(ipar);
+          if (trackParamsOpt) {
+            const auto& [parameters, covariance] = *trackParamsOpt;
+            if (ipar == 0) {
+              //
+              // local hit residual info
+              auto H = state.effectiveProjector();
+              auto resCov = state.effectiveCalibratedCovariance() +
+                            H * covariance * H.transpose();
+              auto res = state.effectiveCalibrated() - H * parameters;
+              m_res_x_hit.push_back(res[Acts::eBoundLoc0]);
+//              m_res_y_hit.push_back(res[Acts::eBoundLoc1]);
+              m_res_y_hit.push_back(NaNfloat);
+              m_err_x_hit.push_back(
+                  sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
+//              m_err_y_hit.push_back(
+//                  sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+              m_err_x_hit.push_back(NaNfloat);
+              m_res_y_hit.push_back(NaNfloat);
+              m_pull_x_hit.push_back(
+                  res[Acts::eBoundLoc0] /
+                  sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
+//              m_pull_y_hit.push_back(
+//                  res[Acts::eBoundLoc1] /
+//                  sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+              m_pull_y_hit.push_back(NaNfloat);
+              m_dim_hit.push_back(state.calibratedSize());
+            }
+
+            // track parameters
+            m_eLOC0[ipar].push_back(parameters[Acts::eBoundLoc0]);
+            m_eLOC1[ipar].push_back(parameters[Acts::eBoundLoc1]);
+            m_ePHI[ipar].push_back(parameters[Acts::eBoundPhi]);
+            m_eTHETA[ipar].push_back(parameters[Acts::eBoundTheta]);
+            m_eQOP[ipar].push_back(parameters[Acts::eBoundQOverP]);
+            m_eT[ipar].push_back(parameters[Acts::eBoundTime]);
+
+            // track parameters residual
+            float resPhi;
+            if (isMC) {
+              m_res_eLOC0[ipar].push_back(parameters[Acts::eBoundLoc0] - truthLOC0);
+              m_res_eLOC1[ipar].push_back(parameters[Acts::eBoundLoc1] - truthLOC1);
+              resPhi = Acts::detail::difference_periodic<float>( parameters[Acts::eBoundPhi], truthPHI,
+                                                                 static_cast<float>(2 * M_PI));
+              m_res_ePHI[ipar].push_back(resPhi);
+              m_res_eTHETA[ipar].push_back(parameters[Acts::eBoundTheta] - truthTHETA);
+              m_res_eQOP[ipar].push_back(parameters[Acts::eBoundQOverP] - truthQOP);
+              m_res_eT[ipar].push_back(parameters[Acts::eBoundTime] - truthTIME);
+
+              // track parameters error
+              m_err_eLOC0[ipar].push_back(
+                  sqrt(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)));
+              m_err_eLOC1[ipar].push_back(
+                  sqrt(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+              m_err_ePHI[ipar].push_back(
+                  sqrt(covariance(Acts::eBoundPhi, Acts::eBoundPhi)));
+              m_err_eTHETA[ipar].push_back(
+                  sqrt(covariance(Acts::eBoundTheta, Acts::eBoundTheta)));
+              m_err_eQOP[ipar].push_back(
+                  sqrt(covariance(Acts::eBoundQOverP, Acts::eBoundQOverP)));
+              m_err_eT[ipar].push_back(
+                  sqrt(covariance(Acts::eBoundTime, Acts::eBoundTime)));
+
+              // track parameters pull
+              m_pull_eLOC0[ipar].push_back(
+                  (parameters[Acts::eBoundLoc0] - truthLOC0) /
+                  sqrt(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)));
+              m_pull_eLOC1[ipar].push_back(
+                  (parameters[Acts::eBoundLoc1] - truthLOC1) /
+                  sqrt(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+              m_pull_ePHI[ipar].push_back(
+                  resPhi / sqrt(covariance(Acts::eBoundPhi, Acts::eBoundPhi)));
+              m_pull_eTHETA[ipar].push_back(
+                  (parameters[Acts::eBoundTheta] - truthTHETA) /
+                  sqrt(covariance(Acts::eBoundTheta, Acts::eBoundTheta)));
+              m_pull_eQOP[ipar].push_back(
+                  (parameters[Acts::eBoundQOverP] - truthQOP) /
+                  sqrt(covariance(Acts::eBoundQOverP, Acts::eBoundQOverP)));
+              m_pull_eT[ipar].push_back(
+                  (parameters[Acts::eBoundTime] - truthTIME) /
+                  sqrt(covariance(Acts::eBoundTime, Acts::eBoundTime)));
+            } else {
+              if (ipar == 0) {
+                // push default values if no track parameters
+                m_res_x_hit.push_back(NaNfloat);
+                m_res_y_hit.push_back(NaNfloat);
+                m_err_x_hit.push_back(NaNfloat);
+                m_err_y_hit.push_back(NaNfloat);
+                m_pull_x_hit.push_back(NaNfloat);
+                m_pull_y_hit.push_back(NaNfloat);
+                m_dim_hit.push_back(NaNint);
+              }
+              // push default values if no track parameters
+              // m_eLOC0[ipar].push_back(NaNfloat);
+              // m_eLOC1[ipar].push_back(NaNfloat);
+              // m_ePHI[ipar].push_back(NaNfloat);
+              // m_eTHETA[ipar].push_back(NaNfloat);
+              // m_eQOP[ipar].push_back(NaNfloat);
+              // m_eT[ipar].push_back(NaNfloat);
+              m_res_eLOC0[ipar].push_back(NaNfloat);
+              m_res_eLOC1[ipar].push_back(NaNfloat);
+              m_res_ePHI[ipar].push_back(NaNfloat);
+              m_res_eTHETA[ipar].push_back(NaNfloat);
+              m_res_eQOP[ipar].push_back(NaNfloat);
+              m_res_eT[ipar].push_back(NaNfloat);
+              m_err_eLOC0[ipar].push_back(NaNfloat);
+              m_err_eLOC1[ipar].push_back(NaNfloat);
+              m_err_ePHI[ipar].push_back(NaNfloat);
+              m_err_eTHETA[ipar].push_back(NaNfloat);
+              m_err_eQOP[ipar].push_back(NaNfloat);
+              m_err_eT[ipar].push_back(NaNfloat);
+              m_pull_eLOC0[ipar].push_back(NaNfloat);
+              m_pull_eLOC1[ipar].push_back(NaNfloat);
+              m_pull_ePHI[ipar].push_back(NaNfloat);
+              m_pull_eTHETA[ipar].push_back(NaNfloat);
+              m_pull_eQOP[ipar].push_back(NaNfloat);
+              m_pull_eT[ipar].push_back(NaNfloat);
+              // m_x[ipar].push_back(NaNfloat);
+              // m_y[ipar].push_back(NaNfloat);
+              // m_z[ipar].push_back(NaNfloat);
+              // m_px[ipar].push_back(NaNfloat);
+              // m_py[ipar].push_back(NaNfloat);
+              // m_pz[ipar].push_back(NaNfloat);
+              // m_pT[ipar].push_back(NaNfloat);
+              // m_eta[ipar].push_back(NaNfloat);
+            }
+
+            // further track parameter info
+            Acts::FreeVector freeParams =
+                Acts::detail::transformBoundToFreeParameters(surface, gctx,
+                                                             parameters);
+            m_x[ipar].push_back(freeParams[Acts::eFreePos0]);
+            m_y[ipar].push_back(freeParams[Acts::eFreePos1]);
+            m_z[ipar].push_back(freeParams[Acts::eFreePos2]);
+            auto p = std::abs(1 / freeParams[Acts::eFreeQOverP]);
+            m_px[ipar].push_back(p * freeParams[Acts::eFreeDir0]);
+            m_py[ipar].push_back(p * freeParams[Acts::eFreeDir1]);
+            m_pz[ipar].push_back(p * freeParams[Acts::eFreeDir2]);
+            m_pT[ipar].push_back(p * std::hypot(freeParams[Acts::eFreeDir0],
+                                                freeParams[Acts::eFreeDir1]));
+            m_eta[ipar].push_back(Acts::VectorHelpers::eta(
+                freeParams.segment<3>(Acts::eFreeDir0)));
+          } else {
+            if (ipar == 0) {
+              // push default values if no track parameters
+              m_res_x_hit.push_back(NaNfloat);
+              m_res_y_hit.push_back(NaNfloat);
+              m_err_x_hit.push_back(NaNfloat);
+              m_err_y_hit.push_back(NaNfloat);
+              m_pull_x_hit.push_back(NaNfloat);
+              m_pull_y_hit.push_back(NaNfloat);
+              m_dim_hit.push_back(NaNint);
+            }
+            // push default values if no track parameters
+            m_eLOC0[ipar].push_back(NaNfloat);
+            m_eLOC1[ipar].push_back(NaNfloat);
+            m_ePHI[ipar].push_back(NaNfloat);
+            m_eTHETA[ipar].push_back(NaNfloat);
+            m_eQOP[ipar].push_back(NaNfloat);
+            m_eT[ipar].push_back(NaNfloat);
+            m_res_eLOC0[ipar].push_back(NaNfloat);
+            m_res_eLOC1[ipar].push_back(NaNfloat);
+            m_res_ePHI[ipar].push_back(NaNfloat);
+            m_res_eTHETA[ipar].push_back(NaNfloat);
+            m_res_eQOP[ipar].push_back(NaNfloat);
+            m_res_eT[ipar].push_back(NaNfloat);
+            m_err_eLOC0[ipar].push_back(NaNfloat);
+            m_err_eLOC1[ipar].push_back(NaNfloat);
+            m_err_ePHI[ipar].push_back(NaNfloat);
+            m_err_eTHETA[ipar].push_back(NaNfloat);
+            m_err_eQOP[ipar].push_back(NaNfloat);
+            m_err_eT[ipar].push_back(NaNfloat);
+            m_pull_eLOC0[ipar].push_back(NaNfloat);
+            m_pull_eLOC1[ipar].push_back(NaNfloat);
+            m_pull_ePHI[ipar].push_back(NaNfloat);
+            m_pull_eTHETA[ipar].push_back(NaNfloat);
+            m_pull_eQOP[ipar].push_back(NaNfloat);
+            m_pull_eT[ipar].push_back(NaNfloat);
+            m_x[ipar].push_back(NaNfloat);
+            m_y[ipar].push_back(NaNfloat);
+            m_z[ipar].push_back(NaNfloat);
+            m_px[ipar].push_back(NaNfloat);
+            m_py[ipar].push_back(NaNfloat);
+            m_pz[ipar].push_back(NaNfloat);
+            m_pT[ipar].push_back(NaNfloat);
+            m_eta[ipar].push_back(NaNfloat);
+          }
+          // fill the track parameters status
+          m_hasParams[ipar].push_back(hasParams[ipar]);
+        }
+
+        // fill the chi2
+        m_chi2.push_back(state.chi2());
+
+        return true;
+      });  // all states
+
+      // fill the variables for one track to tree
+      m_outputTree->Fill();
+
+      // now reset
+      m_t_x.clear();
+      m_t_y.clear();
+      m_t_z.clear();
+      m_t_dx.clear();
+      m_t_dy.clear();
+      m_t_dz.clear();
+      m_t_eLOC0.clear();
+      m_t_eLOC1.clear();
+      m_t_ePHI.clear();
+      m_t_eTHETA.clear();
+      m_t_eQOP.clear();
+      m_t_eT.clear();
+
+      m_volumeID.clear();
+      m_layerID.clear();
+      m_moduleID.clear();
+      m_station.clear();
+      m_layer.clear();
+      m_phi_module.clear();
+      m_eta_module.clear();
+      m_side.clear();
+      m_pathLength.clear();
+      m_lx_hit.clear();
+      m_ly_hit.clear();
+      m_x_hit.clear();
+      m_y_hit.clear();
+      m_z_hit.clear();
+      m_res_x_hit.clear();
+      m_res_y_hit.clear();
+      m_err_x_hit.clear();
+      m_err_y_hit.clear();
+      m_pull_x_hit.clear();
+      m_pull_y_hit.clear();
+      m_dim_hit.clear();
+
+      for (unsigned int ipar = 0; ipar < 3; ++ipar) {
+        m_hasParams[ipar].clear();
+        m_eLOC0[ipar].clear();
+        m_eLOC1[ipar].clear();
+        m_ePHI[ipar].clear();
+        m_eTHETA[ipar].clear();
+        m_eQOP[ipar].clear();
+        m_eT[ipar].clear();
+        m_res_eLOC0[ipar].clear();
+        m_res_eLOC1[ipar].clear();
+        m_res_ePHI[ipar].clear();
+        m_res_eTHETA[ipar].clear();
+        m_res_eQOP[ipar].clear();
+        m_res_eT[ipar].clear();
+        m_err_eLOC0[ipar].clear();
+        m_err_eLOC1[ipar].clear();
+        m_err_ePHI[ipar].clear();
+        m_err_eTHETA[ipar].clear();
+        m_err_eQOP[ipar].clear();
+        m_err_eT[ipar].clear();
+        m_pull_eLOC0[ipar].clear();
+        m_pull_eLOC1[ipar].clear();
+        m_pull_ePHI[ipar].clear();
+        m_pull_eTHETA[ipar].clear();
+        m_pull_eQOP[ipar].clear();
+        m_pull_eT[ipar].clear();
+        m_x[ipar].clear();
+        m_y[ipar].clear();
+        m_z[ipar].clear();
+        m_px[ipar].clear();
+        m_py[ipar].clear();
+        m_pz[ipar].clear();
+        m_eta[ipar].clear();
+        m_pT[ipar].clear();
+      }
+
+      m_chi2.clear();
+    }  // all subtrajectories
+  }    // all trajectories
+
+  return StatusCode::SUCCESS;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/RootTrajectorySummaryWriterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/RootTrajectorySummaryWriterTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..76d76029674dcccbe48c41f5cf31b82b7292ba1b
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/RootTrajectorySummaryWriterTool.cxx
@@ -0,0 +1,466 @@
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h"
+#include "Acts/EventData/MultiTrajectory.hpp"
+#include "Acts/EventData/MultiTrajectoryHelpers.hpp"
+#include "Acts/EventData/detail/TransformationBoundToFree.hpp"
+#include "Acts/Utilities/Helpers.hpp"
+#include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "GeoPrimitives/CLHEPtoEigenConverter.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include "FaserActsKalmanFilter/TrackClassification.h"
+#include "HepMC/GenParticle.h"
+#include "HepMC/GenVertex.h"
+#include <TFile.h>
+#include <TTree.h>
+
+/// NaN values for TTree variables
+constexpr float NaNfloat = std::numeric_limits<float>::quiet_NaN();
+constexpr float NaNint = std::numeric_limits<int>::quiet_NaN();
+
+using ConstTrackStateProxy = Acts::detail_lt::TrackStateProxy<IndexSourceLink, 6, true>;
+
+using Acts::VectorHelpers::eta;
+using Acts::VectorHelpers::perp;
+using Acts::VectorHelpers::phi;
+using Acts::VectorHelpers::theta;
+
+RootTrajectorySummaryWriterTool::RootTrajectorySummaryWriterTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : AthAlgTool(type, name, parent) {}
+
+
+StatusCode RootTrajectorySummaryWriterTool::initialize() {
+  ATH_CHECK(m_simDataCollectionKey.initialize());
+  ATH_CHECK(m_mcEventCollectionKey.initialize());
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+
+  if (!m_noDiagnostics) {
+    std::string filePath = m_filePath;
+    std::string treeName = m_treeName;
+    m_outputFile = TFile::Open(filePath.c_str(), "RECREATE");
+    if (m_outputFile == nullptr) {
+      ATH_MSG_WARNING("Unable to open output file at " << m_filePath);
+      return StatusCode::RECOVERABLE;
+    }
+    m_outputFile->cd();
+    m_outputTree = new TTree(treeName.c_str(), treeName.c_str());
+    if (m_outputTree == nullptr) {
+      ATH_MSG_ERROR("Unable to create TTree");
+      return StatusCode::FAILURE;
+    }
+
+    m_outputTree = new TTree("tree", "tree");
+
+    m_outputTree->Branch("event_nr", &m_eventNr);
+    m_outputTree->Branch("multiTraj_nr", &m_multiTrajNr);
+    m_outputTree->Branch("subTraj_nr", &m_subTrajNr);
+
+    m_outputTree->Branch("nStates", &m_nStates);
+    m_outputTree->Branch("nMeasurements", &m_nMeasurements);
+    m_outputTree->Branch("nOutliers", &m_nOutliers);
+    m_outputTree->Branch("nHoles", &m_nHoles);
+    m_outputTree->Branch("nSharedHits", &m_nSharedHits);
+    m_outputTree->Branch("chi2Sum", &m_chi2Sum);
+    m_outputTree->Branch("NDF", &m_NDF);
+    m_outputTree->Branch("measurementChi2", &m_measurementChi2);
+    m_outputTree->Branch("outlierChi2", &m_outlierChi2);
+    m_outputTree->Branch("measurementVolume", &m_measurementVolume);
+    m_outputTree->Branch("measurementLayer", &m_measurementLayer);
+    m_outputTree->Branch("outlierVolume", &m_outlierVolume);
+    m_outputTree->Branch("outlierLayer", &m_outlierLayer);
+
+    m_outputTree->Branch("nMajorityHits", &m_nMajorityHits);
+    m_outputTree->Branch("majorityParticleId", &m_majorityParticleId);
+    m_outputTree->Branch("t_charge", &m_t_charge);
+    m_outputTree->Branch("t_time", &m_t_time);
+    m_outputTree->Branch("t_vx", &m_t_vx);
+    m_outputTree->Branch("t_vy", &m_t_vy);
+    m_outputTree->Branch("t_vz", &m_t_vz);
+    m_outputTree->Branch("t_px", &m_t_px);
+    m_outputTree->Branch("t_py", &m_t_py);
+    m_outputTree->Branch("t_pz", &m_t_pz);
+    m_outputTree->Branch("t_theta", &m_t_theta);
+    m_outputTree->Branch("t_phi", &m_t_phi);
+    m_outputTree->Branch("t_eta", &m_t_eta);
+    m_outputTree->Branch("t_p", &m_t_p);
+    m_outputTree->Branch("t_pT", &m_t_pT);
+
+    m_outputTree->Branch("hasFittedParams", &m_hasFittedParams);
+    m_outputTree->Branch("eLOC0_fit", &m_eLOC0_fit);
+    m_outputTree->Branch("eLOC1_fit", &m_eLOC1_fit);
+    m_outputTree->Branch("ePHI_fit", &m_ePHI_fit);
+    m_outputTree->Branch("eTHETA_fit", &m_eTHETA_fit);
+    m_outputTree->Branch("eQOP_fit", &m_eQOP_fit);
+    m_outputTree->Branch("eT_fit", &m_eT_fit);
+    m_outputTree->Branch("err_eLOC0_fit", &m_err_eLOC0_fit);
+    m_outputTree->Branch("err_eLOC1_fit", &m_err_eLOC1_fit);
+    m_outputTree->Branch("err_ePHI_fit", &m_err_ePHI_fit);
+    m_outputTree->Branch("err_eTHETA_fit", &m_err_eTHETA_fit);
+    m_outputTree->Branch("err_eQOP_fit", &m_err_eQOP_fit);
+    m_outputTree->Branch("err_eT_fit", &m_err_eT_fit);
+    m_outputTree->Branch("res_eLOC0_fit", &m_res_eLOC0_fit);
+    m_outputTree->Branch("res_eLOC1_fit", &m_res_eLOC1_fit);
+    m_outputTree->Branch("res_ePHI_fit", &m_res_ePHI_fit);
+    m_outputTree->Branch("res_eTHETA_fit", &m_res_eTHETA_fit);
+    m_outputTree->Branch("res_eQOP_fit", &m_res_eQOP_fit);
+    m_outputTree->Branch("res_eT_fit", &m_res_eT_fit);
+    m_outputTree->Branch("pull_eLOC0_fit", &m_pull_eLOC0_fit);
+    m_outputTree->Branch("pull_eLOC1_fit", &m_pull_eLOC1_fit);
+    m_outputTree->Branch("pull_ePHI_fit", &m_pull_ePHI_fit);
+    m_outputTree->Branch("pull_eTHETA_fit", &m_pull_eTHETA_fit);
+    m_outputTree->Branch("pull_eQOP_fit", &m_pull_eQOP_fit);
+    m_outputTree->Branch("pull_eT_fit", &m_pull_eT_fit);
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode RootTrajectorySummaryWriterTool::finalize() {
+  if (!m_noDiagnostics) {
+    m_outputFile->cd();
+    m_outputTree->Write();
+    m_outputFile->Close();
+  }
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode RootTrajectorySummaryWriterTool::write(
+    const Acts::GeometryContext& geoContext, const TrajectoriesContainer& trajectories, bool isMC) const {
+  EventContext ctx = Gaudi::Hive::currentContext();
+
+  std::shared_ptr<TrackerSimDataCollection> simData {nullptr};
+  std::map<int, const HepMC::GenParticle*> particles {};
+
+  if (isMC) {
+    SG::ReadHandle<TrackerSimDataCollection> simDataHandle {m_simDataCollectionKey, ctx};
+    ATH_CHECK(simDataHandle.isValid());
+    simData = std::make_shared<TrackerSimDataCollection>(*simDataHandle);
+
+    SG::ReadHandle<McEventCollection> mcEvents {m_mcEventCollectionKey, ctx};
+    ATH_CHECK(mcEvents.isValid());
+    if (mcEvents->size() != 1) {
+      ATH_MSG_ERROR("There should be exactly one event in the McEventCollection.");
+      return StatusCode::FAILURE;
+    }
+
+    for (const HepMC::GenParticle* particle : mcEvents->front()->particle_range()) {
+      particles[particle->barcode()] = particle;
+    }
+  }
+
+  // For each particle within a track, how many hits did it contribute
+  std::vector<ParticleHitCount> particleHitCounts;
+
+  // Get the event number
+  m_eventNr = ctx.eventID().event_number();
+
+  // Loop over the trajectories
+  for (size_t itraj = 0; itraj < trajectories.size(); ++itraj) {
+    const auto& traj = trajectories[itraj];
+
+    if (traj.empty()) {
+      ATH_MSG_WARNING("Empty trajectories object " << itraj);
+      continue;
+    }
+
+    // The trajectory index
+    m_multiTrajNr.push_back(itraj);
+
+    // The trajectory entry indices and the multiTrajectory
+    const auto& mj = traj.multiTrajectory();
+    const auto& trackTips = traj.tips();
+
+    // Loop over the entry indices for the subtrajectories
+    for (unsigned int isubtraj = 0; isubtraj < trackTips.size(); ++isubtraj) {
+      // The subtrajectory index
+      m_subTrajNr.push_back(isubtraj);
+      // The entry index for this subtrajectory
+      const auto& trackTip = trackTips[isubtraj];
+
+      // Collect the trajectory summary info
+      auto trajState = Acts::MultiTrajectoryHelpers::trajectoryState(mj, trackTip);
+      m_nStates.push_back(trajState.nStates);
+      m_nMeasurements.push_back(trajState.nMeasurements);
+      m_nOutliers.push_back(trajState.nOutliers);
+      m_nHoles.push_back(trajState.nHoles);
+      m_nSharedHits.push_back(trajState.nSharedHits);
+      m_chi2Sum.push_back(trajState.chi2Sum);
+      m_NDF.push_back(trajState.NDF);
+      m_measurementChi2.push_back(trajState.measurementChi2);
+      m_outlierChi2.push_back(trajState.measurementChi2);
+      // They are stored as double (as the vector of vector of int is not known to ROOT)
+      m_measurementVolume.emplace_back(trajState.measurementVolume.begin(), trajState.measurementVolume.end());
+      m_measurementLayer.emplace_back(trajState.measurementLayer.begin(), trajState.measurementLayer.end());
+      m_outlierVolume.emplace_back(trajState.outlierVolume.begin(), trajState.outlierVolume.end());
+      m_outlierLayer.emplace_back(trajState.outlierLayer.begin(), trajState.outlierLayer.end());
+
+      // Initialize the truth particle info
+      uint64_t majorityParticleId = NaNint;
+      unsigned int nMajorityHits = NaNint;
+      float t_charge = NaNint;
+      float t_time = NaNfloat;
+      float t_vlx =  NaNfloat;
+      float t_vly = NaNfloat;
+      float t_vx = NaNfloat;
+      float t_vy = NaNfloat;
+      float t_vz = NaNfloat;
+      float t_px = NaNfloat;
+      float t_py = NaNfloat;
+      float t_pz = NaNfloat;
+      float t_theta = NaNfloat;
+      float t_phi = NaNfloat;
+      float t_eta = NaNfloat;
+      float t_p = NaNfloat;
+      float t_pT = NaNfloat;
+
+      // Get the perigee surface
+      Acts::Surface* pSurface = nullptr;
+      if (traj.hasTrackParameters(trackTip)) {
+        const auto& boundParam = traj.trackParameters(trackTip);
+        pSurface = const_cast<Acts::Surface*>(&boundParam.referenceSurface());
+      }
+
+      if (isMC) {
+        // Get the majority truth particle to this track
+        ATH_MSG_VERBOSE("get majority truth particle");
+        identifyContributingParticles(*simData, traj, trackTip, particleHitCounts);
+        for (const auto& particle : particleHitCounts) {
+          ATH_MSG_VERBOSE(particle.particleId << ": " << particle.hitCount << " hits");
+        }
+
+        bool foundMajorityParticle = false;
+        // Get the truth particle info
+        if (not particleHitCounts.empty()) {
+          // Get the barcode of the majority truth particle
+          majorityParticleId = particleHitCounts.front().particleId;
+          nMajorityHits = particleHitCounts.front().hitCount;
+
+          // Find the truth particle via the barcode
+          auto ip = particles.find(majorityParticleId);
+          if (ip != particles.end()) {
+            foundMajorityParticle = true;
+
+            const HepMC::GenParticle* particle = ip->second;
+            ATH_MSG_DEBUG("Find the truth particle with barcode = " << majorityParticleId);
+
+            // extrapolate parameters from vertex to reference surface at origin.
+            std::unique_ptr<const Acts::BoundTrackParameters> truthParameters
+                = extrapolateToReferenceSurface(ctx, particle);
+            if (!truthParameters) {
+              continue;
+            }
+            // Get the truth particle info at vertex
+            // const HepMC::GenVertex* vertex = particle->production_vertex();
+            t_p = truthParameters->momentum().mag();
+            t_charge = truthParameters->charge();
+            t_time = truthParameters->time();
+            t_vx = truthParameters->position(geoContext).x();
+            t_vy = truthParameters->position(geoContext).y();
+            t_vz = truthParameters->position(geoContext).z();
+            t_px = truthParameters->momentum().x();
+            t_py = truthParameters->momentum().y();
+            t_pz = truthParameters->momentum().z();
+            t_theta = theta(truthParameters->momentum().normalized());
+            t_phi = phi(truthParameters->momentum().normalized());
+            t_eta = eta(truthParameters->momentum().normalized());
+            t_pT = t_p * perp(truthParameters->momentum().normalized());
+
+            auto unitDirection = Acts::Vector3(t_px, t_py, t_pz).normalized();
+            auto positon = Acts::Vector3 (t_vx, t_vy, t_vz);
+
+            if (pSurface) {
+              // get the truth perigee parameter
+              auto lpResult = pSurface->globalToLocal(geoContext, positon, unitDirection);
+              if (lpResult.ok()) {
+                t_vlx = lpResult.value()[Acts::BoundIndices::eBoundLoc0];
+                t_vly = lpResult.value()[Acts::BoundIndices::eBoundLoc1];
+              } else {
+                ATH_MSG_ERROR("Global to local transformation did not succeed.");
+              }
+            }
+          } else {
+            ATH_MSG_WARNING("Truth particle with barcode = " << majorityParticleId << " not found in the input collection!");
+          }
+        }
+        if (not foundMajorityParticle) {
+          ATH_MSG_WARNING("Truth particle for mj " << itraj << " subtraj " << isubtraj << " not found!");
+        }
+      }
+
+      // Push the corresponding truth particle info for the track.
+      // Always push back even if majority particle not found
+      m_majorityParticleId.push_back(majorityParticleId);
+      m_nMajorityHits.push_back(nMajorityHits);
+      m_t_charge.push_back(t_charge);
+      m_t_time.push_back(t_time);
+      m_t_vx.push_back(t_vx);
+      m_t_vy.push_back(t_vy);
+      m_t_vz.push_back(t_vz);
+      m_t_px.push_back(t_px);
+      m_t_py.push_back(t_py);
+      m_t_pz.push_back(t_pz);
+      m_t_theta.push_back(t_theta);
+      m_t_phi.push_back(t_phi);
+      m_t_eta.push_back(t_eta);
+      m_t_p.push_back(t_p);
+      m_t_pT.push_back(t_pT);
+
+      // Initialize the fitted track parameters info
+      std::array<float, Acts::eBoundSize> param = {NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat};
+      std::array<float, Acts::eBoundSize> res = {NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat};
+      std::array<float, Acts::eBoundSize> error = {NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat};
+      std::array<float, Acts::eBoundSize> pull = {NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat, NaNfloat};
+      bool hasFittedParams = false;
+      if (traj.hasTrackParameters(trackTip)) {
+        hasFittedParams = true;
+        const auto& boundParam = traj.trackParameters(trackTip);
+        const auto& parameter = boundParam.parameters();
+        for (unsigned int i = 0; i < Acts::eBoundSize; ++i) {
+          param[i] = parameter[i];
+        }
+
+        res = {param[Acts::eBoundLoc0] - t_vlx,
+               param[Acts::eBoundLoc1] - t_vly,
+               param[Acts::eBoundPhi] - t_phi,
+               param[Acts::eBoundTheta] - t_theta,
+               param[Acts::eBoundQOverP] - t_charge / t_p,
+               param[Acts::eBoundTime] - t_time};
+
+        if (boundParam.covariance().has_value()) {
+          const auto& covariance = *boundParam.covariance();
+          for (unsigned int i = 0; i < Acts::eBoundSize; ++i) {
+            error[i] = std::sqrt(covariance(i, i));
+            pull[i] = res[i] / error[i];
+          }
+        }
+      }
+
+      // Push the fitted track parameters.
+      // Always push back even if no fitted track parameters
+      m_eLOC0_fit.push_back(param[Acts::eBoundLoc0]);
+      m_eLOC1_fit.push_back(param[Acts::eBoundLoc1]);
+      m_ePHI_fit.push_back(param[Acts::eBoundPhi]);
+      m_eTHETA_fit.push_back(param[Acts::eBoundTheta]);
+      m_eQOP_fit.push_back(param[Acts::eBoundQOverP]);
+      m_eT_fit.push_back(param[Acts::eBoundTime]);
+
+      m_res_eLOC0_fit.push_back(res[Acts::eBoundLoc0]);
+      m_res_eLOC1_fit.push_back(res[Acts::eBoundLoc1]);
+      m_res_ePHI_fit.push_back(res[Acts::eBoundPhi]);
+      m_res_eTHETA_fit.push_back(res[Acts::eBoundTheta]);
+      m_res_eQOP_fit.push_back(res[Acts::eBoundQOverP]);
+      m_res_eT_fit.push_back(res[Acts::eBoundTime]);
+
+      m_err_eLOC0_fit.push_back(error[Acts::eBoundLoc0]);
+      m_err_eLOC1_fit.push_back(error[Acts::eBoundLoc1]);
+      m_err_ePHI_fit.push_back(error[Acts::eBoundPhi]);
+      m_err_eTHETA_fit.push_back(error[Acts::eBoundTheta]);
+      m_err_eQOP_fit.push_back(error[Acts::eBoundQOverP]);
+      m_err_eT_fit.push_back(error[Acts::eBoundTime]);
+
+      m_pull_eLOC0_fit.push_back(pull[Acts::eBoundLoc0]);
+      m_pull_eLOC1_fit.push_back(pull[Acts::eBoundLoc1]);
+      m_pull_ePHI_fit.push_back(pull[Acts::eBoundPhi]);
+      m_pull_eTHETA_fit.push_back(pull[Acts::eBoundTheta]);
+      m_pull_eQOP_fit.push_back(pull[Acts::eBoundQOverP]);
+      m_pull_eT_fit.push_back(pull[Acts::eBoundTime]);
+
+      m_hasFittedParams.push_back(hasFittedParams);
+    }  // all subtrajectories
+  }    // all trajectories
+
+
+  // fill the variables
+  m_outputTree->Fill();
+
+  m_multiTrajNr.clear();
+  m_subTrajNr.clear();
+  m_nStates.clear();
+  m_nMeasurements.clear();
+  m_nOutliers.clear();
+  m_nHoles.clear();
+  m_nSharedHits.clear();
+  m_chi2Sum.clear();
+  m_NDF.clear();
+  m_measurementChi2.clear();
+  m_outlierChi2.clear();
+  m_measurementVolume.clear();
+  m_measurementLayer.clear();
+  m_outlierVolume.clear();
+  m_outlierLayer.clear();
+
+  m_nMajorityHits.clear();
+  m_majorityParticleId.clear();
+  m_t_charge.clear();
+  m_t_time.clear();
+  m_t_vx.clear();
+  m_t_vy.clear();
+  m_t_vz.clear();
+  m_t_px.clear();
+  m_t_py.clear();
+  m_t_pz.clear();
+  m_t_theta.clear();
+  m_t_phi.clear();
+  m_t_p.clear();
+  m_t_pT.clear();
+  m_t_eta.clear();
+
+  m_hasFittedParams.clear();
+  m_eLOC0_fit.clear();
+  m_eLOC1_fit.clear();
+  m_ePHI_fit.clear();
+  m_eTHETA_fit.clear();
+  m_eQOP_fit.clear();
+  m_eT_fit.clear();
+  m_err_eLOC0_fit.clear();
+  m_err_eLOC1_fit.clear();
+  m_err_ePHI_fit.clear();
+  m_err_eTHETA_fit.clear();
+  m_err_eQOP_fit.clear();
+  m_err_eT_fit.clear();
+  m_res_eLOC0_fit.clear();
+  m_res_eLOC1_fit.clear();
+  m_res_ePHI_fit.clear();
+  m_res_eTHETA_fit.clear();
+  m_res_eQOP_fit.clear();
+  m_res_eT_fit.clear();
+  m_pull_eLOC0_fit.clear();
+  m_pull_eLOC1_fit.clear();
+  m_pull_ePHI_fit.clear();
+  m_pull_eTHETA_fit.clear();
+  m_pull_eQOP_fit.clear();
+  m_pull_eT_fit.clear();
+
+  return StatusCode::SUCCESS;
+}
+
+
+std::unique_ptr<const Acts::BoundTrackParameters> RootTrajectorySummaryWriterTool::extrapolateToReferenceSurface(
+    const EventContext& ctx, const HepMC::GenParticle* particle) const {
+  const HepMC::FourVector &vertex = particle->production_vertex()->position();
+  const HepMC::FourVector &momentum = particle->momentum();
+
+  // The coordinate system of the Acts::PlaneSurface is defined as
+  // T = Z = normal, U = X x T = -Y, V = T x U = x
+  auto startSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3(0, 0, vertex.z()), Acts::Vector3(0, 0, 1));
+  auto targetSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3(0, 0, 0), Acts::Vector3(0, 0, -1));
+  Acts::BoundVector params = Acts::BoundVector::Zero();
+  params[Acts::eBoundLoc0] = -vertex.y();
+  params[Acts::eBoundLoc1] = vertex.x();
+  params[Acts::eBoundPhi] = momentum.phi();
+  params[Acts::eBoundTheta] = momentum.theta();
+  // FIXME get charge of HepMC::GenParticle, the following does not work, e.g. for pions
+  double charge = particle->pdg_id() > 0 ? -1 : 1;
+  double MeV2GeV = 1e-3;
+  params[Acts::eBoundQOverP] = charge / (momentum.rho() * MeV2GeV);
+  params[Acts::eBoundTime] = vertex.t();
+  Acts::BoundTrackParameters startParameters(std::move(startSurface), params, charge);
+  std::unique_ptr<const Acts::BoundTrackParameters> targetParameters =
+      m_extrapolationTool->propagate(ctx, startParameters, *targetSurface);
+  return targetParameters;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/SeedingAlg.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/SeedingAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5325703b0ba3599a1fdcdafb350850ddc621c555
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/SeedingAlg.cxx
@@ -0,0 +1,22 @@
+#include "FaserActsKalmanFilter/SeedingAlg.h"
+
+SeedingAlg::SeedingAlg(const std::string& name, ISvcLocator* pSvcLocator) :
+    AthAlgorithm(name, pSvcLocator) {}
+
+
+StatusCode SeedingAlg::initialize() {
+  ATH_CHECK(m_trackSeedTool.retrieve());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode SeedingAlg::execute() {
+  // const EventContext& ctx = Gaudi::Hive::currentContext();
+  ATH_CHECK(m_trackSeedTool->run());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode SeedingAlg::finalize() {
+  return StatusCode::SUCCESS;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/SegmentFitClusterTrackFinderTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/SegmentFitClusterTrackFinderTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..b12215257197a931266e251620f2c63ecef7b10a
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/SegmentFitClusterTrackFinderTool.cxx
@@ -0,0 +1,151 @@
+#include "FaserActsKalmanFilter/SegmentFitClusterTrackFinderTool.h"
+
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/Measurement.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "Identifier/Identifier.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrkTrack/Track.h"
+#include "TrkTrack/TrackStateOnSurface.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+//#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include <random>
+
+
+SegmentFitClusterTrackFinderTool::SegmentFitClusterTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode SegmentFitClusterTrackFinderTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackCollection.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode SegmentFitClusterTrackFinderTool::run() {
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+  ATH_CHECK(trackCollection.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+
+  // create source links and measurements
+  const int kSize = 1;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0};
+  std::vector<IndexSourceLink> sourceLinks;
+  std::vector<Measurement> measurements;
+  std::map<int, Amg::Vector3D> positions;
+  std::map<int, Amg::Vector3D> directions;
+  std::map<Index, Identifier> identifierLinkMap;
+  std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+  // TODO only take best track (most hits, smallest chi2)
+  // for now I assume that there is only one track in each station
+  for (const Trk::Track* track : *trackCollection) {
+    auto fitParameters = track->trackParameters()->front();
+    const Amg::Vector3D fitPosition = fitParameters->position();
+    const Amg::Vector3D fitMomentum = fitParameters->momentum();
+    Identifier id;
+    for (const Trk::TrackStateOnSurface* state : *(track->trackStateOnSurfaces())) {
+      auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (
+          state->measurementOnTrack());
+      if (clusterOnTrack) {
+        const Tracker::FaserSCT_Cluster* cluster = clusterOnTrack->prepRawData();
+        id = cluster->detectorElement()->identify();
+        // create source link
+        Acts::GeometryIdentifier geoId = identifierMap->at(id);
+        ATH_MSG_DEBUG(geoId);
+        ATH_MSG_DEBUG(m_idHelper->station(id) << "/" <<
+          m_idHelper->layer(id) << "/" <<
+          m_idHelper->phi_module(id) << "/" <<
+          m_idHelper->eta_module(id) << "/" <<
+          m_idHelper->side(id)
+        );
+        identifierLinkMap[measurements.size()] = id;
+        IndexSourceLink sourceLink(geoId, measurements.size());
+        // create measurement
+        const auto& par = cluster->localPosition();
+        const auto& cov = cluster->localCovariance();
+        Eigen::Matrix<double, 1, 1> myCov {m_sigmaCluster * m_sigmaCluster,};
+        ThisMeasurement meas(sourceLink, Indices, par.head(1), myCov);
+//        ThisMeasurement meas(sourceLink, Indices, par.head(1), cov.block<1,1>(0,0));
+        sourceLinks.push_back(sourceLink);
+        measurements.emplace_back(std::move(meas));
+        clusters.push_back(cluster);
+      }
+    }
+    int station = m_idHelper->station(id);
+    positions[station] = fitPosition;
+    directions[station] = fitMomentum;
+  }
+  std::vector<std::vector<IndexSourceLink>> sourceLinkVector {sourceLinks};
+  m_sourceLinks = std::make_shared<std::vector<std::vector<IndexSourceLink>>>(sourceLinkVector);
+  std::vector<std::vector<Measurement>> measurementVector {measurements};
+  m_measurements = std::make_shared<std::vector<std::vector<Measurement>>>(measurementVector);
+  std::vector<std::map<Index, Identifier>> idLinkVector {identifierLinkMap};
+  m_idLinks = std::make_shared<std::vector<std::map<Index, Identifier>>>(idLinkVector);
+  std::vector<std::vector<const Tracker::FaserSCT_Cluster*>> clusterVector {clusters};
+  m_clusters = std::make_shared<std::vector<std::vector<const Tracker::FaserSCT_Cluster*>>>(clusterVector);
+
+  // TODO how should we handle events without a track in each station?
+  if (positions.size() != 3) {
+    return StatusCode::RECOVERABLE;
+  }
+
+  // create initial surface
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, 0}, Acts::Vector3{0, 0, 1});
+
+  // create initial parameters
+  Acts::Vector3 pos = positions[1] - positions[1].z()/directions[1].z() * directions[1];
+  Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+  Acts::Vector3 dir = positions[2] - positions[1];
+  auto [abs_momentum, charge] = momentum(positions);
+//  Acts::Vector3 pos = positions[1] - positions[1].z()/directions[1].z() * directions[1];
+//  Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+//  Acts::Vector3 dir = {directions[1].x(), directions[1].y(), directions[1].z()};
+//  double abs_momentum = 1000;
+//  double charge = 1;
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  auto initialParameters = Acts::CurvilinearTrackParameters(
+      pos4, dir, abs_momentum, charge, cov);
+  std::vector<Acts::CurvilinearTrackParameters> paramVector {initialParameters};
+  m_initialTrackParameters = std::make_shared<std::vector<Acts::CurvilinearTrackParameters>>(paramVector);
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode SegmentFitClusterTrackFinderTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+std::pair<double, double> SegmentFitClusterTrackFinderTool::momentum(const std::map<int, Amg::Vector3D>& pos, double B) {
+  Acts::Vector3 vec_l = pos.at(3) - pos.at(1);
+  double abs_l = std::sqrt(vec_l.y() * vec_l.y() + vec_l.z() * vec_l.z());
+  double t = (pos.at(2).z() - pos.at(1).z()) / (pos.at(3).z() - pos.at(1).z());
+  Acts::Vector3 vec_m = pos.at(1) + t * vec_l;
+  Acts::Vector3 vec_s = pos.at(2) - vec_m;
+  double abs_s = std::sqrt(vec_s.y() * vec_s.y() + vec_s.z() * vec_s.z());
+  double p_yz = 0.3 * abs_l * abs_l * B / (8 * abs_s * 1000);
+  double charge = vec_s.y() < 0 ? 1 : -1;
+  return std::make_pair(p_yz, charge);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/SegmentFitTrackFinderTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/SegmentFitTrackFinderTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0aa77c3e8b3f18a75b55aafb730e216e388c10f5
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/SegmentFitTrackFinderTool.cxx
@@ -0,0 +1,152 @@
+#include "FaserActsKalmanFilter/SegmentFitTrackFinderTool.h"
+
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/Measurement.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "Identifier/Identifier.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrkTrack/Track.h"
+#include "TrkTrack/TrackStateOnSurface.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include <random>
+
+
+SegmentFitTrackFinderTool::SegmentFitTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode SegmentFitTrackFinderTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackCollection.initialize());
+  ATH_CHECK(m_spacePointContainerKey.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode SegmentFitTrackFinderTool::run() {
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+  ATH_CHECK(trackCollection.isValid());
+
+  SG::ReadHandle<FaserSCT_SpacePointContainer> spacePointContainer {m_spacePointContainerKey};
+  ATH_CHECK(spacePointContainer.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+
+  // create vector of cluster identifiers which have been used by any track fit
+  std::vector<Identifier> trackIds;
+  std::map<int, Amg::Vector3D> positions;
+  std::map<int, Amg::Vector3D> directions;
+  // TODO only take best track (most hits, smallest chi2)
+  // for now I assume that there is only one track in each station
+  for (const Trk::Track* track : *trackCollection) {
+    auto fitParameters = track->trackParameters()->front();
+    const Amg::Vector3D fitPosition = fitParameters->position();
+    const Amg::Vector3D fitMomentum = fitParameters->momentum();
+    Identifier id;
+    for (const Trk::TrackStateOnSurface* state : *(track->trackStateOnSurfaces())) {
+      auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (
+          state->measurementOnTrack());
+      if (clusterOnTrack) {
+        id = clusterOnTrack->identify();
+        trackIds.push_back(id);
+      }
+    }
+    int station = m_idHelper->station(id);
+    positions[station] = fitPosition;
+    directions[station] = fitMomentum;
+  }
+
+  // create source links and measurements
+  const int kSize = 2;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0, Acts::eBoundLoc1};
+  std::vector<IndexSourceLink> sourceLinks;
+  std::vector<Measurement> measurements;
+  std::vector<Tracker::FaserSCT_SpacePoint> spacePoints {};
+  for (const FaserSCT_SpacePointCollection* spacePointCollection : *spacePointContainer) {
+    for (const Tracker::FaserSCT_SpacePoint *spacePoint: *spacePointCollection) {
+      Identifier id1 = spacePoint->cluster1()->identify();
+      Identifier id2 = spacePoint->cluster2()->identify();
+      // check if both clusters have been used to reconstruct track
+      if (std::find(trackIds.begin(), trackIds.end(), id1) != trackIds.end() &&
+          std::find(trackIds.begin(), trackIds.end(), id2) != trackIds.end()) {
+        spacePoints.push_back(*spacePoint);
+        Identifier id = spacePoint->associatedSurface().associatedDetectorElementIdentifier();
+        // create source link
+        Acts::GeometryIdentifier geoId = identifierMap->at(id);
+        IndexSourceLink sourceLink(geoId, measurements.size());
+        // create measurement
+        const auto& par = spacePoint->localParameters();
+        const auto& cov = spacePoint->localCovariance();
+        ThisMeasurement meas(sourceLink, Indices, par, cov);
+        sourceLinks.push_back(sourceLink);
+        measurements.emplace_back(std::move(meas));
+      }
+    }
+  }
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourceLinks);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+  m_spacePoints = std::make_shared<std::vector<Tracker::FaserSCT_SpacePoint>>(spacePoints);
+
+  // TODO how should we handle events without a track in each station?
+  if (positions.size() != 3) {
+    return StatusCode::RECOVERABLE;
+  }
+
+  // create initial surface
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, 0}, Acts::Vector3{0, 0, 1});
+
+  // create initial parameters
+  Acts::Vector3 pos = positions[1] - positions[1].z()/directions[1].z() * directions[1];
+  Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+  Acts::Vector3 dir = positions[2] - positions[1];
+  Acts::Vector3 tmp {directions[1].x(), directions[1].y(), directions[1].z()};
+  ATH_MSG_DEBUG(dir);
+  ATH_MSG_DEBUG(tmp);
+  double abs_momentum = momentum(positions);
+  // TODO get charge from momentum calculation
+  double charge = 1;
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  auto initialParameters = Acts::CurvilinearTrackParameters(
+      pos4, dir, abs_momentum, charge, cov);
+  m_initialTrackParameters = std::make_shared<const Acts::CurvilinearTrackParameters>(initialParameters);
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode SegmentFitTrackFinderTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+double SegmentFitTrackFinderTool::momentum(const std::map<int, Amg::Vector3D>& pos, double B) {
+  Acts::Vector3 vec_l = pos.at(3) - pos.at(1);
+  double abs_l = std::sqrt(vec_l.y() * vec_l.y() + vec_l.z() * vec_l.z());
+  double t = (pos.at(2).z() - pos.at(1).z()) / (pos.at(3).z() - pos.at(1).z());
+  Acts::Vector3 vec_m = pos.at(1) + t * vec_l;
+  Acts::Vector3 vec_s = pos.at(2) - vec_m;
+  double abs_s = std::sqrt(vec_s.y() * vec_s.y() + vec_s.z() * vec_s.z());
+  double p_yz = 0.3 * abs_l * abs_l * B / (8 * abs_s * 1000);
+  return p_yz;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/SummaryPlotTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/SummaryPlotTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f216042a6bbf915f169c41fe9ea0cc9b206f2c73
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/SummaryPlotTool.cxx
@@ -0,0 +1,86 @@
+#include "FaserActsKalmanFilter/SummaryPlotTool.h"
+#include <iostream>
+
+void SummaryPlotTool::book(SummaryPlotTool::SummaryPlotCache &trackSummaryPlotCache) const {
+  PlotHelpers::Binning bEta = m_varBinning.at("Eta");
+  PlotHelpers::Binning bPt = m_varBinning.at("Pt");
+  PlotHelpers::Binning bNum = m_varBinning.at("Num");
+  // number of track states versus eta
+  trackSummaryPlotCache.nStates_vs_eta = PlotHelpers::bookProf(
+      "nStates_vs_eta", "Number of total states vs. #eta", bEta, bNum);
+  // number of measurements versus eta
+  trackSummaryPlotCache.nMeasurements_vs_eta = PlotHelpers::bookProf(
+      "nMeasurements_vs_eta", "Number of measurements vs. #eta", bEta, bNum);
+  // number of holes versus eta
+  trackSummaryPlotCache.nHoles_vs_eta = PlotHelpers::bookProf(
+      "nHoles_vs_eta", "Number of holes vs. #eta", bEta, bNum);
+  // number of outliers versus eta
+  trackSummaryPlotCache.nOutliers_vs_eta = PlotHelpers::bookProf(
+      "nOutliers_vs_eta", "Number of outliers vs. #eta", bEta, bNum);
+  // number of Shared Hits versus eta
+  trackSummaryPlotCache.nSharedHits_vs_eta = PlotHelpers::bookProf(
+      "nSharedHits_vs_eta", "Number of Shared Hits vs. #eta", bEta, bNum);
+  // number of track states versus pt
+  trackSummaryPlotCache.nStates_vs_pt = PlotHelpers::bookProf(
+      "nStates_vs_pT", "Number of total states vs. pT", bPt, bNum);
+  // number of measurements versus pt
+  trackSummaryPlotCache.nMeasurements_vs_pt = PlotHelpers::bookProf(
+      "nMeasurements_vs_pT", "Number of measurements vs. pT", bPt, bNum);
+  // number of holes versus pt
+  trackSummaryPlotCache.nHoles_vs_pt = PlotHelpers::bookProf(
+      "nHoles_vs_pT", "Number of holes vs. pT", bPt, bNum);
+  // number of outliers versus pt
+  trackSummaryPlotCache.nOutliers_vs_pt = PlotHelpers::bookProf(
+      "nOutliers_vs_pT", "Number of outliers vs. pT", bPt, bNum);
+  // number of Shared Hits versus pt
+  trackSummaryPlotCache.nSharedHits_vs_pt = PlotHelpers::bookProf(
+      "nSharedHits_vs_pT", "Number of Shared Hits vs. pT", bPt, bNum);
+}
+
+void SummaryPlotTool::fill(SummaryPlotTool::SummaryPlotCache &trackSummaryPlotCache,
+                           const Acts::BoundTrackParameters &fittedParameters, size_t nStates, size_t nMeasurements,
+                           size_t nOutliers, size_t nHoles, size_t nSharedHits) const {
+  using Acts::VectorHelpers::eta;
+  using Acts::VectorHelpers::perp;
+  const auto& momentum = fittedParameters.momentum();
+  const double fit_eta = eta(momentum);
+  const double fit_pT = perp(momentum);
+
+  PlotHelpers::fillProf(trackSummaryPlotCache.nStates_vs_eta, fit_eta, nStates);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nMeasurements_vs_eta, fit_eta, nMeasurements);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nOutliers_vs_eta, fit_eta, nOutliers);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nHoles_vs_eta, fit_eta, nHoles);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nSharedHits_vs_eta, fit_eta, nSharedHits);
+
+  PlotHelpers::fillProf(trackSummaryPlotCache.nStates_vs_pt, fit_pT, nStates);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nMeasurements_vs_pt, fit_pT, nMeasurements);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nOutliers_vs_pt, fit_pT, nOutliers);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nHoles_vs_pt, fit_pT, nHoles);
+  PlotHelpers::fillProf(trackSummaryPlotCache.nSharedHits_vs_pt, fit_pT, nSharedHits);
+}
+
+void SummaryPlotTool::write(const SummaryPlotTool::SummaryPlotCache &trackSummaryPlotCache) const {
+  trackSummaryPlotCache.nStates_vs_eta->Write();
+  trackSummaryPlotCache.nMeasurements_vs_eta->Write();
+  trackSummaryPlotCache.nOutliers_vs_eta->Write();
+  trackSummaryPlotCache.nHoles_vs_eta->Write();
+  trackSummaryPlotCache.nSharedHits_vs_eta->Write();
+  trackSummaryPlotCache.nStates_vs_pt->Write();
+  trackSummaryPlotCache.nMeasurements_vs_pt->Write();
+  trackSummaryPlotCache.nOutliers_vs_pt->Write();
+  trackSummaryPlotCache.nHoles_vs_pt->Write();
+  trackSummaryPlotCache.nSharedHits_vs_pt->Write();
+}
+
+void SummaryPlotTool::clear(SummaryPlotTool::SummaryPlotCache &trackSummaryPlotCache) const {
+  delete trackSummaryPlotCache.nStates_vs_eta;
+  delete trackSummaryPlotCache.nMeasurements_vs_eta;
+  delete trackSummaryPlotCache.nOutliers_vs_eta;
+  delete trackSummaryPlotCache.nHoles_vs_eta;
+  delete trackSummaryPlotCache.nSharedHits_vs_eta;
+  delete trackSummaryPlotCache.nStates_vs_pt;
+  delete trackSummaryPlotCache.nMeasurements_vs_pt;
+  delete trackSummaryPlotCache.nOutliers_vs_pt;
+  delete trackSummaryPlotCache.nHoles_vs_pt;
+  delete trackSummaryPlotCache.nSharedHits_vs_pt;
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/ThreeStationTrackSeedTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/ThreeStationTrackSeedTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..020c19b9cf4dfcfc1b8a5641d5e6c3765733161d
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/ThreeStationTrackSeedTool.cxx
@@ -0,0 +1,144 @@
+#include "FaserActsKalmanFilter/ThreeStationTrackSeedTool.h"
+#include "TrackerRIO_OnTrack/FaserSCT_ClusterOnTrack.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerPrepRawData/FaserSCT_ClusterCollection.h"
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "Identifier/Identifier.h"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+
+
+ThreeStationTrackSeedTool::ThreeStationTrackSeedTool(
+    const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode ThreeStationTrackSeedTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  ATH_CHECK(m_trackCollection.initialize());
+  ATH_CHECK(m_clusterContainerKey.initialize());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ThreeStationTrackSeedTool::run() {
+  // create track seeds for multiple tracks
+  SG::ReadHandle<TrackCollection> trackCollection {m_trackCollection};
+  ATH_CHECK(trackCollection.isValid());
+
+  SG::ReadHandle<Tracker::FaserSCT_ClusterContainer> clusterContainer {m_clusterContainerKey};
+  ATH_CHECK(clusterContainer.isValid());
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+
+  const int kSize = 1;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> Indices = {Acts::eBoundLoc0};
+  std::vector<IndexSourceLink> sourceLinks;
+  std::vector<Measurement> measurements;
+  std::map<Index, Identifier> identifierLinkMap;
+  std::vector<const Tracker::FaserSCT_Cluster*> clusters {};
+  for (const Tracker::FaserSCT_ClusterCollection* clusterCollection : *clusterContainer) {
+    for (const Tracker::FaserSCT_Cluster* cluster : *clusterCollection) {
+      Identifier id = cluster->detectorElement()->identify();
+      identifierLinkMap[measurements.size()] = id;
+      if (identifierMap->count(id) != 0) {
+        Acts::GeometryIdentifier geoId = identifierMap->at(id);
+        IndexSourceLink sourceLink(geoId, measurements.size(), cluster);
+        // create measurement
+        const auto& par = cluster->localPosition();
+        Eigen::Matrix<double, 1, 1> pos {par.x(),};
+        Eigen::Matrix<double, 1, 1> cov {m_std_cluster * m_std_cluster,};
+        ThisMeasurement meas(sourceLink, Indices, pos, cov);
+        sourceLinks.push_back(sourceLink);
+        measurements.emplace_back(std::move(meas));
+        clusters.push_back(cluster);
+      }
+    }
+  }
+
+
+  std::map<int, std::vector<Tracklet>> station_tracklet_map;
+  for (const Trk::Track* track : *trackCollection) {
+    // auto momentum = track->trackParameters()->front()->momentum();
+    int station = -1;
+    Amg::Vector3D trackletPosition {0, 0, 0};
+    std::vector<const Tracker::FaserSCT_Cluster*> trackletClusters {};
+    for (const Trk::TrackStateOnSurface* trackState : *(track->trackStateOnSurfaces())) {
+      auto clusterOnTrack = dynamic_cast<const Tracker::FaserSCT_ClusterOnTrack*> (trackState->measurementOnTrack());
+      if (clusterOnTrack) {
+        const Tracker::FaserSCT_Cluster* cluster = clusterOnTrack->prepRawData();
+        trackletClusters.push_back(cluster);
+        if (station == -1) {
+          Identifier id = clusterOnTrack->identify();
+          station = m_idHelper->station(id);
+          auto fitParameters = track->trackParameters()->front();
+          trackletPosition = fitParameters->position();
+        }
+      }
+    }
+    station_tracklet_map[station].push_back(Tracklet(trackletPosition, trackletClusters));
+  }
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  std::vector<Acts::CurvilinearTrackParameters> initParams {};
+  std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>> seeds;
+  for (const auto& tracklet1 : station_tracklet_map[1]) {
+    for (const auto& tracklet2 : station_tracklet_map[2]) {
+      for (const auto& tracklet3 : station_tracklet_map[3]) {
+        initParams.push_back(get_params(tracklet1.position(), tracklet2.position(), tracklet3.position(), cov, m_origin));
+        std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3> seed {tracklet1.clusters(), tracklet2.clusters(), tracklet3.clusters()};
+        seeds.push_back(seed);
+      }
+    }
+  }
+
+  m_initialTrackParameters = std::make_shared<std::vector<Acts::CurvilinearTrackParameters>>(initParams);
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourceLinks);
+  m_idLinks = std::make_shared<IdentifierLink>(identifierLinkMap);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+  m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(
+      Acts::Vector3 {0, 0, m_origin}, Acts::Vector3{0, 0, -1});
+  m_clusters = std::make_shared<std::vector<const Tracker::FaserSCT_Cluster*>>(clusters);
+  m_seedClusters = std::make_shared<std::vector<std::array<std::vector<const Tracker::FaserSCT_Cluster*>, 3>>>(seeds);
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode ThreeStationTrackSeedTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+
+Acts::CurvilinearTrackParameters ThreeStationTrackSeedTool::get_params(
+    const Amg::Vector3D& position_st1, const Amg::Vector3D& position_st2, const Amg::Vector3D& position_st3, const Acts::BoundSymMatrix& cov, double origin) {
+  Acts::Vector3 dir = position_st2 - position_st1;
+  Acts::Vector3 pos = position_st1 - (position_st1.z() - origin)/dir.z() * dir;
+  Acts::Vector4 pos4 {pos.x(), pos.y(), pos.z(), 0};
+  auto [abs_momentum, charge] = momentum({{1, position_st1}, {2, position_st2}, {3, position_st3}});
+  return Acts::CurvilinearTrackParameters(pos4, dir, abs_momentum, charge, cov);
+}
+std::pair<double, double> ThreeStationTrackSeedTool::momentum(const std::map<int, Amg::Vector3D>& pos, double B) {
+  Acts::Vector3 vec_l = pos.at(3) - pos.at(1);
+  double abs_l = std::sqrt(vec_l.y() * vec_l.y() + vec_l.z() * vec_l.z());
+  double t = (pos.at(2).z() - pos.at(1).z()) / (pos.at(3).z() - pos.at(1).z());
+  Acts::Vector3 vec_m = pos.at(1) + t * vec_l;
+  Acts::Vector3 vec_s = pos.at(2) - vec_m;
+  double abs_s = std::sqrt(vec_s.y() * vec_s.y() + vec_s.z() * vec_s.z());
+  double p_yz = 0.3 * abs_l * abs_l * B / (8 * abs_s * 1000);
+  double charge = vec_s.y() < 0 ? 1 : -1;
+  return std::make_pair(p_yz, charge);
+}
+
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrackClassification.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TrackClassification.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..6b780fc368e874f4520af430e63d1cc7c4e02575
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrackClassification.cxx
@@ -0,0 +1,93 @@
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "FaserActsKalmanFilter/TrackClassification.h"
+
+namespace {
+
+/// Increase the hit count for the given particle id by one.
+inline void increaseHitCount(std::vector<ParticleHitCount> &particleHitCounts, int particleId) {
+  // linear search since there is no ordering
+  auto it = std::find_if(particleHitCounts.begin(), particleHitCounts.end(),
+                         [=](const ParticleHitCount &phc) {
+                           return (phc.particleId == particleId);
+                         });
+  // either increase count if we saw the particle before or add it
+  if (it != particleHitCounts.end()) {
+    it->hitCount += 1u;
+  } else {
+    particleHitCounts.push_back({particleId, 1u});
+  }
+}
+
+/// Sort hit counts by decreasing values, i.e. majority particle comes first.
+inline void sortHitCount(std::vector<ParticleHitCount> &particleHitCounts) {
+  std::sort(particleHitCounts.begin(), particleHitCounts.end(),
+            [](const ParticleHitCount &lhs, const ParticleHitCount &rhs) {
+              return (lhs.hitCount > rhs.hitCount);
+            });
+}
+
+}  // namespace
+
+
+/// Identify all particles that contribute to a trajectory.
+void identifyContributingParticles(
+    const TrackerSimDataCollection& simDataCollection,
+    const FaserActsRecMultiTrajectory& trajectories, size_t tip,
+    std::vector<ParticleHitCount>& particleHitCounts) {
+  particleHitCounts.clear();
+
+  if (not trajectories.hasTrajectory(tip)) {
+    return;
+  }
+
+  trajectories.multiTrajectory().visitBackwards(tip, [&](const auto& state) {
+    // no truth info with non-measurement state
+    if (not state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) {
+      return true;
+    }
+    std::vector<int> barcodes {};
+    // register all particles that generated this hit
+    for (const Identifier &id : state.uncalibrated().hit()->rdoList()) {
+      if (simDataCollection.count(id) == 0) {
+        return true;
+      }
+      const auto &deposits = simDataCollection.find(id)->second.getdeposits();
+      for (const TrackerSimData::Deposit &deposit : deposits) {
+        int barcode = deposit.first->barcode();
+        if (std::find(barcodes.begin(), barcodes.end(), barcode) == barcodes.end()) {
+          barcodes.push_back(barcode);
+          increaseHitCount(particleHitCounts, deposit.first->barcode());
+        }
+      }
+    }
+    return true;
+  });
+  sortHitCount(particleHitCounts);
+}
+
+/* Identify all particles that contribute to a trajectory.
+ * If a cluster consists of multiple RDOs we check for each from which particle(s) it has been created.
+ * And if multiple particles created a RDO we increase the hit count for each of them.
+ */
+void identifyContributingParticles(
+    const TrackerSimDataCollection& simDataCollection,
+    const std::vector<const Tracker::FaserSCT_Cluster*> clusters,
+    std::vector<ParticleHitCount>& particleHitCounts) {
+  particleHitCounts.clear();
+  for (const Tracker::FaserSCT_Cluster *cluster : clusters) {
+    std::vector<int> barcodes {};
+    for (const Identifier &id : cluster->rdoList()) {
+      if (simDataCollection.count(id) == 0) continue;
+      const auto &deposits = simDataCollection.find(id)->second.getdeposits();
+      for (const TrackerSimData::Deposit &deposit : deposits) {
+        int barcode = deposit.first->barcode();
+        if (std::find(barcodes.begin(), barcodes.end(), barcode) == barcodes.end()) {
+          barcodes.push_back(barcode);
+          increaseHitCount(particleHitCounts, deposit.first->barcode());
+        }
+      }
+    }
+  }
+  sortHitCount(particleHitCounts);
+}
+
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrackFindingAlgorithmFunction.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TrackFindingAlgorithmFunction.cxx
index 954906a0f4263c6e17f17143b23d4639c5d1107a..44a86cbf06d0b10ae6aeca00bdd8bfd4e261d680 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/src/TrackFindingAlgorithmFunction.cxx
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrackFindingAlgorithmFunction.cxx
@@ -6,94 +6,51 @@
 #include "Acts/Propagator/Propagator.hpp"
 #include "Acts/TrackFitting/GainMatrixSmoother.hpp"
 #include "Acts/TrackFitting/GainMatrixUpdater.hpp"
-#include "Acts/MagneticField/ConstantBField.hpp"
-#include "Acts/MagneticField/InterpolatedBFieldMap.hpp"
-#include "Acts/MagneticField/SharedBField.hpp"
 
-#include <boost/variant/variant.hpp>
-#include <boost/variant/apply_visitor.hpp>
-#include <boost/variant/static_visitor.hpp>
 
+namespace {
 
 using Updater = Acts::GainMatrixUpdater;
 using Smoother = Acts::GainMatrixSmoother;
-using Stepper = Acts::EigenStepper<>;
-using Propagator = Acts::Propagator<Stepper, Acts::Navigator>;
-
-namespace ActsExtrapolationDetail {
-  using VariantPropagatorBase = boost::variant<
-      Acts::Propagator<Acts::EigenStepper<>, Acts::Navigator>,
-      Acts::Propagator<Acts::EigenStepper<>, Acts::Navigator>
-  >;
-
-  class VariantPropagator : public VariantPropagatorBase
-  {
-  public:
-    using VariantPropagatorBase::VariantPropagatorBase;
-  };
 
-}
-
-using ActsExtrapolationDetail::VariantPropagator;
+using Stepper = Acts::EigenStepper<>;
+using Navigator = Acts::Navigator;
+using Propagator = Acts::Propagator<Stepper, Navigator>;
+using CKF = Acts::CombinatorialKalmanFilter<Propagator, Updater, Smoother>;
 
+using TrackParametersContainer = std::vector<CombinatorialKalmanFilterAlg::TrackParameters>;
 
-template <typename TrackFinder>
-struct TrackFinderFunctionImpl {
-  TrackFinder trackFinder;
+struct TrackFinderFunctionImpl
+    : public CombinatorialKalmanFilterAlg::TrackFinderFunction {
+  CKF trackFinder;
 
-  TrackFinderFunctionImpl(TrackFinder&& f) : trackFinder(std::move(f)) {}
+  TrackFinderFunctionImpl(CKF&& f) : trackFinder(std::move(f)) {}
 
   CombinatorialKalmanFilterAlg::TrackFinderResult operator()(
-      const IndexSourceLinkContainer& sourceLinks,
-      const std::vector<Acts::CurvilinearTrackParameters>& initialParameters,
-      const CombinatorialKalmanFilterAlg::TrackFinderOptions& options) const
-  {
-    return trackFinder.findTracks(sourceLinks, initialParameters, options);
-  }
+      const IndexSourceLinkContainer& sourcelinks,
+      const TrackParametersContainer& initialParameters,
+      const CombinatorialKalmanFilterAlg::TrackFinderOptions& options)
+  const override {
+    return trackFinder.findTracks(sourcelinks, initialParameters, options);
+  };
 };
 
+}  // namespace
 
-CombinatorialKalmanFilterAlg::TrackFinderFunction 
+std::shared_ptr<CombinatorialKalmanFilterAlg::TrackFinderFunction>
 CombinatorialKalmanFilterAlg::makeTrackFinderFunction(
-    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry)
-{
-  const std::string fieldMode = "FASER";
-  const std::vector<double> constantFieldVector = {0., 0., 0.55};
-
-  Acts::Navigator::Config cfg{trackingGeometry};
-  cfg.resolvePassive   = false;
-  cfg.resolveMaterial  = true;
-  cfg.resolveSensitive = true;
-  Acts::Navigator navigator( cfg );
-
-  std::unique_ptr<ActsExtrapolationDetail::VariantPropagator> varProp;
-
-  if (fieldMode == "FASER") {
-    auto bField = std::make_shared<FASERMagneticFieldWrapper>();
-    auto stepper = Acts::EigenStepper<>(std::move(bField));
-    auto propagator = Acts::Propagator<decltype(stepper), Acts::Navigator>(std::move(stepper),
-                                                                      std::move(navigator));
-    varProp = std::make_unique<VariantPropagator>(propagator);
-  }
-  else if (fieldMode == "Constant") {
-    Acts::Vector3 constantFieldVector = Acts::Vector3(constantFieldVector[0], 
-                                                      constantFieldVector[1], 
-                                                      constantFieldVector[2]);
-
-    auto bField = std::make_shared<Acts::ConstantBField>(constantFieldVector);
-    auto stepper = Acts::EigenStepper<>(std::move(bField));
-    auto propagator = Acts::Propagator<decltype(stepper), Acts::Navigator>(std::move(stepper),
-                                                                      std::move(navigator));
-    varProp = std::make_unique<VariantPropagator>(propagator);
-  }
-
-  return boost::apply_visitor([&](const auto& propagator) -> TrackFinderFunction {
-    using Updater  = Acts::GainMatrixUpdater;
-    using Smoother = Acts::GainMatrixSmoother;
-    using CKF = Acts::CombinatorialKalmanFilter<typename std::decay_t<decltype(propagator)>, Updater, Smoother>;
-
-    CKF trackFinder(std::move(propagator));
-
-    return TrackFinderFunctionImpl<CKF>(std::move(trackFinder));
-  }, *varProp);
-}
\ No newline at end of file
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry,
+    bool resolvePassive, bool resolveMaterial, bool resolveSensitive) {
+  auto magneticField = std::make_shared<FASERMagneticFieldWrapper>();
+  Stepper stepper(std::move(magneticField));
+  Navigator::Config cfg{trackingGeometry};
+  cfg.resolvePassive = resolvePassive;
+  cfg.resolveMaterial = resolveMaterial;
+  cfg.resolveSensitive = resolveSensitive;
+  Navigator navigator(cfg);
+  Propagator propagator(std::move(stepper), std::move(navigator));
+  CKF trackFinder(std::move(propagator));
+
+  // build the track finder functions. owns the track finder object.
+  return std::make_shared<TrackFinderFunctionImpl>(std::move(trackFinder));
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrackFittingFunction.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TrackFittingFunction.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0ece62669f80e7863a75b191a9fc7a51d759ca6c
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrackFittingFunction.cxx
@@ -0,0 +1,118 @@
+#include "FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h"
+#include "FaserActsGeometry/FASERMagneticFieldWrapper.h"
+
+#include "Acts/Propagator/EigenStepper.hpp"
+#include "Acts/Propagator/Navigator.hpp"
+#include "Acts/Propagator/Propagator.hpp"
+#include "Acts/TrackFitting/GainMatrixSmoother.hpp"
+#include "Acts/TrackFitting/GainMatrixUpdater.hpp"
+
+
+namespace {
+
+using Updater = Acts::GainMatrixUpdater;
+using Smoother = Acts::GainMatrixSmoother;
+using Stepper = Acts::EigenStepper<>;
+using Propagator = Acts::Propagator<Stepper, Acts::Navigator>;
+using Fitter = Acts::KalmanFitter<Propagator, Updater, Smoother>;
+
+struct TrackFitterFunctionImpl
+    : public FaserActsKalmanFilterAlg::TrackFitterFunction {
+  Fitter trackFitter;
+
+  TrackFitterFunctionImpl(Fitter &&f) : trackFitter(std::move(f)) {}
+
+  FaserActsKalmanFilterAlg::TrackFitterResult operator()(
+      const std::vector<IndexSourceLink> &sourceLinks,
+      const FaserActsKalmanFilterAlg::TrackParameters &initialParameters,
+      const FaserActsKalmanFilterAlg::TrackFitterOptions &options)
+  const override {
+    return trackFitter.fit(sourceLinks, initialParameters, options);
+  };
+};
+
+}  // namespace
+
+
+std::shared_ptr<FaserActsKalmanFilterAlg::TrackFitterFunction>
+FaserActsKalmanFilterAlg::makeTrackFitterFunction(
+    std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry) {
+  auto magneticField = std::make_shared<FASERMagneticFieldWrapper>();
+  auto stepper = Stepper(std::move(magneticField));
+  Acts::Navigator::Config cfg{trackingGeometry};
+  cfg.resolvePassive = false;
+  cfg.resolveMaterial = true;
+  cfg.resolveSensitive = true;
+  Acts::Navigator navigator(cfg);
+  Propagator propagator(std::move(stepper), std::move(navigator));
+  Fitter trackFitter(std::move(propagator));
+  return std::make_shared<TrackFitterFunctionImpl>(std::move(trackFitter));
+}
+
+/*
+
+namespace ActsExtrapolationDetail {
+using VariantPropagatorBase = boost::variant<
+  Acts::Propagator<Acts::EigenStepper<>, Acts::Navigator>,
+  Acts::Propagator<Acts::EigenStepper<>, Acts::Navigator>>;
+
+class VariantPropagator : public VariantPropagatorBase {
+public:
+  using VariantPropagatorBase::VariantPropagatorBase;
+};
+}  // namespace ActsExtrapolationDetail
+
+
+namespace {
+template <typename Fitter>
+struct FitterFunctionImpl {
+  Fitter fitter;
+  FitterFunctionImpl(Fitter&& f) : fitter(std::move(f)) {}
+  FaserActsKalmanFilterAlg::TrackFitterResult operator()(
+    const std::vector<IndexSourceLink>& sourceLinks,
+    const Acts::CurvilinearTrackParameters& initialParameters,
+    const Acts::KalmanFitterOptions<MeasurementCalibrator, Acts::VoidOutlierFinder, Acts::VoidReverseFilteringLogic>& options) const {
+      return fitter.fit(sourceLinks, initialParameters, options);
+    };
+};
+}  // namespace
+
+std::shared_ptr<FaserActsKalmanFilterAlg::TrackFitterFunction>
+FaserActsKalmanFilterAlg::makeTrackFitterFunction(std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry) {
+
+  const std::string fieldMode = "FASER";
+  const std::vector<double> constantFieldVector = {0., 0., 0.55};
+
+  Acts::Navigator::Config cfg{trackingGeometry};
+  cfg.resolvePassive   = false;
+  cfg.resolveMaterial  = true;
+  cfg.resolveSensitive = true;
+  Acts::Navigator navigator( cfg );
+
+  std::unique_ptr<ActsExtrapolationDetail::VariantPropagator> varProp;
+
+  if (fieldMode == "FASER") {
+    auto bField = std::make_shared<FASERMagneticFieldWrapper>();
+    auto stepper = Acts::EigenStepper<>(std::move(bField));
+    auto propagator = Acts::Propagator<decltype(stepper),
+        Acts::Navigator>(std::move(stepper), std::move(navigator));
+    varProp = std::make_unique<ActsExtrapolationDetail::VariantPropagator>(propagator);
+  } else if (fieldMode == "Constant") {
+    Acts::Vector3 constantFieldVector = Acts::Vector3(
+      constantFieldVector[0], constantFieldVector[1], constantFieldVector[2]);
+    auto bField = std::make_shared<Acts::ConstantBField>(constantFieldVector);
+    auto stepper = Acts::EigenStepper<>(std::move(bField));
+    auto propagator = Acts::Propagator<decltype(stepper),
+        Acts::Navigator>(std::move(stepper), std::move(navigator));
+    varProp = std::make_unique<ActsExtrapolationDetail::VariantPropagator>(propagator);
+  }
+
+  return boost::apply_visitor([&](const auto& propagator) -> TrackFitterFunction {
+    using Updater  = Acts::GainMatrixUpdater;
+    using Smoother = Acts::GainMatrixSmoother;
+    using Fitter = Acts::KalmanFitter<typename std::decay_t<decltype(propagator)>, Updater, Smoother>;
+    Fitter fitter(std::move(propagator));
+    return FitterFunctionImpl<Fitter>(std::move(fitter));
+  }, *varProp);
+}
+ */
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrackSeedWriterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TrackSeedWriterTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..820e53cb18a6190992f3003c3df7fcd5fc751de9
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrackSeedWriterTool.cxx
@@ -0,0 +1,121 @@
+#include "FaserActsKalmanFilter/TrackSeedWriterTool.h"
+#include "TFile.h"
+#include "TTree.h"
+
+TrackSeedWriterTool::TrackSeedWriterTool(const std::string& type,
+                                         const std::string& name,
+                                         const IInterface* parent)
+    : AthAlgTool( type, name, parent ) {}
+
+
+StatusCode TrackSeedWriterTool::initialize() {
+  ATH_MSG_INFO("TrackSeedWriterTool::initialize");
+
+  std::string filePath = m_filePath;
+  std::string treeName = m_treeName;
+
+  m_file = TFile::Open(filePath.c_str(), "RECREATE");
+  if (m_file == nullptr) {
+    ATH_MSG_ERROR("Unable to open output file at " << m_filePath);
+    return StatusCode::FAILURE;
+  }
+  m_file->cd();
+  m_tree = new TTree(treeName.c_str(), "tree");
+  if (m_tree == nullptr) {
+    ATH_MSG_ERROR("Unable to create TTree");
+    return StatusCode::FAILURE;
+  }
+
+  TrackSeedWriterTool::initializeTree();
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TrackSeedWriterTool::finalize() {
+  m_file->cd();
+  m_tree->Write();
+  return StatusCode::SUCCESS;
+}
+
+
+void TrackSeedWriterTool::initializeTree() {
+  m_tree->Branch("run_number", &m_runNumber);
+  m_tree->Branch("event_number", &m_eventNumber);
+  m_tree->Branch("eLOC0", &m_eLOC0);
+  m_tree->Branch("eLOC1", &m_eLOC1);
+  m_tree->Branch("ePHI", &m_ePHI);
+  m_tree->Branch("eTHETA", &m_eTHETA);
+  m_tree->Branch("eQOP", &m_eQOP);
+  m_tree->Branch("err_eLOC0", &m_err_eLOC0);
+  m_tree->Branch("err_eLOC1", &m_err_eLOC1);
+  m_tree->Branch("err_ePHI", &m_err_ePHI);
+  m_tree->Branch("err_eTHETA", &m_err_eTHETA);
+  m_tree->Branch("err_eQOP", &m_err_eQOP);
+  m_tree->Branch("x", &m_x);
+  m_tree->Branch("y", &m_y);
+  m_tree->Branch("z", &m_z);
+  m_tree->Branch("px", &m_px);
+  m_tree->Branch("py", &m_py);
+  m_tree->Branch("pz", &m_pz);
+}
+
+
+void TrackSeedWriterTool::writeout(
+    const Acts::GeometryContext& gctx,
+    const std::shared_ptr<std::vector<Acts::CurvilinearTrackParameters>>& initialParametersContainer) const {
+  m_runNumber = Gaudi::Hive::currentContext().eventID().run_number();
+  m_eventNumber = Gaudi::Hive::currentContext().eventID().event_number();
+  for (Acts::CurvilinearTrackParameters initialParameters : *initialParametersContainer) {
+    const auto& parameters = initialParameters.parameters();
+    const Acts::Vector3& center = initialParameters.referenceSurface().center(gctx);
+    m_eLOC0.push_back(center[Acts::eBoundLoc0]);
+    m_eLOC1.push_back(center[Acts::eBoundLoc1]);
+    m_ePHI.push_back(parameters[Acts::eBoundPhi]);
+    m_eTHETA.push_back(parameters[Acts::eBoundTheta]);
+    m_eQOP.push_back(parameters[Acts::eBoundQOverP]);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+
+    const auto& covariance = *initialParameters.covariance();
+    m_err_eLOC0.push_back(sqrt(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)));
+    m_err_eLOC1.push_back(sqrt(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+    m_err_ePHI.push_back(sqrt(covariance(Acts::eBoundPhi, Acts::eBoundPhi)));
+    m_err_eTHETA.push_back(sqrt(covariance(Acts::eBoundTheta, Acts::eBoundTheta)));
+    m_err_eQOP.push_back(sqrt(covariance(Acts::eBoundQOverP, Acts::eBoundQOverP)));
+
+#pragma GCC diagnostic pop
+
+    const auto& position = initialParameters.position(gctx);
+    const auto& momentum = initialParameters.momentum();
+    m_x.push_back(position.x());
+    m_y.push_back(position.y());
+    m_z.push_back(position.z());
+    m_px.push_back(momentum.x());
+    m_py.push_back(momentum.y());
+    m_pz.push_back(momentum.z());
+  }
+  m_tree->Fill();
+  clearVariables();
+}
+
+
+void TrackSeedWriterTool::clearVariables() const {
+  m_eLOC0.clear();
+  m_eLOC1.clear();
+  m_ePHI.clear();
+  m_eTHETA.clear();
+  m_eQOP.clear();
+  m_err_eLOC0.clear();
+  m_err_eLOC1.clear();
+  m_err_ePHI.clear();
+  m_err_eTHETA.clear();
+  m_err_eQOP.clear();
+  m_x.clear();
+  m_y.clear();
+  m_z.clear();
+  m_px.clear();
+  m_py.clear();
+  m_pz.clear();
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrackSelection.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TrackSelection.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..fdc77b13a95da4e89a7ff0c869f24e781d4ed7cc
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrackSelection.cxx
@@ -0,0 +1,41 @@
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
+#include "FaserActsKalmanFilter/TrackSelection.h"
+
+namespace {
+
+inline void sortTracks(std::vector<TrackQuality>& tracks) {
+  std::sort(tracks.begin(), tracks.end(),
+            [](const TrackQuality& lhs, const TrackQuality& rhs) {
+    if (lhs.nMeasurements != rhs.nMeasurements) {
+      return lhs.nMeasurements > rhs.nMeasurements;
+    }
+    return lhs.chi2 < rhs.chi2;
+  });
+}
+
+}  // namespace
+
+
+void selectTracks(TrackFinderResult& results, std::vector<TrackQuality>& trackQuality) {
+  for (auto& result : results) {
+    if (not result.ok()) {
+      continue;
+    }
+    auto& ckfResult = result.value();
+    auto traj = FaserActsRecMultiTrajectory(ckfResult.fittedStates, ckfResult.lastMeasurementIndices, ckfResult.fittedParameters);
+    const auto& mj = traj.multiTrajectory();
+    const auto& trackTips = traj.tips();
+    auto it = std::max_element(trackTips.begin(), trackTips.end(),
+                               [&](const size_t& lhs, const size_t& rhs) {
+                                 auto trajState_lhs = Acts::MultiTrajectoryHelpers::trajectoryState(mj, lhs);
+                                 auto trajState_rhs = Acts::MultiTrajectoryHelpers::trajectoryState(mj, rhs);
+                                 if (trajState_lhs.nMeasurements != trajState_rhs.nMeasurements) {
+                                   return trajState_lhs.nMeasurements > trajState_rhs.nMeasurements;
+                                 }
+                                 return trajState_lhs.chi2Sum < trajState_rhs.chi2Sum;
+                               });
+    auto trajState = Acts::MultiTrajectoryHelpers::trajectoryState(mj, *it);
+    trackQuality.push_back({ckfResult, trajState.nMeasurements, trajState.chi2Sum});
+  }
+  sortTracks(trackQuality);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TrajectoryWriterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TrajectoryWriterTool.cxx
index aca7bd57a3df15bfbecffc5e6afb996daffb7020..55dfda762765da67bea84b219d5279ce0bf1a197 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/src/TrajectoryWriterTool.cxx
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TrajectoryWriterTool.cxx
@@ -1,3 +1,4 @@
+#include "TrackerPrepRawData/FaserSCT_Cluster.h"
 #include "FaserActsKalmanFilter/TrajectoryWriterTool.h"
 #include "FaserActsKalmanFilter/FaserActsRecMultiTrajectory.h"
 #include "Acts/EventData/MultiTrajectoryHelpers.hpp"
@@ -80,6 +81,15 @@ void TrajectoryWriterTool::initializeTree() {
   m_tree->Branch("g_x_fit" , &m_x_fit);
   m_tree->Branch("g_y_fit" , &m_y_fit);
   m_tree->Branch("g_z_fit" , &m_z_fit);
+  m_tree->Branch("lx_hit" , &m_lx_hit);
+  m_tree->Branch("ly_hit" , &m_ly_hit);
+  m_tree->Branch("x_hit" , &m_x_hit);
+  m_tree->Branch("y_hit" , &m_y_hit);
+  m_tree->Branch("z_hit" , &m_z_hit);
+  m_tree->Branch("meas_eLOC0" , &m_meas_eLOC0);
+  m_tree->Branch("meas_eLOC1" , &m_meas_eLOC1);
+  m_tree->Branch("meas_cov_eLOC0" , &m_meas_cov_eLOC0);
+  m_tree->Branch("meas_cov_eLOC1" , &m_meas_cov_eLOC1);
 
   m_tree->Branch("nPredicted", &m_nPredicted);
   m_tree->Branch("predicted", &m_prt);
@@ -228,7 +238,10 @@ void TrajectoryWriterTool::writeout(TrajectoriesContainer trajectories,
         m_eta_ini.push_back(eta(initialparameters[iTraj].position(geoContext)));
 
     // The trajectory entry indices and the multiTrajectory
-    const auto& [trackTips, mj] = traj.trajectory();
+    // const auto& [trackTips, mj] = traj.multiTrajectory();
+    const auto& mj = traj.multiTrajectory();
+    const auto& trackTips = traj.tips();
+
     if (trackTips.empty()) {
       ATH_MSG_WARNING("Empty multiTrajectory.");
       m_nMeasurements = -1;
@@ -308,11 +321,16 @@ void TrajectoryWriterTool::writeout(TrajectoriesContainer trajectories,
 
       // extract local and global position
       Acts::Vector2 local(meas[Acts::eBoundLoc0], meas[Acts::eBoundLoc1]);
-const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(meas[Acts::eBoundPhi], meas[Acts::eBoundTheta]);
+      const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(meas[Acts::eBoundPhi], meas[Acts::eBoundTheta]);
       Acts::Vector3 mom(1, 1, 1);
       Acts::Vector3 global =
           surface.localToGlobal(geoContext, local, dir);
 
+      m_meas_eLOC0.push_back(state.calibrated()[Acts::eBoundLoc0]);
+      m_meas_eLOC1.push_back(state.calibrated()[Acts::eBoundLoc1]);
+      m_meas_cov_eLOC0.push_back(state.calibratedCovariance()(Acts::eBoundLoc0, Acts::eBoundLoc0));
+      m_meas_cov_eLOC1.push_back(state.calibratedCovariance()(Acts::eBoundLoc1, Acts::eBoundLoc1));
+
       // fill the measurement info
       m_lx_hit.push_back(local[Acts::ePos0]);
       m_ly_hit.push_back(local[Acts::ePos1]);
@@ -340,15 +358,15 @@ const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(meas[Acts::eBoundP
 
         /// Predicted residual
         m_res_eLOC0_prt.push_back(residual(Acts::eBoundLoc0));
-        m_res_eLOC1_prt.push_back(residual(Acts::eBoundLoc1));
+//        m_res_eLOC1_prt.push_back(residual(Acts::eBoundLoc1));
 
         /// Predicted parameter pulls
         m_pull_eLOC0_prt.push_back(
             residual(Acts::eBoundLoc0) /
             sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_eLOC1_prt.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+//        m_pull_eLOC1_prt.push_back(
+//            residual(Acts::eBoundLoc1) /
+//            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
 
         /// Predicted parameter
         m_eLOC0_prt.push_back(parameter.parameters()[Acts::eBoundLoc0]);
@@ -429,15 +447,15 @@ const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(meas[Acts::eBoundP
 
         /// Filtered residual
         m_res_eLOC0_flt.push_back(residual(Acts::eBoundLoc0));
-        m_res_eLOC1_flt.push_back(residual(Acts::eBoundLoc1));
+//        m_res_eLOC1_flt.push_back(residual(Acts::eBoundLoc1));
 
         /// Filtered parameter pulls
         m_pull_eLOC0_flt.push_back(
             residual(Acts::eBoundLoc0) /
             sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_eLOC1_flt.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+//        m_pull_eLOC1_flt.push_back(
+//            residual(Acts::eBoundLoc1) /
+//            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
 
         /// Filtered parameter
         m_eLOC0_flt.push_back(parameter.parameters()[Acts::eBoundLoc0]);
@@ -520,30 +538,30 @@ const Acts::Vector3 dir = Acts::makeDirectionUnitFromPhiTheta(meas[Acts::eBoundP
         auto residual = state.effectiveCalibrated() - H * state.smoothed();
 
         m_res_x_hit.push_back(residual(Acts::eBoundLoc0));
-        m_res_y_hit.push_back(residual(Acts::eBoundLoc1));
+//        m_res_y_hit.push_back(residual(Acts::eBoundLoc1));
         m_err_x_hit.push_back(
             sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_err_y_hit.push_back(
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+//        m_err_y_hit.push_back(
+//            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
         m_pull_x_hit.push_back(
             residual(Acts::eBoundLoc0) /
             sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_y_hit.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+//        m_pull_y_hit.push_back(
+//            residual(Acts::eBoundLoc1) /
+//            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
         m_dim_hit.push_back(state.calibratedSize());
 
         /// Smoothed residual
         m_res_eLOC0_smt.push_back(residual(Acts::eBoundLoc0));
-        m_res_eLOC1_smt.push_back(residual(Acts::eBoundLoc1));
+//        m_res_eLOC1_smt.push_back(residual(Acts::eBoundLoc1));
 
         /// Smoothed parameter pulls
         m_pull_eLOC0_smt.push_back(
             residual(Acts::eBoundLoc0) /
             sqrt(resCov(Acts::eBoundLoc0, Acts::eBoundLoc0)));
-        m_pull_eLOC1_smt.push_back(
-            residual(Acts::eBoundLoc1) /
-            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
+//        m_pull_eLOC1_smt.push_back(
+//            residual(Acts::eBoundLoc1) /
+//            sqrt(resCov(Acts::eBoundLoc1, Acts::eBoundLoc1)));
 
         /// Smoothed parameter
         m_eLOC0_smt.push_back(parameter.parameters()[Acts::eBoundLoc0]);
@@ -638,6 +656,10 @@ void TrajectoryWriterTool::clearVariables() const {
   m_x_hit.clear();
   m_y_hit.clear();
   m_z_hit.clear();
+  m_meas_eLOC0.clear();
+  m_meas_eLOC1.clear();
+  m_meas_cov_eLOC0.clear();
+  m_meas_cov_eLOC1.clear();
   m_res_x_hit.clear();
   m_res_y_hit.clear();
   m_err_x_hit.clear();
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TruthBasedInitialParameterTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TruthBasedInitialParameterTool.cxx
index 85b1036e415fb3f28f9f6b8ee37679afb60304bd..f5b850d8a05877b9d7d48db65faadbff7cfac519 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/src/TruthBasedInitialParameterTool.cxx
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TruthBasedInitialParameterTool.cxx
@@ -51,8 +51,6 @@ Acts::CurvilinearTrackParameters TruthBasedInitialParameterTool::getInitialParam
     }
   }
 
-  std::cout << "?? px = " << momentum.x() << " py = " << momentum.y() << " pz = " << momentum.z() << std::endl;
-
   Acts::Vector3 truthVertex = {vertex.x(), vertex.y(), vertex.z()}; // in mm
   Acts::Vector3 truthMomentum = {momentum.x() / 1000, momentum.y() / 1000, momentum.z() / 1000}; // in GeV
   m_simWriterTool->writeout(truthVertex, truthMomentum);
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TruthSeededTrackFinderTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TruthSeededTrackFinderTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..4a574ee970e5dc9ccc67dcdd94143b2df20d1114
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TruthSeededTrackFinderTool.cxx
@@ -0,0 +1,252 @@
+#include "FaserActsKalmanFilter/TruthSeededTrackFinderTool.h"
+
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/Measurement.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Surfaces/PlaneSurface.hpp"
+#include "Acts/Surfaces/RectangleBounds.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "GeoPrimitives/CLHEPtoEigenConverter.h"
+#include "Identifier/Identifier.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include "TrackerSpacePoint/FaserSCT_SpacePoint.h"
+#include "TrackerSpacePoint/SpacePointForSeed.h"
+#include <array>
+#include <random>
+
+
+TruthSeededTrackFinderTool::TruthSeededTrackFinderTool(
+    const std::string& type, const std::string& name, const IInterface* parent) :
+    base_class(type, name, parent) {}
+
+
+StatusCode TruthSeededTrackFinderTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_spacePointCollectionKey.initialize());
+  ATH_CHECK(m_mcEventCollectionKey.initialize());
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TruthSeededTrackFinderTool::run() {
+//  SG::ReadHandle<FaserSiHitCollection> siHitCollection {m_siHitCollectionKey};
+//      ATH_CHECK(siHitCollection.isValid());
+//  ATH_MSG_DEBUG("Read FaserSiHitCollection with " << siHitCollection->size() << " hits");
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+
+  SG::ReadHandle<SpacePointForSeedCollection> spacePointCollection {m_spacePointCollectionKey};
+  ATH_CHECK(spacePointCollection.isValid());
+
+  Acts::GeometryContext geoctx = m_trackingGeometryTool->getNominalGeometryContext().context();
+
+  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
+      = m_trackingGeometryTool->trackingGeometry();
+
+  const int kSize = 2;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> myIndices = {Acts::eBoundLoc0, Acts::eBoundLoc1};
+
+  std::map<int, Acts::Vector3> spacePoints;
+
+  std::vector<IndexSourceLink> sourcelinks;
+  std::vector<Measurement> measurements;
+
+  for (const SpacePointForSeed* sp : *spacePointCollection) {
+    const Tracker::FaserSCT_SpacePoint* spacePoint = sp->SpacePoint();
+    Identifier id = spacePoint->associatedSurface().associatedDetectorElementIdentifier();
+    Acts::GeometryIdentifier geoId = identifierMap->at(id);
+    // const Acts::Surface *surface = m_trackingGeometryTool->trackingGeometry()->findSurface(geoId);
+
+    auto par = spacePoint->localParameters();
+    ATH_MSG_DEBUG("par " << par);
+    auto cov = spacePoint->localCovariance();
+    ATH_MSG_DEBUG("cov " << cov);
+    ATH_MSG_DEBUG(cov(0, 0) << ", " << cov(0, 1) << ", " << cov(1, 0) << ", " << cov(1, 1));
+    Acts::ActsSymMatrix<2> myCov = Acts::ActsSymMatrix<2>::Zero();
+    myCov(0, 0) = m_covMeas00;
+    myCov(1, 1) = m_covMeas11;
+    myCov(0, 1) = m_covMeas01;
+    myCov(1, 0) = m_covMeas10;
+
+    IndexSourceLink sourceLink(geoId, measurements.size());
+    ThisMeasurement meas(sourceLink, myIndices, par, myCov);
+    sourcelinks.push_back(sourceLink);
+    measurements.emplace_back(std::move(meas));
+
+    Amg::Vector3D globalPosition = spacePoint->globalPosition();
+    int station = m_idHelper->station(id);
+    int layer = m_idHelper->layer(id);
+    // TODO check if map already contains a space point for this layer
+    spacePoints[3*(station-1) + layer] = Acts::Vector3(globalPosition.x(), globalPosition.y(), globalPosition.z());
+
+    if (station == 1 && layer == 0) {
+      const TrackerDD::SiDetectorElement *element = m_detManager->getDetectorElement(id);
+      // Construct a plane surface as the target surface
+      const TrackerDD::SiDetectorDesign &design = element->design();
+      double hlX = design.width()/2. * 1_mm;
+      double hlY = design.length()/2. * 1_mm;
+      auto rectangleBounds = std::make_shared<const Acts::RectangleBounds>(hlX, hlY);
+      Amg::Transform3D g2l = element->getMaterialGeom()->getDefAbsoluteTransform()
+                             * Amg::CLHEPTransformToEigen(element->recoToHitTransform());
+      m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(g2l, rectangleBounds);
+    }
+  }
+
+  // check if there is atleast one space point in each station
+
+  if (map2vector(spacePoints, 1).empty() || map2vector(spacePoints, 2).empty() || map2vector(spacePoints, 0).empty()) {
+    return StatusCode::RECOVERABLE;
+  }
+
+  Acts::Vector4 smearedPosition4;
+  Acts::Vector3 smearedDirection;
+  double smearedAbsoluteMomentum;
+  double charge;
+  if (m_useTrueInitialParameters) {
+    // get initial parameters from generated particle
+    SG::ReadHandle<McEventCollection> mcEventCollection {m_mcEventCollectionKey};
+    ATH_CHECK(mcEventCollection.isValid());
+    for (const HepMC::GenEvent* event : *mcEventCollection) {
+      for (const HepMC::GenParticle* particle : event->particle_range()) {
+        const HepMC::FourVector& momentum = particle->momentum();
+        double abs_momentum = momentum.rho() * Acts::UnitConstants::MeV;
+        Acts::Vector3 direction = Acts::Vector3(momentum.x(), momentum.y(), momentum.z());
+        const HepMC::FourVector vertex = particle->production_vertex()->position();
+        charge = particle->pdg_id() > 0 ? -1 : 1;
+
+        std::random_device rd;
+        std::default_random_engine rng {rd()};
+        std::normal_distribution<> norm;
+
+        auto theta = Acts::VectorHelpers::theta(direction);
+        auto phi = Acts::VectorHelpers::phi(direction);
+        auto angles = Acts::detail::normalizePhiTheta(
+            phi + m_sigmaPhi * norm(rng),
+            theta + m_sigmaTheta * norm(rng));
+        smearedDirection = Acts::Vector3(
+            std::sin(angles.second) * std::cos(angles.first),
+            std::sin(angles.second) * std::sin(angles.first),
+            std::cos(angles.second));
+        smearedAbsoluteMomentum = abs_momentum * (1 + m_sigmaP * norm(rng));
+
+        smearedPosition4 = Acts::Vector4(vertex.x() + m_sigmaLoc0 * norm(rng), vertex.y() + m_sigmaLoc1 * norm(rng), vertex.z(), 0);
+      }
+    }
+  } else {
+    // get initial parameters from track seed
+    auto [p, q] = momentum2(spacePoints);
+    double abs_momentum = p;
+    charge = q;
+    Acts::Vector3 initPos {0, 0, 0};
+    if (spacePoints.count(0)) {
+      initPos = spacePoints.at(0);
+    } else {
+      ATH_MSG_ERROR("Could not find space point on first layer");
+    }
+    smearedPosition4 = Acts::Vector4(initPos.x(), initPos.y(), initPos.z(), 0);
+
+    auto [pos1, dir1] = linear_fit(map2vector(spacePoints, 1));
+    auto [pos2, dir2] = linear_fit(map2vector(spacePoints, 2));
+    smearedDirection = pos2 - pos1;
+//    smearedDirection = dir;
+    smearedAbsoluteMomentum = p;
+  }
+
+  Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+  cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+  cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+  cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+  cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+  cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+  cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+  // auto initialParameters = Acts::BoundTrackParameters(surface->getSharedPtr(), params, charge, cov);
+  auto initialParameters = Acts::CurvilinearTrackParameters(
+      smearedPosition4, smearedDirection, smearedAbsoluteMomentum, charge, cov);
+
+  // write out
+  double initialCharge = initialParameters.charge();
+  double initialAbsoluteMomentum = initialParameters.absoluteMomentum();
+  Acts::Vector3 initialPosition = initialParameters.position(geoctx);
+  Acts::Vector3 initialMomentum = initialParameters.momentum();
+  ATH_MSG_DEBUG("initial charge: " << initialCharge);
+  ATH_MSG_DEBUG("initial absolute momentum: " << initialAbsoluteMomentum);
+  ATH_MSG_DEBUG("initial position: x=" << initialPosition.x() << ", y=" << initialPosition.y() << ", z=" << initialPosition.z());
+  ATH_MSG_DEBUG("initial momentum: x=" << initialMomentum.x() << ", y=" << initialMomentum.y() << ", z=" << initialMomentum.z());
+  m_initialTrackParameters = std::make_shared<const Acts::CurvilinearTrackParameters>(initialParameters);
+
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourcelinks);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode TruthSeededTrackFinderTool::finalize() {
+  return StatusCode::SUCCESS;
+}
+
+Acts::Vector3 TruthSeededTrackFinderTool::average(const std::vector<Acts::Vector3>& spacePoints) {
+  Acts::Vector3 ret {0, 0, 0};
+  if (!spacePoints.empty()) {
+    for (const Acts::Vector3& spacePoint : spacePoints) {
+      ret += spacePoint;
+    }
+    ret /= spacePoints.size();
+  }
+  return ret;
+}
+
+std::pair<Acts::Vector3, Acts::Vector3>
+TruthSeededTrackFinderTool::linear_fit(const std::vector<Acts::Vector3>& hits) {
+  size_t n_hits = hits.size();
+  Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> centers(n_hits, 3);
+  for (size_t i = 0; i < n_hits; ++i) centers.row(i) = hits[i];
+
+  Acts::Vector3 origin = centers.colwise().mean();
+  Eigen::MatrixXd centered = centers.rowwise() - origin.transpose();
+  Eigen::MatrixXd cov = centered.adjoint() * centered;
+  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eig(cov);
+  Acts::Vector3 axis = eig.eigenvectors().col(2).normalized();
+
+  return std::make_pair(origin, axis);
+}
+
+
+std::vector<Acts::Vector3>
+TruthSeededTrackFinderTool::map2vector(const std::map<int, Acts::Vector3>& map, int station) {
+  std::vector<Acts::Vector3> vec;
+  for (int layer = station * 3; layer < station * 3 + 3; ++layer) {
+    if (map.count(layer)) {
+      vec.push_back(map.at(layer));
+    }
+  }
+  return vec;
+}
+
+
+std::pair<double, double> TruthSeededTrackFinderTool::momentum2(const std::map<int, Acts::Vector3>& hits, double B) {
+  Acts::Vector3 pos1 = average(map2vector(hits, 0));
+  Acts::Vector3 pos2 = average(map2vector(hits, 1));
+  Acts::Vector3 pos3 = average(map2vector(hits, 2));
+
+  Acts::Vector3 vec_l = pos3 - pos1;
+  double abs_l = std::sqrt(vec_l.y() * vec_l.y() + vec_l.z() * vec_l.z());
+  double t = (pos2.z() - pos1.z()) / (pos3.z() - pos1.z());
+  Acts::Vector3 vec_m = pos1 + t * vec_l;
+  Acts::Vector3 vec_s = pos2 - vec_m;
+  double abs_s = std::sqrt(vec_s.y() * vec_s.y() + vec_s.z() * vec_s.z());
+  double p_yz = 0.3 * abs_l * abs_l * B / (8 * abs_s * 1000); // in GeV
+  // double charge = vec_s.y() < 0 ? 1 : -1;
+  double charge = 1;
+
+  return std::make_pair(p_yz, charge);
+}
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/TruthTrackFinderTool.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/TruthTrackFinderTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..16882021e4babed59f3a52030a565411290eb9ac
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/TruthTrackFinderTool.cxx
@@ -0,0 +1,191 @@
+#include "FaserActsKalmanFilter/TruthTrackFinderTool.h"
+
+#include "Acts/Definitions/TrackParametrization.hpp"
+#include "Acts/EventData/Measurement.hpp"
+#include "Acts/Geometry/GeometryContext.hpp"
+#include "Acts/Geometry/GeometryIdentifier.hpp"
+#include "Acts/Geometry/TrackingGeometry.hpp"
+#include "Acts/Surfaces/Surface.hpp"
+#include "FaserActsKalmanFilter/IndexSourceLink.h"
+#include "GeoPrimitives/CLHEPtoEigenConverter.h"
+#include "Identifier/Identifier.h"
+#include "TrackerIdentifier/FaserSCT_ID.h"
+#include "TrackerReadoutGeometry/SCT_DetectorManager.h"
+#include "TrackerReadoutGeometry/SiDetectorElement.h"
+#include <array>
+#include <random>
+
+
+TruthTrackFinderTool::TruthTrackFinderTool(const std::string& type, const std::string& name, const IInterface* parent)
+    : base_class(type, name, parent) {}
+
+
+StatusCode TruthTrackFinderTool::initialize() {
+  ATH_CHECK(detStore()->retrieve(m_idHelper, "FaserSCT_ID"));
+  ATH_CHECK(detStore()->retrieve(m_detManager, "SCT"));
+  ATH_CHECK(m_siHitCollectionKey.initialize());
+  ATH_CHECK(m_trackingGeometryTool.retrieve());
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TruthTrackFinderTool::run() {
+  SG::ReadHandle<FaserSiHitCollection> siHitCollection {m_siHitCollectionKey};
+  ATH_CHECK(siHitCollection.isValid());
+  ATH_MSG_DEBUG("Read FaserSiHitCollection with " << siHitCollection->size() << " hits");
+
+  std::shared_ptr<const Acts::TrackingGeometry> trackingGeometry
+      = m_trackingGeometryTool->trackingGeometry();
+
+  using IdentifierMap = std::map<Identifier, Acts::GeometryIdentifier>;
+  std::shared_ptr<IdentifierMap> identifierMap = m_trackingGeometryTool->getIdentifierMap();
+  Acts::GeometryContext gctx = m_trackingGeometryTool->getNominalGeometryContext().context();
+  const int kSize = 2;
+  using ParametersVector = Acts::ActsVector<kSize>;
+  using CovarianceMatrix = Acts::ActsSymMatrix<kSize>;
+  using ThisMeasurement = Acts::Measurement<IndexSourceLink, Acts::BoundIndices, kSize>;
+  std::array<Acts::BoundIndices, kSize> myIndices = {Acts::eBoundLoc0, Acts::eBoundLoc1};
+
+  std::vector<IndexSourceLink> sourcelinks;
+  std::vector<Measurement> measurements;
+
+  std::vector<Acts::GeometryIdentifier> geoIds = {};
+  int station, layer, phi, eta, side;
+  for (const FaserSiHit &hit: *siHitCollection) {
+    if (!hit.particleLink() or std::abs(hit.particleLink()->pdg_id()) != 13) {
+      continue;
+    }
+
+    station = hit.getStation();
+    layer = hit.getPlane();
+    phi = hit.getRow();
+    eta = hit.getModule();
+    side = hit.getSensor();
+
+    if (side == 0) {
+      continue;
+    }
+
+    ATH_MSG_DEBUG(station << "/" << layer << "/" << phi << "/" << eta << "/" << side);
+
+    Identifier id = m_idHelper->wafer_id(station, layer, phi, eta, side);
+    // TODO check if there can be multiple simulated muon hits in the same wafer
+    // currently I take the first hit in each wafer
+    Acts::GeometryIdentifier geoId = identifierMap->at(id);
+    if (std::find(geoIds.begin(), geoIds.end(), geoId) != geoIds.end()) {
+      continue;
+    }
+    geoIds.push_back(geoId);
+    ATH_MSG_DEBUG(geoIds);
+
+    auto momentum = hit.particleLink()->momentum();
+    double abs_momentum = momentum.rho() * Acts::UnitConstants::MeV;
+    double QOverP = 1 / abs_momentum;
+    auto direction = Acts::Vector3(momentum.x(), momentum.y(), momentum.z()).normalized();
+
+    const TrackerDD::SiDetectorElement *element = m_detManager->getDetectorElement(id);
+
+    // create source links and measurements
+    const Acts::Surface *surface = m_trackingGeometryTool->trackingGeometry()->findSurface(geoId);
+    if (surface == nullptr) {
+      ATH_MSG_FATAL("Could not find surface");
+      continue;
+    }
+
+    const HepGeom::Point3D<double> localStartPos = hit.localStartPosition();
+    if (element) {
+      const HepGeom::Point3D<double> globalStartPos =
+          Amg::EigenTransformToCLHEP(element->transformHit()) * HepGeom::Point3D<double>(localStartPos);
+      auto center = surface->center(gctx);
+      Acts::Vector3 position = {globalStartPos.x(), globalStartPos.y(), center.z()};
+      Acts::Result<Acts::BoundVector> boundParamsRes
+          = Acts::detail::transformFreeToBoundParameters(position, hit.meanTime(), direction, QOverP, *surface, gctx);
+      if (!boundParamsRes.ok()) {
+        ATH_MSG_FATAL("Could not construct bound parameters");
+        return StatusCode::FAILURE;
+      }
+      const auto &boundParams = boundParamsRes.value();
+      ATH_MSG_DEBUG(boundParams[0] << ", " << boundParams[1]);
+
+      std::random_device rd;
+      std::default_random_engine rng {rd()};
+      std::normal_distribution<> norm;
+
+      ParametersVector smearedBoundParams = {
+          boundParams[0] + norm(rng) * m_sigma0, boundParams[1] + norm(rng) * m_sigma1};
+
+      ParametersVector par = ParametersVector::Zero();
+      par[0] = smearedBoundParams[0];
+      par[1] = smearedBoundParams[1];
+
+      ATH_MSG_DEBUG("bound parameters: par0=" << boundParams[0] << ", par1=" << boundParams[1]);
+      ATH_MSG_DEBUG("smeared parameters: par0=" << smearedBoundParams[0] << ", par1=" << smearedBoundParams[1]);
+
+      CovarianceMatrix cov = CovarianceMatrix::Zero();
+      cov(0, 0) = std::max(m_covMeasLoc0.value(), m_sigma0 * m_sigma0);
+      cov(1, 1) = std::max(m_covMeasLoc1.value(), m_sigma1 * m_sigma1);
+
+      IndexSourceLink sourceLink(geoId, measurements.size());
+      sourcelinks.emplace_back(sourceLink);
+      ThisMeasurement meas(sourceLink, myIndices, par, cov);
+      measurements.emplace_back(std::move(meas));
+
+      // create initial parameters from hit in first layer
+      if ((station == m_first_station) && (layer == m_first_layer) && (side == m_first_side)) {
+
+        // smear initial direction
+         auto theta = Acts::VectorHelpers::theta(direction);
+         auto phi = Acts::VectorHelpers::phi(direction);
+         auto angles = Acts::detail::normalizePhiTheta(
+             phi + m_sigmaPhi * M_PI/180. * norm(rng),
+             theta + m_sigmaTheta * M_PI/180. * norm(rng));
+         Acts::Vector3 smearedDirection(
+             std::sin(angles.second) * std::cos(angles.first),
+             std::sin(angles.second) * std::sin(angles.first),
+             std::cos(angles.second));
+         double smearedAbsoluteMomentum = abs_momentum * (1 + m_sigmaP * norm(rng));
+
+        double z_init = 0;
+        m_initialSurface = Acts::Surface::makeShared<Acts::PlaneSurface>(Acts::Vector3 {0, 0, z_init}, Acts::Vector3{0, 0, 1});
+
+        // extrapolate position on first layer to initial position
+        // TODO use first layer as initial layer instead?
+        Acts::Vector3 initPosition = position + (z_init - position.z()) / direction.z() * direction;
+        Acts::Vector3 smearedPosition {initPosition.x() + m_sigmaLoc0 * norm(rng), initPosition.y() + m_sigmaLoc1 * norm(rng), initPosition.z()};
+        Acts::Vector4 smearedPosition4 {smearedPosition.x(), smearedPosition.y(), smearedPosition.z(), 0};
+        double charge = hit.particleLink()->pdg_id() > 0 ? -1 : 1;
+
+        Acts::BoundSymMatrix cov = Acts::BoundSymMatrix::Zero();
+        cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = m_covLoc0;
+        cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = m_covLoc1;
+        cov(Acts::eBoundPhi, Acts::eBoundPhi) = m_covPhi;
+        cov(Acts::eBoundTheta, Acts::eBoundTheta) = m_covTheta;
+        cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = m_covQOverP;
+        cov(Acts::eBoundTime, Acts::eBoundTime) = m_covTime;
+
+        // auto initialParameters = Acts::BoundTrackParameters(surface->getSharedPtr(), params, charge, cov);
+        auto initialParameters = Acts::CurvilinearTrackParameters(smearedPosition4, smearedDirection, smearedAbsoluteMomentum, charge, cov);
+
+        // write out
+        double initialCharge = initialParameters.charge();
+        double initialAbsoluteMomentum = initialParameters.absoluteMomentum();
+        Acts::Vector3 initialPosition = initialParameters.position(gctx);
+        Acts::Vector3 initialMomentum = initialParameters.momentum();
+        ATH_MSG_DEBUG("initial charge: " << initialCharge);
+        ATH_MSG_DEBUG("initial absolute momentum: " << initialAbsoluteMomentum);
+        ATH_MSG_DEBUG("initial position: x=" << initialPosition.x() << ", y=" << initialPosition.y() << ", z=" << initialPosition.z());
+        ATH_MSG_DEBUG("initial momentum: x=" << initialMomentum.x() << ", y=" << initialMomentum.y() << ", z=" << initialMomentum.z());
+        m_initialTrackParameters = std::make_shared<const Acts::CurvilinearTrackParameters>(initialParameters);
+      }
+    }
+  }
+
+  m_sourceLinks = std::make_shared<std::vector<IndexSourceLink>>(sourcelinks);
+  m_measurements = std::make_shared<std::vector<Measurement>>(measurements);
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode TruthTrackFinderTool::finalize() {
+  return StatusCode::SUCCESS;
+}
\ No newline at end of file
diff --git a/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx b/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx
index 0b86d895fdd266b4df4bb9ba6ecb3b928581c9db..4c23cb7febdd977df01a7a4a4de997267dbfd4c6 100755
--- a/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx
+++ b/Tracking/Acts/FaserActsKalmanFilter/src/components/FaserActsKalmanFilter_entries.cxx
@@ -2,19 +2,56 @@
   Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
 */
 
-//#include "FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h"
+#include "FaserActsKalmanFilter/FaserActsKalmanFilterAlg.h"
 #include "FaserActsKalmanFilter/CombinatorialKalmanFilterAlg.h"
-#include "FaserActsKalmanFilter/TruthBasedInitialParameterTool.h"
-#include "FaserActsKalmanFilter/SPSeedBasedInitialParameterTool.h"
-#include "FaserActsKalmanFilter/SPSimpleInitialParameterTool.h"
-#include "FaserActsKalmanFilter/TrajectoryWriterTool.h"
-#include "FaserActsKalmanFilter/SimWriterTool.h"
+//#include "FaserActsKalmanFilter/MultiTrackFinderTool.h"
+//#include "FaserActsKalmanFilter/TruthBasedInitialParameterTool.h"
+//#include "FaserActsKalmanFilter/TruthTrackFinderTool.h"
+//#include "FaserActsKalmanFilter/SPSeedBasedInitialParameterTool.h"
+//#include "FaserActsKalmanFilter/SPSimpleInitialParameterTool.h"
+//#include "FaserActsKalmanFilter/TrajectoryWriterTool.h"
+//#include "FaserActsKalmanFilter/SimWriterTool.h"
+//#include "FaserActsKalmanFilter/TruthSeededTrackFinderTool.h"
+//#include "FaserActsKalmanFilter/ProtoTrackWriterTool.h"
+#include "FaserActsKalmanFilter/RootTrajectoryStatesWriterTool.h"
+#include "FaserActsKalmanFilter/RootTrajectorySummaryWriterTool.h"
+//#include "FaserActsKalmanFilter/SegmentFitClusterTrackFinderTool.h"
+//#include "FaserActsKalmanFilter/SegmentFitTrackFinderTool.h"
+//#include "FaserActsKalmanFilter/ClusterTrackSeedTool.h"
+#include "FaserActsKalmanFilter/ThreeStationTrackSeedTool.h"
+#include "FaserActsKalmanFilter/PerformanceWriterTool.h"
+#include "FaserActsKalmanFilter/TrackSeedWriterTool.h"
+#include "FaserActsKalmanFilter/ActsTrackSeedTool.h"
+#include "FaserActsKalmanFilter/CKF2.h"
+#include "FaserActsKalmanFilter/KalmanFitterTool.h"
+#include "FaserActsKalmanFilter/MyTrackSeedTool.h"
+#include "FaserActsKalmanFilter/SeedingAlg.h"
+#include "FaserActsKalmanFilter/CircleFitTrackSeedTool.h"
+#include "FaserActsKalmanFilter/GhostBusters.h"
 
-
-//DECLARE_COMPONENT(FaserActsKalmanFilterAlg)
+DECLARE_COMPONENT(FaserActsKalmanFilterAlg)
 DECLARE_COMPONENT(CombinatorialKalmanFilterAlg)
-DECLARE_COMPONENT(TruthBasedInitialParameterTool)
-DECLARE_COMPONENT(SPSeedBasedInitialParameterTool)
-DECLARE_COMPONENT(SPSimpleInitialParameterTool)
-DECLARE_COMPONENT(TrajectoryWriterTool)
-DECLARE_COMPONENT(SimWriterTool)
+//DECLARE_COMPONENT(TruthBasedInitialParameterTool)
+//DECLARE_COMPONENT(SPSeedBasedInitialParameterTool)
+//DECLARE_COMPONENT(SPSimpleInitialParameterTool)
+//DECLARE_COMPONENT(TrajectoryWriterTool)
+//DECLARE_COMPONENT(TruthTrackFinderTool)
+//DECLARE_COMPONENT(SimWriterTool)
+//DECLARE_COMPONENT(TruthSeededTrackFinderTool)
+//DECLARE_COMPONENT(ProtoTrackWriterTool)
+//DECLARE_COMPONENT(SegmentFitClusterTrackFinderTool)
+//DECLARE_COMPONENT(SegmentFitTrackFinderTool)
+DECLARE_COMPONENT(RootTrajectoryStatesWriterTool)
+DECLARE_COMPONENT(RootTrajectorySummaryWriterTool)
+//DECLARE_COMPONENT(MultiTrackFinderTool)
+//DECLARE_COMPONENT(ClusterTrackSeedTool)
+DECLARE_COMPONENT(ThreeStationTrackSeedTool)
+DECLARE_COMPONENT(PerformanceWriterTool)
+DECLARE_COMPONENT(TrackSeedWriterTool)
+DECLARE_COMPONENT(ActsTrackSeedTool)
+DECLARE_COMPONENT(CKF2)
+DECLARE_COMPONENT(KalmanFitterTool)
+DECLARE_COMPONENT(MyTrackSeedTool)
+DECLARE_COMPONENT(SeedingAlg)
+DECLARE_COMPONENT(CircleFitTrackSeedTool)
+DECLARE_COMPONENT(GhostBusters)
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/CKF2.py b/Tracking/Acts/FaserActsKalmanFilter/test/CKF2.py
new file mode 100644
index 0000000000000000000000000000000000000000..98059c9e65857ac9f702615daa263b9c307f7877
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/CKF2.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from FaserActsKalmanFilter.GhostBustersConfig import GhostBustersCfg
+from FaserActsKalmanFilter.CKF2Config import CKF2Cfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.addFlag("Output.xAODFileName", f"CKF.xAOD.root")
+ConfigFlags.Output.ESDFileName = "CKF.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+ConfigFlags.GeoModel.FaserVersion = "FASERNU-03"
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.TrackingGeometry.MaterialSource = "Input"
+ConfigFlags.Input.isMC = True
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, SharedHitFraction=0.61, MinClustersPerFit=5, TanThetaXZCut=0.083))
+acc.merge(GhostBustersCfg(ConfigFlags))
+acc.merge(CKF2Cfg(ConfigFlags, noDiagnostics=True))
+# acc.getEventAlgo("CKF2").OutputLevel = DEBUG
+
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = VERBOSE
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
+
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+itemList = [
+    "xAOD::EventInfo#*",
+    "xAOD::EventAuxInfo#*",
+    "xAOD::FaserTriggerData#*",
+    "xAOD::FaserTriggerDataAux#*",
+    "FaserSCT_RDO_Container#*",
+    "Tracker::FaserSCT_ClusterContainer#*",
+    "TrackCollection#*",
+]
+acc.merge(OutputStreamCfg(ConfigFlags, "xAOD", itemList))
+
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg.py b/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg.py
index eef29095e39bc064e2dde11149be503b0d0d79ea..308d6f1c55e0eacfa96c59cd4e82d59fd5cb0211 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg.py
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg.py
@@ -10,19 +10,19 @@ from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
 # from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
 from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
 from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
-from TrackerSeedFinder.TrackerSeedFinderConfig import TrackerSeedFinderCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
 from FaserActsKalmanFilter.CombinatorialKalmanFilterConfig import CombinatorialKalmanFilterCfg
 
 log.setLevel(DEBUG)
 Configurable.configurableRun3Behavior = True
 
 ConfigFlags.Input.Files = ['my.RDO.pool.root']
-ConfigFlags.Output.ESDFileName = "myCKF.ESD.pool.root"
-ConfigFlags.Output.AODFileName = "myCKF.AOD.pool.root"
+ConfigFlags.Output.ESDFileName = "CKF.ESD.pool.root"
 ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
-# ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
 ConfigFlags.GeoModel.Align.Dynamic = False
 ConfigFlags.Beam.NumberOfCollisions = 0.
+# ConfigFlags.TrackingGeometry.MaterialSource = "Input"
 ConfigFlags.lock()
 
 acc = MainServicesCfg(ConfigFlags)
@@ -31,8 +31,9 @@ acc.merge(PoolReadCfg(ConfigFlags))
 
 acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
 acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
-acc.merge(TrackerSeedFinderCfg(ConfigFlags))
-acc.merge(CombinatorialKalmanFilterCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, SharedHitFraction=0.51, MinClustersPerFit=5, TanThetaCut=0.25))
+acc.merge(CombinatorialKalmanFilterCfg(ConfigFlags, noDiagnostics=True))
+acc.getEventAlgo("CombinatorialKalmanFilterAlg").OutputLevel = VERBOSE
 
 # logging.getLogger('forcomps').setLevel(INFO)
 # acc.foreach_component("*").OutputLevel = INFO
@@ -42,6 +43,5 @@ acc.merge(CombinatorialKalmanFilterCfg(ConfigFlags))
 # acc.printConfig(withDetails=True)
 # ConfigFlags.dump()
 
-sc = acc.run(maxEvents=1000)
-
+sc = acc.run(maxEvents=-1)
 sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg_Data.py b/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg_Data.py
index 5cf53fa5fb7cb4262c4c276374d54a8ea51402ef..3ea0fccd734ab65888d3b3a9c7505d2d2dc63738 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg_Data.py
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/CombinatorialKalmanFilterAlg_Data.py
@@ -6,25 +6,23 @@ from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
 from AthenaCommon.Configurable import Configurable
 from CalypsoConfiguration.AllConfigFlags import ConfigFlags
 from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
-from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
 from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
-from WaveRecAlgs.WaveRecAlgsConfig import WaveformReconstructionCfg
+from FaserByteStreamCnvSvc.FaserByteStreamCnvSvcConfig import FaserByteStreamCnvSvcCfg
 from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
-from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
-from TrackerSeedFinder.TrackerSeedFinderConfig import TrackerSeedFinderCfg
 from FaserActsKalmanFilter.CombinatorialKalmanFilterConfig import CombinatorialKalmanFilterCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
 
 log.setLevel(DEBUG)
 Configurable.configurableRun3Behavior = True
 
-ConfigFlags.Input.Files = ['/eos/project-f/faser-commissioning/TI12Data/Run-004411/Faser-Physics-004411-00000.raw']
-ConfigFlags.Output.ESDFileName = "myCKF.ESD.pool.root"
-ConfigFlags.Output.AODFileName = "myCKF.AOD.pool.root"
+ConfigFlags.Input.Files = ['/home/tboeckh/Documents/data/raw/TI12/MiddleStationTrack.raw']
+ConfigFlags.Output.ESDFileName = "CKF.ESD.pool.root"
+ConfigFlags.addFlag("Output.xAODFileName", f"CKF.xAOD.root")
 ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
-ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"               # Use MC conditions for now
-ConfigFlags.Input.ProjectName = "data21"                     # Needed to bypass autoconfig
-ConfigFlags.Input.isMC = False                               # Needed to bypass autoconfig
-ConfigFlags.GeoModel.FaserVersion     = "FASER-01"           # FASER geometry
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data21"
+ConfigFlags.Input.isMC = False
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
 ConfigFlags.Common.isOnline = False
 ConfigFlags.GeoModel.Align.Dynamic = False
 ConfigFlags.Beam.NumberOfCollisions = 0.
@@ -32,24 +30,38 @@ ConfigFlags.Detector.GeometryFaserSCT = True
 ConfigFlags.lock()
 
 acc = MainServicesCfg(ConfigFlags)
-#acc.merge(PoolReadCfg(ConfigFlags))
 acc.merge(PoolWriteCfg(ConfigFlags))
-from FaserByteStreamCnvSvc.FaserByteStreamCnvSvcConfig import FaserByteStreamCnvSvcCfg               
 acc.merge(FaserByteStreamCnvSvcCfg(ConfigFlags))
-acc.merge(WaveformReconstructionCfg(ConfigFlags))
-acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, DataObjectName="SCT_EDGEMODE_RDOs"))
-acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
-acc.merge(TrackerSeedFinderCfg(ConfigFlags))
-acc.merge(CombinatorialKalmanFilterCfg(ConfigFlags))
-
-logging.getLogger('forcomps').setLevel(VERBOSE)
-acc.foreach_component("*").OutputLevel = VERBOSE
-acc.foreach_component("*ClassID*").OutputLevel = INFO
-acc.getService("StoreGateSvc").Dump = True
-acc.getService("ConditionStore").Dump = True
-acc.printConfig(withDetails=True)
-ConfigFlags.dump()
-
-sc = acc.run(maxEvents=-1)
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, DataObjectName="SCT_LEVELMODE_RDOs", ClusterToolTimingPattern="X1X"))
+acc.merge(SegmentFitAlgCfg(ConfigFlags))
+acc.merge(CombinatorialKalmanFilterCfg(ConfigFlags, SummaryWriter=False, StatesWriter=False, PerformanceWriter=False))
+acc.getEventAlgo("CombinatorialKalmanFilterAlg").OutputLevel = VERBOSE
 
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+itemList = ["xAOD::EventInfo#*",
+            "xAOD::EventAuxInfo#*",
+            "FaserSCT_RDO_Container#*",
+            "Tracker::FaserSCT_ClusterContainer#*",
+            "TrackCollection#*"]
+acc.merge(OutputStreamCfg(ConfigFlags, "xAOD", itemList))
+
+print( "Writing out xAOD objects:" )
+print( acc.getEventAlgo("OutputStreamxAOD").ItemList )
+
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = INFO
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
+
+# Hack to avoid problem with our use of MC databases when isMC = False
+replicaSvc = acc.getService("DBReplicaSvc")
+replicaSvc.COOLSQLiteVetoPattern = ""
+replicaSvc.UseCOOLSQLite = True
+replicaSvc.UseCOOLFrontier = False
+replicaSvc.UseGeomSQLite = True
+
+sc = acc.run(maxEvents=20)
 sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/FaserActsKalmanFilterAlg.py b/Tracking/Acts/FaserActsKalmanFilter/test/FaserActsKalmanFilterAlg.py
index 06975170f670f0fe8dec65b9a2d608aea6fc4c36..b2efe5323cf53e0220925994ab7b2d544462f81e 100644
--- a/Tracking/Acts/FaserActsKalmanFilter/test/FaserActsKalmanFilterAlg.py
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/FaserActsKalmanFilterAlg.py
@@ -11,6 +11,7 @@ from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
 from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
 from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
 from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
 from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
 from TruthSeededTrackFinder.TruthSeededTrackFinderConfig import TruthSeededTrackFinderCfg
 from FaserActsKalmanFilter.FaserActsKalmanFilterConfig import FaserActsKalmanFilterCfg
@@ -20,9 +21,10 @@ log.setLevel(DEBUG)
 Configurable.configurableRun3Behavior = True
 
 # Configure
-ConfigFlags.Input.Files = ['my.RDO.pool.root']
-ConfigFlags.Output.ESDFileName = "tmp.root"
-ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"             # Always needed; must match FaserVersion
+ConfigFlags.Input.Files = ['../my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "FaserActsKalmanFilter.ESD.root"
+ConfigFlags.Output.AODFileName = "FaserActsKalmanFilter.AOD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
 ConfigFlags.GeoModel.Align.Dynamic = False
 ConfigFlags.Beam.NumberOfCollisions = 0.
 #ConfigFlags.Concurrency.NumThreads = 1
@@ -34,20 +36,20 @@ acc.merge(PoolReadCfg(ConfigFlags))
 
 # Inner Detector
 acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
-acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
-acc.merge(TruthSeededTrackFinderCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags))
+# acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+# acc.merge(TruthSeededTrackFinderCfg(ConfigFlags))
 acc.merge(FaserActsKalmanFilterCfg(ConfigFlags))
+acc.getEventAlgo("FaserActsKalmanFilterAlg").OutputLevel = DEBUG
 
-logging.getLogger('forcomps').setLevel(VERBOSE)
-acc.foreach_component("*").OutputLevel = VERBOSE
-acc.foreach_component("*ClassID*").OutputLevel = INFO
-acc.getService("StoreGateSvc").Dump = True
-acc.getService("ConditionStore").Dump = True
-acc.printConfig(withDetails=True)
-ConfigFlags.dump()
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = INFO
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
 
 # Execute and finish
-sc = acc.run(maxEvents=-1)
-
-# Success should be 0
+sc = acc.run(maxEvents=1000)
 sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/GhostBusters.py b/Tracking/Acts/FaserActsKalmanFilter/test/GhostBusters.py
new file mode 100644
index 0000000000000000000000000000000000000000..297f76e61fd138c325b67e98d0ea195e062eb5f3
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/GhostBusters.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from FaserActsKalmanFilter.SeedingConfig import SeedingCfg
+from FaserActsKalmanFilter.GhostBustersConfig import GhostBustersCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "ghosts.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+# ConfigFlags.TrackingGeometry.MaterialSource = "Input"
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, SharedHitFraction=0.61, MinClustersPerFit=5, TanThetaXZCut=0.083))
+# acc.getEventAlgo("Tracker::SegmentFitAlg").OutputLevel = VERBOSE
+acc.merge(GhostBustersCfg(ConfigFlags, xTolerance=0.5, yTolerance=0.5))
+acc.getEventAlgo("GhostBusters").OutputLevel = DEBUG
+
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = VERBOSE
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
+
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/SeedingDbg.py b/Tracking/Acts/FaserActsKalmanFilter/test/SeedingDbg.py
new file mode 100644
index 0000000000000000000000000000000000000000..01e04b803a6f1ed60ddd54db02fcea952ad8849c
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/SeedingDbg.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from FaserActsKalmanFilter.SeedingConfig import SeedingCfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ['my.RDO.pool.root']
+ConfigFlags.Output.ESDFileName = "circleFitSeeding.ESD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
+ConfigFlags.GeoModel.FaserVersion = "FASER-01"
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+# ConfigFlags.TrackingGeometry.MaterialSource = "Input"
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolReadCfg(ConfigFlags))
+acc.merge(PoolWriteCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, SharedHitFraction=0.51, MinClustersPerFit=5, TanThetaCut=0.25))
+acc.merge(SeedingCfg(ConfigFlags))
+acc.getEventAlgo("SeedingAlg").OutputLevel = VERBOSE
+
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = VERBOSE
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
+
+sc = acc.run(maxEvents=10)
+sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/TI12CKF2.py b/Tracking/Acts/FaserActsKalmanFilter/test/TI12CKF2.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6c5a5af53581d986d57ee32ad2e4d5706c6f6e1
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/TI12CKF2.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+# from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from FaserByteStreamCnvSvc.FaserByteStreamCnvSvcConfig import FaserByteStreamCnvSvcCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from FaserActsKalmanFilter.GhostBustersConfig import GhostBustersCfg
+from FaserActsKalmanFilter.TI12CKF2Config import TI12CKF2Cfg
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+ConfigFlags.Input.Files = ['threeStationRun6833.raw']
+ConfigFlags.Output.ESDFileName = "CKF.ESD.pool.root"
+ConfigFlags.addFlag("Output.xAODFileName", f"CKF.xAOD.root")
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data22"
+ConfigFlags.Input.isMC = False
+ConfigFlags.GeoModel.FaserVersion = "FASER-02"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.TrackingGeometry.MaterialSource = "Input"
+ConfigFlags.lock()
+
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(PoolWriteCfg(ConfigFlags))
+acc.merge(FaserByteStreamCnvSvcCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, DataObjectName="SCT_LEVELMODE_RDOs", ClusterToolTimingPattern="X1X"))
+acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, SharedHitFraction=0.61, MinClustersPerFit=5, TanThetaXZCut=0.083))
+acc.merge(GhostBustersCfg(ConfigFlags))
+acc.merge(TI12CKF2Cfg(ConfigFlags, noDiagnostics=False))
+acc.getEventAlgo("CKF2").OutputLevel = DEBUG
+
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = VERBOSE
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
+
+# Hack to avoid problem with our use of MC databases when isMC = False
+replicaSvc = acc.getService("DBReplicaSvc")
+replicaSvc.COOLSQLiteVetoPattern = ""
+replicaSvc.UseCOOLSQLite = True
+replicaSvc.UseCOOLFrontier = False
+replicaSvc.UseGeomSQLite = True
+
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/FaserActsKalmanFilter/test/TI12KalmanFilter.py b/Tracking/Acts/FaserActsKalmanFilter/test/TI12KalmanFilter.py
new file mode 100644
index 0000000000000000000000000000000000000000..11b7e4217bed8400426f4257ef5cf43f5b7adbfd
--- /dev/null
+++ b/Tracking/Acts/FaserActsKalmanFilter/test/TI12KalmanFilter.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+import sys
+from AthenaCommon.Logging import log, logging
+from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+from AthenaCommon.Configurable import Configurable
+from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+from AthenaConfiguration.TestDefaults import defaultTestFiles
+from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+from FaserByteStreamCnvSvc.FaserByteStreamCnvSvcConfig import FaserByteStreamCnvSvcCfg
+from TrackerPrepRawDataFormation.TrackerPrepRawDataFormationConfig import FaserSCT_ClusterizationCfg
+from TrackerSegmentFit.TrackerSegmentFitConfig import SegmentFitAlgCfg
+from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+from FaserActsKalmanFilter.FaserActsKalmanFilterConfig import FaserActsKalmanFilterCfg
+
+
+log.setLevel(DEBUG)
+Configurable.configurableRun3Behavior = True
+
+# Configure
+ConfigFlags.Input.Files = ['/home/tboeckh/tmp/Faser-Physics-006470-00093.raw_middleStation.SPs']
+ConfigFlags.Output.ESDFileName = "MiddleStation-KalmanFilter.ESD.pool.root"
+ConfigFlags.Output.AODFileName = "MiddleStation-KalmanFilter.AOD.pool.root"
+ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"
+ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"
+ConfigFlags.Input.ProjectName = "data21"
+ConfigFlags.Input.isMC = False
+ConfigFlags.GeoModel.FaserVersion = "FASER-02"
+ConfigFlags.Common.isOnline = False
+ConfigFlags.GeoModel.Align.Dynamic = False
+ConfigFlags.Beam.NumberOfCollisions = 0.
+ConfigFlags.Detector.GeometryFaserSCT = True
+ConfigFlags.lock()
+
+# Core components
+acc = MainServicesCfg(ConfigFlags)
+acc.merge(FaserByteStreamCnvSvcCfg(ConfigFlags))
+acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags, name="LevelClustering", DataObjectName="SCT_LEVELMODE_RDOs", ClusterToolTimingPattern="X1X"))
+acc.merge(SegmentFitAlgCfg(ConfigFlags, name=f"LevelFit", MaxClusters=44))
+# acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
+# acc.merge(SegmentFitAlgCfg(ConfigFlags))
+# acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+acc.merge(FaserActsKalmanFilterCfg(ConfigFlags))
+acc.getEventAlgo("FaserActsKalmanFilterAlg").OutputLevel = DEBUG
+
+replicaSvc = acc.getService("DBReplicaSvc")
+replicaSvc.COOLSQLiteVetoPattern = ""
+replicaSvc.UseCOOLSQLite = True
+replicaSvc.UseCOOLFrontier = False
+replicaSvc.UseGeomSQLite = True
+
+
+# logging.getLogger('forcomps').setLevel(VERBOSE)
+# acc.foreach_component("*").OutputLevel = VERBOSE
+# acc.foreach_component("*ClassID*").OutputLevel = INFO
+# acc.getService("StoreGateSvc").Dump = True
+# acc.getService("ConditionStore").Dump = True
+# acc.printConfig(withDetails=True)
+# ConfigFlags.dump()
+
+# Execute and finish
+sc = acc.run(maxEvents=-1)
+sys.exit(not sc.isSuccess())
diff --git a/Tracking/Acts/README.md b/Tracking/Acts/README.md
index 2a0874fce87ed08d42fa4ac7c77d0226d430e318..5b4c24356a742621045dc568190bcb9c2315acca 100644
--- a/Tracking/Acts/README.md
+++ b/Tracking/Acts/README.md
@@ -6,3 +6,13 @@
 3) To write the tracking geometry, type the command: FaserActsWriteTrackingGeometry.py
 
 4) To run the extrapolator, type the command: FaserActsExtrapolationAlg.py
+
+5) The track finding requires a json file with the correct material in the directory in which you run it. A copy can be found on lxplus: /afs/cern.ch/user/t/tboeckh/public/material/material-maps.json  
+   If you do not want to use the material you have to comment out this line: ConfigFlags.TrackingGeometry.MaterialSource = "Input"
+
+6) To enable additional output set the noDiagnostics flag to False
+
+7) To do the track finding, type the command: CombinatorialKalmanFilterAlg.py
+
+8) To do the track finding and re-fit the best tracks, type the command: CKF2.py  
+   (this is the recommended approach)
diff --git a/Waveform/WaveDigiTools/CMakeLists.txt b/Waveform/WaveDigiTools/CMakeLists.txt
index 692fdb69bc14451ba5a6a660d011bad5b14b66e5..d7e9fd857b273c764dad780aceebd0d3f58acc3a 100644
--- a/Waveform/WaveDigiTools/CMakeLists.txt
+++ b/Waveform/WaveDigiTools/CMakeLists.txt
@@ -13,13 +13,12 @@ atlas_add_library( WaveDigiToolsLib
                    WaveDigiTools/*.h src/*.cxx src/*.h
                    PUBLIC_HEADERS WaveDigiTools
                    PRIVATE_INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
-                   LINK_LIBRARIES AthenaBaseComps AthenaKernel GeoPrimitives WaveRawEvent
+                   LINK_LIBRARIES AthenaBaseComps AthenaKernel GeoPrimitives WaveRawEvent Identifier 
                    PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES}
 		   )
 
 atlas_add_component( WaveDigiTools
 		     src/components/*.cxx 
 		     INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
-                     LINK_LIBRARIES ${ROOT_LIBRARIES} AthenaBaseComps GaudiKernel WaveDigiToolsLib )
-
+                     LINK_LIBRARIES ${ROOT_LIBRARIES} AthenaBaseComps GaudiKernel WaveDigiToolsLib)
 
diff --git a/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.h b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.h
index 7241e8167d36facc42748141b58f5b640b4d33ee..c30351902a1b79e9a58ad33d5f58a5d04ed13faa 100644
--- a/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.h
+++ b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.h
@@ -23,10 +23,15 @@
 #include "WaveRawEvent/RawWaveformContainer.h"
 #include "WaveRawEvent/RawWaveform.h"
 
+#include "Identifier/Identifier.h"
+
 #include "TF1.h"
 #include "TRandom3.h"
 
 #include <utility>
+#include <map>
+#include <vector>
+
 
 ///Interface for waveform digitisation tools
 class IWaveformDigitisationTool : virtual public IAlgTool 
@@ -42,19 +47,25 @@ public:
 
   virtual ~IWaveformDigitisationTool() = default;
 
-  // Digitise HITS to Raw waveform
-  template<class CONT>
-  StatusCode digitise(const CONT* hitCollection, 
-		      RawWaveformContainer* waveContainer, 
-		      TF1* kernel, std::pair<float, float> base
-		      ) const;
+  /// Evaluate time kernel over time samples  
+  virtual std::vector<float> evaluate_timekernel(TF1* kernel) const = 0;
+
+  /// Generate random baseline 
+  virtual unsigned int generate_baseline(int mean, int rms) const = 0;
+ 
+  /// Create structure to store pulse for each channel
+  template <class T> 
+  std::map<Identifier, std::vector<uint16_t>> create_waveform_map(const T* idHelper) const;
+
+  /// Number of time samples
+  unsigned int nsamples() const { return m_nsamples; }
 
 private:
   ServiceHandle<IMessageSvc>      m_msgSvc;
 
 protected:
   TRandom3*                       m_random;
-
+  unsigned int                    m_nsamples;
 };
 
 #include "WaveDigiTools/IWaveformDigitisationTool.icc"
diff --git a/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.icc b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.icc
index b07e1836ac5cedfefa87a8fcbe8c910de886693c..41b8c2650319a448df63d11e098fc8d0784dc056 100644
--- a/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.icc
+++ b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.icc
@@ -1,62 +1,17 @@
-#include <vector>
-#include <map>
+#include "Identifier/Identifier.h"
+#include "Identifier/ExpandedIdentifier.h"
 
-template<class CONT>
-StatusCode IWaveformDigitisationTool::digitise(const CONT* hitCollection,
-					       RawWaveformContainer* container, TF1* kernel,
-					       std::pair<float, float> base) const {
+template <class ID> 
+std::map<Identifier, std::vector<uint16_t>>  IWaveformDigitisationTool::create_waveform_map(const ID* idHelper) const {
 
+  std::map<Identifier, std::vector<uint16_t>> waveforms;
 
-  // Check the container
-  if (!container) {
-    MsgStream log(&(*m_msgSvc), name());
-    log << MSG::ERROR << "HitCollection passed to digitise() is null!" << endmsg;
-    return StatusCode::FAILURE;
+  for (auto itr = idHelper->pmt_begin(); itr != idHelper->pmt_end(); ++itr) {
+    const ExpandedIdentifier& extId = *itr;
+    Identifier id = idHelper->pmt_id(extId);
+    waveforms[id] = std::vector<uint16_t>();
+    waveforms[id].reserve(m_nsamples);
   }
 
-  unsigned int size = 600;  // TODO: how know the correct number of time samples?
-  std::vector<float> time(size);  
-  for (unsigned int i=0; i<size; i++) time[i] = 2.*i;
-
-  std::map<unsigned int, std::vector<uint16_t>> waveforms;
-  //unsigned int baseline = 800; 
-
-  // TODO: varying time to centre of edge of bin (odd = centre, even = edge)
-
-  // Loop over time samples
-  for (const auto& t : time) {
-    std::map<unsigned int, float> counts;
-   
-    unsigned int baseline = m_random->Gaus(base.first, base.second); 
-
-    // Convolve hit energy with kernel and sum for each ID (i.e. channel)
-    for (const auto& hit : *hitCollection) { 
-      counts[hit.identify()] += kernel->Eval(t) * hit.energyLoss();
-      //std::cout << "HIT " << hit.identify() << " @ " << t << ": " <<  kernel->Eval(t) << " " << hit.energyLoss() << " -> " << counts[hit.identify()] << std::endl;
-    }
-
-    // Add count to correct waveform vec
-    for (const auto& c : counts) {
-      int value = baseline - c.second;
-      if (value < 0) {
-	MsgStream log(&(*m_msgSvc), name());
-	log << MSG::WARNING << "Found pulse " << c.second << " larger than baseline " << c.first << endmsg;
-	value = 0; // Protect against scaling signal above baseline
-      }
-      waveforms[c.first].push_back(value);
-      //std::cout << "ADC " << c.first << " @ " << t << ": " << baseline - c.second << std::endl;
-    }
-  }
-
-  // Loop over wavefrom vecs to make and store waveform
-  for (const auto& w : waveforms) {
-    RawWaveform* wfm = new RawWaveform();
-    wfm->setWaveform(0, w.second);
-    wfm->setIdentifier(Identifier(w.first));
-    wfm->setSamples(size);
-    container->push_back(wfm);
-  }
-
-
-  return StatusCode::SUCCESS;
+  return waveforms;
 }
diff --git a/Waveform/WaveDigiTools/src/WaveformDigitisationTool.cxx b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.cxx
index 1c71fe193105f089bec81a5821d4a5558cdb39ca..c62d7f8a753490907bd1e8a513cad5341a2a46be 100644
--- a/Waveform/WaveDigiTools/src/WaveformDigitisationTool.cxx
+++ b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.cxx
@@ -20,8 +20,28 @@ WaveformDigitisationTool::WaveformDigitisationTool(const std::string& type, cons
 StatusCode
 WaveformDigitisationTool::initialize() {
   ATH_MSG_INFO( name() << "::initalize()" );
+
+  m_nsamples = 600;
   m_random = new TRandom3();
+
   return StatusCode::SUCCESS;
 }
 
 
+std::vector<float>
+WaveformDigitisationTool::evaluate_timekernel(TF1* kernel) const {
+  
+  std::vector<float> timekernel;
+  timekernel.reserve(m_nsamples);
+  
+  for (unsigned int i=0; i<m_nsamples; i++) {
+    timekernel.push_back(kernel->Eval(2.*i));  
+  }
+  
+  return timekernel;
+}
+
+unsigned int 
+WaveformDigitisationTool::generate_baseline(int mean, int rms) const {
+  return m_random->Gaus(mean, rms);
+}
diff --git a/Waveform/WaveDigiTools/src/WaveformDigitisationTool.h b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.h
index 8a5ba71f3dd124fcdd2c6b4b8124ee96591512da..e2dd5169152845824927baeeae7ce8fc36ab46f8 100644
--- a/Waveform/WaveDigiTools/src/WaveformDigitisationTool.h
+++ b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.h
@@ -28,6 +28,13 @@ class WaveformDigitisationTool: public extends<AthAlgTool, IWaveformDigitisation
   /// Retrieve the necessary services in initialize
   StatusCode initialize();
 
+  /// Evaluate time kernel over samples  
+  std::vector<float> evaluate_timekernel(TF1* kernel) const;
+
+  /// Generate random baseline 
+  unsigned int generate_baseline(int mean, int rms) const;
+
+
  private:
   // None
 
diff --git a/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py b/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py
index b3e1e73042c8d608c68865d1328572b1e5b98955..ad6bfcd1519674674ff7d101e320bc47176198ed 100644
--- a/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py
+++ b/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py
@@ -2,8 +2,11 @@
 
 from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
 from WaveformConditionsTools.WaveformCableMappingConfig import WaveformCableMappingCfg
+from WaveformConditionsTools.WaveformRangeConfig import WaveformRangeCfg
 
 def WaveByteStreamCfg(configFlags, **kwargs):
-    acc = WaveformCableMappingCfg(configFlags, **kwargs)
+    acc = ComponentAccumulator()
+    acc.merge(WaveformCableMappingCfg(configFlags, **kwargs))
+    acc.merge(WaveformRangeCfg(configFlags, **kwargs))
     return acc
         
diff --git a/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.cxx b/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.cxx
index f3a9f9991fd0d926584727bc7546fb00ea52732d..852ae084edac08380e302ec3026548d667da70c2 100644
--- a/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.cxx
+++ b/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.cxx
@@ -43,7 +43,8 @@ StatusCode
 RawWaveformDecoderTool::convert(const DAQFormats::EventFull* re, 
 				RawWaveformContainer* container,
 				const std::string key,
-				WaveformCableMap cable_map
+				WaveformCableMap cable_map,
+				WaveformRangeMap range_map
 )
 {
   ATH_MSG_DEBUG("RawWaveformDecoderTool::convert("+key+")");
@@ -90,6 +91,8 @@ RawWaveformDecoderTool::convert(const DAQFormats::EventFull* re,
     det_type = std::string("calo");
   } else if (key == std::string("VetoWaveforms")) {
     det_type = std::string("veto");
+  } else if (key == std::string("VetoNuWaveforms")) {
+    det_type = std::string("vetonu");
   } else if (key == std::string("TriggerWaveforms")) {
     det_type = std::string("trigger");
   } else if (key == std::string("PreshowerWaveforms")) {
@@ -141,10 +144,13 @@ RawWaveformDecoderTool::convert(const DAQFormats::EventFull* re,
     }
 
     // Set ID if one exists (clock, for instance, doesn't have an identifier)
-    if (cable_map[channel].second != -1) { // Identifier doesn't have operator>=
+    if (cable_map.at(channel).second != -1) { // Identifier doesn't have operator>=
       wfm->setIdentifier(cable_map[channel].second);
     }
 
+    // Set ADC range
+    wfm->setRange(range_map.at(channel));
+
     container->push_back(wfm);    
 
     // Sanity check
diff --git a/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.h b/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.h
index 1609d3ebacd3940bdfcd89f3c1af39b2a0a9c648..5d3ff24600e76a6d37c7e5bb60e0729e36bb71ac 100644
--- a/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.h
+++ b/Waveform/WaveEventCnv/WaveByteStream/src/RawWaveformDecoderTool.h
@@ -14,6 +14,7 @@
 #include "WaveRawEvent/RawWaveformContainer.h"
 
 #include "WaveformConditionsTools/IWaveformCableMappingTool.h"
+#include "WaveformConditionsTools/IWaveformRangeTool.h"
 
 // This class provides conversion between bytestream and Waveform objects
 
@@ -30,7 +31,7 @@ class RawWaveformDecoderTool : public AthAlgTool {
   virtual StatusCode initialize();
   virtual StatusCode finalize();
 
-  StatusCode convert(const DAQFormats::EventFull* re, RawWaveformContainer* wfm, std::string key, WaveformCableMap cable_map);
+  StatusCode convert(const DAQFormats::EventFull* re, RawWaveformContainer* wfm, std::string key, WaveformCableMap cable_map, WaveformRangeMap range_map);
 
 private:
 };
diff --git a/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.cxx b/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.cxx
index b40fc3bd8dda5469e52be5d297ad29cf4dc2e98b..38d1cc20c5240277d6a985c2ba8e57d064baa49f 100644
--- a/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.cxx
+++ b/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.cxx
@@ -27,6 +27,7 @@ WaveByteStreamCnv::WaveByteStreamCnv(ISvcLocator* svcloc)
   , m_name("WaveByteStreamCnv")
   , m_tool("RawWaveformDecoderTool")
   , m_mappingTool("WaveformCableMappingTool")
+  , m_rangeTool("WaveformRangeTool")
   , m_rdpSvc("FaserROBDataProviderSvc", m_name)
 {
   ATH_MSG_DEBUG(m_name+"::initialize() called");
@@ -49,7 +50,7 @@ StatusCode WaveByteStreamCnv::initialize()
   CHECK(m_rdpSvc.retrieve());
   CHECK(m_tool.retrieve());
   CHECK(m_mappingTool.retrieve());
-
+  CHECK(m_rangeTool.retrieve());
   return StatusCode::SUCCESS;
 }
 
@@ -96,8 +97,11 @@ StatusCode WaveByteStreamCnv::createObj(IOpaqueAddress* pAddr, DataObject*& pObj
   auto mapping = m_mappingTool->getCableMapping();
   ATH_MSG_DEBUG("Cable mapping contains " << mapping.size() << " entries");
 
+  auto range = m_rangeTool->getRangeMapping();
+  ATH_MSG_DEBUG("Range contains " << range.size() << " entries");
+
   // Convert selected channels
-  CHECK( m_tool->convert(re, wfmCont, key, mapping) );
+  CHECK( m_tool->convert(re, wfmCont, key, mapping, range) );
   
   pObj = SG::asStorable(wfmCont);
 
diff --git a/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.h b/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.h
index ce373326fd61a3ab9f8552b5a6f561b449cc6850..960b8759e2f72633eae58efb882d67848cdfee07 100644
--- a/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.h
+++ b/Waveform/WaveEventCnv/WaveByteStream/src/WaveByteStreamCnv.h
@@ -15,6 +15,7 @@
 #include "AthenaBaseComps/AthMessaging.h"
 #include "FaserByteStreamCnvSvcBase/FaserByteStreamAddress.h"
 #include "WaveformConditionsTools/IWaveformCableMappingTool.h"
+#include "WaveformConditionsTools/IWaveformRangeTool.h"
 
 class RawWaveformDecoderTool;
 class IFaserROBDataProviderSvc;
@@ -41,6 +42,7 @@ private:
   std::string m_name;
   ToolHandle<RawWaveformDecoderTool> m_tool;
   ToolHandle<IWaveformCableMappingTool> m_mappingTool;
+  ToolHandle<IWaveformRangeTool> m_rangeTool;
   ServiceHandle<IFaserROBDataProviderSvc> m_rdpSvc;
 };
 
diff --git a/Waveform/WaveRawEvent/WaveRawEvent/RawWaveform.h b/Waveform/WaveRawEvent/WaveRawEvent/RawWaveform.h
index 3770e05513a4d6f81ca16aa08785ea0c36a459cb..a1f42f6e86db846ea544daedb482ba8aba8fbdf5 100644
--- a/Waveform/WaveRawEvent/WaveRawEvent/RawWaveform.h
+++ b/Waveform/WaveRawEvent/WaveRawEvent/RawWaveform.h
@@ -62,11 +62,17 @@ public:
   // Waveform data
   unsigned int channel() const;
   const std::vector<unsigned int>& adc_counts() const;
+  size_t size() const {return m_adc_counts.size();}
 
   // Return channel identifier
   Identifier identify() const;
   Identifier32 identify32() const;
 
+  // Full-scale range (in V) of 14-bit ADC reading
+  // mV per bit is given by range() / 16.384
+  float range() const;
+  float mv_per_bit() const {return m_range / 16.384;}
+
   // some print-out:
   void print() const;
 
@@ -89,6 +95,8 @@ public:
   void setSamples(unsigned int samp) {m_samples = samp;}
   void setCounts(const std::vector<unsigned int>& counts) {m_adc_counts = counts;}
 
+  void setRange(float range) {m_range = range;}
+
   ///////////////////////////////////////////////////////////////////
   // Private data:
   ///////////////////////////////////////////////////////////////////
@@ -105,6 +113,8 @@ private:
   std::vector<unsigned int> m_adc_counts;
 
   Identifier32 m_ID;
+
+  float m_range;  
 };
 
 
@@ -145,6 +155,9 @@ RawWaveform::identify() const { return Identifier(m_ID); }
 inline Identifier32
 RawWaveform::identify32() const { return m_ID; }
 
+inline float
+RawWaveform::range() const { return m_range; }
+
 std::ostream 
 &operator<<(std::ostream &out, const RawWaveform &wfm);
 
diff --git a/Waveform/WaveRawEvent/src/RawWaveform.cxx b/Waveform/WaveRawEvent/src/RawWaveform.cxx
index d6ef63493018955a79d42eb2c4f7fe2eebc40de2..64c401dfad6a9fe9794737a953eca31eff110bc1 100644
--- a/Waveform/WaveRawEvent/src/RawWaveform.cxx
+++ b/Waveform/WaveRawEvent/src/RawWaveform.cxx
@@ -17,7 +17,8 @@ RawWaveform::RawWaveform( ) :
   m_samples(0),
   m_channel(0),
   m_adc_counts(),
-  m_ID(0xffff)
+  m_ID(0xffff),
+  m_range(2.)
 {
 
 }
diff --git a/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py b/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py
index d38eb4fd0a7ce0c26ffa1a78fd52a91f0261ac94..414773bf9dc2e9b8140b33d81a80220c4288e4bf 100644
--- a/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py
+++ b/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py
@@ -6,30 +6,26 @@ from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
 from AthenaConfiguration.ComponentFactory import CompFactory
 
 from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+from WaveformConditionsTools.WaveformTimingConfig import WaveformTimingCfg
 
 WaveformReconstructionTool = CompFactory.WaveformReconstructionTool
 ClockReconstructionTool = CompFactory.ClockReconstructionTool
 
 # One stop shopping for normal FASER data
-def WaveformReconstructionCfg(flags, naive = False):
+def WaveformReconstructionCfg(flags):
     """ Return all algorithms and tools for Waveform reconstruction """
     acc = ComponentAccumulator()
 
     if not flags.Input.isMC:
         acc.merge(WaveformClockRecCfg(flags, "ClockRecAlg"))
 
-    if flags.Input.isMC  and naive:
-        if "TB" not in flags.GeoModel.FaserVersion:
-            acc.merge(PseudoScintHitToWaveformRecCfg(flags, "PseudoTriggerHitWaveformRecAlg", "Trigger"))
-        acc.merge(PseudoScintHitToWaveformRecCfg(flags, "PseudoVetoHitToWaveformRecAlg", "Veto"))
-        acc.merge(PseudoScintHitToWaveformRecCfg(flags, "PseudoPresehowerHitWaveformRecAlg", "Preshower"))
-        acc.merge(PseudoCaloHitToWaveformRecCfg(flags, "PseudoCaloHitWaveformRecAlg"))                
-        return acc
-
     acc.merge(WaveformHitRecCfg(flags, "TriggerWaveformRecAlg", "Trigger"))
     acc.merge(WaveformHitRecCfg(flags, "VetoWaveformRecAlg", "Veto"))
     acc.merge(WaveformHitRecCfg(flags, "PreshowerWaveformRecAlg", "Preshower"))
     acc.merge(WaveformHitRecCfg(flags, "CaloWaveformRecAlg", "Calo"))
+    acc.merge(WaveformHitRecCfg(flags, "VetoNuWaveformRecAlg", "VetoNu"))
+
+    acc.merge(WaveformTimingCfg(flags))
 
     return acc
 
@@ -49,13 +45,13 @@ def WaveformClockRecCfg(flags, name="ClockRecAlg", **kwargs):
     return acc
 
 # Return configured WaveformHit reconstruction algorithm
-# Specify data source (Veto, Trigger, Preshower, Calo, Test)
+# Specify data source (Veto, VetoNu, Trigger, Preshower, Calo, Test)
 def WaveformHitRecCfg(flags, name="WaveformRecAlg", source="", **kwargs):
 
     acc = ComponentAccumulator()
 
-    if flags.Input.isMC:
-        kwargs.setdefault("PeakThreshold", 5)
+    #if flags.Input.isMC:
+    #    kwargs.setdefault("PeakThreshold", 5)
 
     tool = WaveformReconstructionTool(name=source+"WaveformRecTool", **kwargs)
 
diff --git a/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.cxx b/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.cxx
index 97f122e50f51c17f2ac3f0e0ff3174cf2f337347..8f8ac480969aea1f424021e3985e695d21318783 100644
--- a/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.cxx
+++ b/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.cxx
@@ -31,9 +31,10 @@ RawWaveformRecAlg::finalize() {
 
   ATH_MSG_INFO( m_numberOfEvents << " events processed" );
   if ( m_numberOfEvents > 0) { 
-    ATH_MSG_INFO( m_numberOfWaveforms << " waveforms found" );
-    ATH_MSG_INFO( m_numberOfOverflows << " overflows" );
-    ATH_MSG_INFO( m_numberOfFitErrors << " fit errors" );
+    ATH_MSG_INFO( m_numberOfWaveforms   << " waveforms found over threshold" );
+    ATH_MSG_INFO( m_numberOfSecondaries << " secondary waveforms found" );
+    ATH_MSG_INFO( m_numberOfOverflows   << " overflows" );
+    ATH_MSG_INFO( m_numberOfFitErrors   << " fit errors" );
   }
 
   return StatusCode::SUCCESS;
@@ -43,6 +44,9 @@ StatusCode
 RawWaveformRecAlg::execute(const EventContext& ctx) const {
   ATH_MSG_DEBUG("Executing");
 
+  // Keep track of some statistics
+  m_numberOfEvents++;
+
   ATH_MSG_DEBUG("Run: " << ctx.eventID().run_number() 
 		<< " Event: " << ctx.eventID().event_number());
 
@@ -52,41 +56,56 @@ RawWaveformRecAlg::execute(const EventContext& ctx) const {
   ATH_CHECK( waveformHandle.isValid() );
   ATH_MSG_DEBUG("Found ReadHandle for RawWaveformContainer " << m_waveformContainerKey);
 
+  // Find the output waveform container
+  SG::WriteHandle<xAOD::WaveformHitContainer> hitContainerHandle(m_waveformHitContainerKey, ctx);
+  ATH_CHECK( hitContainerHandle.record( std::make_unique<xAOD::WaveformHitContainer>(),
+					std::make_unique<xAOD::WaveformHitAuxContainer>() ) );
+
+  ATH_MSG_DEBUG("WaveformsHitContainer '" << hitContainerHandle.name() << "' initialized");
+
   if (waveformHandle->size() == 0) {
     ATH_MSG_DEBUG("Waveform container found with zero length!");
     return StatusCode::SUCCESS;
   }
 
+  // First reconstruct the primary hit (based on trigger time)
+  for( const auto& wave : *waveformHandle) {
+    ATH_MSG_DEBUG("Reconstruct primary waveform for channel " << wave->channel());
+    CHECK( m_recoTool->reconstructPrimary(*wave, hitContainerHandle.ptr()) );
+  }
+
+  // Second, reconstruct any additional out of time hits
+  if (m_findMultipleHits) {
+    for( const auto& wave : *waveformHandle) {
+      ATH_MSG_DEBUG("Reconstruct secondary waveform for channel " << wave->channel());
+      CHECK( m_recoTool->reconstructSecondary(*wave, hitContainerHandle.ptr()) );
+    }
+  }
+
   // Also find the clock information
   SG::ReadHandle<xAOD::WaveformClock> clockHandle(m_clockKey, ctx);
   const xAOD::WaveformClock* clockptr = NULL;
 
+  // Fix timing for all hits
   // Can survive without this, but make a note
   if ( clockHandle.isValid() ) {
     ATH_MSG_DEBUG("Found ReadHandle for WaveformClock");
     clockptr = clockHandle.ptr();
+    CHECK( m_recoTool->setLocalTime(clockptr, hitContainerHandle.ptr()) );
   } else {
     ATH_MSG_WARNING("Didn't find ReadHandle for WaveformClock!");
   }
 
-  // Find the output waveform container
-  SG::WriteHandle<xAOD::WaveformHitContainer> hitContainerHandle(m_waveformHitContainerKey, ctx);
-  ATH_CHECK( hitContainerHandle.record( std::make_unique<xAOD::WaveformHitContainer>(),
-					std::make_unique<xAOD::WaveformHitAuxContainer>() ) );
-
-  ATH_MSG_DEBUG("WaveformsHitContainer '" << hitContainerHandle.name() << "' initialized");
-
-  // Reconstruct all waveforms
-  CHECK( m_recoTool->reconstructAll(*waveformHandle, clockptr, hitContainerHandle.ptr()) );
-
   ATH_MSG_DEBUG("WaveformsHitContainer '" << hitContainerHandle.name() << "' filled with "<< hitContainerHandle->size() <<" items");
 
   // Keep track of some statistics
-  m_numberOfEvents++;
   for (const auto& hit : *(hitContainerHandle.ptr())) {
     if (hit->status_bit(xAOD::WaveformStatus::THRESHOLD_FAILED)) continue;
+
     m_numberOfWaveforms++;
     if (hit->status_bit(xAOD::WaveformStatus::WAVE_OVERFLOW)) m_numberOfOverflows++;
+    if (hit->status_bit(xAOD::WaveformStatus::SECONDARY)) m_numberOfSecondaries++;
+    
     if (hit->status_bit(xAOD::WaveformStatus::GFIT_FAILED)) {
       m_numberOfFitErrors++;
     } else if (hit->status_bit(xAOD::WaveformStatus::CBFIT_FAILED)) {
diff --git a/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.h b/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.h
index e57501a730dd5f67c38435cb9594b0a7def2ec75..4de415d21d162a3aa2b64fe7f1ef1bb14e0237d9 100644
--- a/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.h
+++ b/Waveform/WaveRecAlgs/src/RawWaveformRecAlg.h
@@ -42,6 +42,10 @@ class RawWaveformRecAlg : public AthReentrantAlgorithm {
   virtual StatusCode finalize() override;
   //@}
 
+  //
+  // Look for more than one hit in each channel
+  BooleanProperty m_findMultipleHits{this, "FindMultipleHits", true};
+
  private:
 
   /** @name Disallow default instantiation, copy, assignment */
@@ -90,6 +94,7 @@ class RawWaveformRecAlg : public AthReentrantAlgorithm {
   //@{
   mutable std::atomic<int> m_numberOfEvents{0};
   mutable std::atomic<int> m_numberOfWaveforms{0};
+  mutable std::atomic<int> m_numberOfSecondaries{0};
   mutable std::atomic<int> m_numberOfOverflows{0};
   mutable std::atomic<int> m_numberOfFitErrors{0};
   //@}
diff --git a/Waveform/WaveRecTools/CMakeLists.txt b/Waveform/WaveRecTools/CMakeLists.txt
index d8f3e6f053232477a1b1501a26e3a88efe9272c1..7c42c848a64280325967fc058b7d9e8ac31170e3 100644
--- a/Waveform/WaveRecTools/CMakeLists.txt
+++ b/Waveform/WaveRecTools/CMakeLists.txt
@@ -13,13 +13,16 @@ atlas_add_library( WaveRecToolsLib
                    WaveRecTools/*.h src/*.cxx src/*.h
                    PUBLIC_HEADERS WaveRecTools
                    PRIVATE_INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
-                   LINK_LIBRARIES AthenaBaseComps AthenaKernel GeoPrimitives WaveRawEvent xAODFaserWaveform
+                   LINK_LIBRARIES AthenaBaseComps AthenaKernel GeoPrimitives 
+		   WaveformConditionsToolsLib WaveRawEvent xAODFaserWaveform Identifier
                    PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES}
 		   )
 
 atlas_add_component( WaveRecTools
 		     src/components/*.cxx 
 		     INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
-                     LINK_LIBRARIES ${ROOT_LIBRARIES} AthenaBaseComps GaudiKernel WaveRecToolsLib )
+                     LINK_LIBRARIES ${ROOT_LIBRARIES} 
+		     WaveformConditionsToolsLib AthenaBaseComps GaudiKernel 
+		     WaveRecToolsLib)
 
 
diff --git a/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.h b/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.h
index 6ba6e4eacdb8288a5f3667b02c3823aeaeb54d7e..fda076b2ae7a2a22bc5743b32eded9b92a006da3 100644
--- a/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.h
+++ b/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.h
@@ -23,6 +23,8 @@
 #include "xAODFaserWaveform/WaveformHitContainer.h"
 #include "xAODFaserWaveform/WaveformHit.h"
 
+class Identifier;
+
 ///Interface for Pseudo waveform rec tools
 class IPseudoSimToWaveformRecTool : virtual public IAlgTool 
 {
@@ -31,9 +33,11 @@ public:
   // InterfaceID
   DeclareInterfaceID(IPseudoSimToWaveformRecTool, 1, 0);
 
+  /*
   IPseudoSimToWaveformRecTool():
     m_msgSvc         ( "MessageSvc",   "ITrkEventCnvTool" )
   {}
+  */
 
   virtual ~IPseudoSimToWaveformRecTool() = default;
 
@@ -42,8 +46,9 @@ public:
   StatusCode reconstruct(const CONT* hitCollection, 
 			 xAOD::WaveformHitContainer* waveContainer) const;
 
-private:
-  ServiceHandle<IMessageSvc>      m_msgSvc;
+  // Make the actual hits (separate this so it doesn't need to be in templated function
+  virtual StatusCode make_hits(const std::map<Identifier, float>& sums, 
+	      xAOD::WaveformHitContainer* waveContainer) const = 0;
 
 };
 
diff --git a/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.icc b/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.icc
index 9f85c6b020b3efc9d81949cbf914b2a424bda19b..29a689bdc2e3c7c665f08df1462ba6afb237fd84 100644
--- a/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.icc
+++ b/Waveform/WaveRecTools/WaveRecTools/IPseudoSimToWaveformRecTool.icc
@@ -2,35 +2,12 @@ template<class CONT>
 StatusCode IPseudoSimToWaveformRecTool::reconstruct(const CONT* hitCollection,
 						    xAOD::WaveformHitContainer* container) const {
 
-
-  // Check the container
-  if (!container) {
-    MsgStream log(&(*m_msgSvc), name());
-    log << MSG::ERROR << "HitCollection passed to reconstruct() is null!" << endmsg;
-    return StatusCode::FAILURE;
-  }
-
-  std::map<int, float> idSums;
+  std::map<Identifier, float> idSums;
 
   // Sum hits in each "channel" 
   for (const auto& hit : *hitCollection) {
-    idSums[hit.identify()] += hit.energyLoss();   
-  }
-
-  for (const auto& id : idSums) {
-      xAOD::WaveformHit* hit = new xAOD::WaveformHit();
-      container->push_back(hit);
-
-      hit->set_id(id.first);
-      hit->set_channel(0);
-      hit->set_peak(0);
-      hit->set_mean(0);
-      hit->set_width(0);
-      hit->set_integral(id.second); 
-      hit->set_localtime(0);
-      hit->set_raw_peak(0);
-      hit->set_raw_integral(0); 
+    idSums[hit.getIdentifier()] += hit.energyLoss();   
   }
 
-  return StatusCode::SUCCESS;
+  return make_hits(idSums, container);	
 }
diff --git a/Waveform/WaveRecTools/WaveRecTools/IWaveformReconstructionTool.h b/Waveform/WaveRecTools/WaveRecTools/IWaveformReconstructionTool.h
index cc10197b262f0695327f7304a84998d198300678..c72c2502c9391861a3aec0696f47d568a7d7aa2b 100644
--- a/Waveform/WaveRecTools/WaveRecTools/IWaveformReconstructionTool.h
+++ b/Waveform/WaveRecTools/WaveRecTools/IWaveformReconstructionTool.h
@@ -32,15 +32,17 @@ class IWaveformReconstructionTool : virtual public IAlgTool
 
   virtual ~IWaveformReconstructionTool() = default;
 
-  // Reconstruct all waveforms
-  virtual StatusCode reconstructAll(const RawWaveformContainer& waveContainer,
-				 const xAOD::WaveformClock* clock, 
-				 xAOD::WaveformHitContainer* container) const = 0;
-				    
-  // Reconstruct all peaks in a raw waveform
-  virtual StatusCode reconstruct(const RawWaveform& wave, 
-				 const xAOD::WaveformClock* clock, 
-				 xAOD::WaveformHitContainer* container) const = 0;
+  // Reconstruct hits in trigger window
+  virtual StatusCode reconstructPrimary(const RawWaveform& wave, 
+					xAOD::WaveformHitContainer* container) const = 0;
+
+  // Reconstruct secondary hits anywhere in the waveform
+  virtual StatusCode reconstructSecondary(const RawWaveform& wave, 
+					  xAOD::WaveformHitContainer* container) const = 0;
+
+  // Set local hit times from LHC clock
+  virtual StatusCode setLocalTime(const xAOD::WaveformClock* clock,
+				  xAOD::WaveformHitContainer* container) const = 0;
 
 };
 
diff --git a/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx b/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx
index c45ea51007362eecd0ab7827f50123c05ddebab7..825cc755862200c0dd008eaf3c273cb23b37f83a 100644
--- a/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx
+++ b/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx
@@ -102,10 +102,10 @@ ClockReconstructionTool::reconstruct(const RawWaveform& raw_wave,
       ATH_MSG_DEBUG("Index: " << i << " Freq: " << i*freqmult << " Mag: " << magnitude[i]);
   }
 
-  // Store results
-  clockdata->set_dc_offset(magnitude[0]);
+  // Store results (amplitides in mV)
+  clockdata->set_dc_offset(raw_wave.mv_per_bit()*magnitude[0]);
+  clockdata->set_amplitude(raw_wave.mv_per_bit()*magnitude[imax]);
   clockdata->set_frequency(imax * freqmult);
-  clockdata->set_amplitude(magnitude[imax]);
   clockdata->set_phase(atan2(im_full[imax], re_full[imax])); // Not a bug, atan2(y,x)!
 
   ATH_MSG_DEBUG("Before correcting for finite resolution:");
@@ -133,7 +133,7 @@ ClockReconstructionTool::reconstruct(const RawWaveform& raw_wave,
 
   clockdata->set_frequency( (imax+dm) * freqmult );
   clockdata->set_phase (phase);
-  clockdata->set_amplitude( 2*M_PI*dm*magnitude[imax] / sin(M_PI * dm) );
+  clockdata->set_amplitude( raw_wave.mv_per_bit() * 2*M_PI*dm*magnitude[imax] / sin(M_PI * dm) );
 
   ATH_MSG_DEBUG("After correcting for finite resolution:");
   ATH_MSG_DEBUG(*clockdata);
diff --git a/Waveform/WaveRecTools/src/ClockReconstructionTool.h b/Waveform/WaveRecTools/src/ClockReconstructionTool.h
index ea7ec2ec7e2a28e49d587f9a7f072678ee4bdbc5..78e6ea1770c069314ee15a068458f88623b63bda 100644
--- a/Waveform/WaveRecTools/src/ClockReconstructionTool.h
+++ b/Waveform/WaveRecTools/src/ClockReconstructionTool.h
@@ -51,8 +51,8 @@ class ClockReconstructionTool: public extends<AthAlgTool, IClockReconstructionTo
   void checkResult(const RawWaveform& raw_wave,
 		   xAOD::WaveformClock* clockdata) const;
 
-  // Limits to print warnings
-  FloatProperty m_amplitude_min{this, "AmplitudeMin", 1000.};
+  // Limits to print warnings (amplitude in mV)
+  FloatProperty m_amplitude_min{this, "AmplitudeMin", 500.};
   FloatProperty m_frequency_min{this, "FrequencyMin", 40.0};
   FloatProperty m_frequency_max{this, "FrequencyMax", 40.1};
 
diff --git a/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.cxx b/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.cxx
index 6d894aeb64125274507105bd71ce37c7ad2bed31..93ae1397e3c591b2f54b3765f0138042878e25bc 100644
--- a/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.cxx
+++ b/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.cxx
@@ -10,18 +10,9 @@
 
 #include "PseudoSimToWaveformRecTool.h"
 
+#include "Identifier/Identifier.h"
 #include "xAODFaserWaveform/WaveformHit.h"
 
-#include "TH1F.h"
-#include "TF1.h"
-#include "TFitResult.h"
-#include "TFitResultPtr.h"
-#include "TGraph.h"
-
-#include <vector>
-#include <tuple>
-#include <math.h>
-
 // Constructor
 PseudoSimToWaveformRecTool::PseudoSimToWaveformRecTool(const std::string& type, const std::string& name, const IInterface* parent) :
   base_class(type, name, parent)
@@ -32,7 +23,43 @@ PseudoSimToWaveformRecTool::PseudoSimToWaveformRecTool(const std::string& type,
 StatusCode
 PseudoSimToWaveformRecTool::initialize() {
   ATH_MSG_INFO( name() << "::initalize()" );
+
+  ATH_CHECK( m_mappingTool.retrieve() );
+
   return StatusCode::SUCCESS;
 }
 
+StatusCode 
+PseudoSimToWaveformRecTool::make_hits(const std::map<Identifier, float>& sums,
+					xAOD::WaveformHitContainer* container) const {
+
+// Check the container
+  if (!container) {
+    ATH_MSG_ERROR("HitCollection passed to reconstruct() is null!");
+    return StatusCode::FAILURE;
+  }
+
+  // Get the nominal trigger time (in ns) from config
+  // For now, hack up the timing to match the configuration
+  //float trigger_time = m_timingTool->nominalTriggerTime();
+  //float offset;
+
+  for (const auto& id : sums) {
+    xAOD::WaveformHit* hit = new xAOD::WaveformHit();
+    container->push_back(hit);
+
+    hit->set_identifier(id.first);
+    hit->set_channel(m_mappingTool->getChannelMapping(id.first));
+    hit->set_peak(0);
+    hit->set_mean(0);
+    hit->set_width(0);
+    hit->set_integral(id.second); 
+    hit->set_localtime(0);
+    hit->set_trigger_time(0);
+    hit->set_raw_peak(0);
+    hit->set_raw_integral(0); 
+  }
+  
+  return StatusCode::SUCCESS;
+}
 
diff --git a/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.h b/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.h
index f3376f6ec4aa93becef3da8fc52c19d003d6dced..e117479737d3dad28c1a8dfb7065ab1742d13884 100644
--- a/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.h
+++ b/Waveform/WaveRecTools/src/PseudoSimToWaveformRecTool.h
@@ -13,10 +13,13 @@
 #include "AthenaBaseComps/AthAlgTool.h"
 #include "WaveRecTools/IPseudoSimToWaveformRecTool.h"
 
+// Tool classes
+#include "WaveformConditionsTools/IWaveformCableMappingTool.h"
+
 //Gaudi
 #include "GaudiKernel/ToolHandle.h"
 
-//STL
+class Identifier;
 
 class PseudoSimToWaveformRecTool: public extends<AthAlgTool, IPseudoSimToWaveformRecTool> {
  public:
@@ -28,8 +31,12 @@ class PseudoSimToWaveformRecTool: public extends<AthAlgTool, IPseudoSimToWavefor
   /// Retrieve the necessary services in initialize
   StatusCode initialize();
 
+  virtual StatusCode make_hits(const std::map<Identifier, float>& sums,
+			       xAOD::WaveformHitContainer* waveContainer) const;
+
  private:
-  // None
+  ToolHandle<IWaveformCableMappingTool> m_mappingTool
+    {this, "WaveformCableMappingTool", "WaveformCableMappingTool"};
 
 };
 
diff --git a/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx b/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx
index ce9504ee183b2a8c25694b62041f6929db6e8290..8882027bfc13a7b28a7043f2b9feb70ed8e25877 100644
--- a/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx
+++ b/Waveform/WaveRecTools/src/WaveformReconstructionTool.cxx
@@ -39,187 +39,290 @@ WaveformReconstructionTool::initialize() {
   } else {
     ATH_MSG_INFO("Will use fit to determine baseline");
   }
+
+  ATH_CHECK( m_timingTool.retrieve() );
+
   return StatusCode::SUCCESS;
 }
 
-// Reconstruction step
+//
+// Form primary hits using trigger time
+//
 StatusCode
-WaveformReconstructionTool::reconstructAll(
-	 const RawWaveformContainer& waveContainer, 
-	 const xAOD::WaveformClock* clock, 
+WaveformReconstructionTool::reconstructPrimary(
+	 const RawWaveform& wave,
 	 xAOD::WaveformHitContainer* hitContainer) const {
 
-  ATH_MSG_DEBUG(" reconstructAll called ");
+  ATH_MSG_DEBUG(" reconstructPrimary called");
 
-  // Reconstruct each waveform
-  for( const auto& wave : waveContainer) {
+  xAOD::WaveformHit* newhit = new xAOD::WaveformHit();
+  hitContainer->push_back(newhit);
 
-    ATH_MSG_DEBUG("Reconstruct waveform for channel " << wave->channel());
+  // Set digitizer channel and identifier
+  newhit->set_channel(wave.channel());
+  newhit->set_identifier(wave.identify());
+
+  // Make sure we have ADC counts
+  if (wave.adc_counts().size() == 0) {
+    ATH_MSG_WARNING( "Found waveform for channel " << wave.channel() 
+		     << " with size " << wave.adc_counts().size() << "!");
+	
+    newhit->set_status_bit(xAOD::WaveformStatus::WAVEFORM_MISSING);
+    return StatusCode::SUCCESS;
+  } 
 
-    // Reconstruct the hits, may be more than one, so pass container
-    CHECK( this->reconstruct(*wave, clock, hitContainer) );
+  if (wave.adc_counts().size() != wave.n_samples()) {
+    ATH_MSG_WARNING( "Found waveform for channel " << wave.channel() 
+		     << " with size " << wave.adc_counts().size() 
+		     << " not equal to number of samples " << wave.n_samples());
+	
+    newhit->set_status_bit(xAOD::WaveformStatus::WAVEFORM_INVALID);
+    return StatusCode::SUCCESS;
   }
 
-  if (m_ensureChannelHits) {
-    ATH_MSG_DEBUG("Ensure all channels have hits at peak time");
-    ensureHits(waveContainer, clock, hitContainer);
+  // Find the baseline for this waveform
+  findBaseline(wave, newhit);
+
+  // Check for problems
+  if (newhit->status_bit(xAOD::WaveformStatus::BASELINE_FAILED)) 
+    return StatusCode::SUCCESS;
+
+  // Get the nominal trigger time (in ns) from config
+  float trigger_time = m_timingTool->nominalTriggerTime();
+
+  // Set range for windowed data in digitizer samples
+  float offset = m_timingTool->triggerTimeOffset(wave.channel());
+
+  int lo_edge = int((trigger_time+offset)/2.) + m_windowStart;
+  int hi_edge = int((trigger_time+offset)/2.) + m_windowStart + m_windowWidth;
+
+  // Fill raw hit values
+  fillRawHitValues(wave, lo_edge, hi_edge, newhit);
+
+  // Check if this is over threshold
+  if (newhit->peak() < newhit->baseline_rms() * m_primaryPeakThreshold) {
+    ATH_MSG_DEBUG("Primary hit failed threshold");
+    newhit->set_status_bit(xAOD::WaveformStatus::THRESHOLD_FAILED);
+  } else {
+    // Reconstruct hit in this range
+    reconstructHit(newhit);
   }
 
   return StatusCode::SUCCESS;
 }
 
 //
-// Make sure we have a hit for each channel at the time when
-// there is a significant pulse found in the detector
+// Form primary hits using trigger time
 //
-void
-WaveformReconstructionTool::ensureHits(
-	 const RawWaveformContainer& waveContainer, 
-	 const xAOD::WaveformClock* clock, 
+StatusCode
+WaveformReconstructionTool::reconstructSecondary(
+	 const RawWaveform& wave,
 	 xAOD::WaveformHitContainer* hitContainer) const {
 
-  ATH_MSG_DEBUG(" ensureHits called ");
+  ATH_MSG_DEBUG(" reconstructSecondary called");
 
-  // Find peak time (most significant hit)
-  xAOD::WaveformHit* peakHit = NULL;
+  // Find existing hit for this channel to get baseline
+  xAOD::WaveformHit* primaryHit = NULL;
 
   for( const auto& hit : *hitContainer) {
 
-    if (peakHit == NULL) {
-      peakHit = hit;
-    } else {
-      if ( hit->peak() > peakHit->peak() ) peakHit = hit;
+    // Use id rather than channel to make sure this works on MC
+    if (hit->identify() == wave.identify()) {
+      ATH_MSG_DEBUG("Found primary hit in channel "<< hit->channel() 
+		    << " with id 0x" << std::hex << hit->identify() << std::dec );
+      primaryHit = hit;
+      break;
     }
+  }
 
+  // Did we find the primary hit for this channel?
+  if (!primaryHit) {
+    ATH_MSG_ERROR("found no primary hit for channel " << wave.channel() << "!");
+    return StatusCode::FAILURE;
   }
 
-  // Didn't find anything?
-  if (peakHit == NULL) return;
-  if (peakHit->status_bit(xAOD::WaveformStatus::THRESHOLD_FAILED)) return;
+  if (primaryHit->status_bit(xAOD::WaveformStatus::WAVEFORM_MISSING)) {
+    ATH_MSG_DEBUG("Found primary hit with waveform missing");
+    return StatusCode::SUCCESS;
+  }
 
-  ATH_MSG_DEBUG("Found peak hit in channel " << peakHit->channel() << " at time " << peakHit->localtime());
+  if (primaryHit->status_bit(xAOD::WaveformStatus::WAVEFORM_INVALID)) {
+    ATH_MSG_DEBUG("Found primary hit with waveform invalid");
+    return StatusCode::SUCCESS;
+  }
 
-  // Now go through all of the channels and check if there is a hit
-  // close in time to the peakHit
-  for( const auto& wave : waveContainer) {
+  WaveformBaselineData baseline;
 
-    // Don't worry about the peak channel, we know this has a hit...
-    if (wave->channel() == peakHit->channel()) continue;
+  baseline.mean = primaryHit->baseline_mean();
+  baseline.rms = primaryHit->baseline_rms();
+  
+  // Find the secondary peak position
+  int ipeak;
 
-    ATH_MSG_DEBUG("Checking for hit in channel " << wave->channel());
+  // Is there already a peak in the primary?
+  if (primaryHit->threshold()) {
 
-    bool found = false;
-    // Look for a baseline-only hit that we can update
-    xAOD::WaveformHit* baselineHit = NULL;
+    ATH_MSG_DEBUG("Looking for secondary hit with primary hit above threshold");
 
-    // There aren't so many hits, just loop over container
-    for( const auto& hit : *hitContainer) {
-      if (hit->channel() != wave->channel()) continue;
+    // Look before and after window
+    int lo_edge = int(primaryHit->time_vector().front()/2.);
+    int hi_edge = int(primaryHit->time_vector().back()/2.);
 
-      // Is this above threshold?
-      if (hit->status_bit(xAOD::WaveformStatus::THRESHOLD_FAILED)) {
-	baselineHit = hit;
-	continue;
-      }
+    std::vector<float> wwave_lo(lo_edge);
+    std::vector<float> wwave_hi(wave.adc_counts().size() - hi_edge - 1);
 
-      // OK, this is the right channel, check the time
-      float dtime = abs(hit->localtime() - peakHit->localtime());
-      if (dtime > m_hitTimeDifference) continue;
+    int ipeak_lo = -1.;
+    int ipeak_hi = -1.;
 
-      // We have found a hit in the right channel at the right time
-      found = true;
-      ATH_MSG_DEBUG("Found hit in channel " << hit->channel() 
-		    << " at time " << hit->localtime());
-      break;
-    }
+    // Look before
+    if (m_findSecondaryBefore) {
+      for (int i=0; i<lo_edge; i++) {
+	wwave_lo[i] = baseline.mean - wave.mv_per_bit() * wave.adc_counts()[i];
+      }
 
-    // Is there a hit?  If so, go to next waveform/channel
-    if (found) continue;
+      ipeak_lo = findPeak(baseline, m_secondaryPeakThreshold, wwave_lo);
 
-    ATH_MSG_DEBUG("No hit found for channel " << wave->channel() 
-		  << " at time " << peakHit->localtime());
+      if (ipeak_lo < 0) {
+	ATH_MSG_DEBUG("No hit found before " << lo_edge);
+      } else {
+	ATH_MSG_DEBUG("Hit found at " << ipeak_lo << " before " << lo_edge);
+      }
+    }
 
-    // Do we have a baseline-only hit we can use?
-    xAOD::WaveformHit* newhit = NULL;
-    if (baselineHit == NULL) {
-      // No, make a new hit here
-      newhit = new xAOD::WaveformHit();
-      hitContainer->push_back(newhit);
+    // Look after
+    if (m_findSecondaryAfter) {
+      for (unsigned int i=(hi_edge+1); i<wave.adc_counts().size(); i++) {
+	wwave_hi[(i-(hi_edge+1))] = baseline.mean - wave.mv_per_bit() * wave.adc_counts()[i];
+      }
 
-      // Mark this as a secondary hit
-      newhit->set_status_bit(xAOD::WaveformStatus::THRESHOLD_FAILED);
-      newhit->set_status_bit(xAOD::WaveformStatus::SECONDARY);
+      ipeak_hi = findPeak(baseline, m_secondaryPeakThreshold, wwave_hi);
 
-      // Set digitizer channel and identifier
-      newhit->set_channel(wave->channel());
-      newhit->set_id(wave->identify32().get_compact());
+      // Is this too close to the primary hit?
+      if (ipeak_hi < 5) {
+	ATH_MSG_DEBUG("Found hit after at " << (ipeak_hi + hi_edge + 1)<< " but too close to edge");
+	ipeak_hi = -1;
+      }
 
-      // Make sure we have ADC counts
-      if (wave->adc_counts().size() == 0) {
-	ATH_MSG_WARNING( "Found waveform for channel " << wave->channel() 
-			 << " with size " << wave->adc_counts().size() << "!");
-	
-	newhit->set_status_bit(xAOD::WaveformStatus::WAVEFORM_MISSING);
-	continue;
-      } 
-      
-      if (wave->adc_counts().size() != wave->n_samples()) {
-	ATH_MSG_WARNING( "Found waveform for channel " << wave->channel() 
-			 << " with size " << wave->adc_counts().size() 
-			 << " not equal to number of samples " << wave->n_samples());
-	
-	newhit->set_status_bit(xAOD::WaveformStatus::WAVEFORM_INVALID);
-	continue;
+      if (ipeak_hi < 0) {
+	ATH_MSG_DEBUG("No hit found after " << hi_edge);
+      } else {
+	ATH_MSG_DEBUG("Hit found at " << ipeak_hi << " after " << hi_edge);
       }
+    }
 
-      findBaseline(*wave, newhit);
+    // Nothing found
+    if (ipeak_lo < 0 && ipeak_hi < 0)
+      return StatusCode::SUCCESS;
+
+    // Both?
+    if (ipeak_lo >= 0 && ipeak_hi >= 0) {
+
+      // Pick the largest signal
+      if (wwave_lo[ipeak_lo] >= wwave_hi[ipeak_hi]) {
+	ipeak = ipeak_lo;
+	ATH_MSG_DEBUG("Picked before as " << wwave_lo[ipeak_lo]
+		      << " > " << wwave_hi[ipeak_hi]);
+      } else {
+	ipeak = ipeak_hi + hi_edge + 1;
+	ATH_MSG_DEBUG("Picked after as " << wwave_lo[ipeak_lo]
+		      << " < " << wwave_hi[ipeak_hi]);
+      }
 
+    
+    } else if (ipeak_lo > 0) {
+      ipeak = ipeak_lo;
+      ATH_MSG_DEBUG("Peak before with " << wwave_lo[ipeak_lo]);
     } else {
-      // Use the existing baseline hit
-      newhit = baselineHit;
+      ATH_MSG_DEBUG("Peak after with " << wwave_hi[ipeak_hi]);
+      ipeak = ipeak_hi+hi_edge+1;
     }
 
-    // Check for problems
-    if (newhit->status_bit(xAOD::WaveformStatus::BASELINE_FAILED)) continue;
-
-    // Set range for windowed data
-    unsigned int lo_edge = peakHit->time_vector().front()/2.;
-    unsigned int hi_edge = peakHit->time_vector().back()/2.;
-
-    ATH_MSG_DEBUG("Windowing waveform from " << lo_edge << " to " << hi_edge);
-    std::vector<float> wtime(hi_edge-lo_edge+1);
-    std::vector<float> wwave(hi_edge-lo_edge+1);
-    for (unsigned int i=lo_edge; i<=hi_edge; i++) {
-      unsigned int j = i-lo_edge;
-      wtime[j] = 2.*i;
-      wwave[j] = newhit->baseline_mean() - wave->adc_counts()[i];
-      //ATH_MSG_DEBUG(" Time: " << wtime[j] << " Wave: " << wwave[j]);
+  } else {
+
+    ATH_MSG_DEBUG("Looking for secondary hit without primary hit above threshold");
+    std::vector<float> wwave(wave.adc_counts().size());
+    for (unsigned int i=0; i<wave.adc_counts().size(); i++) {
+      wwave[i] = baseline.mean - wave.mv_per_bit() * wave.adc_counts()[i];
     }
 
-    newhit->set_time_vector(wtime);
-    newhit->set_wave_vector(wwave);
+    ipeak = findPeak(baseline, m_secondaryPeakThreshold, wwave);
 
-    //
-    // Find some raw values
-    WaveformFitResult raw = findRawHitValues(wtime, wwave);
-    newhit->set_peak(raw.peak);
-    newhit->set_mean(raw.mean);
-    newhit->set_width(raw.sigma);
-    newhit->set_integral(raw.integral);
-    newhit->set_localtime(raw.mean);
-    newhit->set_raw_peak(raw.peak);
-    newhit->set_raw_integral(raw.integral);
+    // Nothing found
+    if (ipeak < 0) 
+      return StatusCode::SUCCESS;
+
+    ATH_MSG_DEBUG("Found secondary peak with no primary " << wwave[ipeak]);
+  }
+
+  // We seem to have a secondary hit
+  xAOD::WaveformHit* newhit = new xAOD::WaveformHit();
+  hitContainer->push_back(newhit);
+
+  // Fill values
+  newhit->set_channel(wave.channel());
+  newhit->set_identifier(wave.identify());
+  newhit->set_status_bit(xAOD::WaveformStatus::SECONDARY);
+  newhit->set_baseline_mean(baseline.mean);
+  newhit->set_baseline_rms(baseline.rms);
+
+  // Set range for windowed data in digitizer samples
+  int lo_edge = ipeak + m_windowStart;
+  int hi_edge = ipeak + m_windowStart + m_windowWidth;
+
+  // Fill raw hit values
+  fillRawHitValues(wave, lo_edge, hi_edge, newhit);
+
+  // Must be over threshold, so reconstruct here
+  reconstructHit(newhit);
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode
+WaveformReconstructionTool::setLocalTime(const xAOD::WaveformClock* clock,
+					 xAOD::WaveformHitContainer* container) const {
+
+  ATH_MSG_DEBUG(" setLocalTime called ");
+
+  // Check the container
+  if (!container) {
+    ATH_MSG_ERROR("WaveformHitCollection passed to setLocalTime() is null!");
+    return StatusCode::FAILURE;
+  }
+
+  bool clock_valid;
+
+  //
+  // Find time from clock
+  if (!clock || (clock->frequency() <= 0.)) {
+    clock_valid = false;
+  } else {
+    clock_valid = true;
+  }
+
+  float trigger_time = m_timingTool->nominalTriggerTime();
+  float offset;
+
+  // Should actually find the time of the trigger here 
+  // and set bcid time offset from that
+  // Loop through hits and set local time
+  for( const auto& hit : *container) {
 
     //
     // Find time from clock
-    if (!clock || (clock->frequency() <= 0.)) {
-      newhit->set_status_bit(xAOD::WaveformStatus::CLOCK_INVALID);
-      newhit->set_bcid_time(-1.);
+    if (clock_valid) {
+      hit->set_bcid_time(clock->time_from_clock(hit->localtime()));
     } else {
-      newhit->set_bcid_time(clock->time_from_clock(newhit->localtime()));
+      hit->set_status_bit(xAOD::WaveformStatus::CLOCK_INVALID);
+      hit->set_bcid_time(-1.);
     }
 
-  } // End of loop over waveContainer
+    // Also set time with respect to nominal trigger
+    offset = m_timingTool->triggerTimeOffset(hit->channel());
+    hit->set_trigger_time(hit->localtime() - (trigger_time + offset));
+  }
+
+  return StatusCode::SUCCESS;
 }
 
 // Find the baseline
@@ -245,187 +348,131 @@ WaveformReconstructionTool::findBaseline(const RawWaveform& raw_wave,
 
   } else {
     // Save baseline to hit collection object
-    hit->set_baseline_mean(baseline.mean);
-    hit->set_baseline_rms(baseline.rms);
+    hit->set_baseline_mean(raw_wave.mv_per_bit()*baseline.mean);
+    hit->set_baseline_rms(raw_wave.mv_per_bit()*baseline.rms);
+    ATH_MSG_DEBUG("Baseline found with mean = " << hit->baseline_mean()
+		  << " mV and rms = " << hit->baseline_rms()
+		  << " mV");
   }
 
   return baseline;
 }
 
-StatusCode
-WaveformReconstructionTool::reconstruct(const RawWaveform& raw_wave,
-					const xAOD::WaveformClock* clock, 
-					xAOD::WaveformHitContainer* container) const {
-
-  ATH_MSG_DEBUG(" reconstruct called ");
-
-  // Check the container
-  if (!container) {
-    ATH_MSG_ERROR("WaveformHitCollection passed to reconstruct() is null!");
-    return StatusCode::FAILURE;
-  }
-
-  //
-  // We always want to create at least one hit, so create it here
-  xAOD::WaveformHit* hit = new xAOD::WaveformHit();
-  container->push_back(hit);
-
-  // Set digitizer channel and identifier
-  hit->set_channel(raw_wave.channel());
-  hit->set_id(raw_wave.identify32().get_compact());
+// Fill the raw hit parameters
+void
+WaveformReconstructionTool::fillRawHitValues(const RawWaveform& wave,
+					   int lo_edge, int hi_edge, 
+					   xAOD::WaveformHit* hit) const {
 
-  // Make sure we have ADC counts
-  if (raw_wave.adc_counts().size() == 0) {
-    ATH_MSG_WARNING( "Found waveform for channel " << raw_wave.channel() 
-		     << " with size " << raw_wave.adc_counts().size() << "!");
+  // First, make sure we don't overflow the waveform range
+  if (lo_edge < 0) lo_edge = 0;
+  if (hi_edge >= int(wave.size())) hi_edge = wave.size() - 1;
 
-    hit->set_status_bit(xAOD::WaveformStatus::WAVEFORM_MISSING);
-    return StatusCode::SUCCESS;
-  } 
+  ATH_MSG_DEBUG("Fill channel " << wave.channel() 
+		<< " waveform from sample " << lo_edge << " to " << hi_edge);
 
-  if (raw_wave.adc_counts().size() != raw_wave.n_samples()) {
-    ATH_MSG_WARNING( "Found waveform for channel " << raw_wave.channel() 
-		     << " with size " << raw_wave.adc_counts().size() 
-		     << " not equal to number of samples " << raw_wave.n_samples());
+  // Fill hit window with data from wave
+  std::vector<float> wtime(hi_edge-lo_edge+1);
+  std::vector<float> wwave(hi_edge-lo_edge+1);
 
-    hit->set_status_bit(xAOD::WaveformStatus::WAVEFORM_INVALID);
-    return StatusCode::SUCCESS;
+  for (int i=lo_edge; i<=hi_edge; i++) {
+    unsigned int j = i-lo_edge;
+    wtime[j] = 2.*i; // 2ns per sample at 500 MHz
+    wwave[j] = hit->baseline_mean() - wave.mv_per_bit() * wave.adc_counts()[i];
   }
 
-  // Find the baseline
-  WaveformBaselineData baseline = findBaseline(raw_wave, hit);
-
-  // Check that we have data to work with
-  // If any status bits are set, this is bad
-  if (hit->status()) return StatusCode::SUCCESS;
-
-  //
-  // Create baseline-subtracted data array for both time and signal
-  // Time in ns from start of readout
-  unsigned int size = raw_wave.adc_counts().size();
-  std::vector<float> time(size);  
-  for (unsigned int i=0; i<size; i++)
-    time[i] = 2.*i;
-
-  // Baseline subtracted (and inverted) ADC waveform values
-  std::vector<float> wave(raw_wave.adc_counts().begin(), raw_wave.adc_counts().end());
-  for (auto& element : wave)
-    element = baseline.mean - element;
-
-  bool first = true;
-
-  // Now we iteratively find peaks and fit
-  while(true) {
-
-    //
-    // Find peak in array and return time and value arrays
-    // This range of data is also *removed* from original arrays
-    std::vector<float> wtime;
-    std::vector<float> wwave;
-
-    // All done if we don't have any peaks above threshold
-    // If we do find a significant peak, fill the window
-    if (! findPeak(baseline, time, wave, wtime, wwave) ) {
-      if (first) hit->set_status_bit(xAOD::WaveformStatus::THRESHOLD_FAILED);
-      break;
-    }
-
-    //
-    // Create new hit to fill
-    if (!first) {
-      hit = new xAOD::WaveformHit();
-      container->push_back(hit);
-      hit->set_status_bit(xAOD::WaveformStatus::SECONDARY);
-    }
-    first = false;
+  hit->set_time_vector(wtime);
+  hit->set_wave_vector(wwave);
 
-    //
-    // Save windowed waveform to Hit object
-    hit->set_channel(raw_wave.channel());
-    hit->set_baseline_mean(baseline.mean);
-    hit->set_baseline_rms(baseline.rms);
-    hit->set_time_vector(wtime);
-    hit->set_wave_vector(wwave);
+  // Set raw values
+  WaveformFitResult raw = findRawHitValues(wtime, wwave);
+  hit->set_peak(raw.peak);
+  hit->set_mean(raw.mean);
+  hit->set_width(raw.sigma);
+  hit->set_integral(raw.integral);
+  hit->set_localtime(raw.mean);
+  hit->set_raw_peak(raw.peak);
+  hit->set_raw_integral(raw.integral);
 
-    //
-    // Find some raw values
-    WaveformFitResult raw = findRawHitValues(wtime, wwave);
-    hit->set_peak(raw.peak);
-    hit->set_mean(raw.mean);
-    hit->set_width(raw.sigma);
-    hit->set_integral(raw.integral);
-    hit->set_localtime(raw.mean);
-    hit->set_raw_peak(raw.peak);
-    hit->set_raw_integral(raw.integral);
+}
 
-    //
-    // Perform Gaussian fit to waveform
-    WaveformFitResult gfit = fitGaussian(raw, wtime, wwave);
-    if (! gfit.valid) {
-      // Lets try again with a more restricted width
-      ATH_MSG_WARNING( " Gaussian waveform fit failed with width " << raw.sigma << " try reducing width to 1 " );
-      raw.sigma = 1.;
-      gfit = fitGaussian(raw, wtime, wwave);
-      if (!gfit.valid) {
-	hit->set_status_bit(xAOD::WaveformStatus::GFIT_FAILED);
-      }
-    } 
+// Reconstruct a hit from the RawWaveform in the range specified
+// Range is in units digitizer samples (not ns)
+void
+WaveformReconstructionTool::reconstructHit(xAOD::WaveformHit* hit) const {
 
-    // Fit results (or raw if it failed)
-    hit->set_peak(gfit.peak);
-    hit->set_mean(gfit.mean);
-    hit->set_width(gfit.sigma);
-    hit->set_integral(gfit.integral);
-    hit->set_localtime(gfit.time);
+  // Time and waveform vectors
+  // Don't use reference as we may modify this below
+  std::vector<float> wtime = hit->time_vector();
+  std::vector<float> wwave = hit->wave_vector();
 
-    //
-    // Check for overflow
-    if (m_removeOverflow && findOverflow(baseline, wtime, wwave)) {
-      ATH_MSG_INFO("Found waveform overflow");
-      hit->set_status_bit(xAOD::WaveformStatus::WAVE_OVERFLOW);
-    }
+  ATH_MSG_DEBUG("Reconstruct channel " << hit->channel() 
+		<< " waveform from " << wtime.front() 
+		<< " to " << wtime.back());
 
-    //
-    // Perform CB fit
-    WaveformFitResult cbfit = fitCBall(gfit, wtime, wwave);
-    if (! cbfit.valid) {
-      ATH_MSG_WARNING("CrystalBall fit failed!");
-      // Still have gaussian parameters as an estimate
-      hit->set_status_bit(xAOD::WaveformStatus::CBFIT_FAILED);
-    } else {
-      hit->set_peak(cbfit.peak);
-      hit->set_mean(cbfit.mean);
-      hit->set_width(cbfit.sigma);
-      hit->set_integral(cbfit.integral);
-      hit->set_localtime(cbfit.time);
-
-      hit->set_alpha(cbfit.alpha);
-      hit->set_nval(cbfit.nval);
-    }
+  // Fill values needed for fit (peak, mean, and sigma)
+  WaveformFitResult raw;
+  raw.peak = hit->peak();
+  raw.mean = hit->mean();
+  raw.sigma = hit->width();
 
-    //
-    // Find time from clock
-    if (!clock || (clock->frequency() <= 0.)) {
-      hit->set_status_bit(xAOD::WaveformStatus::CLOCK_INVALID);
-      hit->set_bcid_time(-1.);
-    } else {
-      hit->set_bcid_time(clock->time_from_clock(hit->localtime()));
+  //
+  // Perform Gaussian fit to waveform
+  WaveformFitResult gfit = fitGaussian(raw, wtime, wwave);
+  if (! gfit.valid) {
+    // Lets try again with a more restricted width
+    ATH_MSG_WARNING( " Gaussian waveform fit failed with width " << raw.sigma << " try reducing width to 1 " );
+    raw.sigma = 1.;
+    gfit = fitGaussian(raw, wtime, wwave);
+    if (!gfit.valid) {
+      hit->set_status_bit(xAOD::WaveformStatus::GFIT_FAILED);
     }
+  } 
 
-    if (! m_findMultipleHits) break;
-
-  } // End of loop over waveform data
+  // Fit results (or raw if it failed)
+  hit->set_peak(gfit.peak);
+  hit->set_mean(gfit.mean);
+  hit->set_width(gfit.sigma);
+  hit->set_integral(gfit.integral);
+  hit->set_localtime(gfit.time);
+  
+  //
+  // Check for overflow
+  if (m_removeOverflow && findOverflow(hit->baseline_mean(), wtime, wwave)) {
+    ATH_MSG_INFO("Found waveform overflow");
+    hit->set_status_bit(xAOD::WaveformStatus::WAVE_OVERFLOW);
+  }
+  
+  //
+  // Perform CB fit
+  WaveformFitResult cbfit = fitCBall(gfit, wtime, wwave);
+  if (! cbfit.valid) {
+    ATH_MSG_WARNING("CrystalBall fit failed for channel " << hit->channel() << "!");
+    // Still have gaussian parameters as an estimate
+    hit->set_status_bit(xAOD::WaveformStatus::CBFIT_FAILED);
+  } else {
+    hit->set_peak(cbfit.peak);
+    hit->set_mean(cbfit.mean);
+    hit->set_width(cbfit.sigma);
+    hit->set_integral(cbfit.integral);
+    hit->set_localtime(cbfit.time);
+    
+    hit->set_alpha(cbfit.alpha);
+    hit->set_nval(cbfit.nval);
+  }
 
-  ATH_MSG_DEBUG( "WaveformReconstructionTool finished for channel " 
-		 << raw_wave.channel() << " container size= " << container->size());
+  ATH_MSG_DEBUG("Done reconstructing channel " << hit->channel() 
+		<< " waveform from " << wtime.front() << " to " << wtime.back());
 
-  return StatusCode::SUCCESS;
 }
 
-bool
+// Returns location of peak in array wave
+// Return value is -1 if peak is below threshold
+int
 WaveformReconstructionTool::findPeak(WaveformBaselineData& baseline, 
-   std::vector<float>& time, std::vector<float>& wave,
-   std::vector<float>& windowed_time, std::vector<float>& windowed_wave) const {
+				     float threshold,
+				     std::vector<float>& wave) const 
+{
 
   ATH_MSG_DEBUG("findPeak called");
 
@@ -435,44 +482,31 @@ WaveformReconstructionTool::findPeak(WaveformBaselineData& baseline,
   ATH_MSG_DEBUG( "Found peak value " << maxval << " at position " << imax );
 
   // Check if this is over threshold (in sigma)
-  if (maxval < m_peakThreshold*baseline.rms) {
+  if (maxval < threshold*baseline.rms) {
     ATH_MSG_DEBUG("Failed threshold");
-    return false;
+    return -1;
   }
 
-  // Make a window around this peak, values are in bins, so units of 2ns
-  // Ensure our window is within the vector range
-  int lo_edge = ((int(imax) + m_windowStart) >= 0 ? (imax + m_windowStart) : 0); 
-  int hi_edge = ((imax + m_windowStart + m_windowWidth) < wave.size() ? (imax + m_windowStart + m_windowWidth) : wave.size());
-  
-  ATH_MSG_DEBUG("Windowing waveform from " << lo_edge << " to " << hi_edge);
-  windowed_time = std::vector<float> (time.begin()+lo_edge, time.begin()+hi_edge);
-  windowed_wave = std::vector<float> (wave.begin()+lo_edge, wave.begin()+hi_edge);
-
-  // Remove these values from the original arrays so we can iterate
-  time.erase(time.begin()+lo_edge, time.begin()+hi_edge);
-  wave.erase(wave.begin()+lo_edge, wave.begin()+hi_edge);
-
-  return true;
+  return imax;
 }
 
 bool
-WaveformReconstructionTool::findOverflow(const WaveformBaselineData& base, 
+WaveformReconstructionTool::findOverflow(float baseline,
 	      std::vector<float>& time, std::vector<float>& wave) const {
 
   auto peakloc = std::max_element(wave.begin(), wave.end());
 
   // If peak value is less than baseline, we have no overflow
-  if (*peakloc < int(base.mean)) return false;
+  if (*peakloc < baseline) return false;
 
   ATH_MSG_DEBUG("Removing overflows from waveform with length " << wave.size());
 
   // We have an overflow, remove all elements that are overflowing
   unsigned int i = peakloc - wave.begin();
   for (; i<wave.size(); i++) {
-    if (wave[i] < int(base.mean)) continue;
+    if (wave[i] < baseline) continue;
 
-    ATH_MSG_DEBUG("Removing position "<< i<< " with value " << wave[i] << " > " << int(base.mean));
+    ATH_MSG_DEBUG("Removing position "<< i<< " with value " << wave[i] << " > " << baseline);
     // This is an overflow, remove elements
     time.erase(time.begin() + i);
     wave.erase(wave.begin() + i);
@@ -703,7 +737,7 @@ WaveformReconstructionTool::fitGaussian(const WaveformFitResult& raw, const std:
   // Define fit function and preset range
   TF1 gfunc("gfunc", "gaus");
   gfunc.SetParameters(raw.peak, raw.mean, raw.sigma);
-  gfunc.SetParError(0, std::sqrt(raw.peak));
+  gfunc.SetParError(0, raw.peak/10.);
   gfunc.SetParError(1, raw.sigma);
   gfunc.SetParError(2, raw.sigma / 5.);
   gfunc.SetParLimits(2, 0., 20.);  // Constrain width
@@ -756,7 +790,7 @@ WaveformReconstructionTool::fitCBall(const WaveformFitResult& gfit,
   // Define fit function and preset values
   TF1 cbfunc("cbfunc", "crystalball");
   cbfunc.SetParameter(0, cbfit.peak); // Peak height
-  cbfunc.SetParError(0, std::sqrt(cbfit.peak));
+  cbfunc.SetParError(0,  cbfit.peak/10.);
   cbfunc.SetParameter(1, cbfit.mean); // Mean
   cbfunc.SetParError(1, cbfit.sigma);
   cbfunc.SetParameter(2, cbfit.sigma);  // Width
@@ -771,7 +805,7 @@ WaveformReconstructionTool::fitCBall(const WaveformFitResult& gfit,
   TFitResultPtr cbfitptr = tg.Fit(&cbfunc, "QNS", "");
 
   if (!cbfitptr->IsValid()) {
-    ATH_MSG_WARNING( " First Crystal Ball waveform fit failed! ");
+    ATH_MSG_DEBUG( " First Crystal Ball waveform fit failed! ");
   }
 
   // Now try releasing the tail parameter
@@ -786,7 +820,7 @@ WaveformReconstructionTool::fitCBall(const WaveformFitResult& gfit,
   cbfit.valid = (cbfit.fit_status == 0);
 
   if (!cbfitptr->IsValid()) {
-    ATH_MSG_WARNING( " Crystal Ball waveform fit failed! ");
+    ATH_MSG_DEBUG( " Full Crystal Ball waveform fit failed! ");
   } else {
     // Improve estimation with fit results
     cbfit.peak = cbfitptr->Parameter(0);
diff --git a/Waveform/WaveRecTools/src/WaveformReconstructionTool.h b/Waveform/WaveRecTools/src/WaveformReconstructionTool.h
index 7a39f883434051173099d3702e9643fb64cc9f51..45402ff505f5fd89420299f69cbc5a1ae9ab593b 100644
--- a/Waveform/WaveRecTools/src/WaveformReconstructionTool.h
+++ b/Waveform/WaveRecTools/src/WaveformReconstructionTool.h
@@ -20,6 +20,9 @@
 #include "WaveformBaselineData.h"
 #include "WaveformFitResult.h"
 
+// Tool classes
+#include "WaveformConditionsTools/IWaveformTimingTool.h"
+
 //Gaudi
 #include "GaudiKernel/ToolHandle.h"
 
@@ -37,16 +40,18 @@ class WaveformReconstructionTool: public extends<AthAlgTool, IWaveformReconstruc
   /// Retrieve the necessary services in initialize
   StatusCode initialize();
 
-  /// Reconstruct all hits from waveform container
-  virtual StatusCode reconstructAll(const RawWaveformContainer& waveContainer,
-				    const xAOD::WaveformClock* clock,
-				    xAOD::WaveformHitContainer* hitContainer) const;
+  /// Reconstruct primary hits from waveform (in trigger window)
+  virtual StatusCode reconstructPrimary(const RawWaveform& wave,
+					xAOD::WaveformHitContainer* hitContainer) const;
+
+  /// Reconstruct primary hits from waveform (in trigger window)
+  virtual StatusCode reconstructSecondary(const RawWaveform& wave,
+					xAOD::WaveformHitContainer* hitContainer) const;
+
+  /// Set local hit times from LHC clock
+  virtual StatusCode setLocalTime(const xAOD::WaveformClock* clock,
+				  xAOD::WaveformHitContainer* container) const;
 
-  /// Reconstruct hits from waveform
-  
-  virtual StatusCode reconstruct(const RawWaveform& wave,
-				 const xAOD::WaveformClock* clock,
-				 xAOD::WaveformHitContainer* hitContainer) const;
 
  private:
 
@@ -54,6 +59,9 @@ class WaveformReconstructionTool: public extends<AthAlgTool, IWaveformReconstruc
   // Baseline Estimation Parameters
   BooleanProperty m_useSimpleBaseline{this, "UseSimpleBaseline", false};
 
+  ToolHandle<IWaveformTimingTool> m_timingTool
+    {this, "WaveformTimingTool", "WaveformTimingTool"};
+
   // Minimum number of samples needed to calculate simple baseline
   // Just average these first n values
   IntegerProperty m_samplesForBaselineAverage{this, "SamplesForBaselineAverage", 40};
@@ -79,32 +87,40 @@ class WaveformReconstructionTool: public extends<AthAlgTool, IWaveformReconstruc
   FloatProperty m_baselineFitWindow{this, "BaselineFitWindow", 2.};
 
   //
-  // Peak threshold (in sigma of baseline RMS) to find a hit
-  FloatProperty m_peakThreshold{this, "PeakThreshold", 10.};
+  // Peak threshold (in sigma of baseline RMS)
+  // Primary threshold is requirement to try a fit for the in-time window
+  // Secondary threshold is requirement to produce a secondary hit
+  // from a local maximum
+  FloatProperty m_primaryPeakThreshold{this, "PrimaryPeakThreshold", 5.};
+  FloatProperty m_secondaryPeakThreshold{this, "SecondaryPeakThreshold", 10.};
  
   //
   // Window to define fitting range, in samples (2ns/sample)
-  IntegerProperty m_windowStart{this, "FitWindowStart", -15};
+  IntegerProperty m_windowStart{this, "FitWindowStart", -20};  
   IntegerProperty m_windowWidth{this, "FitWindowWidth", 60};
 
   //
   // Remove overflow values from CB fit
   BooleanProperty m_removeOverflow{this, "RemoveOverflow", true};
 
-  //
-  // Look for more than one hit in each channel
-  BooleanProperty m_findMultipleHits{this, "FindMultipleHits", false};
-
   //
   // Fraction of peak to set local hit time
-  FloatProperty m_timingPeakFraction{this, "TimingPeakFraction", 0.45};
+  FloatProperty m_timingPeakFraction{this, "TimingPeakFraction", 0.4};
 
   //
-  // Ensure each channel has a waveform hit at time of most significant 
-  // hit in the event
-  BooleanProperty m_ensureChannelHits{this, "EnsureChannelHits", true};
-  // Max Time difference in ns to say a hit exists in a different channel
-  FloatProperty m_hitTimeDifference{this, "HitTimeDifference", 10.};
+  // When looking for secondary hits with a primary found above threshold
+  // should we look before or after the primary hit?
+  BooleanProperty m_findSecondaryBefore{this, "FindSecondaryBefore", true};
+  BooleanProperty m_findSecondaryAfter{this, "FindSecondaryAfter", false};
+
+  // Reco algorithms
+  // Fill hit with raw data from waveform
+  void fillRawHitValues(const RawWaveform& wave,
+			int lo_edge, int hi_edge,
+			xAOD::WaveformHit* hit) const;
+
+  // Perform fits to WaveformHit data
+  void reconstructHit(xAOD::WaveformHit* hit) const;
 
   // Baseline algorithms
   WaveformBaselineData& findSimpleBaseline(const RawWaveform& wave) const;
@@ -113,12 +129,10 @@ class WaveformReconstructionTool: public extends<AthAlgTool, IWaveformReconstruc
 				     xAOD::WaveformHit* hit) const;
 
 
-  // Find peak in wave, return windowed region in windowed_time and windowed_wave
-  // Windowed region is removed from original vectors
-  // Returns true if peak found, false if not
-  bool findPeak(WaveformBaselineData& baseline,
-		std::vector<float>& time, std::vector<float>& wave,
-		std::vector<float>& windowed_time, std::vector<float>& windowed_wave) const;
+  // Find peak in wave, return index to peak position, or -1 if 
+  // peak isn't greater than threshold
+  int findPeak(WaveformBaselineData& baseline, float threshold, 
+		std::vector<float>& wave) const;
 
   // Get estimate from waveform data itself
   WaveformFitResult& findRawHitValues(const std::vector<float> time, 
@@ -130,7 +144,7 @@ class WaveformReconstructionTool: public extends<AthAlgTool, IWaveformReconstruc
 				 const std::vector<float> wave) const;
 
   // Find overflows and remove points from arrays
-  bool findOverflow(const WaveformBaselineData& baseline, 
+  bool findOverflow(float baseline, 
 		    std::vector<float>& time, std::vector<float>& wave) const;
 
   // Fit windowed data to CrystalBall function
@@ -138,12 +152,6 @@ class WaveformReconstructionTool: public extends<AthAlgTool, IWaveformReconstruc
 			      const std::vector<float> time, 
 			      const std::vector<float> wave) const;
 
-
-  /// Create hit in all channels at time of peak signal
-  void ensureHits(const RawWaveformContainer& waveContainer,
-		  const xAOD::WaveformClock* clock,
-		  xAOD::WaveformHitContainer* hitContainer) const;
-
 };
 
 #endif // WAVERECTOOLS_WAVEFORMRECONSTRUCTIONTOOL_H
diff --git a/Waveform/WaveformConditions/WaveCondUtils/CMakeLists.txt b/Waveform/WaveformConditions/WaveCondUtils/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1c295bd9cab84b213f7ec0227a3cb4f95486edc8
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveCondUtils/CMakeLists.txt
@@ -0,0 +1,9 @@
+################################################################################
+# Package: WaveCondUtils
+################################################################################
+
+# Declare the package name:
+atlas_subdir( WaveCondUtils )
+
+atlas_install_scripts( scripts/*.sh scripts/*.py )
+
diff --git a/Waveform/WaveformConditions/WaveCondUtils/scripts/makeTimingDB.py b/Waveform/WaveformConditions/WaveCondUtils/scripts/makeTimingDB.py
new file mode 100755
index 0000000000000000000000000000000000000000..b3a1c64a23ad23ae70eaa000e3cbd76064bebfcd
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveCondUtils/scripts/makeTimingDB.py
@@ -0,0 +1,221 @@
+#!/bin/env python
+
+# Requires python 3.8 or higher
+#
+# Can test results with
+# AtlCoolConsole.py "sqlite://;schema=waveform_reco.db;dbname=OFLP200"
+
+filename = 'waveform_reco.db'
+
+# Nominal trigger time in ns
+nominal_data = {
+    0: 820.,
+    4272: 830.,
+    6525: 820.
+}
+
+offset_channels = 16
+
+# Run 
+# 0 - initial data
+# 3395 - Testbeam
+# 
+
+ehn1_offsets = [ -20., -20., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0. ]
+ti12_offsets = [ -20., -20., -20., -20., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0. ]
+
+offset_data = {
+  0:    [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0. ],
+# Initial TI12
+  1324: [ -10., -10., -10., -10.,   0.,   0., 0., 0., 0., 0., 0., 0., 18., 18., 0., 0. ],
+# Testbeam geometry
+  3247: [ -10., -10., -10., -10., -10., -10., 15., 15., -20., -20., 0., 0., 0., 0., 0., 0. ],
+# TI12
+  4272: ti12_offsets,
+# EHN1 (interleaved with TI12 running)
+  4360: ehn1_offsets,
+  4399: ti12_offsets,
+  4409: ehn1_offsets,
+  4411: ti12_offsets,
+  4429: ehn1_offsets,
+  4439: ti12_offsets,
+  4876: ehn1_offsets,
+  4892: ti12_offsets,
+  4904: ehn1_offsets,
+  4912: ti12_offsets,
+  4954: ehn1_offsets,
+  4989: ti12_offsets,
+  4991: ehn1_offsets,
+  4993: ti12_offsets,
+  4996: ehn1_offsets,
+  4997: ti12_offsets,
+  5042: ehn1_offsets,
+  5050: ti12_offsets,
+# IFT and VetoNu installed
+  6525: [ -10., -10., -10., -10., -25., -25., 0., 0., 0., 0., 0., 0., 18., 18., 0., 0. ]
+}
+
+attr_list_desc = '<timeStamp>run-lumi</timeStamp><addrHeader><address_header service_type="71" clid="40774348" /></addrHeader><typeName>AthenaAttributeList</typeName>'
+
+cond_attr_list_desc = '<timeStamp>run-lumi</timeStamp><addrHeader><address_header clid="1238547719" service_type="71" /></addrHeader><typeName>CondAttrListCollection</typeName>'
+
+maxInt32 = 0xFFFFFFFF
+
+
+# Look for data entry errors
+
+print('Validating nominal data')
+
+lastRun = -1
+for run, data in nominal_data.items():
+    assert isinstance(run, int), 'Run number is not integer'
+    assert isinstance(data, float), 'Time is not float'
+    assert run > lastRun, 'Run numbers out of order'
+    assert run <= maxInt32, 'Run number out of range'
+    lastRun = run
+
+print('Validating offset data')
+lastRun = -1
+for run, data in offset_data.items():
+    assert isinstance(run, int), 'Run number is not integer'
+    assert run > lastRun, 'Run numbers out of order'
+    assert run <= maxInt32, 'Run number out of range'
+    lastRun = run
+    assert len(data) == offset_channels, 'Offset data does not have '+str(offset_channels)+' entries'
+    for i in range(offset_channels):
+        assert isinstance(data[i], float), 'Offset time is not float'
+
+# Data looks OK
+
+
+from PyCool import cool
+
+dbSvc = cool.DatabaseSvcFactory.databaseService()
+connectString = f'sqlite://;schema={filename};dbname=CONDBR3'
+
+print('Creating database')
+
+dbSvc.dropDatabase( connectString )
+db = dbSvc.createDatabase( connectString )
+
+# Nominal trigger times
+nominalSpec = cool.RecordSpecification()
+nominalSpec.extend( 'NominalTriggerTime', cool.StorageType.Float )
+
+nominalFolderSpec = cool.FolderSpecification(cool.FolderVersioning.SINGLE_VERSION, nominalSpec)
+nominalFolder = db.createFolder('/WAVE/DAQ/Timing', nominalFolderSpec, attr_list_desc, True)
+
+# There should be one record entered per IOV
+lastValid = cool.ValidityKeyMax
+for firstValidRun, time in reversed(nominal_data.items()):
+    firstValid = (firstValidRun << 32)
+    nominalRecord = cool.Record(nominalSpec)
+    nominalRecord[ 'NominalTriggerTime' ] = float(time)
+    nominalFolder.storeObject( firstValid, lastValid, nominalRecord, cool.ChannelId(0))
+    lastValid = ((firstValidRun - 1) << 32) | (cool.ValidityKeyMax & 0x00000000FFFFFFFF)
+
+
+# Trigger offset times
+
+offsetSpec = cool.RecordSpecification()
+offsetSpec.extend( 'TriggerOffset', cool.StorageType.Float )
+
+offsetFolderSpec = cool.FolderSpecification(cool.FolderVersioning.SINGLE_VERSION, offsetSpec)
+offsetFolder = db.createFolder('/WAVE/DAQ/TimingOffset', offsetFolderSpec, cond_attr_list_desc, True)
+
+# There should be one record entered per IOV
+lastValid = cool.ValidityKeyMax
+for firstValidRun, offset_list in reversed(offset_data.items()):
+    firstValid = (firstValidRun << 32)
+    for channel in range(offset_channels):
+        offsetRecord = cool.Record(offsetSpec)
+        offsetRecord[ 'TriggerOffset' ] = float(offset_list[channel])
+        offsetFolder.storeObject( firstValid, lastValid, offsetRecord, cool.ChannelId(channel) )
+
+    lastValid = ((firstValidRun - 1) << 32) | (cool.ValidityKeyMax & 0x00000000FFFFFFFF)
+
+
+db.closeDatabase()
+
+print('Database completed')
+
+print('Working on MC database')
+
+# Nominal data 
+nominal_data = {
+    0: 820.
+}
+# No offsets by default
+offset_data = {
+  0: [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]
+}
+
+# Validate again just in case
+print('Validating nominal data')
+
+lastRun = -1
+for run, data in nominal_data.items():
+    assert isinstance(run, int), 'Run number is not integer'
+    assert isinstance(data, float), 'Time is not float'
+    assert run > lastRun, 'Run numbers out of order'
+    assert run <= maxInt32, 'Run number out of range'
+    lastRun = run
+
+print('Validating offset data')
+lastRun = -1
+for run, data in offset_data.items():
+    assert isinstance(run, int), 'Run number is not integer'
+    assert run > lastRun, 'Run numbers out of order'
+    assert run <= maxInt32, 'Run number out of range'
+    lastRun = run
+    assert len(data) == offset_channels, 'Offset data does not have '+str(offset_channels)+' entries'
+    for i in range(offset_channels):
+        assert isinstance(data[i], float), 'Offset time is not float'
+
+# Data looks OK
+
+connectString = f'sqlite://;schema={filename};dbname=OFLP200'
+
+dbSvc.dropDatabase( connectString )
+db = dbSvc.createDatabase( connectString )
+
+# Nominal trigger times
+nominalSpec = cool.RecordSpecification()
+nominalSpec.extend( 'NominalTriggerTime', cool.StorageType.Float )
+
+nominalFolderSpec = cool.FolderSpecification(cool.FolderVersioning.SINGLE_VERSION, nominalSpec)
+nominalFolder = db.createFolder('/WAVE/DAQ/Timing', nominalFolderSpec, attr_list_desc, True)
+
+# There should be one record entered per IOV
+lastValid = cool.ValidityKeyMax
+for firstValidRun, time in reversed(nominal_data.items()):
+    firstValid = (firstValidRun << 32)
+    nominalRecord = cool.Record(nominalSpec)
+    nominalRecord[ 'NominalTriggerTime' ] = float(time)
+    nominalFolder.storeObject( firstValid, lastValid, nominalRecord, cool.ChannelId(0))
+    lastValid = ((firstValidRun - 1) << 32) | (cool.ValidityKeyMax & 0x00000000FFFFFFFF)
+
+
+# Trigger offset times
+
+offsetSpec = cool.RecordSpecification()
+offsetSpec.extend( 'TriggerOffset', cool.StorageType.Float )
+
+offsetFolderSpec = cool.FolderSpecification(cool.FolderVersioning.SINGLE_VERSION, offsetSpec)
+offsetFolder = db.createFolder('/WAVE/DAQ/TimingOffset', offsetFolderSpec, cond_attr_list_desc, True)
+
+# There should be one record entered per IOV
+lastValid = cool.ValidityKeyMax
+for firstValidRun, offset_list in reversed(offset_data.items()):
+    firstValid = (firstValidRun << 32)
+    for channel in range(offset_channels):
+        offsetRecord = cool.Record(offsetSpec)
+        offsetRecord[ 'TriggerOffset' ] = float(offset_list[channel])
+        offsetFolder.storeObject( firstValid, lastValid, offsetRecord, cool.ChannelId(channel) )
+
+    lastValid = ((firstValidRun - 1) << 32) | (cool.ValidityKeyMax & 0x00000000FFFFFFFF)
+
+
+db.closeDatabase()
+
+print('Database completed')
diff --git a/Waveform/WaveformConditions/WaveCondUtils/scripts/wave_timing_check.py b/Waveform/WaveformConditions/WaveCondUtils/scripts/wave_timing_check.py
new file mode 100755
index 0000000000000000000000000000000000000000..6c8f6332ff42e1ffc06fb168d290539c1db89208
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveCondUtils/scripts/wave_timing_check.py
@@ -0,0 +1,337 @@
+#!/usr/bin/env python3
+#
+import os
+import sys
+import math
+import array
+import itertools
+
+# Triggers: 0x01 - calo, 0x02 - veto, 0x03 - timing, 0x10 - random
+
+def usage():
+    print("Usage: timing_check.py <filename>|<dirname> [triggermask]")
+
+if len(sys.argv) == 1:
+    usage()
+    sys.exit(-1)
+
+# Extract tracker station requirements
+if len(sys.argv) == 3:
+    trigmask = int(sys.argv[2])
+    extra = f"_{triggermask}"
+else:
+    trigmask = 0xFF
+    extra = ''
+
+from pathlib import Path
+
+import ROOT
+ROOT.xAOD.Init().ignore()
+ROOT.xAOD.AuxContainerBase()
+os.environ["XAOD_ACCESSTRACER_FRACTION"] = "0.0"
+
+#
+# Open file or files
+pathname = Path(sys.argv[1])
+
+# Is this a directory?
+if pathname.is_dir():
+    print(f"Opening files in directory {pathname.name}")
+
+    t2 = ROOT.TChain("CollectionTree")
+    nfiles = t2.Add(str(pathname)+'/Faser-Physics*.root')
+
+    if (nfiles == 0):
+        print(f"TChain found no files!")
+        usage()
+        sys.exit(0)
+
+    # Make transient tree
+    t1 = ROOT.xAOD.MakeTransientTree(t2)
+
+    # Make output file name
+    outfile = pathname.name + "_timing"+extra+".pdf"
+
+    print(f"TChain found {nfiles} files with {t2.GetEntries()} events")
+
+    avperfile = t2.GetEntries() / nfiles
+
+# Is this a file?
+elif pathname.is_file():
+    print(f"Opening file {pathname.name}")
+
+    t2 = ROOT.TChain("CollectionTree")
+    nfiles = t2.Add(str(pathname))
+
+    if (nfiles != 1):
+        print(f"TChain error opening file!")
+        usage()
+        sys.exit(0)
+
+    print(f"Opened file with {t2.GetEntries()} events")
+
+    avperfile = t2.GetEntries()
+
+    # Make transient tree
+    t1 = ROOT.xAOD.MakeTransientTree(t2)
+
+    # Make outfile name from input
+    outfile = pathname.stem + "_timing"+extra+".pdf"
+
+# Neither?
+else:
+    print(f"Can't understand {pathname.name}")
+    usage()
+    sys.exit(-1)
+
+class ClockPlots:
+
+    def __init__(self):
+
+        # Ranges for plots
+        self.freq_bins = 80
+        self.freq_lo = 40.0
+        self.freq_hi = 40.2
+
+        self.th_bins = 100
+
+    def init(self, tree):
+
+        self.h_freq = ROOT.TH1I("", "Clock Frequency", self.freq_bins, self.freq_lo, self.freq_hi)
+        self.h_freq.GetXaxis().SetTitle("Clock Frequency (MHz)")
+        self.h_freq.GetYaxis().SetTitle("Events")
+        #self.h_freq.Sumw2()
+
+        self.h_phase = ROOT.TH1I("", "Clock Phase", 60, 2*(-3.1416), 2*3.1416)
+        self.h_phase.GetXaxis().SetTitle("Clock Phase")
+        self.h_phase.GetYaxis().SetTitle("Events")
+
+        self.h_amp = ROOT.TH1I("", "Amplitude", 50, 0, 2000.)
+        self.h_amp.GetXaxis().SetTitle("Clock Amplitude (mV)")
+        self.h_amp.GetYaxis().SetTitle("Events")
+
+        self.h_off = ROOT.TH1I("", "Offset", 50, 0, 2000.)
+        self.h_off.GetXaxis().SetTitle("Clock Offset (mV)")
+        self.h_off.GetYaxis().SetTitle("Events")
+
+    def fill(self, tree):
+
+        # First, create the histograms
+        self.init(tree)
+
+        # Iterate over all entries
+        nev = tree.GetEntries()
+        iev = 0
+        for ev in tree:
+            self.h_freq.Fill(ev.WaveformClock.frequency())
+            self.h_phase.Fill(ev.WaveformClock.phase())
+            self.h_amp.Fill(ev.WaveformClock.amplitude())
+            self.h_off.Fill(ev.WaveformClock.dc_offset())
+
+            # Protect against reading off the end
+            iev += 1
+            if iev == nev: break
+
+    def draw(self, canvas, outfile):
+
+        # Under/overflows, mean, rms, and entries
+        ROOT.gStyle.SetOptStat(111110)
+
+        canvas.Clear()
+        canvas.Divide(2,2)
+        canvas.cd(1)
+        self.h_freq.Draw()
+        canvas.cd(2)
+        self.h_phase.Draw()
+        canvas.cd(3)
+        self.h_amp.Draw()
+        canvas.cd(4)
+        self.h_off.Draw()
+        canvas.Update()
+        canvas.Print(outfile)
+
+    def print_stats(self):
+
+        freq_mean = self.h_freq.GetMean()
+        freq_rms = self.h_freq.GetStdDev()
+        freq_n = self.h_freq.GetEntries()
+        print(f"LHC Clock: {freq_mean:.6} +/- {freq_rms/math.sqrt(freq_n):.6}")
+
+class WavePlots:
+
+    def __init__(self, triggerMask=0xFF):
+
+        # Number of waveforms channels
+        self.nchan = 15
+
+        # Trigger mask
+        self.mask = triggerMask
+
+        self.chan_hist_list = []
+        self.log_list = []
+
+        # Maaximum peak value
+        self.peak_max = 16000.
+
+    def init(self, tree):
+
+        # Keyed by channel
+        self.createChannelHist('h_localtime', 40, 750, 950, "Local Time")
+        self.createChannelHist('h_triggertime', 40, -80, 80, "Trigger Time")
+        self.createChannelHist('h_bcidtime', 50, -10, 40, "BCID Time")
+
+    def createChannelHist(self, name, nbins, xlo, xhi, xtitle='', ytitle='Waveforms', stats=True, log=False):
+
+        setattr(self, name, dict())
+        x = getattr(self, name)
+        for chan in range(self.nchan):
+            x[chan] = ROOT.TH1I("", "", nbins, xlo, xhi)
+            if len(xtitle) > 0:
+                x[chan].GetXaxis().SetTitle(f"Ch {chan} {xtitle}")
+            if len(ytitle) > 0:
+                x[chan].GetYaxis().SetTitle(ytitle)
+            x[chan].SetStats(stats)
+
+        self.chan_hist_list.append(name)
+        if log:
+            self.log_list.append(name)
+
+    def fill(self, tree):
+
+        # First, create the histograms
+        self.init(tree)
+
+        # Iterate over all entries
+        nev = tree.GetEntries()
+        iev = 0
+        for ev in tree:
+
+            time = ev.EventInfo.timeStamp()
+            trig = ev.FaserTriggerData.tap()
+
+            if not (trig & self.mask): 
+                iev += 1
+                if iev == nev: 
+                    break
+                else: 
+                    continue
+
+            # Process waveforms
+            try:
+                wave_list = itertools.chain(ev.CaloWaveformHits, ev.PreshowerWaveformHits, ev.TriggerWaveformHits, ev.VetoWaveformHits, ev.VetoNuWaveformHits)
+            except:
+                wave_list = itertools.chain(ev.CaloWaveformHits, ev.PreshowerWaveformHits)
+
+            for wave in wave_list:
+
+                channel = wave.channel()
+
+                # Check if failed threshold
+                if wave.status_bit(0): continue
+
+                # Fill fit parameters
+                self.h_localtime[channel].Fill(wave.localtime())
+                self.h_triggertime[channel].Fill(wave.trigger_time())
+                self.h_bcidtime[channel].Fill(wave.bcid_time())
+
+            # End of loop over waveforms
+
+            # Protect against reading off the end
+            iev+=1
+            if iev == nev: break
+
+        # End of loop over events
+
+        # Put overflows in last bin of plots
+        self.fixOverflow(self.h_localtime)
+        self.fixOverflow(self.h_triggertime)
+
+    def fixOverflow(self, hdict):
+
+        for h in hdict.values():
+
+            if h.GetNbinsY() == 1:
+                self.fixOverflow1D(h)
+            else:
+                self.fixOverflow2D(h)
+
+    def fixOverflow1D(self, hist):
+        nbins = hist.GetNbinsX()
+        nlast = hist.GetBinContent(nbins)
+        nover = hist.GetBinContent(nbins+1)
+        hist.SetBinContent(nbins, nlast+nover)
+
+    def fixOverflow2D(self, hist):
+        nbx = hist.GetNbinsX()
+        nby = hist.GetNbinsY()
+
+        for ibinx in range(nbx+1):
+            nlast = hist.GetBinContent(ibinx, nby)
+            nover = hist.GetBinContent(ibinx, nby+1)
+            hist.SetBinContent(ibinx, nby, nlast+nover)
+
+        for ibiny in range(nby+1):
+            nlast = hist.GetBinContent(nbx, ibiny)
+            nover = hist.GetBinContent(nbx+1, ibiny)
+            hist.SetBinContent(nbx, ibiny, nlast+nover)
+
+        # Also the double overflow
+        nlast = hist.GetBinContent(nbx, nby)
+        nover = hist.GetBinContent(nbx+1, nby+1)
+        hist.SetBinContent(nbx, nby, nlast+nover)
+
+
+    def draw(self, canvas, outfile):
+
+        #
+        # Plot channel plots
+        for name in self.chan_hist_list:
+            canvas.Clear()
+            canvas.Divide(4,4)
+
+            if name in self.log_list:
+                setlog = True
+            else:
+                setlog = False
+
+            for chan in range(self.nchan):
+                canvas.cd(chan+1)
+                x = getattr(self, name)
+                x[chan].Draw()
+                if setlog: 
+                    ROOT.gPad.SetLogy(True)
+                else:
+                    ROOT.gPad.SetLogy(False)
+
+            canvas.Print(outfile)
+
+    def print_stats(self):
+
+        for chan in range(self.nchan):
+            local_mean = self.h_localtime[chan].GetMean()
+            trig_mean  = self.h_triggertime[chan].GetMean()
+            bcid_mean  = self.h_bcidtime[chan].GetMean()
+            print(f"Chan {chan:2}: Entries {int(self.h_localtime[chan].GetEntries()):8} Local {local_mean:6.1f} Trigger {trig_mean:6.2f} BCID {bcid_mean:6.2f}")
+
+#print("xAOD tree")
+#t1.Print()
+#print("non xAOD tree")
+#t2.Print()
+
+cp = ClockPlots()
+cp.fill(t1)
+
+# Triggers: 0x01 - calo, 0x02 - veto, 0x03 - timing, 0x10 - random
+wp = WavePlots(triggerMask=trigmask)
+wp.fill(t1)
+
+c = ROOT.TCanvas()
+c.Print(outfile+"[")
+
+cp.draw(c, outfile)
+wp.draw(c, outfile)
+
+c.Print(outfile+"]")
+
+cp.print_stats()
+wp.print_stats()
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformCableMappingTool.h b/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformCableMappingTool.h
index 8886506e5a04efe269bb75ca91fc36c8fe283d85..461c087e0fb3957e90c1a1e50723e87a2baff996 100644
--- a/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformCableMappingTool.h
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformCableMappingTool.h
@@ -2,7 +2,7 @@
   Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and FAsER collaborations
 */
 
-/** @file ISCT_CableMappingTool.h Interface file for SCT_CableMappingTool.
+/** @file IWaveformCableMappingTool.h Interface file for WaveformCableMappingTool.
  */
 
 // Multiple inclusion protection
@@ -38,6 +38,10 @@ class IWaveformCableMappingTool: virtual public IAlgTool {
   virtual WaveformCableMap getCableMapping(const EventContext& ctx) const = 0;
   virtual WaveformCableMap getCableMapping(void) const = 0;
 
+  virtual int getChannelMapping(const EventContext& ctx, const Identifier id) const = 0;
+  virtual int getChannelMapping(const Identifier id) const = 0;
+
+
 };
 
 //---------------------------------------------------------------------- 
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformRangeTool.h b/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformRangeTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..316d9c4953a100e83c4ec1a0a052714372e1fdaf
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformRangeTool.h
@@ -0,0 +1,41 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and FAsER collaborations
+*/
+
+/** @file IWaveformRangeTool.h Interface file for WaveformRangeTool.
+ */
+
+// Multiple inclusion protection
+#ifndef IWAVEFORMRANGETOOL
+#define IWAVEFORMRANGETOOL
+
+//STL includes
+#include <map>
+
+//Gaudi Includes
+#include "GaudiKernel/IAlgTool.h"
+#include "GaudiKernel/EventContext.h"
+
+// ADC range in volts indexed by digitizer channel number
+typedef std::map<int, float> WaveformRangeMap;
+
+class IWaveformRangeTool: virtual public IAlgTool {
+
+ public:
+  
+  //----------Public Member Functions----------//
+  // Structors
+  virtual ~IWaveformRangeTool() = default; //!< Destructor
+
+  /// Creates the InterfaceID and interfaceID() method
+  DeclareInterfaceID(IWaveformRangeTool, 1, 0);
+
+  // Methods to return cable-mapping data 
+  // Key is digitizer channel, pair is <type, identifier>
+  virtual WaveformRangeMap getRangeMapping(const EventContext& ctx) const = 0;
+  virtual WaveformRangeMap getRangeMapping(void) const = 0;
+
+};
+
+//---------------------------------------------------------------------- 
+#endif // WAVEFORMRANGETOOL
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformTimingTool.h b/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformTimingTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..de2a2dcbd1dde01c8a48e2a6a67822efc13e7519
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/WaveformConditionsTools/IWaveformTimingTool.h
@@ -0,0 +1,50 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and FAsER collaborations
+*/
+
+/** @file IWaveformTimingTool.h Interface file for WaveformTimingTool.
+ * 
+ * Provides times and offsets (in ns) for different channels in the
+ * waveform digitizer.  This aligns the input signals for different
+ * path lengths and cable delays.
+ *
+ */
+
+// Multiple inclusion protection
+#ifndef IWAVEFORMTIMINGTOOL
+#define IWAVEFORMTIMINGTOOL
+
+//STL includes
+#include <map>
+
+//Gaudi Includes
+#include "GaudiKernel/IAlgTool.h"
+#include "GaudiKernel/EventContext.h"
+
+
+class IWaveformTimingTool: virtual public IAlgTool {
+
+ public:
+  
+  //----------Public Member Functions----------//
+  // Structors
+  virtual ~IWaveformTimingTool() = default; //!< Destructor
+
+  /// Creates the InterfaceID and interfaceID() method
+  DeclareInterfaceID(IWaveformTimingTool, 1, 0);
+
+  // Methods to return timing data
+
+  // Nominal trigger time (in ns) in the digitizer readout
+  virtual float nominalTriggerTime(void) const = 0;
+  virtual float nominalTriggerTime(const EventContext& ctx) const = 0;
+
+  // Channel-by-channel corrections to the nominal trigger time (in ns)
+  // A given channel should be centered at nominal + offset
+  virtual float triggerTimeOffset(int channel) const = 0;
+  virtual float triggerTimeOffset(const EventContext& ctx, int channel) const = 0;
+
+};
+
+//---------------------------------------------------------------------- 
+#endif // WAVEFORMTIMINGTOOL
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/python/WaveformRangeConfig.py b/Waveform/WaveformConditions/WaveformConditionsTools/python/WaveformRangeConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..6450bcec88b3cb8c7194b01edeb59aa6b97e242f
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/python/WaveformRangeConfig.py
@@ -0,0 +1,27 @@
+""" Define methods to configure WaveformRangeTool
+
+Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+"""
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from IOVDbSvc.IOVDbSvcConfig import addFolders
+WaveformRangeTool=CompFactory.WaveformRangeTool
+
+def WaveformRangeToolCfg(flags, name="WaveformRangeTool", **kwargs):
+    """ Return a configured WaveformRangeTool"""
+    return WaveformRangeTool(name, **kwargs)
+
+def WaveformRangeCfg(flags, **kwargs):
+    """ Return configured ComponentAccumulator and tool for Waveform Range 
+
+    WaveformRangeTool may be provided in kwargs
+    """
+
+    acc = ComponentAccumulator()
+    # tool = kwargs.get("WaveformRangeTool", WaveformRangeTool(flags))
+    # Probably need to figure this out!
+    dbInstance = kwargs.get("dbInstance", "TDAQ_OFL")
+    dbFolder = kwargs.get("dbFolder", "/WAVE/DAQ/Range")
+    acc.merge(addFolders(flags, dbFolder, dbInstance, className="CondAttrListCollection"))
+    return acc
+
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/python/WaveformTimingConfig.py b/Waveform/WaveformConditions/WaveformConditionsTools/python/WaveformTimingConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b95ed388208d7d4ab51a3d5621ab868d438c433
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/python/WaveformTimingConfig.py
@@ -0,0 +1,32 @@
+""" Define methods to configure WaveformTimingTool
+
+Copyright (C) 2022 CERN for the benefit of the FASER collaboration
+"""
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+from IOVDbSvc.IOVDbSvcConfig import addFolders
+WaveformTimingTool=CompFactory.WaveformTimingTool
+
+def WaveformTimingToolCfg(flags, name="WaveformTimingTool", **kwargs):
+    """ Return a configured WaveformTimingTool"""
+    return WaveformTimingTool(name, **kwargs)
+
+def WaveformTimingCfg(flags, **kwargs):
+    """ Return configured ComponentAccumulator and tool for Waveform Timing 
+
+    WaveformTimingTool may be provided in kwargs
+    """
+
+    acc = ComponentAccumulator()
+    # tool = kwargs.get("WaveformTimingTool", WaveformTimingTool(flags))
+    # Probably need to figure this out!
+    dbInstance = kwargs.get("dbInstance", "TRIGGER_OFL")
+    if flags.Input.isMC:
+        dbname = "OFLP200"
+    else:
+        dbname = "CONDBR3"
+
+    acc.merge(addFolders(flags, "/WAVE/DAQ/Timing", dbInstance, className="AthenaAttributeList", db=dbname))
+    acc.merge(addFolders(flags, "/WAVE/DAQ/TimingOffset", dbInstance, className="CondAttrListCollection", db=dbname))
+    return acc
+
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx
index a846fe8da6a51bd3293f02b73cd960439d8d77b5..3922d0605339fce204cff01c9dbd69d3c6f21a97 100644
--- a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.cxx
@@ -27,6 +27,7 @@ WaveformCableMappingTool::initialize() {
   // Set up helpers
   ATH_CHECK(detStore()->retrieve(m_ecalID, "EcalID"));
   ATH_CHECK(detStore()->retrieve(m_vetoID, "VetoID"));
+  ATH_CHECK(detStore()->retrieve(m_vetoNuID, "VetoNuID"));
   ATH_CHECK(detStore()->retrieve(m_triggerID, "TriggerID"));
   ATH_CHECK(detStore()->retrieve(m_preshowerID, "PreshowerID"));
 
@@ -98,6 +99,9 @@ WaveformCableMappingTool::getCableMapping(const EventContext& ctx) const {
       else if (det_type == "veto") {
 	identifier = m_vetoID->pmt_id(stationVal, plateVal, pmtVal);
       }
+      else if (det_type == "vetonu") {
+	identifier = m_vetoNuID->pmt_id(stationVal, plateVal, pmtVal);
+      }
       else if (det_type == "trigger") {
 	identifier = m_triggerID->pmt_id(stationVal, plateVal, pmtVal);
       }
@@ -135,5 +139,104 @@ WaveformCableMappingTool::getCableMapping(void) const {
   return getCableMapping(ctx);
 }
 
+//----------------------------------------------------------------------
+int
+WaveformCableMappingTool::getChannelMapping(const EventContext& ctx, const Identifier id) const {
+  // Print where you are
+  ATH_MSG_DEBUG("in getChannelMapping("<< id <<")");
+  int channel = -1;
+
+  // Read Cond Handle
+  SG::ReadCondHandle<CondAttrListCollection> readHandle{m_readKey, ctx};
+  const CondAttrListCollection* readCdo{*readHandle}; 
+  if (readCdo==nullptr) {
+    ATH_MSG_FATAL("Null pointer to the read conditions object");
+    return channel;
+  }
+  // Get the validitiy range
+  EventIDRange rangeW;
+  if (not readHandle.range(rangeW)) {
+    ATH_MSG_FATAL("Failed to retrieve validity range for " << readHandle.key());
+    return channel;
+  }
+  ATH_MSG_DEBUG("Size of CondAttrListCollection " << readHandle.fullKey() << " readCdo->size()= " << readCdo->size());
+  ATH_MSG_DEBUG("Range of input is " << rangeW);
+  
+  // Read mapping info
+  CondAttrListCollection::const_iterator attrList{readCdo->begin()};
+  CondAttrListCollection::const_iterator end{readCdo->end()};
+  // CondAttrListCollection doesn't support C++11 type loops, no generic 'begin'
+  for (; attrList!=end; ++attrList) {
+    // A CondAttrListCollection is a map of ChanNum and AttributeList
+    CondAttrListCollection::ChanNum channelNumber{attrList->first};
+    const CondAttrListCollection::AttributeList &payload{attrList->second};
+    if (payload.exists("type") and not payload["type"].isNull()) {
+
+      std::string det_type{payload["type"].data<std::string>()};
+
+      int stationVal{payload["station"].data<int>()};
+      int plateVal  {payload["plate"].data<int>()};
+      int rowVal    {payload["row"].data<int>()};
+      int moduleVal {payload["module"].data<int>()};
+      int pmtVal    {payload["pmt"].data<int>()};
+      Identifier identifier;
+
+      // Ugh, cant use switch statement with strings
+      // Must do this using an if ladder
+      if (det_type == "calo") {
+	identifier = m_ecalID->pmt_id(rowVal, moduleVal, pmtVal);
+      }
+      else if (det_type == "veto") {
+	identifier = m_vetoID->pmt_id(stationVal, plateVal, pmtVal);
+      }
+      else if (det_type == "vetonu") {
+	identifier = m_vetoNuID->pmt_id(stationVal, plateVal, pmtVal);
+      }
+      else if (det_type == "trigger") {
+	identifier = m_triggerID->pmt_id(stationVal, plateVal, pmtVal);
+      }
+      else if (det_type == "preshower") {
+	identifier = m_preshowerID->pmt_id(stationVal, plateVal, pmtVal);
+      }
+      else if (det_type == "clock") {
+	// No valid identifiers for these
+	identifier = -1;
+      }
+      else if (det_type == "none") {
+	identifier = -1;
+      }
+      else {
+	ATH_MSG_WARNING("Detector type " << det_type << " not known for channel " << channelNumber << "!");
+	det_type = std::string("none");
+	identifier = -1;
+      }
+
+      // ATH_MSG_DEBUG("Comparing " << det_type << " ID: " 
+      //	    << identifier.get_identifier()  
+      //	    << " to requested " << id);
+
+      // Is this the identifier we are looking for?
+      if (id != identifier) continue;
+
+      ATH_MSG_DEBUG("Mapped identifier " << det_type << " ID: " << identifier << " to digitizer channel " << channelNumber);
+
+      channel = channelNumber;
+      break;
+    }
 
+  } // End of loop over attributes
+
+  if (channel < 0) {
+    ATH_MSG_WARNING("No channel found for identifier " << id << "!");
+
+  }
+
+  return channel;
+} 
+
+int
+WaveformCableMappingTool::getChannelMapping(const Identifier id) const {
+  const EventContext& ctx{Gaudi::Hive::currentContext()};
+  return getChannelMapping(ctx, id);
+}
 
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.h b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.h
index 19e6e6d9b5f294692ba0496b1594741494ef223a..8443cb1815e4db7a9c0181515b5fd405cd27d48c 100644
--- a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.h
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformCableMappingTool.h
@@ -21,6 +21,7 @@
 #include "Identifier/Identifier.h"
 #include "FaserCaloIdentifier/EcalID.h"
 #include "ScintIdentifier/VetoID.h"
+#include "ScintIdentifier/VetoNuID.h"
 #include "ScintIdentifier/TriggerID.h"
 #include "ScintIdentifier/PreshowerID.h"
 
@@ -56,6 +57,11 @@ class WaveformCableMappingTool: public extends<AthAlgTool, IWaveformCableMapping
   virtual WaveformCableMap getCableMapping(const EventContext& ctx) const override;
   virtual WaveformCableMap getCableMapping(void) const override;
 
+  // Reverse mapping, reads idenfifier and returns digitizer channel
+  // Returns -1 if match not found for given identifier
+  virtual int getChannelMapping(const EventContext& ctx, const Identifier id) const override;
+  virtual int getChannelMapping(const Identifier id) const override;
+
  private:
   // Read Cond Handle
   SG::ReadCondHandleKey<CondAttrListCollection> m_readKey{this, "ReadKey", "/WAVE/DAQ/CableMapping", "Key of input cabling folder"};
@@ -65,6 +71,7 @@ class WaveformCableMappingTool: public extends<AthAlgTool, IWaveformCableMapping
   // ID helpers
   const EcalID* m_ecalID{nullptr};
   const VetoID* m_vetoID{nullptr};
+  const VetoNuID* m_vetoNuID{nullptr};
   const TriggerID* m_triggerID{nullptr};
   const PreshowerID* m_preshowerID{nullptr};
 
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformRangeTool.cxx b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformRangeTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5c7670e3cbc2dcc9e967107b9f304743695987fc
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformRangeTool.cxx
@@ -0,0 +1,90 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and FASER collaborations
+*/
+
+/** @file WaveformRangeTool.cxx Implementation file for WaveformRangeTool.
+    @author Eric Torrence (05/02/22)
+*/
+
+#include "WaveformRangeTool.h"
+
+//----------------------------------------------------------------------
+WaveformRangeTool::WaveformRangeTool (const std::string& type, const std::string& name, const IInterface* parent) :
+  base_class(type, name, parent)
+{
+}
+
+//----------------------------------------------------------------------
+StatusCode 
+WaveformRangeTool::initialize() {
+  // Read Cond Handle Key
+
+  ATH_MSG_DEBUG("WaveformRangeTool::initialize()");
+
+  ATH_CHECK(m_readKey.initialize());
+
+  return StatusCode::SUCCESS;
+}
+
+//----------------------------------------------------------------------
+StatusCode
+WaveformRangeTool::finalize() {
+  // Print where you are
+  return StatusCode::SUCCESS;
+}
+
+//----------------------------------------------------------------------
+WaveformRangeMap
+WaveformRangeTool::getRangeMapping(const EventContext& ctx) const {
+  // Print where you are
+  ATH_MSG_DEBUG("in getRangeMapping()");
+  WaveformRangeMap mappingData;
+
+  // Read Cond Handle
+  SG::ReadCondHandle<CondAttrListCollection> readHandle{m_readKey, ctx};
+  const CondAttrListCollection* readCdo{*readHandle}; 
+  if (readCdo==nullptr) {
+    ATH_MSG_FATAL("Null pointer to the read conditions object");
+    return mappingData;
+  }
+  // Get the validitiy range
+  EventIDRange rangeW;
+  if (not readHandle.range(rangeW)) {
+    ATH_MSG_FATAL("Failed to retrieve validity range for " << readHandle.key());
+    return mappingData;
+  }
+  ATH_MSG_DEBUG("Size of CondAttrListCollection " << readHandle.fullKey() << " readCdo->size()= " << readCdo->size());
+  ATH_MSG_DEBUG("Range of input is " << rangeW);
+  
+  // Read range info
+
+  CondAttrListCollection::const_iterator attrList{readCdo->begin()};
+  CondAttrListCollection::const_iterator end{readCdo->end()};
+  // CondAttrListCollection doesn't support C++11 type loops, no generic 'begin'
+  for (; attrList!=end; ++attrList) {
+    // A CondAttrListCollection is a map of ChanNum and AttributeList
+    CondAttrListCollection::ChanNum channelNumber{attrList->first};
+    const CondAttrListCollection::AttributeList &payload{attrList->second};
+    if (payload.exists("range") and not payload["range"].isNull()) {
+
+      float range {payload["range"].data<float>()};
+
+      ATH_MSG_DEBUG("Found digitizer channel " << channelNumber << " range as " << range);
+
+      mappingData.emplace(std::make_pair(channelNumber, range));
+
+    }
+
+  } // End of loop over attributes
+
+  return mappingData;
+} 
+
+WaveformRangeMap
+WaveformRangeTool::getRangeMapping(void) const {
+  const EventContext& ctx{Gaudi::Hive::currentContext()};
+  return getRangeMapping(ctx);
+}
+
+
+
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformRangeTool.h b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformRangeTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..e33123f243a41e286f32cf09059e748306a1348c
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformRangeTool.h
@@ -0,0 +1,60 @@
+// -*- C++ -*-
+
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and CERN collaborations
+*/
+
+/** @file WaveformRangeTool.h Header file for WaveformRangeTool.
+    @author Eric Torrence, 20/04/22
+*/
+
+// Multiple inclusion protection
+#ifndef WAVEFORM_RANGE_TOOL
+#define WAVEFORM_RANGE_TOOL
+
+// Include interface class
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "WaveformConditionsTools/IWaveformRangeTool.h"
+
+// Include Athena stuff
+#include "AthenaPoolUtilities/CondAttrListCollection.h"
+#include "StoreGate/ReadCondHandleKey.h"
+
+#include "GaudiKernel/ICondSvc.h"
+#include "Gaudi/Property.h"
+
+// Include Gaudi classes
+#include "GaudiKernel/EventContext.h"
+
+/** This class contains a Tool that reads Waveform range data and makes it available to 
+    other algorithms. The current implementation reads the data from a COOL database. 
+*/
+
+class WaveformRangeTool: public extends<AthAlgTool, IWaveformRangeTool> {
+
+ public:
+  //----------Public Member Functions----------//
+  // Structors
+  WaveformRangeTool(const std::string& type, const std::string& name, const IInterface* parent); //!< Constructor
+  virtual ~WaveformRangeTool() = default; //!< Destructor
+
+  // Standard Gaudi functions
+  virtual StatusCode initialize() override; //!< Gaudi initialiser
+  virtual StatusCode finalize() override; //!< Gaudi finaliser
+
+  // Methods to return calibration data
+  // Map indexed by digitizer channel number
+  // Returns full-scale ADC range as float
+  virtual WaveformRangeMap getRangeMapping(const EventContext& ctx) const override;
+  virtual WaveformRangeMap getRangeMapping(void) const override;
+
+ private:
+  // Read Cond Handle
+  SG::ReadCondHandleKey<CondAttrListCollection> m_readKey{this, "ReadKey", "/WAVE/DAQ/Range", "Key of range folder"};
+
+  ServiceHandle<ICondSvc> m_condSvc{this, "CondSvc", "CondSvc"};
+
+};
+
+//---------------------------------------------------------------------- 
+#endif // WAVEFORM_CABLE_MAPPING_TOOL
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformTimingTool.cxx b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformTimingTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f163c65620a107ea8a5812818e5f8c5ba4049bac
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformTimingTool.cxx
@@ -0,0 +1,130 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and FASER collaborations
+*/
+
+/** @file WaveformTimingTool.cxx Implementation file for WaveformTimingTool.
+    @author Eric Torrence (04/05/22)
+*/
+
+#include "WaveformTimingTool.h"
+
+//----------------------------------------------------------------------
+WaveformTimingTool::WaveformTimingTool (const std::string& type, const std::string& name, const IInterface* parent) :
+  base_class(type, name, parent)
+{
+}
+
+//----------------------------------------------------------------------
+StatusCode 
+WaveformTimingTool::initialize() {
+  // Read Cond Handle Key
+
+  ATH_MSG_DEBUG("WaveformTimingTool::initialize()");
+
+  ATH_CHECK(m_timingReadKey.initialize());
+  ATH_CHECK(m_offsetReadKey.initialize());
+
+  return StatusCode::SUCCESS;
+}
+
+//----------------------------------------------------------------------
+StatusCode
+WaveformTimingTool::finalize() {
+  // Print where you are
+  return StatusCode::SUCCESS;
+}
+
+//----------------------------------------------------------------------
+float 
+WaveformTimingTool::nominalTriggerTime(const EventContext& ctx) const {
+  // Print where you are
+  ATH_MSG_DEBUG("in nominalTriggerTime()");
+
+  float time=-1.;
+
+  // Read Cond Handle
+  SG::ReadCondHandle<AthenaAttributeList> readHandle{m_timingReadKey, ctx};
+  const AthenaAttributeList* readCdo(*readHandle);
+
+  if (readCdo==nullptr) {
+    ATH_MSG_FATAL("Null pointer to the read conditions object");
+    return time;
+  }
+
+  // Get the validitiy range
+  EventIDRange rangeW;
+  if (not readHandle.range(rangeW)) {
+    ATH_MSG_FATAL("Failed to retrieve validity range for " << readHandle.key());
+    return time;
+  }
+  ATH_MSG_DEBUG("Range of input is " << rangeW);
+  
+  // Read time info
+
+  const CondAttrListCollection::AttributeList &payload{*readCdo};
+  if (payload.exists("NominalTriggerTime") and not payload["NominalTriggerTime"].isNull()) {
+    time = payload["NominalTriggerTime"].data<float>();
+    ATH_MSG_DEBUG("Found nominal trigger time "<<time<<" ns");
+  } else {
+    ATH_MSG_WARNING("No valid nominal trigger time found!");
+  }
+
+  return time;
+} 
+
+//----------------------------------------------------------------------
+float
+WaveformTimingTool::triggerTimeOffset(const EventContext& ctx, int channel) const {
+
+  ATH_MSG_DEBUG("in triggerTimeOffset("<<channel<<")");
+
+  float time=0.;
+
+  // Read Cond Handle
+  SG::ReadCondHandle<CondAttrListCollection> readHandle{m_offsetReadKey, ctx};
+  const CondAttrListCollection* readCdo{*readHandle}; 
+  if (readCdo==nullptr) {
+    ATH_MSG_FATAL("Null pointer to the read conditions object");
+    return time;
+  }
+  // Get the validitiy range
+  EventIDRange rangeW;
+  if (not readHandle.range(rangeW)) {
+    ATH_MSG_FATAL("Failed to retrieve validity range for " << readHandle.key());
+    return time;
+  }
+  ATH_MSG_DEBUG("Size of CondAttrListCollection " << readHandle.fullKey() << " readCdo->size()= " << readCdo->size());
+  ATH_MSG_DEBUG("Range of input is " << rangeW);
+
+  // Read offset for specific channel
+  const CondAttrListCollection::AttributeList& payload{readCdo->attributeList(channel)};
+
+  if (payload.exists("TriggerOffset") and not payload["TriggerOffset"].isNull()) {
+    time = payload["TriggerOffset"].data<float>();
+    ATH_MSG_DEBUG("Found digitizer channel " << channel << " triger offset as " << time);
+  } else {
+    ATH_MSG_WARNING("No valid trigger offset found for channel "<<channel<<"!");
+  }
+
+  return time;
+
+}
+
+//----------------------------------------------------------------------
+float
+WaveformTimingTool::nominalTriggerTime(void) const {
+  const EventContext& ctx(Gaudi::Hive::currentContext());
+  return nominalTriggerTime(ctx);
+}
+
+float
+WaveformTimingTool::triggerTimeOffset(int channel) const {
+  const EventContext& ctx(Gaudi::Hive::currentContext());
+  return triggerTimeOffset(ctx, channel);
+}
+
+
+
+
+
+
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformTimingTool.h b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformTimingTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..b69e52b200907a369865f85b5b709aec5341d0dd
--- /dev/null
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/WaveformTimingTool.h
@@ -0,0 +1,69 @@
+// -*- C++ -*-
+
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS and CERN collaborations
+*/
+
+/** @file WaveformTimingTool.h Header file for WaveformTimingTool.
+    @author Eric Torrence, 20/04/22
+*/
+
+// Multiple inclusion protection
+#ifndef WAVEFORM_TIMING_TOOL
+#define WAVEFORM_TIMING_TOOL
+
+// Include interface class
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "WaveformConditionsTools/IWaveformTimingTool.h"
+
+// Include Athena stuff
+#include "AthenaPoolUtilities/CondAttrListCollection.h"
+#include "StoreGate/ReadCondHandleKey.h"
+
+#include "GaudiKernel/ICondSvc.h"
+#include "Gaudi/Property.h"
+
+// Include Gaudi classes
+#include "GaudiKernel/EventContext.h"
+
+/** This class contains a Tool that reads Waveform timing data and makes it available 
+    to other algorithms. 
+*/
+
+class WaveformTimingTool: public extends<AthAlgTool, IWaveformTimingTool> {
+
+ public:
+  //----------Public Member Functions----------//
+  // Structors
+  WaveformTimingTool(const std::string& type, const std::string& name, const IInterface* parent); //!< Constructor
+  virtual ~WaveformTimingTool() = default; //!< Destructor
+
+  // Standard Gaudi functions
+  virtual StatusCode initialize() override; //!< Gaudi initialiser
+  virtual StatusCode finalize() override; //!< Gaudi finaliser
+
+  // Methods to return timing data
+  // Channels indexed by digitizer channel number
+  // All times are in ns
+
+  // Nominal trigger time (in ns) in the digitizer readout
+  virtual float nominalTriggerTime(void) const override;
+  virtual float nominalTriggerTime(const EventContext& ctx) const override;
+
+  // Channel-by-channel corrections to the nominal trigger time (in ns)
+  // A given channel should be centered at nominal + offset
+  virtual float triggerTimeOffset(int channel) const override;
+  virtual float triggerTimeOffset(const EventContext& ctx, int channel) const override;
+
+ private:
+
+  // Read Cond Handle
+  SG::ReadCondHandleKey<AthenaAttributeList> m_timingReadKey{this, "TimingReadKey", "/WAVE/DAQ/Timing", "Key of timing folder"};
+  SG::ReadCondHandleKey<CondAttrListCollection> m_offsetReadKey{this, "OffsetReadKey", "/WAVE/DAQ/TimingOffset", "Key of timing offset folder"};
+
+  ServiceHandle<ICondSvc> m_condSvc{this, "CondSvc", "CondSvc"};
+
+};
+
+//---------------------------------------------------------------------- 
+#endif // WAVEFORM_CABLE_MAPPING_TOOL
diff --git a/Waveform/WaveformConditions/WaveformConditionsTools/src/components/WaveformConditionsTools_entries.cxx b/Waveform/WaveformConditions/WaveformConditionsTools/src/components/WaveformConditionsTools_entries.cxx
index 1f6a41ac923beab0533fcd60066b86143f813b90..f694fef647428802bfb98ab393e4f784180ae2b0 100644
--- a/Waveform/WaveformConditions/WaveformConditionsTools/src/components/WaveformConditionsTools_entries.cxx
+++ b/Waveform/WaveformConditions/WaveformConditionsTools/src/components/WaveformConditionsTools_entries.cxx
@@ -1,3 +1,7 @@
+#include "../WaveformRangeTool.h"
+#include "../WaveformTimingTool.h"
 #include "../WaveformCableMappingTool.h"
 
+DECLARE_COMPONENT( WaveformRangeTool )
+DECLARE_COMPONENT( WaveformTimingTool )
 DECLARE_COMPONENT( WaveformCableMappingTool )
diff --git a/faser-common b/faser-common
index 5124b0e78dbcc4a05c22f511700d5dbcdb4808df..69a90ec95da88a00097fb809bede6c2bae8c02d6 160000
--- a/faser-common
+++ b/faser-common
@@ -1 +1 @@
-Subproject commit 5124b0e78dbcc4a05c22f511700d5dbcdb4808df
+Subproject commit 69a90ec95da88a00097fb809bede6c2bae8c02d6
diff --git a/graphics/VTI12/README.md b/graphics/VTI12/README.md
index 708260f7e77ac3591b3d629b1f6f76107b0a6eb8..bfd96be854ac9668b93117ed4ea20c04ba0fb023 100644
--- a/graphics/VTI12/README.md
+++ b/graphics/VTI12/README.md
@@ -8,7 +8,7 @@ To run on Calypso MC data (from an installation (run) directory):
 
 Note that VP1PLUGINPATH can be ninja-changed by asetup, and if it does not include the Calypso installation library folder, nothing will work.  Also note that it must be an ABSOLUTE (not relative) path!
 
-You can also give the -detdescr="FASER-01" (baseline detector), -detdescr="FASER-02" (baseline + IFT), -detdescr="FASERNU-02" (baseline + IFT + emulsion) or -detdescr="FASER-TB00" (2021 Test-beam) to specify the geometry.
+You can also give the -detdescr="FASER-01" (baseline detector), -detdescr="FASER-02" (baseline + IFT), -detdescr="FASERNU-03" (baseline + IFT + emulsion) or -detdescr="FASER-TB00" (2021 Test-beam) to specify the geometry.
 
 You also need either -globcond="OFLCOND-FASER-01" (baseline) or -globcond="OFLCOND-FASER-02" (IFT with or without emulsion), or -globcond="OFLCOND-FASER-TB00" (test-beam) flags to specify the conditions.  
 
diff --git a/graphics/VTI12/VTI12Algs/share/vti12.py b/graphics/VTI12/VTI12Algs/share/vti12.py
index 3878899db88d845cd247602b91faf6a1204cee7b..07a3a97a2ca79ae0f920f1516590a03c55a2a13b 100644
--- a/graphics/VTI12/VTI12Algs/share/vti12.py
+++ b/graphics/VTI12/VTI12Algs/share/vti12.py
@@ -32,7 +32,8 @@ if not 'vp1NoSortDBReplicas' in dir(): vp1NoSortDBReplicas=False
 if not 'vp1FilterEvents' in dir(): vp1FilterEvents=""
 if not 'vp1NoGui' in dir(): vp1NoGui=False
 if not 'vp1SpacePoints' in dir(): vp1SpacePoints=False
-if not 'vp1Cavern' in dir(): vp1Cavern=False
+# if not 'vp1Cavern' in dir(): vp1Cavern=False
+vp1Cavern=True
 if not 'vp1NoAutoConf' in dir(): vp1NoAutoConf=False
 if not 'vp1Trig' in dir(): vp1Trig=False
 if not 'vp1NSW' in dir(): vp1NSW=False
diff --git a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/VTI12GeometrySystems/VP1GeoFlags.h b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/VTI12GeometrySystems/VP1GeoFlags.h
index a50cf0d19605f3bbd922459105c416271968d1d7..8aefd8e48631325fefad9f9a25253e5c2f147a13 100644
--- a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/VTI12GeometrySystems/VP1GeoFlags.h
+++ b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/VTI12GeometrySystems/VP1GeoFlags.h
@@ -27,14 +27,16 @@ public:
     
     None                    = 0x00000000,
     Emulsion                = 0x00000001,
-    Veto                    = 0x00000002,
-    Trigger                 = 0x00000004,
-    Preshower               = 0x00000008,
+    VetoNu                  = 0x00000002,
+    Veto                    = 0x00000004,
+    Trigger                 = 0x00000008,
+    Preshower               = 0x00000010,
 
-    SCT                     = 0x00000010,
-    Dipole                  = 0x00000020,
+    SCT                     = 0x00000020,
+    Dipole                  = 0x00000040,
 
-    Ecal                    = 0x00000040,
+    Ecal                    = 0x00000080,
+    CavernInfra             = 0x00000100,
     // Pixel                   = 0x00000001, // bit 0
     // SCT                     = 0x00000002, // 1
     // TRT                     = 0x00000004, // 2
diff --git a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/GeoSysController.cxx b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/GeoSysController.cxx
index e6325f8f2ff62915d13dc462bd87e626b23c9227..fbb27a254fe2aa6454d1e5b80060e083804ffc1e 100644
--- a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/GeoSysController.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/GeoSysController.cxx
@@ -150,6 +150,7 @@ GeoSysController::GeoSysController(IVP1System * sys)
   m_d->subSysCheckBoxMap[VP1GeoFlags::Emulsion] = m_d->ui.checkBox_Emulsion;
 
   // SCINTILLATOR
+  m_d->subSysCheckBoxMap[VP1GeoFlags::VetoNu] = m_d->ui.checkBox_VetoNu;
   m_d->subSysCheckBoxMap[VP1GeoFlags::Veto] = m_d->ui.checkBox_Veto;
   m_d->subSysCheckBoxMap[VP1GeoFlags::Trigger] = m_d->ui.checkBox_Trigger;
   m_d->subSysCheckBoxMap[VP1GeoFlags::Preshower] = m_d->ui.checkBox_Preshower;
@@ -161,6 +162,9 @@ GeoSysController::GeoSysController(IVP1System * sys)
   // Calorimeter
   m_d->subSysCheckBoxMap[VP1GeoFlags::Ecal] = m_d->ui.checkBox_Ecal;
 
+  // Cavern
+  m_d->subSysCheckBoxMap[VP1GeoFlags::CavernInfra] = m_d->ui.checkBox_CavernInfra;
+
   // MISCELLANEOUS
   // OTHER
   m_d->subSysCheckBoxMap[VP1GeoFlags::AllUnrecognisedVolumes] = m_d->ui.checkBox_other;
diff --git a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VP1GeometrySystem.cxx b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VP1GeometrySystem.cxx
index 8e4b61921f3d849bdd127f00f5c0c4ca1b868aa2..e5c0eed5d6622562488cc3075dc3115659796fcc 100644
--- a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VP1GeometrySystem.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VP1GeometrySystem.cxx
@@ -329,12 +329,14 @@ QWidget * VP1GeometrySystem::buildController()
                           bool negategrandchildrenregexp = false // wheter we want to negate teh granchildren regex
    */
     m_d->addSubSystem( VP1GeoFlags::Emulsion, "Emulsion");
+    m_d->addSubSystem( VP1GeoFlags::VetoNu,   "VetoNu");
     m_d->addSubSystem( VP1GeoFlags::Veto,     "Veto");
     m_d->addSubSystem( VP1GeoFlags::Trigger,  "Trigger");
     m_d->addSubSystem( VP1GeoFlags::Preshower,"Preshower");
     m_d->addSubSystem( VP1GeoFlags::SCT,      "SCT");
     m_d->addSubSystem( VP1GeoFlags::Dipole,   "Dipole");
     m_d->addSubSystem( VP1GeoFlags::Ecal,     "Ecal");
+    m_d->addSubSystem( VP1GeoFlags::CavernInfra, "Cavern");
 
 
 
@@ -1109,6 +1111,12 @@ void VP1GeometrySystem::Imp::createPathExtras(const VolumeHandle* volhandle, QSt
       entries.push("Veto::Veto");
       return;
     }
+    case VP1GeoFlags::VetoNu:{
+      prefix = QString("VetoNu::");
+      entries.push("SCINT::SCINT");
+      entries.push("VetoNu::VetoNu");
+      return;
+    }
     case VP1GeoFlags::Trigger:{
       prefix = QString("Trigger::");
       entries.push("SCINT::SCINT");
@@ -1139,6 +1147,13 @@ void VP1GeometrySystem::Imp::createPathExtras(const VolumeHandle* volhandle, QSt
       entries.push("Ecal::Ecal");
       return;
     }
+    case VP1GeoFlags::CavernInfra:{
+      prefix = QString("Cavern::");
+      entries.push("CAVERN::CAVERN");
+      entries.push("Trench::Trench");
+      return;
+    }
+
   case VP1GeoFlags::None:
   case VP1GeoFlags::AllUnrecognisedVolumes:
   default:{
diff --git a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VisAttributes.cxx b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VisAttributes.cxx
index 82825c71806f9cddaef0e065a30086fffae4e270..27fd3362c4a18bfa999517529fc925951fbff20c 100644
--- a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VisAttributes.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VisAttributes.cxx
@@ -160,6 +160,7 @@ DetVisAttributes::DetVisAttributes() {
     material->specularColor.setValue(.915152, .915152, .915152);
     material->shininess.setValue(0.642424);
     add("Veto",material);
+    add("VetoNu",material);
     add("Trigger",material);
     add("Preshower",material);
   }
diff --git a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VolumeTreeModel.cxx b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VolumeTreeModel.cxx
index b1aff16c0a2c354c77428af96bf7fb8b7c1d2dc6..ea681cdd6fe6b261a90d6701c5b8fa864777b39c 100644
--- a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VolumeTreeModel.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/VolumeTreeModel.cxx
@@ -31,7 +31,7 @@
 class VolumeTreeModel::Imp {
 public:
   //Static definitions of sections and which subsystems goes in which sections:
-  enum SECTION { UNKNOWN, NEUTRINO, SCINT, TRACKER, CALO, MISC };
+  enum SECTION { UNKNOWN, NEUTRINO, SCINT, TRACKER, CALO, MISC, CAVERN };
   static std::map<SECTION,QString> section2string;
   static std::map<VP1GeoFlags::SubSystemFlag,SECTION> subsysflag2section;
   static std::map<VP1GeoFlags::SubSystemFlag,QString> subsysflag2string;
@@ -108,6 +108,7 @@ VolumeTreeModel::VolumeTreeModel( QObject * parent )
     Imp::section2string[Imp::SCINT] = "Scintillators";
     Imp::section2string[Imp::TRACKER] = "Tracker";
     Imp::section2string[Imp::CALO] = "Calorimeter";
+    Imp::section2string[Imp::CAVERN] = "Cavern";
     Imp::section2string[Imp::MISC] = "Miscellaneous";
   }
   if (Imp::subsysflag2section.empty()) {
@@ -116,6 +117,7 @@ VolumeTreeModel::VolumeTreeModel( QObject * parent )
     Imp::defineSubSystem(VP1GeoFlags::Emulsion,  "Emulsion",  Imp::NEUTRINO);
     // Scintillator
     Imp::defineSubSystem(VP1GeoFlags::Veto,      "Veto",      Imp::SCINT);
+    Imp::defineSubSystem(VP1GeoFlags::VetoNu,    "VetoNu",    Imp::SCINT);
     Imp::defineSubSystem(VP1GeoFlags::Trigger,   "Trigger",   Imp::SCINT);
     Imp::defineSubSystem(VP1GeoFlags::Preshower, "Preshower", Imp::SCINT);
     // Tracker
@@ -123,6 +125,8 @@ VolumeTreeModel::VolumeTreeModel( QObject * parent )
     Imp::defineSubSystem(VP1GeoFlags::Dipole,    "Dipole",    Imp::TRACKER);
     // Calorimeter
     Imp::defineSubSystem(VP1GeoFlags::Ecal,      "Ecal",      Imp::CALO);
+    // Cavern
+    Imp::defineSubSystem(VP1GeoFlags::CavernInfra, "Cavern",  Imp::CAVERN);
   }
 }
 
diff --git a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/geometrysystemcontroller.ui b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/geometrysystemcontroller.ui
index be9ac807d6b5d2511d214a4a6265064f0bd7ef26..a5ef9228de57c9a314e6348a78af6eca572d31cb 100644
--- a/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/geometrysystemcontroller.ui
+++ b/graphics/VTI12/VTI12Systems/VTI12GeometrySystems/src/geometrysystemcontroller.ui
@@ -239,20 +239,27 @@
              <number>0</number>
             </property>
             <item row="0" column="0">
+             <widget class="QCheckBox" name="checkBox_VetoNu">
+              <property name="text">
+               <string>VetoNu</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
              <widget class="QCheckBox" name="checkBox_Veto">
               <property name="text">
                <string>Veto</string>
               </property>
              </widget>
             </item>
-            <item row="0" column="1">
+            <item row="1" column="0">
              <widget class="QCheckBox" name="checkBox_Trigger">
               <property name="text">
                <string>Trigger</string>
               </property>
              </widget>
             </item>
-            <item row="0" column="2">
+            <item row="1" column="1">
              <widget class="QCheckBox" name="checkBox_Preshower">
               <property name="text">
                <string>Preshower</string>
@@ -333,7 +340,7 @@
          <property name="title">
           <string>Calorimeter</string>
          </property>
-         <layout class="QVBoxLayout" name="_12">
+         <layout class="QVBoxLayout" name="_121">
           <property name="spacing">
            <number>0</number>
           </property>
@@ -350,7 +357,7 @@
            <number>4</number>
           </property>
           <item>
-           <layout class="QHBoxLayout" name="_14">
+           <layout class="QHBoxLayout" name="_141">
             <item>
              <widget class="QCheckBox" name="checkBox_Ecal">
               <property name="text">
diff --git a/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/VP1SimHitSystem.cxx b/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/VP1SimHitSystem.cxx
index 19634a20a2b55431ade9a88704a8b9ca6cf06ba4..a9eec098eed63415284583faeaf4b0e4fe71dd0c 100755
--- a/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/VP1SimHitSystem.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/VP1SimHitSystem.cxx
@@ -64,6 +64,7 @@ QWidget* VP1SimHitSystem::buildController()
 
   // Populate Check Box Names Map
   m_clockwork->checkBoxNamesMap.insert(ui.chbxEmulsionHits,"Emulsion");
+  m_clockwork->checkBoxNamesMap.insert(ui.chbxVetoNuHits,"VetoNu");
   m_clockwork->checkBoxNamesMap.insert(ui.chbxVetoHits,"Veto");
   m_clockwork->checkBoxNamesMap.insert(ui.chbxTriggerHits,"Trigger");
   m_clockwork->checkBoxNamesMap.insert(ui.chbxPreshowerHits,"Preshower");
@@ -84,6 +85,7 @@ void VP1SimHitSystem::systemcreate(StoreGateSvc* /*detstore*/)
 {
   // Populate Color Map
   m_clockwork->colorMap.insert("Emulsion",SbColor(1,0,1));
+  m_clockwork->colorMap.insert("VetoNu",SbColor(0,1,1));
   m_clockwork->colorMap.insert("Veto",SbColor(0,0,1));
   m_clockwork->colorMap.insert("Trigger",SbColor(1,1,1));
   m_clockwork->colorMap.insert("Preshower",SbColor(1,0,0));
@@ -227,6 +229,25 @@ void VP1SimHitSystem::buildHitTree(const QString& detector)
     else
       message("Unable to retrieve Veto Hits");
   }
+  else if(detector=="VetoNu")
+  {
+    //
+    // VetoNu:
+    //
+    const ScintHitCollection* p_collection = nullptr;
+    if(sg->retrieve(p_collection, "VetoNuHits")==StatusCode::SUCCESS)
+    {
+      for(ScintHitConstIterator i_hit=p_collection->begin(); i_hit!=p_collection->end(); ++i_hit)
+      {
+        GeoScintHit ghit(*i_hit);
+        if(!ghit) continue;
+        HepGeom::Point3D<double> u = ghit.getGlobalPosition();
+        hitVtxProperty->vertex.set1Value(hitCount++,u.x(),u.y(),u.z());
+      }
+    }
+    else
+      message("Unable to retrieve VetoNu Hits");
+  }
   else if(detector=="Trigger")
   {
     //
diff --git a/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/simhitcontrollerform.ui b/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/simhitcontrollerform.ui
index 67f62d51f9450ac5416d1452f106c37633f25809..b9797015a001150b7da85edc1463ee245a168c9d 100755
--- a/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/simhitcontrollerform.ui
+++ b/graphics/VTI12/VTI12Systems/VTI12SimHitSystems/src/simhitcontrollerform.ui
@@ -106,6 +106,13 @@
             <string>Scintillator</string>
            </property>
            <layout class="QVBoxLayout" name="verticalLayout">
+            <item>
+             <widget class="QCheckBox" name="chbxVetoNuHits">
+              <property name="text">
+               <string>VetoNu Hits</string>
+              </property>
+             </widget>
+            </item>
             <item>
              <widget class="QCheckBox" name="chbxVetoHits">
               <property name="text">
@@ -181,7 +188,7 @@
        <property name="title">
         <string>Calorimeter</string>
        </property>
-       <layout class="QVBoxLayout" name="verticalLayout_3">
+       <layout class="QVBoxLayout" name="verticalLayout_31">
         <item>
          <widget class="QCheckBox" name="chbxCaloHits">
           <property name="text">
diff --git a/graphics/VTI12/VTI12Systems/VTI12TrackSystems/src/TrackCollHandle_TruthTracks.cxx b/graphics/VTI12/VTI12Systems/VTI12TrackSystems/src/TrackCollHandle_TruthTracks.cxx
index ef731fa9549323d72b6b4f7d516fb7989b4182b3..eedf4ed49e4a50c1c6c6534f47cb8297659593e5 100644
--- a/graphics/VTI12/VTI12Systems/VTI12TrackSystems/src/TrackCollHandle_TruthTracks.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12TrackSystems/src/TrackCollHandle_TruthTracks.cxx
@@ -127,8 +127,9 @@ QStringList TrackCollHandle_TruthTracks::availableCollections( IVP1System*sys )
 
   if (VP1JobConfigInfo::hasVetoGeometry() ||
       VP1JobConfigInfo::hasTriggerGeometry() ||
-      VP1JobConfigInfo::hasPreshowerGeometry())
-    keys_scintillatorhits = sgcont.getKeys<ScintHitCollection>();//"VetoHits", "TriggerHits" and "PreshowerHits"
+      VP1JobConfigInfo::hasPreshowerGeometry() ||
+      VP1JobConfigInfo::hasVetoNuGeometry())
+    keys_scintillatorhits = sgcont.getKeys<ScintHitCollection>();//"VetoHits", "VetoNuHits", "TriggerHits" and "PreshowerHits"
   if (VP1JobConfigInfo::hasSCTGeometry())
     keys_siliconhits = sgcont.getKeys<FaserSiHitCollection>();//"SCT_Hits"
   if (VP1JobConfigInfo::hasEcalGeometry())
@@ -264,6 +265,7 @@ bool TrackCollHandle_TruthTracks::Imp::loadHitLists(std::map<SimBarCode,SimHitLi
    if (VP1JobConfigInfo::hasEmulsionGeometry())
      addHitCollections<NeutrinoHitCollection>(hitLists);
    if (VP1JobConfigInfo::hasVetoGeometry() || 
+      VP1JobConfigInfo::hasVetoNuGeometry() ||
       VP1JobConfigInfo::hasTriggerGeometry() || 
       VP1JobConfigInfo::hasPreshowerGeometry())
       // std::cout << "Called addHitCollections" << std::endl;
diff --git a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformHitSystem.h b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformHitSystem.h
index fa0831b3f579c8c052fd3d38639da1a491888d7c..52312553b7c1a6aa39d64e8535faa1a463f7c946 100644
--- a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformHitSystem.h
+++ b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformHitSystem.h
@@ -45,6 +45,7 @@ public:
 public slots:
 
   void updateVetoElements(bool);
+  void updateVetoNuElements(bool);
   void updateTriggerElements(bool);
   void updatePreshowerElements(bool);
   void updateCalorimeterElements(bool);
diff --git a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformSystem.h b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformSystem.h
index d17e3b60f5e58fc5709064be6c77215c21d7a5b4..73535d6ad2f19aac9982a89b8f559615268de775 100644
--- a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformSystem.h
+++ b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/VP1WaveformSystem.h
@@ -45,6 +45,7 @@ public:
 public slots:
 
   void updateVetoElements(bool);
+  void updateVetoNuElements(bool);
   void updateTriggerElements(bool);
   void updatePreshowerElements(bool);
   void updateCalorimeterElements(bool);
diff --git a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/WaveformSysController.h b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/WaveformSysController.h
index ebb3c4c34ca693d245a300b2f47813ce738b4597..74a2191c7277af9cbf31374466911804ff531e1e 100644
--- a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/WaveformSysController.h
+++ b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/VTI12WaveformSystems/WaveformSysController.h
@@ -40,6 +40,7 @@ public:
   ///////////////////////////////////
 
   bool vetoEnabled() const;
+  bool vetoNuEnabled() const;
   bool triggerEnabled() const;
   bool preshowerEnabled() const;
   bool calorimeterEnabled() const;
@@ -52,6 +53,7 @@ public:
   ///////////////////////////////////////
 signals:
   void vetoEnabledChanged(bool);
+  void vetoNuEnabledChanged(bool);
   void triggerEnabledChanged(bool);
   void preshowerEnabledChanged(bool);
   void calorimeterEnabledChanged(bool);
@@ -65,6 +67,7 @@ private:
 
 private slots:
   void possibleChange_vetoEnabled();
+  void possibleChange_vetoNuEnabled();
   void possibleChange_triggerEnabled();
   void possibleChange_preshowerEnabled();
   void possibleChange_calorimeterEnabled();
diff --git a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformHitSystem.cxx b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformHitSystem.cxx
index 7d0c07c942414273964ffed851b27b745cca2756..c46089cf4a23397d324b422ea07d4dcc008e896f 100644
--- a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformHitSystem.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformHitSystem.cxx
@@ -46,6 +46,7 @@ public:
   VP1WaveformHitSystem *theclass;
   WaveformSysController * controller;
   std::vector<QChart*> vetoCharts;
+  std::vector<QChart*> vetoNuCharts;
   std::vector<QChart*> triggerCharts;
   std::vector<QChart*> preshowerCharts;
   std::vector<QChart*> calorimeterCharts;
@@ -82,6 +83,7 @@ QWidget * VP1WaveformHitSystem::buildController()
   m_d->controller = new WaveformSysController(this);
 
   connect(m_d->controller,SIGNAL(vetoEnabledChanged(bool)),this,SLOT(updateVetoElements(bool)));
+  connect(m_d->controller,SIGNAL(vetoNuEnabledChanged(bool)),this,SLOT(updateVetoNuElements(bool)));
   connect(m_d->controller,SIGNAL(triggerEnabledChanged(bool)),this,SLOT(updateTriggerElements(bool)));
   connect(m_d->controller,SIGNAL(preshowerEnabledChanged(bool)),this,SLOT(updatePreshowerElements(bool)));
   connect(m_d->controller,SIGNAL(calorimeterEnabledChanged(bool)),this,SLOT(updateCalorimeterElements(bool)));
@@ -99,6 +101,7 @@ void VP1WaveformHitSystem::buildEventItemCollection(StoreGateSvc* sg, VP1Graphic
   }
 
   m_d->vetoCharts.clear();
+  m_d->vetoNuCharts.clear();
   m_d->triggerCharts.clear();
   m_d->preshowerCharts.clear();
   m_d->calorimeterCharts.clear();
@@ -107,6 +110,7 @@ void VP1WaveformHitSystem::buildEventItemCollection(StoreGateSvc* sg, VP1Graphic
 //   m_d->nCharts = 0;
 
   m_d->createCharts(sg, root, m_d->controller->vetoEnabled(), m_d->vetoCharts, "VetoWaveformHits");
+  m_d->createCharts(sg, root, m_d->controller->vetoNuEnabled(), m_d->vetoNuCharts, "VetoNuWaveformHits");
   m_d->createCharts(sg, root, m_d->controller->triggerEnabled(), m_d->triggerCharts, "TriggerWaveformHits");
   m_d->createCharts(sg, root, m_d->controller->preshowerEnabled(), m_d->preshowerCharts, "PreshowerWaveformHits");
   m_d->createCharts(sg, root, m_d->controller->calorimeterEnabled(), m_d->calorimeterCharts, "CaloWaveformHits");
@@ -264,6 +268,7 @@ QChart* VP1WaveformHitSystem::Imp::createChart(const std::vector<float>& times,
 void VP1WaveformHitSystem::Imp::getVisible()
 {
     visibleCharts.clear();
+    getVisible(vetoNuCharts);
     getVisible(vetoCharts);
     getVisible(triggerCharts);
     getVisible(preshowerCharts);
@@ -356,6 +361,16 @@ void VP1WaveformHitSystem::updateVetoElements(bool enabled)
     m_d->layoutCharts();
 }
 
+void VP1WaveformHitSystem::updateVetoNuElements(bool enabled)
+{
+    for (QChart* c : m_d->vetoNuCharts)
+    {
+        c->setVisible(enabled);
+    }
+    m_d->layoutCharts();
+}
+
+
 void VP1WaveformHitSystem::updateTriggerElements(bool enabled)
 {
     for (QChart* c : m_d->triggerCharts)
diff --git a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformSystem.cxx b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformSystem.cxx
index ffe6bd00acecfd6f3fe611c01b24a1f2ea6afd1a..172bdebd9d22b183a4304bd8414b6e6f3be3985e 100644
--- a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformSystem.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/VP1WaveformSystem.cxx
@@ -45,6 +45,7 @@ public:
   VP1WaveformSystem *theclass;
   WaveformSysController * controller;
   std::vector<QChart*> vetoCharts;
+  std::vector<QChart*> vetoNuCharts;
   std::vector<QChart*> triggerCharts;
   std::vector<QChart*> preshowerCharts;
   std::vector<QChart*> calorimeterCharts;
@@ -80,6 +81,7 @@ QWidget * VP1WaveformSystem::buildController()
   m_d->controller = new WaveformSysController(this);
 
   connect(m_d->controller,SIGNAL(vetoEnabledChanged(bool)),this,SLOT(updateVetoElements(bool)));
+  connect(m_d->controller,SIGNAL(vetoNuEnabledChanged(bool)),this,SLOT(updateVetoNuElements(bool)));
   connect(m_d->controller,SIGNAL(triggerEnabledChanged(bool)),this,SLOT(updateTriggerElements(bool)));
   connect(m_d->controller,SIGNAL(preshowerEnabledChanged(bool)),this,SLOT(updatePreshowerElements(bool)));
   connect(m_d->controller,SIGNAL(calorimeterEnabledChanged(bool)),this,SLOT(updateCalorimeterElements(bool)));
@@ -97,6 +99,7 @@ void VP1WaveformSystem::buildEventItemCollection(StoreGateSvc* sg, VP1GraphicsIt
   }
 
   m_d->vetoCharts.clear();
+  m_d->vetoNuCharts.clear();
   m_d->triggerCharts.clear();
   m_d->preshowerCharts.clear();
   m_d->calorimeterCharts.clear();
@@ -105,6 +108,7 @@ void VP1WaveformSystem::buildEventItemCollection(StoreGateSvc* sg, VP1GraphicsIt
 //   m_d->nCharts = 0;
 
   m_d->createCharts(sg, root, m_d->controller->vetoEnabled(), m_d->vetoCharts, "VetoWaveforms");
+  m_d->createCharts(sg, root, m_d->controller->vetoNuEnabled(), m_d->vetoNuCharts, "VetoNuWaveforms");
   m_d->createCharts(sg, root, m_d->controller->triggerEnabled(), m_d->triggerCharts, "TriggerWaveforms");
   m_d->createCharts(sg, root, m_d->controller->preshowerEnabled(), m_d->preshowerCharts, "PreshowerWaveforms");
   m_d->createCharts(sg, root, m_d->controller->calorimeterEnabled(), m_d->calorimeterCharts, "CaloWaveforms");
@@ -194,6 +198,7 @@ void VP1WaveformSystem::Imp::getVisible()
 {
     visibleCharts.clear();
     getVisible(vetoCharts);
+    getVisible(vetoNuCharts);
     getVisible(triggerCharts);
     getVisible(preshowerCharts);
     getVisible(calorimeterCharts);
@@ -286,6 +291,15 @@ void VP1WaveformSystem::updateVetoElements(bool enabled)
     m_d->layoutCharts();
 }
 
+void VP1WaveformSystem::updateVetoNuElements(bool enabled)
+{
+    for (QChart* c : m_d->vetoNuCharts)
+    {
+        c->setVisible(enabled);
+    }
+    m_d->layoutCharts();
+}
+
 void VP1WaveformSystem::updateTriggerElements(bool enabled)
 {
     for (QChart* c : m_d->triggerCharts)
diff --git a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/WaveformSysController.cxx b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/WaveformSysController.cxx
index 765ce0d4d702245b2c2733f4003824b21a150ba2..856fc0071f9679cfbf585bd0279587a521fcc220 100644
--- a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/WaveformSysController.cxx
+++ b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/WaveformSysController.cxx
@@ -45,6 +45,7 @@ public:
   }
 
   bool last_vetoEnabled;
+  bool last_vetoNuEnabled;
   bool last_triggerEnabled;
   bool last_preshowerEnabled;
   bool last_calorimeterEnabled;
@@ -62,6 +63,9 @@ WaveformSysController::WaveformSysController(IVP1System * sys)
   addUpdateSlot(SLOT(possibleChange_vetoEnabled()));
   connectToLastUpdateSlot(m_d->ui.checkBox_vetoEnabled);
 
+  addUpdateSlot(SLOT(possibleChange_vetoNuEnabled()));
+  connectToLastUpdateSlot(m_d->ui.checkBox_vetoNuEnabled);
+
   addUpdateSlot(SLOT(possibleChange_triggerEnabled()));
   connectToLastUpdateSlot(m_d->ui.checkBox_triggerEnabled);
 
@@ -91,6 +95,11 @@ bool WaveformSysController::vetoEnabled() const
   return m_d->ui.checkBox_vetoEnabled->isChecked();
 }
 
+bool WaveformSysController::vetoNuEnabled() const
+{
+  return m_d->ui.checkBox_vetoNuEnabled->isChecked();
+}
+
 bool WaveformSysController::triggerEnabled() const
 {
   return m_d->ui.checkBox_triggerEnabled->isChecked();
@@ -126,6 +135,7 @@ void WaveformSysController::actualSaveSettings(VP1Serialise&s) const
 {
 
   s.save(m_d->ui.checkBox_vetoEnabled);
+  s.save(m_d->ui.checkBox_vetoNuEnabled);
   s.save(m_d->ui.checkBox_triggerEnabled);
   s.save(m_d->ui.checkBox_preshowerEnabled);
   s.save(m_d->ui.checkBox_calorimeterEnabled);
@@ -143,6 +153,7 @@ void WaveformSysController::actualRestoreSettings(VP1Deserialise& s)
   }
 
   s.restore(m_d->ui.checkBox_vetoEnabled);
+  s.restore(m_d->ui.checkBox_vetoNuEnabled);
   s.restore(m_d->ui.checkBox_triggerEnabled);
   s.restore(m_d->ui.checkBox_preshowerEnabled);
   s.restore(m_d->ui.checkBox_calorimeterEnabled);
@@ -157,6 +168,7 @@ void WaveformSysController::actualRestoreSettings(VP1Deserialise& s)
 #define VP1CONTROLLERCLASSNAME WaveformSysController
 #include "VP1Base/VP1ControllerMacros.h"
 POSSIBLECHANGE_IMP(vetoEnabled)
+POSSIBLECHANGE_IMP(vetoNuEnabled)
 POSSIBLECHANGE_IMP(triggerEnabled)
 POSSIBLECHANGE_IMP(preshowerEnabled)
 POSSIBLECHANGE_IMP(calorimeterEnabled)
diff --git a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/waveformcontrollerform.ui b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/waveformcontrollerform.ui
index cb854170cb4351c4f61e0672cd51cf6853af6fe2..a90933874c20e67ba073ae34ccfa1053b86d2a41 100644
--- a/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/waveformcontrollerform.ui
+++ b/graphics/VTI12/VTI12Systems/VTI12WaveformSystems/src/waveformcontrollerform.ui
@@ -32,6 +32,16 @@
       <number>0</number>
      </property>
      <item row="0" column="0">
+      <widget class="QCheckBox" name="checkBox_vetoNuEnabled">
+       <property name="text">
+        <string>VetoNu</string>
+       </property>
+       <property name="checked">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
       <widget class="QCheckBox" name="checkBox_vetoEnabled">
        <property name="text">
         <string>Veto</string>
@@ -41,7 +51,7 @@
        </property>
       </widget>
      </item>
-     <item row="1" column="0">
+     <item row="2" column="0">
       <widget class="QCheckBox" name="checkBox_triggerEnabled">
        <property name="text">
         <string>Trigger</string>
@@ -51,7 +61,7 @@
        </property>
       </widget>
      </item>
-     <item row="2" column="0">
+     <item row="3" column="0">
       <widget class="QCheckBox" name="checkBox_preshowerEnabled">
        <property name="text">
         <string>Preshower</string>
@@ -61,7 +71,7 @@
        </property>
       </widget>
      </item>
-     <item row="3" column="0">
+     <item row="4" column="0">
       <widget class="QCheckBox" name="checkBox_calorimeterEnabled">
        <property name="text">
         <string>Calorimeter</string>
@@ -71,7 +81,7 @@
        </property>
       </widget>
      </item>
-     <item row="4" column="0">
+     <item row="5" column="0">
       <widget class="QCheckBox" name="checkBox_clockEnabled">
        <property name="text">
         <string>Clock</string>
@@ -81,7 +91,7 @@
        </property>
       </widget>
      </item>
-     <item row="5" column="0">
+     <item row="6" column="0">
       <widget class="QCheckBox" name="checkBox_testEnabled">
        <property name="text">
         <string>Test</string>
diff --git a/graphics/VTI12/VTI12Utils/VTI12Utils/VP1DetInfo.h b/graphics/VTI12/VTI12Utils/VTI12Utils/VP1DetInfo.h
index ff521e608e83558942909b2af4eb7f5f3d227f8d..46ae0c23a4ed356d0149f79b0378dc047fdc737d 100644
--- a/graphics/VTI12/VTI12Utils/VTI12Utils/VP1DetInfo.h
+++ b/graphics/VTI12/VTI12Utils/VTI12Utils/VP1DetInfo.h
@@ -23,6 +23,7 @@ class StoreGateSvc;
 namespace NeutrinoDD { class EmulsionDetectorManager; }
 namespace TrackerDD { class SCT_DetectorManager; }
 namespace ScintDD { class VetoDetectorManager; }
+namespace ScintDD { class VetoNuDetectorManager; }
 namespace ScintDD { class TriggerDetectorManager; }
 namespace ScintDD { class PreshowerDetectorManager; }
 namespace CaloDD  { class EcalDetectorManager; }
@@ -31,6 +32,7 @@ class FaserDetectorID;
 class EmulsionDetectorID;
 class ScintDetectorID;
 class VetoID;
+class VetoNuID;
 class TriggerID;
 class PreshowerID;
 class FaserSCT_ID;
@@ -51,6 +53,7 @@ public:
   static const NeutrinoDD::EmulsionDetectorManager * emulsionDetMgr();
 
   static const ScintDD::VetoDetectorManager * vetoDetMgr();
+  static const ScintDD::VetoNuDetectorManager * vetoNuDetMgr();
   static const ScintDD::TriggerDetectorManager * triggerDetMgr();
   static const ScintDD::PreshowerDetectorManager * preshowerDetMgr();
 
@@ -66,6 +69,7 @@ public:
    static const EmulsionID * emulsionIDHelper();
 
    static const VetoID * vetoIDHelper();
+   static const VetoNuID * vetoNuIDHelper();
    static const TriggerID * triggerIDHelper();
    static const PreshowerID * preshowerIDHelper();
 
diff --git a/graphics/VTI12/VTI12Utils/VTI12Utils/VP1JobConfigInfo.h b/graphics/VTI12/VTI12Utils/VTI12Utils/VP1JobConfigInfo.h
index a90910903a46ee80f207ee10a8e5d2295602fe33..96c80ac470dedb37562dc2b6d7e1c38ff5bec9c3 100644
--- a/graphics/VTI12/VTI12Utils/VTI12Utils/VP1JobConfigInfo.h
+++ b/graphics/VTI12/VTI12Utils/VTI12Utils/VP1JobConfigInfo.h
@@ -35,6 +35,7 @@ public:
   static bool hasEmulsionGeometry();
 
   static bool hasVetoGeometry();
+  static bool hasVetoNuGeometry();
   static bool hasTriggerGeometry();
   static bool hasPreshowerGeometry();
 
@@ -42,6 +43,8 @@ public:
   
   static bool hasEcalGeometry();
 
+  static bool hasCavernInfraGeometry();
+
   //Top geomodel volume link (not strictly "JobConfig", but here it is):
   static const GeoPVConstLink * geoModelWorld();//might return 0
 
diff --git a/graphics/VTI12/VTI12Utils/src/VP1DetInfo.cxx b/graphics/VTI12/VTI12Utils/src/VP1DetInfo.cxx
index ba2ecb3fb349040c013933feb8d81474ddc2f5a8..b92e7cf2c2f1f066c2177de36448b49f8bf78fcb 100644
--- a/graphics/VTI12/VTI12Utils/src/VP1DetInfo.cxx
+++ b/graphics/VTI12/VTI12Utils/src/VP1DetInfo.cxx
@@ -25,6 +25,7 @@
 #include "NeutrinoReadoutGeometry/EmulsionDetectorManager.h"
 
 #include "ScintReadoutGeometry/VetoDetectorManager.h"
+#include "ScintReadoutGeometry/VetoNuDetectorManager.h"
 #include "ScintReadoutGeometry/TriggerDetectorManager.h"
 #include "ScintReadoutGeometry/PreshowerDetectorManager.h"
 
@@ -37,6 +38,7 @@
 #include "NeutrinoIdentifier/EmulsionID.h"
 
 #include "ScintIdentifier/VetoID.h"
+#include "ScintIdentifier/VetoNuID.h"
 #include "ScintIdentifier/TriggerID.h"
 #include "ScintIdentifier/PreshowerID.h"
 
@@ -58,6 +60,7 @@ public:
   static const NeutrinoDD::EmulsionDetectorManager * m_emulsionDetMgr;
 
   static const ScintDD::VetoDetectorManager * m_vetoDetMgr;
+  static const ScintDD::VetoNuDetectorManager * m_vetoNuDetMgr;
   static const ScintDD::TriggerDetectorManager * m_triggerDetMgr;
   static const ScintDD::PreshowerDetectorManager * m_preshowerDetMgr;
 
@@ -70,6 +73,7 @@ public:
   static const EmulsionID *   m_emulsionIDHelper;
 
   static const VetoID *       m_vetoIDHelper;
+  static const VetoNuID *     m_vetoNuIDHelper;
   static const TriggerID *    m_triggerIDHelper;
   static const PreshowerID *  m_preshowerIDHelper;
 
@@ -84,6 +88,7 @@ const char VP1DetInfo::Imp::m_badInitFlag = ' ';
 const NeutrinoDD::EmulsionDetectorManager * VP1DetInfo::Imp::m_emulsionDetMgr = 0;
 
 const ScintDD::VetoDetectorManager * VP1DetInfo::Imp::m_vetoDetMgr = 0;
+const ScintDD::VetoNuDetectorManager * VP1DetInfo::Imp::m_vetoNuDetMgr = 0;
 const ScintDD::TriggerDetectorManager * VP1DetInfo::Imp::m_triggerDetMgr = 0;
 const ScintDD::PreshowerDetectorManager * VP1DetInfo::Imp::m_preshowerDetMgr = 0;
 
@@ -96,6 +101,7 @@ const FaserDetectorID * VP1DetInfo::Imp::m_faserIDHelper = 0;
 const EmulsionID * VP1DetInfo::Imp::m_emulsionIDHelper = 0;
 
 const VetoID * VP1DetInfo::Imp::m_vetoIDHelper = 0;
+const VetoNuID * VP1DetInfo::Imp::m_vetoNuIDHelper = 0;
 const TriggerID * VP1DetInfo::Imp::m_triggerIDHelper = 0;
 const PreshowerID * VP1DetInfo::Imp::m_preshowerIDHelper = 0;
 
@@ -144,6 +150,7 @@ const T * VP1DetInfo::Imp::cachedRetrieve(const T*& cachedPtr, const char* prefe
 const NeutrinoDD::EmulsionDetectorManager * VP1DetInfo::emulsionDetMgr() { return Imp::cachedRetrieve(Imp::m_emulsionDetMgr,"Emulsion",VP1JobConfigInfo::hasEmulsionGeometry()); }
 
 const ScintDD::VetoDetectorManager * VP1DetInfo::vetoDetMgr() { return Imp::cachedRetrieve(Imp::m_vetoDetMgr,"Veto",VP1JobConfigInfo::hasVetoGeometry()); }
+const ScintDD::VetoNuDetectorManager * VP1DetInfo::vetoNuDetMgr() { return Imp::cachedRetrieve(Imp::m_vetoNuDetMgr,"VetoNu",VP1JobConfigInfo::hasVetoNuGeometry()); }
 const ScintDD::TriggerDetectorManager * VP1DetInfo::triggerDetMgr() { return Imp::cachedRetrieve(Imp::m_triggerDetMgr,"Trigger",VP1JobConfigInfo::hasTriggerGeometry()); }
 const ScintDD::PreshowerDetectorManager * VP1DetInfo::preshowerDetMgr() { return Imp::cachedRetrieve(Imp::m_preshowerDetMgr,"Preshower",VP1JobConfigInfo::hasPreshowerGeometry()); }
 
@@ -156,6 +163,7 @@ const FaserDetectorID * VP1DetInfo::faserIDHelper() { return Imp::cachedRetrieve
 const EmulsionID * VP1DetInfo::emulsionIDHelper() { return Imp::cachedRetrieve(Imp::m_emulsionIDHelper,"EmulsionID",VP1JobConfigInfo::hasEmulsionGeometry()); }
 
 const VetoID * VP1DetInfo::vetoIDHelper() { return Imp::cachedRetrieve(Imp::m_vetoIDHelper,"VetoID",VP1JobConfigInfo::hasVetoGeometry()); }
+const VetoNuID * VP1DetInfo::vetoNuIDHelper() { return Imp::cachedRetrieve(Imp::m_vetoNuIDHelper,"VetoNuID",VP1JobConfigInfo::hasVetoNuGeometry()); }
 const TriggerID * VP1DetInfo::triggerIDHelper() { return Imp::cachedRetrieve(Imp::m_triggerIDHelper,"TriggerID",VP1JobConfigInfo::hasTriggerGeometry()); }
 const PreshowerID * VP1DetInfo::preshowerIDHelper() { return Imp::cachedRetrieve(Imp::m_preshowerIDHelper,"PreshowerID",VP1JobConfigInfo::hasPreshowerGeometry()); }
 
@@ -179,6 +187,8 @@ bool VP1DetInfo::isUnsafe( const Identifier& id ) {
   if (idhelper->is_scint(id)) {
     if (!VP1JobConfigInfo::hasVetoGeometry() && idhelper->is_veto(id))
       return true;
+    if (!VP1JobConfigInfo::hasVetoNuGeometry() && idhelper->is_vetonu(id))
+      return true;
     if (!VP1JobConfigInfo::hasTriggerGeometry() && idhelper->is_trigger(id))
       return true;
     if (!VP1JobConfigInfo::hasPreshowerGeometry() && idhelper->is_preshower(id))
diff --git a/graphics/VTI12/VTI12Utils/src/VP1JobConfigInfo.cxx b/graphics/VTI12/VTI12Utils/src/VP1JobConfigInfo.cxx
index 03a305c6851e984faa31a33eec57e41f11297177..bd325e0d306fb4c93cdc23b82683cf6ac5244e7a 100644
--- a/graphics/VTI12/VTI12Utils/src/VP1JobConfigInfo.cxx
+++ b/graphics/VTI12/VTI12Utils/src/VP1JobConfigInfo.cxx
@@ -37,6 +37,7 @@ public:
   static bool hasGeoModelExperiment;
   static bool hasEmulsionGeometry;
   static bool hasVetoGeometry;
+  static bool hasVetoNuGeometry;
   static bool hasTriggerGeometry;
   static bool hasPreshowerGeometry;
   static bool hasSCTGeometry;
@@ -52,6 +53,7 @@ bool VP1JobConfigInfo::Imp::initialised = false;
 bool VP1JobConfigInfo::Imp::hasGeoModelExperiment = false;
 bool VP1JobConfigInfo::Imp::hasEmulsionGeometry = false;
 bool VP1JobConfigInfo::Imp::hasVetoGeometry = false;
+bool VP1JobConfigInfo::Imp::hasVetoNuGeometry = false;
 bool VP1JobConfigInfo::Imp::hasTriggerGeometry = false;
 bool VP1JobConfigInfo::Imp::hasPreshowerGeometry = false;
 bool VP1JobConfigInfo::Imp::hasSCTGeometry = false;
@@ -64,6 +66,7 @@ void VP1JobConfigInfo::Imp::turnOffAll()
   hasGeoModelExperiment = false;
   hasEmulsionGeometry = false;
   hasVetoGeometry = false;
+  hasVetoNuGeometry = false;
   hasTriggerGeometry = false;
   hasPreshowerGeometry = false;
   hasSCTGeometry = false;
@@ -89,12 +92,15 @@ void VP1JobConfigInfo::Imp::ensureInit()
     VP1Msg::messageVerbose("VTI12JobConfigInfo => hasGeoModelExperiment = "+QString(hasGeoModelExperiment?"On":"Off"));
     VP1Msg::messageVerbose("VTI12JobConfigInfo => hasEmulsionGeometry = "+QString(hasEmulsionGeometry?"On":"Off"));
     VP1Msg::messageVerbose("VTI12JobConfigInfo => hasVetoGeometry = "+QString(hasVetoGeometry?"On":"Off"));
+    VP1Msg::messageVerbose("VTI12JobConfigInfo => hasVetoNuGeometry = "+QString(hasVetoNuGeometry?"On":"Off"));
     VP1Msg::messageVerbose("VTI12JobConfigInfo => hasTriggerGeometry = "+QString(hasTriggerGeometry?"On":"Off"));
     VP1Msg::messageVerbose("VTI12JobConfigInfo => hasPreshowerGeometry = "+QString(hasPreshowerGeometry?"On":"Off"));
 
     VP1Msg::messageVerbose("VTI12JobConfigInfo => hasSCTGeometry = "+QString(hasSCTGeometry?"On":"Off"));
 
     VP1Msg::messageVerbose("VTI12JobConfigInfo => hasEcalGeometry = "+QString(hasEcalGeometry?"On":"Off"));
+    VP1Msg::messageVerbose("VTI12JobConfigInfo => hasCavernInfraGeometry = "+QString(hasCavernInfraGeometry?"On":"Off"));
+
   }
 
 }
@@ -103,12 +109,14 @@ void VP1JobConfigInfo::Imp::ensureInit()
 bool VP1JobConfigInfo::hasGeoModelExperiment() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasGeoModelExperiment; }
 bool VP1JobConfigInfo::hasEmulsionGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasEmulsionGeometry; }
 bool VP1JobConfigInfo::hasVetoGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasVetoGeometry; }
+bool VP1JobConfigInfo::hasVetoNuGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasVetoNuGeometry; }
 bool VP1JobConfigInfo::hasTriggerGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasTriggerGeometry; }
 bool VP1JobConfigInfo::hasPreshowerGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasPreshowerGeometry; }
 
 bool VP1JobConfigInfo::hasSCTGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasSCTGeometry; }
 
 bool VP1JobConfigInfo::hasEcalGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasEcalGeometry; }
+bool VP1JobConfigInfo::hasCavernInfraGeometry() { if (!Imp::initialised) Imp::ensureInit(); return Imp::hasCavernInfraGeometry; }
 
 //____________________________________________________________________
 bool VP1JobConfigInfo::Imp::actualInit( StoreGateSvc* detStore )
@@ -159,10 +167,12 @@ bool VP1JobConfigInfo::Imp::actualInit( StoreGateSvc* detStore )
     VP1Msg::message( QString { name.c_str() } );
     if ( !hasEmulsionGeometry && name=="Emulsion") hasEmulsionGeometry = true;
     if ( !hasVetoGeometry && name=="Veto") hasVetoGeometry = true;
+    if ( !hasVetoNuGeometry && name=="VetoNu") hasVetoNuGeometry = true;
     if ( !hasTriggerGeometry && name=="Trigger") hasTriggerGeometry = true;
     if ( !hasPreshowerGeometry && name=="Preshower") hasPreshowerGeometry = true;
     if ( !hasSCTGeometry && name=="SCT") hasSCTGeometry = true;
     if ( !hasEcalGeometry && name=="Ecal") hasEcalGeometry = true;
+    if ( !hasCavernInfraGeometry && name == "Trench") hasCavernInfraGeometry = true;
 
     //FIXME: Look for CavernInfra as well!!!
 
diff --git a/xAOD/xAODFaserWaveform/Root/WaveformHit_v1.cxx b/xAOD/xAODFaserWaveform/Root/WaveformHit_v1.cxx
index df91c67cf77ef79b749934687ee910146ee3e309..59562e632c5704d53ca09eec67b0d036c6ac45b0 100644
--- a/xAOD/xAODFaserWaveform/Root/WaveformHit_v1.cxx
+++ b/xAOD/xAODFaserWaveform/Root/WaveformHit_v1.cxx
@@ -27,6 +27,8 @@ namespace xAOD {
 
   AUXSTORE_PRIMITIVE_SETTER_AND_GETTER( WaveformHit_v1, float, bcid_time, set_bcid_time )
 
+  AUXSTORE_PRIMITIVE_SETTER_AND_GETTER( WaveformHit_v1, float, trigger_time, set_trigger_time )
+
   AUXSTORE_PRIMITIVE_SETTER_AND_GETTER( WaveformHit_v1, float, raw_peak, set_raw_peak )
 
   AUXSTORE_PRIMITIVE_SETTER_AND_GETTER( WaveformHit_v1, float, raw_integral, set_raw_integral )
diff --git a/xAOD/xAODFaserWaveform/xAODFaserWaveform/versions/WaveformHit_v1.h b/xAOD/xAODFaserWaveform/xAODFaserWaveform/versions/WaveformHit_v1.h
index 9ea7b6dc573d53a533df44d02ff3edbf92c8a9ce..e3615dbedd60ab1863d1d749cd7595dc961cacb2 100644
--- a/xAOD/xAODFaserWaveform/xAODFaserWaveform/versions/WaveformHit_v1.h
+++ b/xAOD/xAODFaserWaveform/xAODFaserWaveform/versions/WaveformHit_v1.h
@@ -48,12 +48,19 @@ namespace xAOD {
     void set_channel(unsigned int value);
 
     /// 32-bit version of channel identifier
+    /// From Identifier::get_identifier32()::get_compact()
     unsigned int id() const;
     void set_id(unsigned int value);
 
+    /// Interface for proper identifier
     Identifier identify() const {
       return Identifier(this->id());
     }
+    void set_identifier(const Identifier& id) {
+      set_id(id.get_identifier32().get_compact());
+    }
+
+    /// All values are in units of ns and mV
 
     /// Best results
     float localtime() const;
@@ -72,6 +79,10 @@ namespace xAOD {
     float bcid_time() const;
     void set_bcid_time(float value);
 
+    /// Time with respect to nominal trigger time (including known offsets)
+    float trigger_time() const;
+    void set_trigger_time(float value);
+
     /// Raw values from waveform
     float raw_peak() const;
     void set_raw_peak(float value);
@@ -102,7 +113,7 @@ namespace xAOD {
     float nval() const;
     void set_nval(float value);
 
-    /// Raw time and waveform data
+    /// Raw time and waveform data (in ns and mV)
     std::vector<float> time_vector() const;
     void set_time_vector(std::vector<float> value);