diff --git a/Calorimeter/CaloDigiAlgs/CMakeLists.txt b/Calorimeter/CaloDigiAlgs/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..42991aa03cc03af6e51ab161d36d1d61df1a7956
--- /dev/null
+++ b/Calorimeter/CaloDigiAlgs/CMakeLists.txt
@@ -0,0 +1,15 @@
+################################################################################
+# Package: CaloDigiAlgs
+################################################################################
+
+# Declare the package name:
+atlas_subdir( CaloDigiAlgs )
+
+# Component(s) in the package:
+atlas_add_component( CaloDigiAlgs
+                     src/*.cxx src/*.h
+                     src/components/*.cxx
+                     LINK_LIBRARIES AthenaBaseComps Identifier StoreGateLib WaveRawEvent FaserCaloSimEvent WaveDigiToolsLib)
+
+atlas_install_python_modules( python/*.py )
+
diff --git a/Calorimeter/CaloDigiAlgs/python/CaloDigiAlgsConfig.py b/Calorimeter/CaloDigiAlgs/python/CaloDigiAlgsConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd0c9a454139d5906dd2e01ee0d1cc09b1697f76
--- /dev/null
+++ b/Calorimeter/CaloDigiAlgs/python/CaloDigiAlgsConfig.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2020-2021 CERN for the benefit of the FASER collaboration
+
+from re import VERBOSE
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+
+
+# One stop shopping for normal FASER data
+def CaloWaveformDigitizationCfg(flags):
+    """ Return all algorithms and tools for Waveform digitization """
+    acc = ComponentAccumulator()
+
+    if not flags.Input.isMC:
+        return acc
+
+    acc.merge(CaloWaveformDigiCfg(flags, "CaloWaveformDigiAlg"))
+    acc.merge(CaloWaveformDigitizationOutputCfg(flags))
+    return acc
+
+# Return configured digitization algorithm from SIM hits
+def CaloWaveformDigiCfg(flags, name="CaloWaveformDigiAlg", **kwargs):
+
+    acc = ComponentAccumulator()
+
+    tool = CompFactory.WaveformDigitisationTool(name="CaloWaveformDigtisationTool", **kwargs)
+    
+    kwargs.setdefault("CaloHitContainerKey", "EcalHits")
+    kwargs.setdefault("WaveformContainerKey", "CaloWaveforms")
+
+    digiAlg = CompFactory.CaloWaveformDigiAlg(name, **kwargs)
+    kwargs.setdefault("WaveformDigitisationTool", tool)
+    
+    digiAlg.CB_alpha = -0.9
+    digiAlg.CB_n = 10
+    digiAlg.CB_sigma = 4
+    digiAlg.CB_mean = 820
+
+
+    acc.addEventAlgo(digiAlg)
+
+    return acc
+
+def CaloWaveformDigitizationOutputCfg(flags, **kwargs):
+    """ Return ComponentAccumulator with output for Waveform Digi"""
+    acc = ComponentAccumulator()
+    ItemList = [
+        "RawWaveformContainer#*"
+    ]
+    acc.merge(OutputStreamCfg(flags, "RDO"))
+    ostream = acc.getEventAlgo("OutputStreamRDO")
+    ostream.TakeItemsFromInput = True # Copies all data from input file to output
+    ostream.ItemList += ItemList
+    return acc
diff --git a/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.cxx b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..8e60aefaf9db417d6a583b2b2ee59dd279d1f05a
--- /dev/null
+++ b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.cxx
@@ -0,0 +1,84 @@
+#include "CaloWaveformDigiAlg.h"
+
+#include "Identifier/Identifier.h"
+
+#include <vector>
+#include <map>
+
+CaloWaveformDigiAlg::CaloWaveformDigiAlg(const std::string& name, 
+					 ISvcLocator* pSvcLocator)
+  : AthReentrantAlgorithm(name, pSvcLocator) { 
+
+}
+
+StatusCode 
+CaloWaveformDigiAlg::initialize() {
+  ATH_MSG_INFO(name() << "::initalize()" );
+
+  // Initalize tools
+  ATH_CHECK( m_digiTool.retrieve() );
+
+
+  // Set key to read waveform from
+  ATH_CHECK( m_caloHitContainerKey.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);
+  
+  m_kernel = new TF1("PDF", "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);
+
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode 
+CaloWaveformDigiAlg::finalize() {
+  ATH_MSG_INFO(name() << "::finalize()");
+
+  delete m_kernel;
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode 
+CaloWaveformDigiAlg::execute(const EventContext& ctx) const {
+  ATH_MSG_DEBUG("Executing");
+
+  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);
+
+  ATH_CHECK( caloHitHandle.isValid() );
+  ATH_MSG_DEBUG("Found ReadHandle for CaloHitCollection " << m_caloHitContainerKey);
+
+  // Find the output waveform container
+  SG::WriteHandle<RawWaveformContainer> waveformContainerHandle(m_waveformContainerKey, ctx);
+  ATH_CHECK( waveformContainerHandle.record( std::make_unique<RawWaveformContainer>()) );
+
+  ATH_MSG_DEBUG("WaveformsContainer '" << waveformContainerHandle.name() << "' initialized");
+
+  if (caloHitHandle->size() == 0) {
+    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) );
+
+  ATH_MSG_DEBUG("WaveformsHitContainer " << waveformContainerHandle.name() << "' filled with "<< waveformContainerHandle->size() <<" items");
+
+  return StatusCode::SUCCESS;
+}
diff --git a/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.h b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..9de68257e540890d29143df6a054578334f12503
--- /dev/null
+++ b/Calorimeter/CaloDigiAlgs/src/CaloWaveformDigiAlg.h
@@ -0,0 +1,92 @@
+#ifndef CALODIGIALGS_CALOWAVEFORMDIGIALG_H
+#define CALODIGIALGS_CALOWAVEFORMDIGIALG_H
+
+// Base class
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+
+// Data classes
+#include "WaveRawEvent/RawWaveformContainer.h"
+#include "FaserCaloSimEvent/CaloHitCollection.h"
+
+// Tool classes
+#include "WaveDigiTools/IWaveformDigitisationTool.h"
+
+// Handles
+#include "StoreGate/ReadHandleKey.h"
+#include "StoreGate/WriteHandleKey.h"
+
+// Gaudi
+#include "GaudiKernel/ServiceHandle.h"
+#include "GaudiKernel/ToolHandle.h"
+
+// ROOT
+#include "TF1.h"
+
+// STL
+#include <string>
+
+class CaloWaveformDigiAlg : public AthReentrantAlgorithm {
+
+ public:
+  // Constructor
+  CaloWaveformDigiAlg(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~CaloWaveformDigiAlg() = default;
+
+  /** @name Usual algorithm methods */
+  //@{
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext& ctx) const override;
+  virtual StatusCode finalize() override;
+  //@}
+
+ private:
+
+  /** @name Disallow default instantiation, copy, assignment */
+  //@{
+  CaloWaveformDigiAlg() = delete;
+  CaloWaveformDigiAlg(const CaloWaveformDigiAlg&) = delete;
+  CaloWaveformDigiAlg &operator=(const CaloWaveformDigiAlg&) = delete;
+  //@}
+
+  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"};
+  Gaudi::Property<double> m_CB_sigma {this, "CB_sigma", 0, "Sigma of the crystal ball function"};
+
+
+
+
+
+  /// Kernel PDF
+  TF1* m_kernel;
+
+
+  /**
+   * @name Digitisation tool
+   */
+  ToolHandle<IWaveformDigitisationTool> m_digiTool
+    {this, "WaveformDigitisationTool", "WaveformDigitisationTool"};
+
+
+  /**
+   * @name Input HITS using SG::ReadHandleKey
+   */
+  //@{
+
+  SG::ReadHandleKey<CaloHitCollection> m_caloHitContainerKey 
+  {this, "CaloHitContainerKey", ""};
+
+  //@}
+
+
+  /**
+   * @name Output data using SG::WriteHandleKey
+   */
+  //@{
+  SG::WriteHandleKey<RawWaveformContainer> m_waveformContainerKey
+    {this, "WaveformContainerKey", ""};
+  //@}
+
+};
+
+#endif // CALODIGIALGS_CALODIGIALG_H
diff --git a/Calorimeter/CaloDigiAlgs/src/components/CaloDigiAlgs_entries.cxx b/Calorimeter/CaloDigiAlgs/src/components/CaloDigiAlgs_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..832cfbb096ffdb13430ba92dc2bfc7b1125bd53c
--- /dev/null
+++ b/Calorimeter/CaloDigiAlgs/src/components/CaloDigiAlgs_entries.cxx
@@ -0,0 +1,2 @@
+#include "../CaloWaveformDigiAlg.h"
+DECLARE_COMPONENT( CaloWaveformDigiAlg )
diff --git a/Control/CalypsoExample/BarcodeChecker/CMakeLists.txt b/Control/CalypsoExample/BarcodeChecker/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8f1e720b867398acbdbdcba483abcf61ed84fefc
--- /dev/null
+++ b/Control/CalypsoExample/BarcodeChecker/CMakeLists.txt
@@ -0,0 +1,11 @@
+################################################################################
+# Package: BarcodeChecker
+################################################################################
+
+# Declare the package name:
+atlas_subdir( BarcodeChecker )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
+atlas_install_joboptions( share/*.py )
\ No newline at end of file
diff --git a/Control/CalypsoExample/BarcodeChecker/python/BarcodeCheckerAlg.py b/Control/CalypsoExample/BarcodeChecker/python/BarcodeCheckerAlg.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc92bd8ac574dbaccfe522366666462b6219d988
--- /dev/null
+++ b/Control/CalypsoExample/BarcodeChecker/python/BarcodeCheckerAlg.py
@@ -0,0 +1,38 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+import AthenaPython.PyAthena as PyAthena
+from AthenaPython.PyAthena import StatusCode, McEventCollection, HepMC, CLHEP
+import McParticleEvent.Pythonizations
+
+__author__ = "Dave Caser <dcasper@uci.edu>"
+
+class BarcodeCheckerAlg(PyAthena.Alg):
+    def __init__(self, name="BarcodeChecker", MCEventKey="TruthEvent"):
+        super(BarcodeCheckerAlg,self).__init__(name=name)
+        self.MCEventKey = MCEventKey
+        return
+
+    def initialize(self):
+        self.maxLow = 0
+        self.maxMid = 0
+        self.maxHi = 0
+        return StatusCode.Success
+
+
+    def execute(self):
+        evtCollection = self.evtStore[self.MCEventKey] 
+        for mcEvt in evtCollection:
+            for mcParticle in mcEvt.particles:
+                barCode = mcParticle.barcode()
+                self.maxLow = max(self.maxLow, barCode%200000)
+                if barCode%1000000 > 200000:
+                    self.maxMid = max(self.maxMid, barCode%1000000 - 200000)
+                self.maxHi = max(self.maxHi, barCode//1000000)
+        return StatusCode.Success
+
+    def finalize(self):
+        print("Low part: ", self.maxLow, " out of 200000 (",100*self.maxLow/200000,"% of overflow)")
+        print("Mid part: ", self.maxMid, " out of ", 1000000 - 200000, " (",100*self.maxMid/(1000000-200000),"% of overflow")
+        print("Hi  part: ", self.maxHi, " out of ", (1<<31)//1000000, " (", 100*self.maxHi/((1<<31)//1000000),"% of overflow")
+
+        return StatusCode.Success
\ No newline at end of file
diff --git a/Control/CalypsoExample/BarcodeChecker/python/__init__.py b/Control/CalypsoExample/BarcodeChecker/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d13ae164caa4e93bf2899cea4e67d0ec515784a2
--- /dev/null
+++ b/Control/CalypsoExample/BarcodeChecker/python/__init__.py
@@ -0,0 +1 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
diff --git a/Control/CalypsoExample/BarcodeChecker/share/BarcodeChecker_jobOptions.py b/Control/CalypsoExample/BarcodeChecker/share/BarcodeChecker_jobOptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e739beb0c3865e924721b21fe5683b9f18fdaec
--- /dev/null
+++ b/Control/CalypsoExample/BarcodeChecker/share/BarcodeChecker_jobOptions.py
@@ -0,0 +1,29 @@
+#
+#  Usage: athena.py -c'INPUT=["faser.1.gfaser.root","faser.2.gfaser.root"]' BarcodeChecker/BarcodeChecker_jobOptions.py >& BarcodeChecker.log
+#
+#  INPUT is mandatory (INPUT can be a list, as shown above)
+#
+
+if not 'INPUT' in dir():
+    print("Missing INPUT parameter")
+    exit()
+
+if not isinstance(INPUT, (list,tuple)):
+    INPUT = [INPUT]
+    pass
+
+from AthenaCommon.GlobalFlags import globalflags
+
+globalflags.InputFormat.set_Value_and_Lock('pool')
+
+import AthenaPoolCnvSvc.ReadAthenaPool
+
+svcMgr.EventSelector.InputCollections = INPUT
+
+from BarcodeChecker.BarcodeCheckerAlg import BarcodeCheckerAlg
+
+from AthenaCommon.AlgSequence import AlgSequence
+job = AlgSequence()
+job += BarcodeCheckerAlg()
+
+theApp.EvtMax = -1
diff --git a/Control/CalypsoExample/Digitization/CMakeLists.txt b/Control/CalypsoExample/Digitization/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f8caebb598f6cea848edafea449a0bd19e657c6d
--- /dev/null
+++ b/Control/CalypsoExample/Digitization/CMakeLists.txt
@@ -0,0 +1,26 @@
+################################################################################
+# Package: Digitization
+################################################################################
+
+# Declare the package name:
+atlas_subdir( Digitization )
+
+# Component(s) in the package:
+#atlas_add_component( GeoModelTest
+#                     src/GeoModelTestAlg.cxx
+#                     src/components/GeoModelTest_entries.cxx
+#                     INCLUDE_DIRS ${GEOMODEL_INCLUDE_DIRS}
+#                     LINK_LIBRARIES ${GEOMODEL_LIBRARIES} AthenaBaseComps GeoModelFaserUtilities ScintReadoutGeometry TrackerReadoutGeometry MagFieldInterfaces MagFieldElements MagFieldConditions )
+
+# Install files from the package:
+#atlas_install_joboptions( share/*.py )
+#atlas_install_python_modules( python/*.py )
+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 )
+#atlas_add_test( ProdRecoTestBeam
+#    SCRIPT scripts/faser_reco.py ${CMAKE_CURRENT_SOURCE_DIR}/../RAWDATA/Faser-Physics-003613-filtered.raw TestBeamData
+#    PROPERTIES TIMEOUT 300 )
+
diff --git a/Control/CalypsoExample/Digitization/scripts/faser_digi.py b/Control/CalypsoExample/Digitization/scripts/faser_digi.py
new file mode 100755
index 0000000000000000000000000000000000000000..5ef818999634d592b21142b88ad2cee5c1a324f1
--- /dev/null
+++ b/Control/CalypsoExample/Digitization/scripts/faser_digi.py
@@ -0,0 +1,207 @@
+#!/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"
+# 
+# runtype - MANDATORY flag to specify the data type (TI12OldMC or TI12MC or TestBeamMC).
+#   Not extracted (yet) from file path for MC data 
+#
+import sys
+import argparse
+
+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
+
+# Extract runtype from path
+# Should be directory above run
+# i.e.: TestBeamData/Run-004150/Faser-Physics-004150-00000.raw"
+else:
+    if True or len(filepath.parts) < 3:
+        print("Can't determine run type from path - specify on command line ")
+        sys.exit(-1)
+
+#    runtype = filepath.parts[-3]
+
+print(f"Starting digitization of {filepath.name} with type {runtype}")
+if args.nevents > 0:
+    print(f"Reconstructing {args.nevents} 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-02" 
+    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.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))
+
+# 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))
+
+# # SpacePoints
+# from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
+# acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
+
+# # Try Dave's fitter
+# from TrackerClusterFit.TrackerClusterFitConfig import ClusterFitAlgCfg
+# acc.merge(ClusterFitAlgCfg(ConfigFlags))
+
+#
+# Configure output
+# from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+# itemList = [ "xAOD::EventInfo#*"
+#              , "xAOD::EventAuxInfo#*"
+#              , "xAOD::FaserTriggerData#*"
+#              , "xAOD::FaserTriggerDataAux#*"
+#              , "FaserSCT_RDO_Container#*"
+#             #  , "Tracker::FaserSCT_ClusterContainer#*"
+#             #  , "FaserSCT_SpacePointContainer#*"
+#             #  , "FaserSCT_SpacePointOverlapCollection#*"
+#             #  , "TrackCollection#*"
+# ]
+# 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
+# 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
+
+    #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
+
+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()))
diff --git a/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py b/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py
index 7c1f662f4cfdad38026a90a7c01c82e834a0ef01..f0fa35b4aaa071c3a0e699e74f8a949a8f7765c9 100755
--- a/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py
+++ b/Control/CalypsoExample/Reconstruction/scripts/faser_reco.py
@@ -7,9 +7,11 @@
 # 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 - optional flag to specify the data type (TI12Data or TestBeamData).
+# runtype - optionally specify the data type (TI12Data, TI12Data02, 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 argparse
@@ -26,6 +28,8 @@ 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)")
 
 args = parser.parse_args()
 
@@ -47,6 +51,23 @@ else:
 
     runtype = filepath.parts[-3]
 
+    # Fix TI12 geometry versions as well (needed in production)
+    # Probably better to do this from configuration in upstream production scripts,
+    # so lets call this a hack for now
+    if runtype == "TI12Data":
+
+        runname = filepath.parts[-2]
+        try:
+            runnumber = int(runname.split('-')[1])
+        except Exception as e:
+            print(f"Failed to find run number in {filepath}")
+            print(f"Couldn't parse {runname}")
+            print(f"Leave runtype as {runtype}!")
+        else:
+            if runnumber > 5302: # Last TI12 run on Nov. 23, 2021 without IFT
+                print(f"Found run number {runnumber}, using TI12 configuration with IFT")
+                runtype = "TI12Data02"
+
 print(f"Starting reconstruction of {filepath.name} with type {runtype}")
 if args.nevents > 0:
     print(f"Reconstructing {args.nevents} events by command-line option")
@@ -75,10 +96,15 @@ if runtype == "TI12Data":
     ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"
 
 # Testbeam setup 
-elif runtype == "TestBeamData":
+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"
+
 else:
     print("Invalid run type found:", runtype)
     print("Specify correct type or update list")
@@ -135,9 +161,18 @@ acc.merge(FaserSCT_ClusterizationCfg(ConfigFlags))
 from TrackerSpacePointFormation.TrackerSpacePointFormationConfig import TrackerSpacePointFinderCfg
 acc.merge(TrackerSpacePointFinderCfg(ConfigFlags))
 
-# Try Dave's fitter
-from TrackerClusterFit.TrackerClusterFitConfig import ClusterFitAlgCfg
-acc.merge(ClusterFitAlgCfg(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))
 
 #
 # Configure output
diff --git a/Control/CalypsoExample/TruthEventDumper/CMakeLists.txt b/Control/CalypsoExample/TruthEventDumper/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ceea9c78f5a8d088b1d17d9f3c6af2d325758f9c
--- /dev/null
+++ b/Control/CalypsoExample/TruthEventDumper/CMakeLists.txt
@@ -0,0 +1,11 @@
+################################################################################
+# Package: TruthEventDumper
+################################################################################
+
+# Declare the package name:
+atlas_subdir( TruthEventDumper )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
+atlas_install_joboptions( share/*.py )
\ No newline at end of file
diff --git a/Control/CalypsoExample/TruthEventDumper/python/TruthEventDumperAlg.py b/Control/CalypsoExample/TruthEventDumper/python/TruthEventDumperAlg.py
new file mode 100644
index 0000000000000000000000000000000000000000..934433e2f375d6f0883a5a66e66dba70f1515427
--- /dev/null
+++ b/Control/CalypsoExample/TruthEventDumper/python/TruthEventDumperAlg.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+import AthenaPython.PyAthena as PyAthena
+from AthenaPython.PyAthena import StatusCode, McEventCollection, HepMC, CLHEP
+import McParticleEvent.Pythonizations
+
+__author__ = "Dave Caser <dcasper@uci.edu>"
+
+class TruthEventDumperAlg(PyAthena.Alg):
+    def __init__(self, name="TruthEventDumper", MCEventKey="TruthEvent"):
+        super(TruthEventDumperAlg,self).__init__(name=name)
+        self.MCEventKey = MCEventKey
+        return
+
+    def initialize(self):
+        # self.maxLow = 0
+        # self.maxMid = 0
+        # self.maxHi = 0
+        return StatusCode.Success
+
+
+    def execute(self):
+        evtCollection = self.evtStore[self.MCEventKey] 
+        for mcEvt in evtCollection:
+            mcEvt.print()
+            # for mcParticle in mcEvt.particles:
+            #     barCode = mcParticle.barcode()
+            #     self.maxLow = max(self.maxLow, barCode%200000)
+            #     if barCode%1000000 > 200000:
+            #         self.maxMid = max(self.maxMid, barCode%1000000 - 200000)
+            #     self.maxHi = max(self.maxHi, barCode//1000000)
+        return StatusCode.Success
+
+    def finalize(self):
+        # print("Low part: ", self.maxLow, " out of 200000 (",100*self.maxLow/200000,"% of overflow)")
+        # print("Mid part: ", self.maxMid, " out of ", 1000000 - 200000, " (",100*self.maxMid/(1000000-200000),"% of overflow")
+        # print("Hi  part: ", self.maxHi, " out of ", (1<<31)//1000000, " (", 100*self.maxHi/((1<<31)//1000000),"% of overflow")
+
+        return StatusCode.Success
\ No newline at end of file
diff --git a/Control/CalypsoExample/TruthEventDumper/python/__init__.py b/Control/CalypsoExample/TruthEventDumper/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d13ae164caa4e93bf2899cea4e67d0ec515784a2
--- /dev/null
+++ b/Control/CalypsoExample/TruthEventDumper/python/__init__.py
@@ -0,0 +1 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
diff --git a/Control/CalypsoExample/TruthEventDumper/share/TruthEventDumper_jobOptions.py b/Control/CalypsoExample/TruthEventDumper/share/TruthEventDumper_jobOptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..303f4b8d26db18b0cf96f30e500548ecc1a84208
--- /dev/null
+++ b/Control/CalypsoExample/TruthEventDumper/share/TruthEventDumper_jobOptions.py
@@ -0,0 +1,41 @@
+#
+#  Usage: athena.py -c'INPUT=["faser.1.gfaser.root","faser.2.gfaser.root"]; COLLECTION="TruthEvent"; MAXEVT=-1; SKIPEVT=0' TruthEventDumper/TruthEventDumper_jobOptions.py >& TruthEventDumper.log
+#
+#  INPUT is mandatory (INPUT can be a list, as shown above)
+#  COLLECTION would normally be either "TruthEvent" (Geant4 particles) or "BeamTruthEvent" (generator particles)
+#  MAXEVT and SKIPEVT are self-explanatory and optional
+#
+
+if not 'INPUT' in dir():
+    print("Missing INPUT parameter")
+    exit()
+
+if not 'MAXEVT' in dir():
+    MAXEVT = -1
+
+if not 'SKIPEVT' in dir():
+    SKIPEVT = 0
+
+if not 'COLLECTION' in dir():
+    COLLECTION = "TruthEvent"
+
+if not isinstance(INPUT, (list,tuple)):
+    INPUT = [INPUT]
+    pass
+
+from AthenaCommon.GlobalFlags import globalflags
+
+globalflags.InputFormat.set_Value_and_Lock('pool')
+
+import AthenaPoolCnvSvc.ReadAthenaPool
+
+svcMgr.EventSelector.InputCollections = INPUT
+
+from TruthEventDumper.TruthEventDumperAlg import TruthEventDumperAlg
+
+from AthenaCommon.AlgSequence import AlgSequence
+job = AlgSequence()
+job += TruthEventDumperAlg(MCEventKey=COLLECTION)
+
+theApp.EvtMax = MAXEVT
+theApp.SkipEvents = SKIPEVT
diff --git a/Generators/FaserParticleGun/python/FaserParticleGunConfig.py b/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
index f5ba68bcba67791b3d7fa6792518ba74b90d1bb6..28bb6147a3276b510f3b4c3de83ad281a2997af4 100644
--- a/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
+++ b/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
@@ -12,6 +12,10 @@ from AthenaConfiguration.ComponentFactory import CompFactory
 from AthenaCommon.SystemOfUnits import TeV
 from AthenaCommon.PhysicalConstants import pi
 
+### add radial pos sampler ### with gaussian beam implemented
+from FaserParticleGun.RadialPosSampler import RadialPosSampler
+
+
 
 def FaserParticleGunCommonCfg(ConfigFlags, **kwargs) :
     cfg = ComponentAccumulator(AthSequencer("AthBeginSeq", Sequential = True))
@@ -34,6 +38,7 @@ def FaserParticleGunSingleParticleCfg(ConfigFlags, **kwargs) :
 
     pg = cfg.getPrimary()
 
+    pg.sampler.n   = kwargs.setdefault("n", 1)
     pg.sampler.pid = kwargs.setdefault("pid", -13)
     pg.sampler.mom = PG.EThetaMPhiSampler(energy = kwargs.setdefault("energy", 1*TeV),
                                           theta = kwargs.setdefault("theta", [0, pi/20]), 
@@ -46,6 +51,32 @@ def FaserParticleGunSingleParticleCfg(ConfigFlags, **kwargs) :
 
     return cfg
 
+def FaserParticleGunSingleEcalParticleCfg(ConfigFlags, **kwargs) :
+    cfg = FaserParticleGunCommonCfg(ConfigFlags, **kwargs)
+
+    pg = cfg.getPrimary()
+
+    pg.sampler.pid = kwargs.setdefault("pid", 13)
+    pg.sampler.mom = PG.EThetaMPhiSampler(energy = kwargs.setdefault("energy", 1*TeV),
+                                          theta = kwargs.setdefault("theta", 0.0), 
+                                          phi = kwargs.setdefault("phi", 0.0), 
+                                          mass = kwargs.setdefault("mass", 105.7) )    
+
+    if "radius" in kwargs:
+        pg.sampler.pos =  RadialPosSampler(x = kwargs.setdefault("x", 0.0), 
+                                   y = kwargs.setdefault("y", 0.0), 
+                                   z = kwargs.setdefault("z", 0.0),
+                                   r = kwargs.setdefault("radius", 1.0),
+                                   t = kwargs.setdefault("t", 0.0) )
+    else:
+        pg.sampler.pos = PG.PosSampler(x = kwargs.setdefault("x", 0.0), 
+                                   y = kwargs.setdefault("y", 0.0), 
+                                   z = kwargs.setdefault("z", 0.0),
+                                   t = kwargs.setdefault("t", 0.0) )
+
+    return cfg
+
+'''
 def FaserParticleGunSingleEcalParticleCfg(ConfigFlags, **kwargs) :
     cfg = FaserParticleGunCommonCfg(ConfigFlags, **kwargs)
 
@@ -62,7 +93,7 @@ def FaserParticleGunSingleEcalParticleCfg(ConfigFlags, **kwargs) :
                                    t = kwargs.setdefault("t", 0.0) )
 
     return cfg
-
+'''
 def FaserParticleGunCosmicsCfg(ConfigFlags, **kwargs) :
     # Supported keyword arguments:
     #
diff --git a/Generators/GenieReader/python/GenieReaderAlg.py b/Generators/GenieReader/python/GenieReaderAlg.py
index fc1cb184f0016fe4f73599a9f1eb1edab209f5f1..c97ea494b089a25ff88e4c2a1f2abdd4bf472c6b 100644
--- a/Generators/GenieReader/python/GenieReaderAlg.py
+++ b/Generators/GenieReader/python/GenieReaderAlg.py
@@ -2,7 +2,8 @@
 
 from AthenaCommon.AppMgr import ServiceMgr as svcMgr
 from GeneratorModules.EvgenAlg import EvgenAlg
-from AthenaPython.PyAthena import StatusCode
+from AthenaPython.PyAthena import StatusCode, EventInfo, EventID, EventType
+from AthenaCommon.SystemOfUnits import GeV, m
 import ROOT
 
 __author__ = "Dave Caser <dcasper@uci.edu>"
@@ -20,7 +21,23 @@ class GenieReader(EvgenAlg):
           from AthenaPython.PyAthena import HepMC   as HepMC
         evt.weights().push_back(1.0)
 
-        pos = HepMC.FourVector(self.evtStore["vx"]*1000, self.evtStore["vy"]*1000, self.evtStore["vz"]*1000, 0)
+        treeEventInfo = self.evtStore["TTreeEventInfo"]
+        treeEventId = treeEventInfo.event_ID()
+        runNumber = treeEventId.run_number()
+        eventNumber = treeEventId.event_number()
+        # print("found run/event number ", runNumber, "/", eventNumber)
+
+        mcEventType = EventType()
+        mcEventType.add_type(EventType.IS_SIMULATION)
+
+        mcEventId = EventID(run_number = runNumber, event_number = eventNumber)
+        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)
+
+        pos = HepMC.FourVector(self.evtStore["vx"]*m, self.evtStore["vy"]*m, self.evtStore["vz"]*m, 0)
         gv = HepMC.GenVertex(pos)
         ROOT.SetOwnership(gv, False)
         evt.add_vertex(gv)
@@ -32,13 +49,13 @@ class GenieReader(EvgenAlg):
         py = list(self.evtStore["py"])
         pz = list(self.evtStore["pz"])
         E = list(self.evtStore["E"])
-        M = list(self.evtStore["M"])
+        M = list(self.evtStore["M"])    
 
         for i in range(nParticles):
             gp = HepMC.GenParticle()
-            mom = HepMC.FourVector(px[i], py[i], pz[i], E[i])            
+            mom = HepMC.FourVector(px[i]*GeV, py[i]*GeV, pz[i]*GeV, E[i]*GeV)            
             gp.set_momentum(mom)
-            gp.set_generated_mass(M[i])
+            gp.set_generated_mass(M[i]*GeV)
             gp.set_pdg_id(pdgc[i])
             genie_status = status[i]
             if (genie_status == 0):   # initial particle
diff --git a/Generators/GenieReader/share/GenieReader_jobOptions.py b/Generators/GenieReader/share/GenieReader_jobOptions.py
index 91ad7c1318b772fd8cb015eda03ea274b0a6815a..649e705b66eb05f7b5ac2b331890a914794bec60 100644
--- a/Generators/GenieReader/share/GenieReader_jobOptions.py
+++ b/Generators/GenieReader/share/GenieReader_jobOptions.py
@@ -30,9 +30,10 @@ job += GenieReader()
 
 from AthenaPoolCnvSvc.WriteAthenaPool import AthenaPoolOutputStream
 ostream = AthenaPoolOutputStream( "StreamEVGEN" , OUTPUT, noTag=True )
-ostream.ItemList += [ "xAOD::EventInfo#*", "xAOD::EventAuxInfo#*",
-                      "EventInfo#*", 
+ostream.ItemList.remove("EventInfo#*")
+ostream.ItemList += [ "EventInfo#McEventInfo", 
                       "McEventCollection#*" ]
+print(ostream.ItemList)
 if not 'EVTMAX' in dir():
     EVTMAX = -1
 
diff --git a/Scintillator/ScintDigiAlgs/CMakeLists.txt b/Scintillator/ScintDigiAlgs/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..43695c5f90d96053a8b44fa3ac41c0f4c159ad99
--- /dev/null
+++ b/Scintillator/ScintDigiAlgs/CMakeLists.txt
@@ -0,0 +1,15 @@
+################################################################################
+# Package: ScintDigiAlgs
+################################################################################
+
+# Declare the package name:
+atlas_subdir( ScintDigiAlgs )
+
+# Component(s) in the package:
+atlas_add_component( ScintDigiAlgs
+                     src/*.cxx src/*.h
+                     src/components/*.cxx
+                     LINK_LIBRARIES AthenaBaseComps Identifier StoreGateLib WaveRawEvent ScintSimEvent WaveDigiToolsLib)
+
+atlas_install_python_modules( python/*.py )
+
diff --git a/Scintillator/ScintDigiAlgs/python/ScintDigiAlgsConfig.py b/Scintillator/ScintDigiAlgs/python/ScintDigiAlgsConfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..243e4493dd117e6758c894a7904115b3d3941ec3
--- /dev/null
+++ b/Scintillator/ScintDigiAlgs/python/ScintDigiAlgsConfig.py
@@ -0,0 +1,66 @@
+# Copyright (C) 2020-2021 CERN for the benefit of the FASER collaboration
+
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+
+# 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)
+# Parameters are per scintillator source, but not per channel.
+dict_CB_param = {}
+dict_CB_param["Trigger"]=dict(CB_alpha=-0.38, CB_n=25, CB_mean=815, CB_sigma=7.7)
+dict_CB_param["Timing"]=dict(CB_alpha=-0.32, CB_n=65, CB_mean=846, CB_sigma=5.3) # 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) # 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)
+
+# One stop shopping for normal FASER data
+def ScintWaveformDigitizationCfg(flags):
+    """ Return all algorithms and tools for Waveform digitization """
+    acc = ComponentAccumulator()
+
+    if not flags.Input.isMC:
+        return acc
+
+    if "TB" not in flags.GeoModel.FaserVersion:
+        acc.merge(ScintWaveformDigiCfg(flags, "TimingWaveformDigiAlg", "Trigger"))
+    acc.merge(ScintWaveformDigiCfg(flags, "VetoWaveformDigiAlg", "Veto"))
+    acc.merge(ScintWaveformDigiCfg(flags, "PreshowerWaveformDigiAlg", "Preshower"))
+    acc.merge(ScintWaveformDigitizationOutputCfg(flags))
+    return acc
+
+# Return configured digitization algorithm from SIM hits
+# Specify data source (Veto, Trigger, Preshower)
+def ScintWaveformDigiCfg(flags, name="ScintWaveformDigiAlg", source="", **kwargs):
+
+    acc = ComponentAccumulator()
+
+    tool = CompFactory.WaveformDigitisationTool(name=source+"WaveformDigtisationTool", **kwargs)
+    
+    kwargs.setdefault("ScintHitContainerKey", source+"Hits")
+    kwargs.setdefault("WaveformContainerKey", source+"Waveforms")
+
+    digiAlg = CompFactory.ScintWaveformDigiAlg(name, **kwargs)
+    digiAlg.CB_alpha = dict_CB_param[source]["CB_alpha"]
+    digiAlg.CB_n = dict_CB_param[source]["CB_n"]
+    digiAlg.CB_mean = dict_CB_param[source]["CB_mean"]
+    digiAlg.CB_sigma = dict_CB_param[source]["CB_sigma"]
+    
+    kwargs.setdefault("WaveformDigitisationTool", tool)
+
+    acc.addEventAlgo(digiAlg)
+
+    return acc
+
+def ScintWaveformDigitizationOutputCfg(flags, **kwargs):
+    """ Return ComponentAccumulator with output for Waveform Digi"""
+    acc = ComponentAccumulator()
+    ItemList = [
+        "RawWaveformContainer#*"
+    ]
+    acc.merge(OutputStreamCfg(flags, "RDO"))
+    ostream = acc.getEventAlgo("OutputStreamRDO")
+    ostream.TakeItemsFromInput = True # Copies all data from input file to output
+    ostream.ItemList += ItemList
+    return acc
diff --git a/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.cxx b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..b25080419ad0a7ea64f3933a0eb3acfdfe4c7eac
--- /dev/null
+++ b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.cxx
@@ -0,0 +1,85 @@
+
+#include "ScintWaveformDigiAlg.h"
+
+#include "Identifier/Identifier.h"
+
+#include <vector>
+#include <map>
+
+
+ScintWaveformDigiAlg::ScintWaveformDigiAlg(const std::string& name, 
+					 ISvcLocator* pSvcLocator)
+  : AthReentrantAlgorithm(name, pSvcLocator) { 
+
+}
+
+StatusCode 
+ScintWaveformDigiAlg::initialize() {
+  ATH_MSG_INFO(name() << "::initalize()" );
+
+  // Initalize tools
+  ATH_CHECK( m_digiTool.retrieve() );
+
+
+  // Set key to read waveform from
+  ATH_CHECK( m_scintHitContainerKey.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
+ 
+
+  
+  m_kernel = new TF1("PDF", "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);
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode 
+ScintWaveformDigiAlg::finalize() {
+  ATH_MSG_INFO(name() << "::finalize()");
+
+  delete m_kernel;
+
+  return StatusCode::SUCCESS;
+}
+
+StatusCode 
+ScintWaveformDigiAlg::execute(const EventContext& ctx) const {
+  ATH_MSG_DEBUG("Executing");
+
+  ATH_MSG_DEBUG("Run: " << ctx.eventID().run_number() 
+		<< " Event: " << ctx.eventID().event_number());
+
+  // Find the input HIT collection
+  SG::ReadHandle<ScintHitCollection> scintHitHandle(m_scintHitContainerKey, ctx);
+
+  ATH_CHECK( scintHitHandle.isValid() );
+  ATH_MSG_DEBUG("Found ReadHandle for ScintHitCollection " << m_scintHitContainerKey);
+
+  // Find the output waveform container
+  SG::WriteHandle<RawWaveformContainer> waveformContainerHandle(m_waveformContainerKey, ctx);
+  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;
+  }
+
+  // Digitise the hits
+  CHECK( m_digiTool->digitise<ScintHitCollection>(scintHitHandle.ptr(),
+						  waveformContainerHandle.ptr(), m_kernel) );
+
+  ATH_MSG_DEBUG("WaveformsHitContainer " << waveformContainerHandle.name() << "' filled with "<< waveformContainerHandle->size() <<" items");
+
+  return StatusCode::SUCCESS;
+}
diff --git a/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.h b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..af60e02d8169194ccc2459f0b7a69727b2544eba
--- /dev/null
+++ b/Scintillator/ScintDigiAlgs/src/ScintWaveformDigiAlg.h
@@ -0,0 +1,91 @@
+#ifndef SCINTDIGIALGS_SCINTWAVEFORMDIGIALG_H
+#define SCINTDIGIALGS_SCINTWAVEFORMDIGIALG_H
+
+// Base class
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+
+// Data classes
+#include "WaveRawEvent/RawWaveformContainer.h"
+#include "ScintSimEvent/ScintHitCollection.h"
+
+// Tool classes
+#include "WaveDigiTools/IWaveformDigitisationTool.h"
+
+// Handles
+#include "StoreGate/ReadHandleKey.h"
+#include "StoreGate/WriteHandleKey.h"
+
+// Gaudi
+#include "GaudiKernel/ServiceHandle.h"
+#include "GaudiKernel/ToolHandle.h"
+
+// ROOT
+#include "TF1.h"
+
+
+// STL
+#include <string>
+
+class ScintWaveformDigiAlg : public AthReentrantAlgorithm {
+
+ public:
+  // Constructor
+  ScintWaveformDigiAlg(const std::string& name, ISvcLocator* pSvcLocator);
+  virtual ~ScintWaveformDigiAlg() = default;
+
+  /** @name Usual algorithm methods */
+  //@{
+  virtual StatusCode initialize() override;
+  virtual StatusCode execute(const EventContext& ctx) const override;
+  virtual StatusCode finalize() override;
+  //@}
+
+
+ private:
+
+  /** @name Disallow default instantiation, copy, assignment */
+  //@{
+  ScintWaveformDigiAlg() = delete;
+  ScintWaveformDigiAlg(const ScintWaveformDigiAlg&) = delete;
+  ScintWaveformDigiAlg &operator=(const ScintWaveformDigiAlg&) = delete;
+  //@}
+
+  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"};  
+  Gaudi::Property<double> m_CB_sigma {this, "CB_sigma", 0, "Sigma of the crystal ball function"};
+
+
+  /// Kernel PDF
+  TF1* m_kernel;
+
+
+  /**
+   * @name Digitisation tool
+   */
+  ToolHandle<IWaveformDigitisationTool> m_digiTool
+    {this, "WaveformDigitisationTool", "WaveformDigitisationTool"};
+
+
+  /**
+   * @name Input HITS using SG::ReadHandleKey
+   */
+  //@{
+
+  SG::ReadHandleKey<ScintHitCollection> m_scintHitContainerKey 
+  {this, "ScintHitContainerKey", ""};
+
+  //@}
+
+
+  /**
+   * @name Output data using SG::WriteHandleKey
+   */
+  //@{
+  SG::WriteHandleKey<RawWaveformContainer> m_waveformContainerKey
+    {this, "WaveformContainerKey", ""};
+  //@}
+
+};
+
+#endif // SCINTDIGIALGS_SCINTDIGIALG_H
diff --git a/Scintillator/ScintDigiAlgs/src/components/ScintDigiAlgs_entries.cxx b/Scintillator/ScintDigiAlgs/src/components/ScintDigiAlgs_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..3a8deff1f06b692e1cf0928472fb12426274f059
--- /dev/null
+++ b/Scintillator/ScintDigiAlgs/src/components/ScintDigiAlgs_entries.cxx
@@ -0,0 +1,2 @@
+#include "../ScintWaveformDigiAlg.h"
+DECLARE_COMPONENT( ScintWaveformDigiAlg )
diff --git a/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.cxx b/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.cxx
index 88592d298b2eb974007a73c15ad2413beb193648..f9c937aa22c8a69153e7c32174d4c1009e9b5845 100644
--- a/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.cxx
+++ b/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.cxx
@@ -3,6 +3,7 @@
 */
 
 #include "ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1.h"
+#include "ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1a.h"
 #include "ScintSimEventTPCnv/ScintHits/ScintHit_p1.h"
 #include "ScintHitCollectionCnv.h"
 
@@ -18,11 +19,19 @@ ScintHitCollection_PERS* ScintHitCollectionCnv::createPersistent(ScintHitCollect
 ScintHitCollection* ScintHitCollectionCnv::createTransient() {
     MsgStream mlog(msgSvc(), "ScintHitCollectionConverter" );
     ScintHitCollectionCnv_p1   converter_p1;
+    ScintHitCollectionCnv_p1a  converter_p1a;
 
     static const pool::Guid   p1_guid("B2573A16-4B46-4E1E-98E3-F93421680779");
+    static const pool::Guid   p1a_guid("0DFC461F-9FF5-4447-B4F0-7CC7157191D1");
 
     ScintHitCollection       *trans_cont(0);
-    if( this->compareClassGuid(p1_guid)) {
+    if( this->compareClassGuid(p1a_guid)) 
+    {
+      std::unique_ptr< ScintHitCollection_p1a >   col_vect( this->poolReadObject< ScintHitCollection_p1a >() );
+      trans_cont = converter_p1a.createTransient( col_vect.get(), mlog );
+    }
+    else if( this->compareClassGuid(p1_guid)) 
+    {
       std::unique_ptr< ScintHitCollection_p1 >   col_vect( this->poolReadObject< ScintHitCollection_p1 >() );
       trans_cont = converter_p1.createTransient( col_vect.get(), mlog );
     }  else {
diff --git a/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.h b/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.h
index ca9c329ddad032c35766ad6a271fd7637734b1c0..4cdaf2046a595f763c424baae5f6942ab91a33fd 100644
--- a/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.h
+++ b/Scintillator/ScintEventCnv/ScintSimEventAthenaPool/src/ScintHitCollectionCnv.h
@@ -8,12 +8,14 @@
 #include "ScintSimEvent/ScintHitCollection.h"
 #include "ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1.h"
 #include "ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1.h"
+#include "ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1a.h"
+#include "ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1a.h"
 #include "AthenaPoolCnvSvc/T_AthenaPoolCustomCnv.h"
 // Gaudi
 #include "GaudiKernel/MsgStream.h"
 // typedef to the latest persistent version
-typedef ScintHitCollection_p1     ScintHitCollection_PERS;
-typedef ScintHitCollectionCnv_p1  ScintHitCollectionCnv_PERS;
+typedef ScintHitCollection_p1a     ScintHitCollection_PERS;
+typedef ScintHitCollectionCnv_p1a  ScintHitCollectionCnv_PERS;
 
 class ScintHitCollectionCnv  : public T_AthenaPoolCustomCnv<ScintHitCollection, ScintHitCollection_PERS > {
   friend class CnvFactory<ScintHitCollectionCnv>;
diff --git a/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1a.h b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1a.h
new file mode 100644
index 0000000000000000000000000000000000000000..4f8de2b4c704240402bdf5ea23a36cf3a12acefe
--- /dev/null
+++ b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1a.h
@@ -0,0 +1,41 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef SCINTHITCOLLECTIONCNV_P1A_H
+#define SCINTHITCOLLECTIONCNV_P1A_H
+
+// ScintHitCollectionCnv_p1, T/P separation of Scint Hits
+// author D.Costanzo <davide.costanzo@cern.ch>
+// author O.Arnaez <olivier.arnaez@cern.ch>
+
+#include "AthenaPoolCnvSvc/T_AthenaPoolTPConverter.h"
+#include "ScintSimEvent/ScintHitCollection.h"
+#include "ScintHitCollection_p1a.h"
+
+
+class ScintHitCollectionCnv_p1a : public T_AthenaPoolTPCnvBase<ScintHitCollection, ScintHitCollection_p1a>
+{
+ public:
+
+  ScintHitCollectionCnv_p1a()  {};
+
+  virtual ScintHitCollection* createTransient(const ScintHitCollection_p1a* persObj, MsgStream &log);
+
+  virtual void	persToTrans(const ScintHitCollection_p1a* persCont,
+                            ScintHitCollection* transCont,
+                            MsgStream &log) ;
+  virtual void	transToPers(const ScintHitCollection* transCont,
+                            ScintHitCollection_p1a* persCont,
+                            MsgStream &log) ;
+
+ private:
+
+  static const double m_persEneUnit;
+  static const double m_persLenUnit;
+  static const double m_persAngUnit;
+  static const double m_2bHalfMaximum;
+  static const int m_2bMaximum;
+};
+
+#endif
diff --git a/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1a.h b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1a.h
new file mode 100644
index 0000000000000000000000000000000000000000..8e714d6e571c746ae6376536ed62c5681476232e
--- /dev/null
+++ b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1a.h
@@ -0,0 +1,57 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef SCINTHITCOLLECTION_P1A_H
+#define SCINTHITCOLLECTION_P1A_H
+
+/*
+
+Authors: Davide Costanzo Rob Duxfield
+
+*/
+
+#include <vector>
+#include <string>
+
+class ScintHitCollection_p1a
+{
+ public:
+/// Default constructor
+  ScintHitCollection_p1a ();
+  //private:
+
+  std::vector<float>          m_hit1_meanTime;   //  1 element per string
+  std::vector<float>          m_hit1_x0;         //
+  std::vector<float>          m_hit1_y0;         //
+  std::vector<float>          m_hit1_z0;         //
+  std::vector<float>          m_hit1_theta;      //
+  std::vector<float>          m_hit1_phi;        //
+  std::vector<unsigned long>  m_nHits;           //
+
+  std::vector<unsigned short> m_hitEne_2b;       //  1 element per hit
+  std::vector<unsigned short> m_hitLength_2b;    //
+
+  std::vector<unsigned short> m_dTheta;          //  1 element per hit except for first hit in string
+  std::vector<unsigned short> m_dPhi;            //
+
+  std::vector<float>          m_hitEne_4b;       //  1 element per hit with  m_hitEne_2b[i] == 2**16
+
+  std::vector<float>          m_hitLength_4b;    //  1 element per hit with  m_hitLength_2b[i] == 2**16
+
+  std::vector<unsigned long>  m_barcode;
+  std::vector<unsigned long>  m_mcEvtIndex;
+  std::vector<char>           m_evtColl;
+  std::vector<unsigned long>  m_nBC;
+
+  std::vector<unsigned long>  m_id;
+  std::vector<unsigned long> m_nId;
+};
+
+
+// inlines
+
+inline
+ScintHitCollection_p1a::ScintHitCollection_p1a () {}
+
+#endif
diff --git a/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintSimEventTPCnvDict.h b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintSimEventTPCnvDict.h
index f0042a97ec671d78b8fb783af5f516116570952a..1606077dd0efcd14371c1c7445d7a553b0b0579d 100644
--- a/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintSimEventTPCnvDict.h
+++ b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/ScintSimEventTPCnvDict.h
@@ -15,6 +15,8 @@
 #include "ScintSimEventTPCnv/ScintHits/ScintHitCnv_p1.h"
 #include "ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1.h"
 #include "ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1.h"
+#include "ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1a.h"
+#include "ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1a.h"
 #include "ScintSimEventTPCnv/ScintHits/ScintHit_p1.h"
 
 #endif // SCINTEVENTTPCNV_SCINTSIMEVENTTPCNVDICT_H
diff --git a/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/selection.xml b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/selection.xml
index 6b0d915eac9a1e2fedb5f8254bbbf419dec16dac..345d08165aeb11723e94f19e7195f42fcb5a88af 100644
--- a/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/selection.xml
+++ b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/ScintSimEventTPCnv/selection.xml
@@ -4,4 +4,5 @@
     <class name="ScintHit_p1" />
     <class name="std::vector<ScintHit_p1>" />
     <class name="ScintHitCollection_p1" id="B2573A16-4B46-4E1E-98E3-F93421680779" />     
+    <class name="ScintHitCollection_p1a" id="0DFC461F-9FF5-4447-B4F0-7CC7157191D1" />     
 </lcgdict>
diff --git a/Scintillator/ScintEventCnv/ScintSimEventTPCnv/src/ScintHits/ScintHitCollectionCnv_p1a.cxx b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/src/ScintHits/ScintHitCollectionCnv_p1a.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..cd162d1ad3c8064966a59c3517b19b233bc29c80
--- /dev/null
+++ b/Scintillator/ScintEventCnv/ScintSimEventTPCnv/src/ScintHits/ScintHitCollectionCnv_p1a.cxx
@@ -0,0 +1,334 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "ScintSimEvent/ScintHit.h"
+#include "ScintSimEvent/ScintHitCollection.h"
+#include "ScintSimEventTPCnv/ScintHits/ScintHitCollection_p1a.h"
+#include "ScintSimEventTPCnv/ScintHits/ScintHitCollectionCnv_p1a.h"
+#include "GeneratorObjects/HepMcParticleLink.h"
+
+#include <cmath>
+
+//CLHEP
+#include "CLHEP/Geometry/Point3D.h"
+// Gaudi
+#include "GaudiKernel/MsgStream.h"
+#include "GaudiKernel/ThreadLocalContext.h"
+
+// Athena
+#include "AthenaKernel/ExtendedEventContext.h"
+#include "StoreGate/StoreGateSvc.h"
+
+//  * * *  stolen from eflowRec  * * *  //
+inline double phicorr(double a)
+{
+  if (a <= -M_PI)
+    {
+      return a+(2*M_PI*floor(-(a-M_PI)/(2*M_PI)));
+    }
+  else if (a > M_PI)
+    {
+      return a-(2*M_PI*floor((a+M_PI)/(2*M_PI)));
+    }
+  else
+    {
+      return a;
+    }
+}
+
+//  * * *  stolen from eflowRec  * * *  //
+inline double cycle(double a, double b)
+{
+  double del = b-a;
+  if (del > M_PI)
+    {
+      return a+2.0*M_PI;
+    }
+  else if (del < -M_PI)
+    {
+      return a-2.0*M_PI;
+    }
+  else
+    {
+      return a;
+    }
+}
+
+
+const double ScintHitCollectionCnv_p1a::m_persEneUnit = 1.0e-5;
+const double ScintHitCollectionCnv_p1a::m_persLenUnit = 1.0e-5;
+const double ScintHitCollectionCnv_p1a::m_persAngUnit = 1.0e-5;
+const double ScintHitCollectionCnv_p1a::m_2bHalfMaximum = pow(2.0, 15.0);
+const int ScintHitCollectionCnv_p1a::m_2bMaximum = (unsigned short)(-1);
+
+
+void ScintHitCollectionCnv_p1a::transToPers(const ScintHitCollection* transCont, ScintHitCollection_p1a* persCont, MsgStream &log)
+{
+  // Finds hits belonging to a "string" (in which the end point of one hit is the same as the start point of the next) and
+  // persistifies the end point of each hit plus the start point of the first hit in each string.
+  //
+  // Further compression is achieved by optimising the storage of the position vectors:- start (x,y,z) and (theta,phi) of
+  // first hit are stored as floats, (delta_theta,delta_phi) relative to the fisrst hit are stored as 2 byte numbers and
+  // used to specify the hit direction. All hit lengths are stored as 2 byte numbers.
+  //
+  // Additional savings are achieved by storing the energy loss for each hit as a 2 byte number and only storing the mean
+  // time of the first hit per string.
+  //
+  // See http://indico.cern.ch/getFile.py/access?contribId=11&resId=2&materialId=slides&confId=30893 for more info.
+
+  static const double dRcut = 1.0e-7;
+  static const double dTcut = 1.0;
+
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+  const IProxyDict* proxy = Atlas::getExtendedEventContext(ctx).proxy();
+  const HepMcParticleLink * lastLink=nullptr;
+  int lastId = -1;
+  double stringFirstTheta = 0.0;
+  double stringFirstPhi = 0.0;
+  double lastT = 0.0;
+  double persSumE = 0.0;
+  double transSumE = 0.0;
+  unsigned int idx = 0;
+  unsigned int endBC = 0;
+  unsigned int endId = 0;
+  unsigned int endHit = 0;
+  HepGeom::Point3D<double> lastTransEnd(0.0, 0.0, 0.0);
+  HepGeom::Point3D<double> lastPersEnd(0.0, 0.0, 0.0);
+
+  for (ScintHitCollection::const_iterator it = transCont->begin(); it != transCont->end(); ++it) {
+
+    ScintHitCollection::const_iterator siHit = it;
+
+
+    if ( !lastLink || (siHit->particleLink() != *lastLink) ) {
+
+      // store barcode, eventIndex and McEventCollection once for set of consecutive hits with same barcode
+
+      lastLink = &(siHit->particleLink());
+      persCont->m_barcode.push_back(lastLink->barcode());
+      unsigned short index{0};
+      const HepMcParticleLink::index_type position =
+        HepMcParticleLink::getEventPositionInCollection(lastLink->eventIndex(),
+                                                        lastLink->getEventCollection(),
+                                                        proxy).at(0);
+      if (position!=0) {
+        index = lastLink->eventIndex();
+        if(lastLink->eventIndex()!=static_cast<HepMcParticleLink::index_type>(index)) {
+          log << MSG::WARNING << "Attempting to persistify an eventIndex larger than max unsigned short!" << endmsg;
+        }
+      }
+      persCont->m_mcEvtIndex.push_back(index);
+      persCont->m_evtColl.push_back(lastLink->getEventCollectionAsChar());
+
+      if (idx > 0) {
+        persCont->m_nBC.push_back(idx - endBC);
+        endBC = idx;
+      }
+    }
+
+    if ( (int)siHit->identify() != lastId ) {
+
+      // store id once for set of consecutive hits with same barcode
+
+      lastId = siHit->identify();
+      persCont->m_id.push_back(lastId);
+
+      if (idx > 0) {
+        persCont->m_nId.push_back(idx - endId);
+        endId = idx;
+      }
+    }
+
+    HepGeom::Point3D<double> st = siHit->localStartPosition();
+    HepGeom::Point3D<double> en = siHit->localEndPosition();
+
+    const double dx = st.x() - lastTransEnd.x();
+    const double dy = st.y() - lastTransEnd.y();
+    const double dz = st.z() - lastTransEnd.z();
+    const double t = siHit->meanTime();
+
+    const double dRLast = sqrt(dx * dx + dy * dy + dz * dz);  // dR between end of previous hit and start of current one
+    const double dTLast = fabs(t - lastT);
+
+    CLHEP::Hep3Vector direction(0.0, 0.0, 0.0);
+    double theta = 0.0;
+    double phi = 0.0;
+    bool startNewString = false;
+
+    if (dRLast < dRcut && dTLast < dTcut) {
+
+      // hit is part of existing string
+
+      direction = CLHEP::Hep3Vector( en.x() - lastPersEnd.x(), en.y() - lastPersEnd.y(), en.z() - lastPersEnd.z() );
+
+      theta = direction.theta();
+      phi = phicorr( direction.phi() );
+
+      const int dTheta_2b = (int)( (theta - stringFirstTheta) / m_persAngUnit + m_2bHalfMaximum + 0.5 );
+      const int dPhi_2b = (int)( (cycle(phi, stringFirstPhi) - stringFirstPhi) / m_persAngUnit + m_2bHalfMaximum + 0.5 );
+
+      if ( dTheta_2b < m_2bMaximum && dTheta_2b >= 0 && dPhi_2b < m_2bMaximum && dPhi_2b >= 0) {
+        persCont->m_dTheta.push_back(dTheta_2b);
+        persCont->m_dPhi.push_back(dPhi_2b);
+        theta = stringFirstTheta + ( (double)dTheta_2b - m_2bHalfMaximum ) * m_persAngUnit;
+        phi = stringFirstPhi + ( (double)dPhi_2b - m_2bHalfMaximum ) * m_persAngUnit;
+        phi = phicorr(phi);
+      }
+      else {
+        startNewString = true;
+      }
+    }
+
+    if (startNewString || dRLast >= dRcut || dTLast >= dTcut) {
+
+      // begin new hit string
+
+      direction = CLHEP::Hep3Vector( en.x() - st.x(), en.y() - st.y(), en.z() - st.z() );
+
+      theta = direction.theta();
+      phi = phicorr( direction.phi() );
+
+      persCont->m_hit1_meanTime.push_back(t);
+      persCont->m_hit1_x0.push_back(st.x());
+      persCont->m_hit1_y0.push_back(st.y());
+      persCont->m_hit1_z0.push_back(st.z());
+      persCont->m_hit1_theta.push_back(theta);
+      persCont->m_hit1_phi.push_back(phi);
+
+      lastPersEnd = HepGeom::Point3D<double>(st.x(), st.y(), st.z());
+
+      stringFirstTheta = theta;
+      stringFirstPhi = phi;
+
+      if (idx > 0) {
+        persCont->m_nHits.push_back(idx - endHit);
+        endHit = idx;
+      }
+    }
+
+    lastTransEnd = HepGeom::Point3D<double>(en.x(), en.y(), en.z());
+    transSumE += siHit->energyLoss();
+
+    const int eneLoss_2b = (int)((transSumE - persSumE) / m_persEneUnit + 0.5);  // calculated to allow recovery sum over
+                                                                                 // whole hit string to chosen precision
+
+    const int hitLength_2b = (int)(direction.mag() / m_persLenUnit + 0.5);  // calculated to give the correct position to
+                                                                            // the chosen precision, NOT the length of the
+                                                                            // hit (small difference in practice).
+    double eneLoss = 0.0;
+
+    if (eneLoss_2b >= m_2bMaximum) {
+      eneLoss = siHit->energyLoss();
+      persCont->m_hitEne_2b.push_back(m_2bMaximum);
+      persCont->m_hitEne_4b.push_back(eneLoss);
+    }
+    else {
+      eneLoss = eneLoss_2b * m_persEneUnit;
+      persCont->m_hitEne_2b.push_back(eneLoss_2b);
+    }
+
+    double length = 0.0;
+
+    if (hitLength_2b >= m_2bMaximum) {
+      length = direction.mag();
+      persCont->m_hitLength_2b.push_back(m_2bMaximum);
+      persCont->m_hitLength_4b.push_back(direction.mag());
+    }
+    else {
+      length = hitLength_2b * m_persLenUnit;
+      persCont->m_hitLength_2b.push_back(hitLength_2b);
+    }
+
+    CLHEP::Hep3Vector persDir(length, 0.0, 0.0);
+    persDir.setTheta(theta);
+    persDir.setPhi(phi);
+
+    lastPersEnd = (CLHEP::Hep3Vector)lastPersEnd + persDir;
+    persSumE += eneLoss;
+    lastT = t;
+
+    ++idx;
+  }
+
+  persCont->m_nBC.push_back(idx - endBC);
+  persCont->m_nId.push_back(idx - endId);
+  persCont->m_nHits.push_back(idx - endHit);
+}
+
+
+ScintHitCollection* ScintHitCollectionCnv_p1a::createTransient(const ScintHitCollection_p1a* persObj, MsgStream &log) {
+  std::unique_ptr<ScintHitCollection> trans(std::make_unique<ScintHitCollection>("DefaultCollectionName",persObj->m_nHits.size()));
+  persToTrans(persObj, trans.get(), log);
+  return(trans.release());
+}
+
+
+void ScintHitCollectionCnv_p1a::persToTrans(const ScintHitCollection_p1a* persCont, ScintHitCollection* transCont, MsgStream &/*log*/)
+{
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+
+  unsigned int hitCount = 0;
+  unsigned int angleCount = 0;
+  unsigned int idxBC = 0;
+  unsigned int idxId = 0;
+  unsigned int idxEne4b = 0;
+  unsigned int idxLen4b = 0;
+  unsigned int endHit = 0;
+  unsigned int endBC = 0;
+  unsigned int endId = 0;
+
+  for (unsigned int i = 0; i < persCont->m_nHits.size(); i++) {
+
+    if (persCont->m_nHits[i]) {
+
+      const unsigned int start = endHit;
+      endHit += persCont->m_nHits[i];
+
+      const double t0 = persCont->m_hit1_meanTime[i];
+      const double theta0 = persCont->m_hit1_theta[i];
+      const double phi0 = persCont->m_hit1_phi[i];
+      HepGeom::Point3D<double> endLast(persCont->m_hit1_x0[i], persCont->m_hit1_y0[i], persCont->m_hit1_z0[i]);
+
+      for (unsigned int j = start; j < endHit; j++) {
+
+        if (j >= endBC + persCont->m_nBC[idxBC])
+          endBC += persCont->m_nBC[idxBC++];
+
+        if (j >= endId + persCont->m_nId[idxId])
+          endId += persCont->m_nId[idxId++];
+
+        const double eneLoss_2b = persCont->m_hitEne_2b[hitCount];
+        const double hitLength_2b = persCont->m_hitLength_2b[hitCount];
+
+        const double eneLoss = (eneLoss_2b < m_2bMaximum) ? eneLoss_2b * m_persEneUnit : persCont->m_hitEne_4b[idxEne4b++];
+        const double length = (hitLength_2b < m_2bMaximum) ? hitLength_2b * m_persLenUnit : persCont->m_hitLength_4b[idxLen4b++];
+
+        const double dTheta = (j > start) ? ((double)persCont->m_dTheta[angleCount] - m_2bHalfMaximum) * m_persAngUnit : 0.0;
+        const double dPhi = (j > start) ? ((double)persCont->m_dPhi[angleCount] - m_2bHalfMaximum) * m_persAngUnit : 0.0;
+
+        const double meanTime = t0;
+        const double theta = theta0 + dTheta;
+        const double phi = phicorr(phi0 + dPhi);
+
+        CLHEP::Hep3Vector r(length, 0.0, 0.0);
+        r.setTheta(theta);
+        r.setPhi(phi);
+
+        HepGeom::Point3D<double> endThis( endLast + r );
+
+        HepMcParticleLink::PositionFlag flag = HepMcParticleLink::IS_INDEX;
+        if (persCont->m_mcEvtIndex[idxBC] == 0) {
+          flag = HepMcParticleLink::IS_POSITION;
+        }
+        HepMcParticleLink partLink( persCont->m_barcode[idxBC], persCont->m_mcEvtIndex[idxBC], HepMcParticleLink::ExtendedBarCode::eventCollectionFromChar(persCont->m_evtColl[idxBC]), flag, ctx );
+        transCont->Emplace( endLast, endThis, eneLoss, meanTime, partLink, persCont->m_id[idxId]);
+
+        endLast = endThis;
+
+        ++hitCount;
+        if (j > start) ++angleCount;
+      }
+    }
+  }
+}
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 7260d59fc1e6df1b327196817f8a44c865d72a16..e507545cd536d1a85889e6768330a77c5755c5c5 100644
--- a/Simulation/ISF/ISF_Core/FaserISF_Services/python/FaserISF_ServicesConfigNew.py
+++ b/Simulation/ISF/ISF_Core/FaserISF_Services/python/FaserISF_ServicesConfigNew.py
@@ -10,7 +10,8 @@ from AthenaConfiguration.ComponentFactory import CompFactory
 
 from BarcodeServices.BarcodeServicesConfigNew import MC15aPlusBarcodeSvcCfg
 from ISF_HepMC_Tools.ISF_HepMC_ToolsConfigNew import ParticleFinalStateFilterCfg, GenParticleInteractingFilterCfg
-from FaserISF_HepMC_Tools.FaserISF_HepMC_ToolsConfigNew import FaserTruthStrategyCfg, FaserDipoleTruthStrategyCfg
+# from FaserISF_HepMC_Tools.FaserISF_HepMC_ToolsConfigNew import FaserTruthStrategyCfg, FaserDipoleTruthStrategyCfg
+from FaserISF_HepMC_Tools.FaserISF_HepMC_ToolsConfigNew import TruthStrategyGroupCfg
 
 ISF__FaserTruthSvc, ISF__FaserGeoIDSvc, ISF__FaserInputConverter = CompFactory.getComps("ISF::FaserTruthSvc","ISF::FaserGeoIDSvc","ISF::FaserInputConverter")
 
@@ -60,10 +61,12 @@ def FaserTruthServiceCfg(ConfigFlags, name="FaserISF_TruthService", **kwargs):
     result = MC15aPlusBarcodeSvcCfg(ConfigFlags)
     kwargs.setdefault('BarcodeSvc', result.getService("Barcode_MC15aPlusBarcodeSvc") )
     
-    acc = FaserTruthStrategyCfg(ConfigFlags)
-    acc2= FaserDipoleTruthStrategyCfg(ConfigFlags)
-    kwargs.setdefault('TruthStrategies',[result.popToolsAndMerge(acc), result.popToolsAndMerge(acc2)])
-
+    # acc = FaserTruthStrategyCfg(ConfigFlags)
+    # acc2= FaserDipoleTruthStrategyCfg(ConfigFlags)
+    # kwargs.setdefault('TruthStrategies',[result.popToolsAndMerge(acc), result.popToolsAndMerge(acc2)])
+    acc = TruthStrategyGroupCfg(ConfigFlags)
+    kwargs.setdefault('TruthStrategies', [result.popToolsAndMerge(acc)])
+    
     kwargs.setdefault('SkipIfNoChildren', True)
     kwargs.setdefault('SkipIfNoParentBarcode', True)
 
diff --git a/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.cxx b/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.cxx
index b1a64408271187de69318c448fb727056c66f704..c43929542e093404fe9f53c884b59196f510e8d5 100644
--- a/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.cxx
+++ b/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.cxx
@@ -128,6 +128,8 @@ StatusCode ISF::FaserTruthSvc::finalize()
 StatusCode ISF::FaserTruthSvc::initializeTruthCollection()
 {
   ATH_CHECK( m_barcodeSvc->initializeBarcodes() );
+  m_incrementProcs.clear();
+  m_secondaryProcs.clear();
   return StatusCode::SUCCESS;
 }
 
@@ -264,13 +266,20 @@ void ISF::FaserTruthSvc::recordIncidentToMCTruth( ISF::IFaserTruthIncident& ti)
     newPrimBC = this->maxGeneratedParticleBarcode(ti.parentParticle()->parent_event())+1;
   }
   else {
+    if (m_incrementProcs.count(processCode) == 0)
+      m_incrementProcs[processCode] = 0;
+    m_incrementProcs[processCode]++;
     newPrimBC = m_barcodeSvc->incrementBarcode( parentBC, processCode);
   }
   if ( newPrimBC == Barcode::fUndefinedBarcode) {
+    for (auto pair : m_incrementProcs)
+    {
+      ATH_MSG_ALWAYS("Process code: " << pair.first << " : " << pair.second);
+    }
     if (m_ignoreUndefinedBarcodes) {
       ATH_MSG_WARNING("Unable to generate new Particle Barcode. Continuing due to 'IgnoreUndefinedBarcodes'==True");
     } else {
-      ATH_MSG_FATAL("Unable to generate new Particle Barcode in region " << ti.geoID() << ", at vertex (" <<  vtx->position().x() << ", " << vtx->position().y() << ", " << vtx->position().z() << "). Aborting");
+      ATH_MSG_FATAL("Unable to generate new Particle Barcode from " << parentBC << "( id= " << ti.parentPdgCode() << ", Ekin = " << ti.parentEkin()/Gaudi::Units::GeV << " GeV) with process code " << processCode << " in region " << ti.geoID() << ", at vertex (" <<  vtx->position().x() << ", " << vtx->position().y() << ", " << vtx->position().z() << "). Aborting");
       abort();
     }
   }
@@ -370,12 +379,20 @@ void ISF::FaserTruthSvc::recordIncidentToMCTruth( ISF::IFaserTruthIncident& ti)
       }
       else {
         // generate a new barcode for the child particle
+        if (m_secondaryProcs.count(processCode) == 0)
+          m_secondaryProcs[processCode] = 0;
+        m_secondaryProcs[processCode]++;
         Barcode::ParticleBarcode secBC = m_barcodeSvc->newSecondary( parentBC, processCode);
         if ( secBC == Barcode::fUndefinedBarcode) {
+          for (auto pair : m_secondaryProcs)
+          {
+            ATH_MSG_ALWAYS("Process code: " << pair.first << " : " << pair.second);
+          }
+
           if (m_ignoreUndefinedBarcodes)
             ATH_MSG_WARNING("Unable to generate new Secondary Particle Barcode. Continuing due to 'IgnoreUndefinedBarcodes'==True");
           else {
-            ATH_MSG_ERROR("Unable to generate new Secondary Particle Barcode. Aborting");
+            ATH_MSG_FATAL("Unable to generate new Secondary Particle Barcode from " << parentBC << " with process code " << processCode <<" in region " << ti.geoID() << ", at vertex (" <<  vtx->position().x() << ", " << vtx->position().y() << ", " << vtx->position().z() << "). Aborting");
             abort();
           }
         }
diff --git a/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.h b/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.h
index d674d80782cd3cbc5dfbef9e27939a1f4923dea8..21b4d2717eab17f96cf6e5a40cd25e45499c2b8e 100644
--- a/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.h
+++ b/Simulation/ISF/ISF_Core/FaserISF_Services/src/FaserTruthSvc.h
@@ -126,6 +126,10 @@ namespace ISF {
 
     bool                                      m_quasiStableParticlesIncluded; //!< does this job simulate quasi-stable particles.
 
+    mutable std::map<int, int>                m_incrementProcs;
+    mutable std::map<int, int>                m_secondaryProcs;
+    
+
   };
 }
 
diff --git a/Simulation/ISF/ISF_HepMC/FaserISF_HepMC_Tools/python/FaserISF_HepMC_ToolsConfigNew.py b/Simulation/ISF/ISF_HepMC/FaserISF_HepMC_Tools/python/FaserISF_HepMC_ToolsConfigNew.py
index 0f251682c98dfba7a08fd7e9a7427fb6a330e91b..4f739f6c0ca89afab6020eab6f42e096e1f53545 100644
--- a/Simulation/ISF/ISF_HepMC/FaserISF_HepMC_Tools/python/FaserISF_HepMC_ToolsConfigNew.py
+++ b/Simulation/ISF/ISF_HepMC/FaserISF_HepMC_Tools/python/FaserISF_HepMC_ToolsConfigNew.py
@@ -153,44 +153,65 @@ def FaserParticleGenericFilterCfg(ConfigFlags, name="ISF_FaserGenericFilter", **
 #  http://www-geant4.kek.jp/lxr/source//processes/hadronic/management/include/G4HadronicProcessType.hh#L46
 
 
-def FaserDipoleTruthStrategyCfg(ConfigFlags, name="ISF_FaserDipoleTruthStrategy", **kwargs):
-    result = ComponentAccumulator()
+def TruthStrategyGroupCfg(ConfigFlags, name="ISF_MCTruthStrategyGroupID", **kwargs):
 
     import ROOT, cppyy
     cppyy.load_library('FaserDetDescrDict')
     FaserRegion = ROOT.FaserDetDescr.FaserRegion
-    #
-    # Save truth in Dipole region
-    #
-    kwargs.setdefault('Regions', [FaserRegion.fFaserDipole,
-                                  FaserRegion.fFaserNeutrino,
+
+    result = ComponentAccumulator()
+    kwargs.setdefault("ParentMinEkin", 100.*MeV)
+    kwargs.setdefault("ChildMinEkin" , 100.*MeV)
+    kwargs.setdefault("VertexTypes", [3, 14, 15, 4, 5, 6, 7, 2, 12, 13])
+    kwargs.setdefault("VertexTypeRangeLow"  , 201)  # All kinds of decay processes
+    kwargs.setdefault("VertexTypeRangeHigh" , 298)  # ...
+    kwargs.setdefault("Regions", [FaserRegion.fFaserNeutrino,
+                                  FaserRegion.fFaserScintillator,
+                                  FaserRegion.fFaserTracker,
+                                  FaserRegion.fFaserCalorimeter,
                                   FaserRegion.fFaserCavern])
-    kwargs.setdefault('ParentMinEkin', 1000.0*MeV)
-    kwargs.setdefault('ChildMinEkin', 1000.0*MeV)
-    result.setPrivateTools(ISF__FaserTruthStrategy(name, **kwargs))
+    result.setPrivateTools(CompFactory.ISF.FaserTruthStrategy(name, **kwargs))
     return result
 
 
-def FaserTruthStrategyCfg(ConfigFlags, name="ISF_FaserTruthStrategy", **kwargs):
-    result = ComponentAccumulator()
+# def FaserDipoleTruthStrategyCfg(ConfigFlags, name="ISF_FaserDipoleTruthStrategy", **kwargs):
+#     result = ComponentAccumulator()
 
-    import ROOT, cppyy
-    cppyy.load_library('FaserDetDescrDict')
-    FaserRegion = ROOT.FaserDetDescr.FaserRegion
-    #
-    # Save truth in all regions except Dipole
-    #
-    kwargs.setdefault('Regions', [
-                                #   FaserRegion.fFaserNeutrino,
-                                  FaserRegion.fFaserScintillator,
-                                  FaserRegion.fFaserTracker])
-                                #   FaserRegion.fFaserDipole,
-                                #   FaserRegion.fFaserCalorimeter,
-                                #   FaserRegion.fFaserCavern])
-    # kwargs.setdefault('ParentMinEkin', 0.1*MeV)
-    # kwargs.setdefault('ChildMinEkin', 0.1*MeV)
-    result.setPrivateTools(ISF__FaserTruthStrategy(name, **kwargs))
-    return result
+#     import ROOT, cppyy
+#     cppyy.load_library('FaserDetDescrDict')
+#     FaserRegion = ROOT.FaserDetDescr.FaserRegion
+#     #
+#     # Save truth in Dipole region
+#     #
+#     kwargs.setdefault('Regions', [FaserRegion.fFaserDipole,
+#                                   FaserRegion.fFaserNeutrino,
+#                                   FaserRegion.fFaserCavern])
+#     kwargs.setdefault('ParentMinEkin', 1000.0*MeV)
+#     kwargs.setdefault('ChildMinEkin', 1000.0*MeV)
+#     result.setPrivateTools(ISF__FaserTruthStrategy(name, **kwargs))
+#     return result
+
+
+# def FaserTruthStrategyCfg(ConfigFlags, name="ISF_FaserTruthStrategy", **kwargs):
+#     result = ComponentAccumulator()
+
+#     import ROOT, cppyy
+#     cppyy.load_library('FaserDetDescrDict')
+#     FaserRegion = ROOT.FaserDetDescr.FaserRegion
+#     #
+#     # Save truth in all regions except Dipole
+#     #
+#     kwargs.setdefault('Regions', [
+#                                 #   FaserRegion.fFaserNeutrino,
+#                                   FaserRegion.fFaserScintillator,
+#                                   FaserRegion.fFaserTracker])
+#                                 #   FaserRegion.fFaserDipole,
+#                                 #   FaserRegion.fFaserCalorimeter,
+#                                 #   FaserRegion.fFaserCavern])
+#     # kwargs.setdefault('ParentMinEkin', 0.1*MeV)
+#     # kwargs.setdefault('ChildMinEkin', 0.1*MeV)
+#     result.setPrivateTools(ISF__FaserTruthStrategy(name, **kwargs))
+#     return result
 
 # def TruthStrategyGroupID_MC15Cfg(ConfigFlags, name="ISF_MCTruthStrategyGroupID_MC15", **kwargs):
 #     result = ComponentAccumulator()
diff --git a/Tracker/TrackerDetDescr/TrackerReadoutGeometry/TrackerReadoutGeometry/SiDetectorElement.h b/Tracker/TrackerDetDescr/TrackerReadoutGeometry/TrackerReadoutGeometry/SiDetectorElement.h
index 652add49be92b54bcf1b273d8d1da11f1ad4fd52..3286b43b3f8b66d91ee8fe864cd3218a8301802c 100644
--- a/Tracker/TrackerDetDescr/TrackerReadoutGeometry/TrackerReadoutGeometry/SiDetectorElement.h
+++ b/Tracker/TrackerDetDescr/TrackerReadoutGeometry/TrackerReadoutGeometry/SiDetectorElement.h
@@ -221,6 +221,8 @@ namespace TrackerDD {
     // Position 
     /// Local (simulation/hit frame) to global transform
     virtual const GeoTrf::Transform3D& transformHit() const;
+    /// Local (simulation/hit frame) to module transform
+    virtual const GeoTrf::Transform3D& transformModule() const;
     /// Local (reconstruction frame) to global transform
     virtual const Amg::Transform3D& transform() const override final;
     /**
@@ -340,40 +342,37 @@ namespace TrackerDD {
       
        @name Module Frame 
        Methods to help work with the module frame. 
-       This is mainly of of use in the SCT where the module frame can
-       in general be different from the element frame. It is actully
-       defined as the frame of one of the sides (currently the axial
-       side), but using these methods one does not need to make any
-       assumptions about what the actual frame is.  In the following
-       methods the local element frame is the local reconstruction
-       frame of this element.
+       This is mainly of of use in the SCT where the module frame is
+       different from the element frame. We have removed the ATLAS convention
+       in which the module frame was one of the wafers.
     */
       
     //@{
     
+
     /// Module to global frame transform. 
     /// Includes misalignment. The module frame is defined to be the
-    /// local reconstruction frame of the axial layer in the SCT. For
-    /// the pixel it is the same as the element.
+    /// frame in which the wafers are centered, rotated by +/- stereo angle, and equally offset in depth.
+    /// For the pixel it is the same as the element.
     //const HepGeom::Transform3D & moduleTransform() const;
+    const HepGeom::Transform3D& moduleTransformCLHEP() const;   
     const Amg::Transform3D& moduleTransform() const;
     
     /// Default module to global frame transform, ie with no misalignment. 
     /// The module frame is defined to be the
-    /// local reconstruction frame of the axial layer in the SCT. For
-    /// the pixel it is the same as the element.
+    /// frame in which each wafer is centered, rotated by +/- stereo angle, and offset in depth.
+    /// For the pixel it is the same as the element.
     Amg::Transform3D defModuleTransform() const;
+    /// Default to global transform
+    /// ie with no misalignment. 
+    const HepGeom::Transform3D defModuleTransformCLHEP() const;
       
     
     /// Take a transform of the local element frame and return its equivalent in the module frame.
     //HepGeom::Transform3D localToModuleFrame(const HepGeom::Transform3D & localTransform) const;
     Amg::Transform3D localToModuleFrame(const Amg::Transform3D& localTransform) const;
     
-    /// Transformation from local element to module frame.  This can be
-    /// used to take a local position in the element frame and transform
-    /// it to a position in the module frame. If one is already in the
-    /// module frame it will return the Identity transform.
-    //HepGeom::Transform3D localToModuleTransform() const;
+    /// Transformation from local element to module frame.  
     Amg::Transform3D localToModuleTransform() const;
     
     /// Check if the element and module frame are the same.
@@ -695,6 +694,8 @@ namespace TrackerDD {
 
     mutable Amg::Transform3D m_transform ATLAS_THREAD_SAFE; // Guarded by m_mutex
     mutable HepGeom::Transform3D m_transformCLHEP ATLAS_THREAD_SAFE; // Guarded by m_mutex
+    mutable Amg::Transform3D m_moduleTransform ATLAS_THREAD_SAFE; // Guarded by m_mutex
+    mutable HepGeom::Transform3D m_moduleTransformCLHEP ATLAS_THREAD_SAFE; // Guarded by m_mutex
 
     mutable Amg::Vector3D m_normal ATLAS_THREAD_SAFE; // Guarded by m_mutex
     mutable Amg::Vector3D m_etaAxis ATLAS_THREAD_SAFE; // Guarded by m_mutex
diff --git a/Tracker/TrackerDetDescr/TrackerReadoutGeometry/src/SiDetectorElement.cxx b/Tracker/TrackerDetDescr/TrackerReadoutGeometry/src/SiDetectorElement.cxx
index c9a29684d559305ac76ad06b2b632701df8cdc3c..07a1fadae9b0560bd722ed281f0a01b83d514477 100644
--- a/Tracker/TrackerDetDescr/TrackerReadoutGeometry/src/SiDetectorElement.cxx
+++ b/Tracker/TrackerDetDescr/TrackerReadoutGeometry/src/SiDetectorElement.cxx
@@ -122,6 +122,9 @@ namespace TrackerDD {
   SiDetectorElement::updateCache() const
   {
     const GeoTrf::Transform3D& geoTransform = transformHit();
+    const GeoTrf::Transform3D& geoModuleTransform = transformModule();
+    // ATH_MSG_ALWAYS("Wafer transform: " << geoTransform.translation() << "//" << geoTransform.rotation() );
+    // ATH_MSG_ALWAYS("Module transform: " << geoModuleTransform.translation() << "//" << geoModuleTransform.rotation() );
 
     double radialShift = 0.;
       
@@ -215,6 +218,9 @@ namespace TrackerDD {
     m_transformCLHEP = Amg::EigenTransformToCLHEP(geoTransform) * recoToHitTransformImpl();
     m_transform = Amg::CLHEPTransformToEigen(m_transformCLHEP);
   
+    m_moduleTransformCLHEP = Amg::EigenTransformToCLHEP(geoModuleTransform) * recoToHitTransformImpl();
+    m_moduleTransform = Amg::CLHEPTransformToEigen(m_moduleTransformCLHEP);
+
     // Check that local frame is right-handed. (ie transform has no reflection)
     // This can be done by checking that the determinant is >0.
     if (m_firstTime) { // Only need to check this once.
@@ -286,6 +292,28 @@ namespace TrackerDD {
     return getMaterialGeom()->getAbsoluteTransform();
   }
 
+  const GeoTrf::Transform3D&
+  SiDetectorElement::transformModule() const
+  {
+    PVConstLink parent = getMaterialGeom()->getParent()->getParent();
+    const GeoVFullPhysVol* fullParent = dynamic_cast<const GeoVFullPhysVol*>(&(*parent));
+    if (fullParent == nullptr)
+    {
+      ATH_MSG_FATAL("Unable to reach parent module volume");
+      if (m_geoAlignStore) {
+        const GeoTrf::Transform3D* ptrXf = m_geoAlignStore->getAbsPosition(getMaterialGeom());
+        if (ptrXf) return *ptrXf;
+      }
+      return getMaterialGeom()->getAbsoluteTransform();
+    }
+    // ATH_MSG_ALWAYS("Found full parent named: " << fullParent->getLogVol()->getName() << " with id " << fullParent->getId());
+    if (m_geoAlignStore) {
+      const GeoTrf::Transform3D* ptrXf = m_geoAlignStore->getAbsPosition(fullParent);
+      if (ptrXf) return *ptrXf;
+    }
+    return fullParent->getAbsoluteTransform();
+  }
+
   const Amg::Transform3D&
   SiDetectorElement::transform() const
   {
@@ -377,40 +405,81 @@ namespace TrackerDD {
   const Amg::Transform3D&
   SiDetectorElement::moduleTransform() const
   {
-    return (isModuleFrame()) ? transform() : m_otherSide->transform();
+    if (!m_cacheValid) {
+      std::lock_guard<std::mutex> lock(m_mutex);
+      if (!m_cacheValid) updateCache();
+    }
+
+    return m_moduleTransform;
+  }
+
+  const HepGeom::Transform3D&
+  SiDetectorElement::moduleTransformCLHEP() const
+  {
+    //stuff to get the CLHEP version of the local to global transform.
+    if (!m_cacheValid) {
+      std::lock_guard<std::mutex> lock(m_mutex);
+      if (!m_cacheValid) updateCache();
+    }
+
+    return m_moduleTransformCLHEP;
   }
 
   Amg::Transform3D
   SiDetectorElement::defModuleTransform() const
   {
-    return (isModuleFrame()) ? defTransform() : m_otherSide->defTransform();
+    HepGeom::Transform3D tmpTransform = defModuleTransformCLHEP();
+    return Amg::CLHEPTransformToEigen(tmpTransform);
   }  
 
+  const HepGeom::Transform3D 
+  SiDetectorElement::defModuleTransformCLHEP() const
+  {
+    PVConstLink parent = getMaterialGeom()->getParent()->getParent();
+    const GeoVFullPhysVol* fullParent = dynamic_cast<const GeoVFullPhysVol*>(&(*parent));
+    if (fullParent == nullptr)
+    {
+      ATH_MSG_FATAL("Unable to reach parent module volume");
+      if (m_geoAlignStore) {
+        const GeoTrf::Transform3D* ptrXf = m_geoAlignStore->getDefAbsPosition(getMaterialGeom());
+        if (ptrXf) return Amg::EigenTransformToCLHEP(*ptrXf) * recoToHitTransform();
+      }
+      return Amg::EigenTransformToCLHEP(getMaterialGeom()->getDefAbsoluteTransform()) * recoToHitTransform();
+    }
+    // ATH_MSG_ALWAYS("Found full parent named: " << fullParent->getLogVol()->getName() << " with id " << fullParent->getId());
+    if (m_geoAlignStore) {
+      const GeoTrf::Transform3D* ptrXf = m_geoAlignStore->getDefAbsPosition(fullParent);
+      if (ptrXf) return Amg::EigenTransformToCLHEP(*ptrXf) * recoToHitTransform();
+    }
+    return Amg::EigenTransformToCLHEP(fullParent->getDefAbsoluteTransform()) * recoToHitTransform();
+  }  
+
+
   // Take a transform in the local reconstruction and return it in the module frame
   // For a given transform l in frame A. The equivalent transform in frame B is
   //  B.inverse() * A * l * A.inverse() * B
   // Here A is the local to global transform of the element and B is the local to global
   // transform of the module.
-  // If we are already in the module frame then there is nothing to do, we just return the
-  // transform that is input. Otherwise we use the above formula.
   Amg::Transform3D 
   SiDetectorElement::localToModuleFrame(const Amg::Transform3D& localTransform) const
   {
-    if (isModuleFrame()) {
-      return localTransform;
-    } else {
-      return m_otherSide->transform().inverse() * transform() * localTransform * transform().inverse() * m_otherSide->transform();
-    }
+    return moduleTransform().inverse() * transform() * localTransform * transform().inverse() * moduleTransform();
+    // if (isModuleFrame()) {
+    //   return localTransform;
+    // } else {
+    //   return m_otherSide->transform().inverse() * transform() * localTransform * transform().inverse() * m_otherSide->transform();
+    // }
   }
 
   Amg::Transform3D 
   SiDetectorElement::localToModuleTransform() const
   {
-    if (isModuleFrame()) {
-      return Amg::Transform3D(); // Identity
-    } else {
-      return m_otherSide->transform().inverse() * transform();
-    }
+    return transform().inverse() * moduleTransform();
+    // if (isModuleFrame()) {
+    //   return Amg::Transform3D(); // Identity
+    // } else {
+    //   return m_otherSide->transform().inverse() * transform();
+    // }
   }
 
   bool 
diff --git a/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py b/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py
index a4bd9fb2c54f97e9983106eb6743c00bb772e585..c9f267da7d9d2ba0c6bc973c3fa949b65c217ecc 100644
--- a/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py
+++ b/Tracker/TrackerDigitization/FaserSCT_Digitization/python/FaserSCT_DigitizationConfigNew.py
@@ -240,9 +240,10 @@ def FaserSCT_OutputCfg(flags):
     if flags.Digitization.TruthOutput:
         ItemList += ["TrackerSimDataCollection#*"]
         acc.merge(TruthDigitizationOutputCfg(flags))
-    acc.merge(OutputStreamCfg(flags, "RDO", ItemList))
+    acc.merge(OutputStreamCfg(flags, "RDO"))
     ostream = acc.getEventAlgo("OutputStreamRDO")
     ostream.TakeItemsFromInput = True
+    ostream.ItemList += ItemList
     return acc
 
 
diff --git a/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.cxx b/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.cxx
index 51514fb5b599630e9c32d04c406439c5ea891991..2149bdb33ed2ef55ea57a0c1bf55b025dd69dc16 100644
--- a/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.cxx
+++ b/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.cxx
@@ -3,7 +3,6 @@
 */
 
 #include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3.h"
-//#include "TrackerSimEventTPCnv/TrackerHits/FaserSiHit_p2.h"
 #include "FaserSiHitCollectionCnv.h"
 
 
@@ -18,14 +17,23 @@ FaserSiHitCollection_PERS* FaserSiHitCollectionCnv::createPersistent(FaserSiHitC
 FaserSiHitCollection* FaserSiHitCollectionCnv::createTransient() {
     MsgStream mlog(msgSvc(), "FaserSiHitCollectionConverter" );
     FaserSiHitCollectionCnv_p3   converter_p3;
+    FaserSiHitCollectionCnv_p3a   converter_p3a;
 
     static const pool::Guid   p3_guid("FF9508DE-3E25-425D-9556-16D319DCE0E1");
+    static const pool::Guid   p3a_guid("72FD9F51-AB1B-4DF7-B430-6CCAE0A994DB");
 
     FaserSiHitCollection       *trans_cont(0);
-    if( this->compareClassGuid(p3_guid)) {
+    if( this->compareClassGuid(p3a_guid)) 
+    {
+      std::unique_ptr< FaserSiHitCollection_p3a >   col_vect( this->poolReadObject< FaserSiHitCollection_p3a >() );
+      trans_cont = converter_p3a.createTransient( col_vect.get(), mlog );
+    }
+    else if( this->compareClassGuid(p3_guid)) 
+    {
       std::unique_ptr< FaserSiHitCollection_p3 >   col_vect( this->poolReadObject< FaserSiHitCollection_p3 >() );
       trans_cont = converter_p3.createTransient( col_vect.get(), mlog );
-    }  else {
+    }  
+    else {
       throw std::runtime_error("Unsupported persistent version of Data container");
     }
     return trans_cont;
diff --git a/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.h b/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.h
index 3a81e19fa0671a028948f7217aa256c06e94c9a1..47892fe8a5cd1738428aa90858c95e83d46fe232 100644
--- a/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.h
+++ b/Tracker/TrackerEventCnv/TrackerSimEventAthenaPool/src/FaserSiHitCollectionCnv.h
@@ -8,12 +8,14 @@
 #include "TrackerSimEvent/FaserSiHitCollection.h"
 #include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3.h"
 #include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3.h"
+#include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3a.h"
+#include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3a.h"
 #include "AthenaPoolCnvSvc/T_AthenaPoolCustomCnv.h"
 // Gaudi
 #include "GaudiKernel/MsgStream.h"
 // typedef to the latest persistent version
-typedef FaserSiHitCollection_p3     FaserSiHitCollection_PERS;
-typedef FaserSiHitCollectionCnv_p3  FaserSiHitCollectionCnv_PERS;
+typedef FaserSiHitCollection_p3a     FaserSiHitCollection_PERS;
+typedef FaserSiHitCollectionCnv_p3a  FaserSiHitCollectionCnv_PERS;
 
 class FaserSiHitCollectionCnv  : public T_AthenaPoolCustomCnv<FaserSiHitCollection, FaserSiHitCollection_PERS > {
   friend class CnvFactory<FaserSiHitCollectionCnv>;
diff --git a/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3a.h b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3a.h
new file mode 100644
index 0000000000000000000000000000000000000000..8f453cf57ab52efb41fbe739d6543d75ba77c72a
--- /dev/null
+++ b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3a.h
@@ -0,0 +1,41 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef FASERSIHITCOLLECTIONCNV_P3A_H
+#define FASERSIHITCOLLECTIONCNV_P3A_H
+
+// FaserSiHitCollectionCnv_p3, T/P separation of FaserSi Hits
+// author D.Costanzo <davide.costanzo@cern.ch>
+// author O.Arnaez <olivier.arnaez@cern.ch>
+
+#include "AthenaPoolCnvSvc/T_AthenaPoolTPConverter.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "FaserSiHitCollection_p3a.h"
+
+
+class FaserSiHitCollectionCnv_p3a : public T_AthenaPoolTPCnvBase<FaserSiHitCollection, FaserSiHitCollection_p3a>
+{
+ public:
+
+  FaserSiHitCollectionCnv_p3a()  {};
+
+  virtual FaserSiHitCollection* createTransient(const FaserSiHitCollection_p3a* persObj, MsgStream &log);
+
+  virtual void	persToTrans(const FaserSiHitCollection_p3a* persCont,
+                            FaserSiHitCollection* transCont,
+                            MsgStream &log) ;
+  virtual void	transToPers(const FaserSiHitCollection* transCont,
+                            FaserSiHitCollection_p3a* persCont,
+                            MsgStream &log) ;
+
+ private:
+
+  static const double m_persEneUnit;
+  static const double m_persLenUnit;
+  static const double m_persAngUnit;
+  static const double m_2bHalfMaximum;
+  static const int m_2bMaximum;
+};
+
+#endif
diff --git a/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3a.h b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3a.h
new file mode 100644
index 0000000000000000000000000000000000000000..137ebd68b84c0ea6e6643132023d90c49b23df0f
--- /dev/null
+++ b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3a.h
@@ -0,0 +1,57 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef FASERSIHITCOLLECTION_P3A_H
+#define FASERSIHITCOLLECTION_P3A_H
+
+/*
+
+Authors: Davide Costanzo Rob Duxfield
+
+*/
+
+#include <vector>
+#include <string>
+
+class FaserSiHitCollection_p3a
+{
+ public:
+/// Default constructor
+  FaserSiHitCollection_p3a ();
+  //private:
+
+  std::vector<float>          m_hit1_meanTime;   //  1 element per string
+  std::vector<float>          m_hit1_x0;         //
+  std::vector<float>          m_hit1_y0;         //
+  std::vector<float>          m_hit1_z0;         //
+  std::vector<float>          m_hit1_theta;      //
+  std::vector<float>          m_hit1_phi;        //
+  std::vector<unsigned long> m_nHits;           //
+
+  std::vector<unsigned short> m_hitEne_2b;       //  1 element per hit
+  std::vector<unsigned short> m_hitLength_2b;    //
+
+  std::vector<unsigned short> m_dTheta;          //  1 element per hit except for first hit in string
+  std::vector<unsigned short> m_dPhi;            //
+
+  std::vector<float>          m_hitEne_4b;       //  1 element per hit with  m_hitEne_2b[i] == 2**16
+
+  std::vector<float>          m_hitLength_4b;    //  1 element per hit with  m_hitLength_2b[i] == 2**16
+
+  std::vector<unsigned long>  m_barcode;
+  std::vector<unsigned long> m_mcEvtIndex;
+  std::vector<char>           m_evtColl;
+  std::vector<unsigned long> m_nBC;
+
+  std::vector<unsigned long>  m_id;
+  std::vector<unsigned long> m_nId;
+};
+
+
+// inlines
+
+inline
+FaserSiHitCollection_p3a::FaserSiHitCollection_p3a () {}
+
+#endif
diff --git a/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnvDict.h b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnvDict.h
index a730fca90074a9da71420e41651384173aa298cf..a898e3095bee7e4b25d2090b0e7365cbdcc4d00e 100644
--- a/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnvDict.h
+++ b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnvDict.h
@@ -15,6 +15,8 @@
 #include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCnv_p2.h"
 #include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3.h"
 #include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3.h"
+#include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3a.h"
+#include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3a.h"
 #include "TrackerSimEventTPCnv/TrackerHits/FaserSiHit_p2.h"
 
 #endif // TRACKERSIMEVENTTPCNV_TRACKERSIMEVENTTPCNVDICT_H
diff --git a/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/selection.xml b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/selection.xml
index 7684404421c37267df94656b57ff9067dcce9503..6b34f88fcb38eb134efb2a283ac6635860a76928 100644
--- a/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/selection.xml
+++ b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/TrackerSimEventTPCnv/selection.xml
@@ -3,4 +3,5 @@
     <class name="FaserSiHit_p2" />
     <class name="std::vector<FaserSiHit_p2>" />
     <class name="FaserSiHitCollection_p3" id="FF9508DE-3E25-425D-9556-16D319DCE0E1" />     
+    <class name="FaserSiHitCollection_p3a" id="72FD9F51-AB1B-4DF7-B430-6CCAE0A994DB" />     
 </lcgdict>
diff --git a/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/src/TrackerHits/FaserSiHitCollectionCnv_p3a.cxx b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/src/TrackerHits/FaserSiHitCollectionCnv_p3a.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0aebca47dc6ae6eb0d6662d4a2b4abe1a52dc1ae
--- /dev/null
+++ b/Tracker/TrackerEventCnv/TrackerSimEventTPCnv/src/TrackerHits/FaserSiHitCollectionCnv_p3a.cxx
@@ -0,0 +1,351 @@
+/*
+  Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "TrackerSimEvent/FaserSiHit.h"
+#include "TrackerSimEvent/FaserSiHitCollection.h"
+#include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollection_p3a.h"
+#include "TrackerSimEventTPCnv/TrackerHits/FaserSiHitCollectionCnv_p3a.h"
+#include "GeneratorObjects/HepMcParticleLink.h"
+
+#include <cmath>
+
+//CLHEP
+#include "CLHEP/Geometry/Point3D.h"
+// Gaudi
+#include "GaudiKernel/MsgStream.h"
+#include "GaudiKernel/ThreadLocalContext.h"
+
+// Athena
+#include "StoreGate/StoreGateSvc.h"
+#include "AthenaKernel/ExtendedEventContext.h"
+
+
+//  * * *  stolen from eflowRec  * * *  //
+inline double phicorr(double a)
+{
+  if (a <= -M_PI)
+    {
+      return a+(2*M_PI*floor(-(a-M_PI)/(2*M_PI)));
+    }
+  else if (a > M_PI)
+    {
+      return a-(2*M_PI*floor((a+M_PI)/(2*M_PI)));
+    }
+  else
+    {
+      return a;
+    }
+}
+
+//  * * *  stolen from eflowRec  * * *  //
+inline double cycle(double a, double b)
+{
+  double del = b-a;
+  if (del > M_PI)
+    {
+      return a+2.0*M_PI;
+    }
+  else if (del < -M_PI)
+    {
+      return a-2.0*M_PI;
+    }
+  else
+    {
+      return a;
+    }
+}
+
+
+const double FaserSiHitCollectionCnv_p3a::m_persEneUnit = 1.0e-5;
+const double FaserSiHitCollectionCnv_p3a::m_persLenUnit = 1.0e-5;
+const double FaserSiHitCollectionCnv_p3a::m_persAngUnit = 1.0e-5;
+const double FaserSiHitCollectionCnv_p3a::m_2bHalfMaximum = pow(2.0, 15.0);
+const int FaserSiHitCollectionCnv_p3a::m_2bMaximum = (unsigned short)(-1);
+
+
+void FaserSiHitCollectionCnv_p3a::transToPers(const FaserSiHitCollection* transCont, FaserSiHitCollection_p3a* persCont, MsgStream &log)
+{
+  // Finds hits belonging to a "string" (in which the end point of one hit is the same as the start point of the next) and
+  // persistifies the end point of each hit plus the start point of the first hit in each string.
+  //
+  // Further compression is achieved by optimising the storage of the position vectors:- start (x,y,z) and (theta,phi) of
+  // first hit are stored as floats, (delta_theta,delta_phi) relative to the fisrst hit are stored as 2 byte numbers and
+  // used to specify the hit direction. All hit lengths are stored as 2 byte numbers.
+  //
+  // Additional savings are achieved by storing the energy loss for each hit as a 2 byte number and only storing the mean
+  // time of the first hit per string.
+  //
+  // See http://indico.cern.ch/getFile.py/access?contribId=11&resId=2&materialId=slides&confId=30893 for more info.
+
+  static const double dRcut = 1.0e-7;
+  static const double dTcut = 1.0;
+
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+  const IProxyDict* proxy = Atlas::getExtendedEventContext(ctx).proxy();
+  const HepMcParticleLink * lastLink=nullptr;
+  int lastId = -1;
+  double stringFirstTheta = 0.0;
+  double stringFirstPhi = 0.0;
+  double lastT = 0.0;
+  double persSumE = 0.0;
+  double transSumE = 0.0;
+  unsigned int idx = 0;
+  unsigned int endBC = 0;
+  unsigned int endId = 0;
+  unsigned int endHit = 0;
+  HepGeom::Point3D<double> lastTransEnd(0.0, 0.0, 0.0);
+  HepGeom::Point3D<double> lastPersEnd(0.0, 0.0, 0.0);
+
+  for (FaserSiHitCollection::const_iterator it = transCont->begin(); it != transCont->end(); ++it) {
+
+    FaserSiHitCollection::const_iterator siHit = it;
+
+
+    if ( !lastLink || (siHit->particleLink() != *lastLink) ) {
+
+      // store barcode, eventIndex and McEventCollection once for set of consecutive hits with same barcode
+
+      lastLink = &(siHit->particleLink());
+      persCont->m_barcode.push_back(lastLink->barcode());
+      // log << MSG::ALWAYS << "TP: Event collection from link: " << lastLink->getEventCollection() << " ; char value: " << lastLink->getEventCollectionAsChar() << " barcode: " << lastLink->barcode() << endmsg;
+      unsigned short index{0};
+      const HepMcParticleLink::index_type position =
+        HepMcParticleLink::getEventPositionInCollection(lastLink->eventIndex(),
+                                                        lastLink->getEventCollection(),
+                                                        proxy).at(0);
+      if (position!=0) {
+        index = lastLink->eventIndex();
+        if(lastLink->eventIndex()!=static_cast<HepMcParticleLink::index_type>(index)) {
+          log << MSG::WARNING << "Attempting to persistify an eventIndex larger than max unsigned short!" << endmsg;
+        }
+      }
+      persCont->m_mcEvtIndex.push_back(index);
+      persCont->m_evtColl.push_back(lastLink->getEventCollectionAsChar());
+
+      if (idx > 0) {
+        persCont->m_nBC.push_back(idx - endBC);
+        endBC = idx;
+      }
+    }
+
+    if ( (int)siHit->identify() != lastId ) {
+
+      // store id once for set of consecutive hits with same barcode
+
+      lastId = siHit->identify();
+      persCont->m_id.push_back(lastId);
+
+      if (idx > 0) {
+        persCont->m_nId.push_back(idx - endId);
+        endId = idx;
+      }
+    }
+
+    HepGeom::Point3D<double> st = siHit->localStartPosition();
+    HepGeom::Point3D<double> en = siHit->localEndPosition();
+
+    const double dx = st.x() - lastTransEnd.x();
+    const double dy = st.y() - lastTransEnd.y();
+    const double dz = st.z() - lastTransEnd.z();
+    const double t = siHit->meanTime();
+
+    const double dRLast = sqrt(dx * dx + dy * dy + dz * dz);  // dR between end of previous hit and start of current one
+    const double dTLast = fabs(t - lastT);
+
+    CLHEP::Hep3Vector direction(0.0, 0.0, 0.0);
+    double theta = 0.0;
+    double phi = 0.0;
+    bool startNewString = false;
+
+    if (dRLast < dRcut && dTLast < dTcut) {
+
+      // hit is part of existing string
+
+      direction = CLHEP::Hep3Vector( en.x() - lastPersEnd.x(), en.y() - lastPersEnd.y(), en.z() - lastPersEnd.z() );
+
+      theta = direction.theta();
+      phi = phicorr( direction.phi() );
+
+      const int dTheta_2b = (int)( (theta - stringFirstTheta) / m_persAngUnit + m_2bHalfMaximum + 0.5 );
+      const int dPhi_2b = (int)( (cycle(phi, stringFirstPhi) - stringFirstPhi) / m_persAngUnit + m_2bHalfMaximum + 0.5 );
+
+      if ( dTheta_2b < m_2bMaximum && dTheta_2b >= 0 && dPhi_2b < m_2bMaximum && dPhi_2b >= 0) {
+        persCont->m_dTheta.push_back(dTheta_2b);
+        persCont->m_dPhi.push_back(dPhi_2b);
+        theta = stringFirstTheta + ( (double)dTheta_2b - m_2bHalfMaximum ) * m_persAngUnit;
+        phi = stringFirstPhi + ( (double)dPhi_2b - m_2bHalfMaximum ) * m_persAngUnit;
+        phi = phicorr(phi);
+      }
+      else {
+        startNewString = true;
+      }
+    }
+
+    if (startNewString || dRLast >= dRcut || dTLast >= dTcut) {
+
+      // begin new hit string
+
+      direction = CLHEP::Hep3Vector( en.x() - st.x(), en.y() - st.y(), en.z() - st.z() );
+
+      theta = direction.theta();
+      phi = phicorr( direction.phi() );
+
+      persCont->m_hit1_meanTime.push_back(t);
+      persCont->m_hit1_x0.push_back(st.x());
+      persCont->m_hit1_y0.push_back(st.y());
+      persCont->m_hit1_z0.push_back(st.z());
+      persCont->m_hit1_theta.push_back(theta);
+      persCont->m_hit1_phi.push_back(phi);
+
+      lastPersEnd = HepGeom::Point3D<double>(st.x(), st.y(), st.z());
+
+      stringFirstTheta = theta;
+      stringFirstPhi = phi;
+
+      if (idx > 0) {
+        persCont->m_nHits.push_back(idx - endHit);
+        endHit = idx;
+      }
+    }
+
+    lastTransEnd = HepGeom::Point3D<double>(en.x(), en.y(), en.z());
+    transSumE += siHit->energyLoss();
+
+    const int eneLoss_2b = (int)((transSumE - persSumE) / m_persEneUnit + 0.5);  // calculated to allow recovery sum over
+                                                                                 // whole hit string to chosen precision
+
+    const int hitLength_2b = (int)(direction.mag() / m_persLenUnit + 0.5);  // calculated to give the correct position to
+                                                                            // the chosen precision, NOT the length of the
+                                                                            // hit (small difference in practice).
+    double eneLoss = 0.0;
+
+    if (eneLoss_2b >= m_2bMaximum) {
+      eneLoss = siHit->energyLoss();
+      persCont->m_hitEne_2b.push_back(m_2bMaximum);
+      persCont->m_hitEne_4b.push_back(eneLoss);
+    }
+    else {
+      eneLoss = eneLoss_2b * m_persEneUnit;
+      persCont->m_hitEne_2b.push_back(eneLoss_2b);
+    }
+
+    double length = 0.0;
+
+    if (hitLength_2b >= m_2bMaximum) {
+      length = direction.mag();
+      persCont->m_hitLength_2b.push_back(m_2bMaximum);
+      persCont->m_hitLength_4b.push_back(direction.mag());
+    }
+    else {
+      length = hitLength_2b * m_persLenUnit;
+      persCont->m_hitLength_2b.push_back(hitLength_2b);
+    }
+
+    CLHEP::Hep3Vector persDir(length, 0.0, 0.0);
+    persDir.setTheta(theta);
+    persDir.setPhi(phi);
+
+    lastPersEnd = (CLHEP::Hep3Vector)lastPersEnd + persDir;
+    persSumE += eneLoss;
+    lastT = t;
+
+    ++idx;
+  }
+
+  persCont->m_nBC.push_back(idx - endBC);
+  persCont->m_nId.push_back(idx - endId);
+  persCont->m_nHits.push_back(idx - endHit);
+
+  // log << MSG::ALWAYS << "nBC: " << persCont->m_nBC << endmsg;
+  // log << MSG::ALWAYS << "nId: " << persCont->m_nId << endmsg;
+  // log << MSG::ALWAYS << "nHits:" << persCont->m_nHits << endmsg;
+
+}
+
+
+FaserSiHitCollection* FaserSiHitCollectionCnv_p3a::createTransient(const FaserSiHitCollection_p3a* persObj, MsgStream &log) {
+  // for (size_t i = 0; i < persObj->m_evtColl.size(); i++)
+  // {
+  //   if (persObj->m_evtColl[i] != 'a')
+  //     log << MSG::ALWAYS << "Corrupted in createTransient: " << persObj->m_evtColl[i] << endmsg;
+  // }
+  std::unique_ptr<FaserSiHitCollection> trans(std::make_unique<FaserSiHitCollection>("DefaultCollectionName",persObj->m_nHits.size()));
+  persToTrans(persObj, trans.get(), log);
+  return(trans.release());
+}
+
+
+void FaserSiHitCollectionCnv_p3a::persToTrans(const FaserSiHitCollection_p3a* persCont, FaserSiHitCollection* transCont, MsgStream &/*log*/)
+{
+  const EventContext& ctx = Gaudi::Hive::currentContext();
+
+  unsigned int hitCount = 0;
+  unsigned int angleCount = 0;
+  unsigned int idxBC = 0;
+  unsigned int idxId = 0;
+  unsigned int idxEne4b = 0;
+  unsigned int idxLen4b = 0;
+  unsigned int endHit = 0;
+  unsigned int endBC = 0;
+  unsigned int endId = 0;
+
+  // log << MSG::ALWAYS << "nHits.size(): " << persCont->m_nHits.size() << " nBC.size(): " << persCont->m_nBC.size() << endmsg;
+  // log << MSG::ALWAYS << " nBC: " << persCont->m_nBC << endmsg;
+
+  for (unsigned int i = 0; i < persCont->m_nHits.size(); i++) {
+    if (persCont->m_nHits[i]) {
+      const unsigned int start = endHit;
+      endHit += persCont->m_nHits[i];
+      // log << MSG::ALWAYS << "i: " << i << " persCont->m_nHits[i]: " << persCont->m_nHits[i] << " start: " << start << " endHit: " << endHit << endmsg;
+
+      const double t0 = persCont->m_hit1_meanTime[i];
+      const double theta0 = persCont->m_hit1_theta[i];
+      const double phi0 = persCont->m_hit1_phi[i];
+      HepGeom::Point3D<double> endLast(persCont->m_hit1_x0[i], persCont->m_hit1_y0[i], persCont->m_hit1_z0[i]);
+
+      for (unsigned int j = start; j < endHit; j++) {
+
+        if (j >= endBC + persCont->m_nBC[idxBC])
+          endBC += persCont->m_nBC[idxBC++];
+
+        if (j >= endId + persCont->m_nId[idxId])
+          endId += persCont->m_nId[idxId++];
+
+        const double eneLoss_2b = persCont->m_hitEne_2b[hitCount];
+        const double hitLength_2b = persCont->m_hitLength_2b[hitCount];
+
+        const double eneLoss = (eneLoss_2b < m_2bMaximum) ? eneLoss_2b * m_persEneUnit : persCont->m_hitEne_4b[idxEne4b++];
+        const double length = (hitLength_2b < m_2bMaximum) ? hitLength_2b * m_persLenUnit : persCont->m_hitLength_4b[idxLen4b++];
+
+        const double dTheta = (j > start) ? ((double)persCont->m_dTheta[angleCount] - m_2bHalfMaximum) * m_persAngUnit : 0.0;
+        const double dPhi = (j > start) ? ((double)persCont->m_dPhi[angleCount] - m_2bHalfMaximum) * m_persAngUnit : 0.0;
+
+        const double meanTime = t0;
+        const double theta = theta0 + dTheta;
+        const double phi = phicorr(phi0 + dPhi);
+
+        CLHEP::Hep3Vector r(length, 0.0, 0.0);
+        r.setTheta(theta);
+        r.setPhi(phi);
+
+        HepGeom::Point3D<double> endThis( endLast + r );
+
+        HepMcParticleLink::PositionFlag flag = HepMcParticleLink::IS_INDEX;
+        if (persCont->m_mcEvtIndex[idxBC] == 0) {
+          flag = HepMcParticleLink::IS_POSITION;
+        }
+        // log << MSG::ALWAYS << "PT: Event collection from persCont: " << persCont->m_evtColl[idxBC] << " ; idxBC: " << idxBC << " evtColl.size(): " << persCont->m_evtColl.size() << " evtIndex.size(): " << persCont->m_mcEvtIndex.size() << endmsg;
+
+        HepMcParticleLink partLink( persCont->m_barcode[idxBC], persCont->m_mcEvtIndex[idxBC], HepMcParticleLink::ExtendedBarCode::eventCollectionFromChar(persCont->m_evtColl[idxBC]), flag, ctx );
+        transCont->Emplace( endLast, endThis, eneLoss, meanTime, partLink, persCont->m_id[idxId]);
+
+        endLast = endThis;
+
+        ++hitCount;
+        if (j > start) ++angleCount;
+      }
+    }
+  }
+  // log << MSG::ALWAYS << "hitCount: " << hitCount << endmsg;
+}
diff --git a/Waveform/WaveDigiTools/CMakeLists.txt b/Waveform/WaveDigiTools/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..692fdb69bc14451ba5a6a660d011bad5b14b66e5
--- /dev/null
+++ b/Waveform/WaveDigiTools/CMakeLists.txt
@@ -0,0 +1,25 @@
+################################################################################
+# Package: WaveDigiTools
+################################################################################
+
+# Declare the package name:
+atlas_subdir( WaveDigiTools )
+
+# External dependencies:
+find_package( ROOT )
+
+# Component(s) in the package:
+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
+                   PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES}
+		   )
+
+atlas_add_component( WaveDigiTools
+		     src/components/*.cxx 
+		     INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+                     LINK_LIBRARIES ${ROOT_LIBRARIES} AthenaBaseComps GaudiKernel WaveDigiToolsLib )
+
+
diff --git a/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.h b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..3e85ff839a04f6d02c794fa74567841450c9cfeb
--- /dev/null
+++ b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.h
@@ -0,0 +1,55 @@
+/*
+  Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+*/
+
+/**
+ * @file IWaveformDigitisationTool.h
+ * Header file for the IWaveformDigitisationTool class
+ * @author Carl Gwilliam, 2021
+ */
+
+
+#ifndef WAVEDIGITOOLS_IWAVEFORMDIGITISATIONTOOL_H
+#define WAVEDIGITOOLS_IWAVEFORMDIGITISATIONTOOL_H
+
+// Base class
+#include "GaudiKernel/IAlgTool.h"
+#include "GaudiKernel/ToolHandle.h"
+
+#include "GaudiKernel/ServiceHandle.h"
+#include "GaudiKernel/IMessageSvc.h"
+#include "GaudiKernel/MsgStream.h"
+
+#include "WaveRawEvent/RawWaveformContainer.h"
+#include "WaveRawEvent/RawWaveform.h"
+
+#include "TF1.h"
+
+///Interface for waveform digitisation tools
+class IWaveformDigitisationTool : virtual public IAlgTool 
+{
+public:
+
+  // InterfaceID
+  DeclareInterfaceID(IWaveformDigitisationTool, 1, 0);
+
+  IWaveformDigitisationTool():
+    m_msgSvc         ( "MessageSvc",   "ITrkEventCnvTool" )
+  {}
+
+  virtual ~IWaveformDigitisationTool() = default;
+
+  // Digitise HITS to Raw waveform
+  template<class CONT>
+  StatusCode digitise(const CONT* hitCollection, 
+		      RawWaveformContainer* waveContainer, TF1* kernel) const;
+
+private:
+  ServiceHandle<IMessageSvc>      m_msgSvc;
+
+};
+
+#include "WaveDigiTools/IWaveformDigitisationTool.icc"
+
+
+#endif //WAVEDIGITOOLS_IWAVEFORMDIGITISATIONTOOL_H
diff --git a/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.icc b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.icc
new file mode 100644
index 0000000000000000000000000000000000000000..57d4839bda5f2d30286ec412e3f01c92ce353b11
--- /dev/null
+++ b/Waveform/WaveDigiTools/WaveDigiTools/IWaveformDigitisationTool.icc
@@ -0,0 +1,51 @@
+#include <vector>
+#include <map>
+
+template<class CONT>
+StatusCode IWaveformDigitisationTool::digitise(const CONT* hitCollection,
+					       RawWaveformContainer* container, TF1* kernel) const {
+
+
+  // Check the container
+  if (!container) {
+    MsgStream log(&(*m_msgSvc), name());
+    log << MSG::ERROR << "HitCollection passed to digitise() is null!" << endmsg;
+    return StatusCode::FAILURE;
+  }
+
+  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 = 8000; // TODO: vary this + add noise
+
+  // Loop over time samples
+  for (const auto& t : time) {
+    std::map<unsigned int, float> counts;
+   
+    // 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) {
+      waveforms[c.first].push_back(baseline - c.second);
+      //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;
+}
diff --git a/Waveform/WaveDigiTools/share/WaveformDigiAndRecoExample_jobOptions.py b/Waveform/WaveDigiTools/share/WaveformDigiAndRecoExample_jobOptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..779031828c9c5736f9403e403514e19285029499
--- /dev/null
+++ b/Waveform/WaveDigiTools/share/WaveformDigiAndRecoExample_jobOptions.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+import sys
+
+if __name__ == "__main__":
+
+    fileroot = "output"
+    if len(sys.argv) > 1:
+       filename = sys.argv[1]
+
+    doRDO = False
+    if len(sys.argv) > 2:
+       filename = sys.argv[2]
+
+    from AthenaCommon.Logging import log, logging
+    from AthenaCommon.Constants import DEBUG, VERBOSE, INFO
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    from AthenaCommon.Configurable import Configurable
+        
+    Configurable.configurableRun3Behavior = True
+
+    log.setLevel(VERBOSE)
+    
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02"             # Always needed; must match FaserVersion
+    ConfigFlags.IOVDb.DatabaseInstance = "OFLP200"               # Use MC conditions for now
+    ConfigFlags.Input.ProjectName = "mc21"                       # Needed to bypass autoconfig
+    ConfigFlags.Input.isMC = True                                # Needed to bypass autoconfig
+    ConfigFlags.GeoModel.FaserVersion     = "FASER-TB00"         # FASER geometry
+    ConfigFlags.Common.isOnline = False
+    ConfigFlags.GeoModel.Align.Dynamic = False
+    
+    ConfigFlags.Input.Files = [
+        "/eos/project-f/faser-commissioning/Simulation/Test/TB.Elec.200GeV.SIM.root"
+        ]
+
+    if doRDO:
+        ConfigFlags.Output.RDOFileName = f"{fileroot}.RDO.root"
+    else:
+        ConfigFlags.addFlag("Output.xAODFileName", f"{fileroot}.xAOD.root")
+        ConfigFlags.Output.ESDFileName = f"{fileroot}.ESD.root"
+
+    ConfigFlags.lock()
+
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    acc = MainServicesCfg(ConfigFlags)
+
+    from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+    from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg
+
+    acc.merge(PoolReadCfg(ConfigFlags))
+    acc.merge(PoolWriteCfg(ConfigFlags))
+
+    if doRDO:
+         from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+         itemList = [
+             "RawWaveformContainer#*"
+             ]
+         acc.merge(OutputStreamCfg(ConfigFlags, "RDO", itemList,disableEventTag=True))
+
+
+    else:
+        from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+        itemList = [
+            "xAOD::EventInfo#*",
+            "xAOD::WaveformHitContainer#*",
+            "xAOD::WaveformHitAuxContainer#*",
+            ]
+
+        acc.merge(OutputStreamCfg(ConfigFlags, "xAOD", itemList, disableEventTag=True))
+
+    from ScintDigiAlgs.ScintDigiAlgsConfig import ScintWaveformDigitizationCfg
+    acc.merge(ScintWaveformDigitizationCfg(ConfigFlags))
+
+    from CaloDigiAlgs.CaloDigiAlgsConfig import CaloWaveformDigitizationCfg
+    acc.merge(CaloWaveformDigitizationCfg(ConfigFlags))
+
+    if not doRDO:
+        from WaveRecAlgs.WaveRecAlgsConfig import WaveformReconstructionCfg
+        acc.merge(WaveformReconstructionCfg(ConfigFlags))
+
+    #acc.foreach_component("*").OutputLevel = VERBOSE
+
+    # Execute and finish
+    sc = acc.run(maxEvents=100)
+
+    # Success should be 0
+    sys.exit(not sc.isSuccess())
diff --git a/Waveform/WaveDigiTools/src/WaveformDigitisationTool.cxx b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..e4776da3bf1b140fa1c614642d939f46557b0ba6
--- /dev/null
+++ b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.cxx
@@ -0,0 +1,26 @@
+/*
+  Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+*/
+
+/**
+ * @file WaveformDigitisationTool.cxx
+ * Implementation file for the WaveformDigitisationTool class
+ * @ author C. Gwilliam, 2021
+ **/
+
+#include "WaveformDigitisationTool.h"
+
+// Constructor
+WaveformDigitisationTool::WaveformDigitisationTool(const std::string& type, const std::string& name, const IInterface* parent) :
+  base_class(type, name, parent)
+{
+}
+
+// Initialization
+StatusCode
+WaveformDigitisationTool::initialize() {
+  ATH_MSG_INFO( name() << "::initalize()" );
+  return StatusCode::SUCCESS;
+}
+
+
diff --git a/Waveform/WaveDigiTools/src/WaveformDigitisationTool.h b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a5ba71f3dd124fcdd2c6b4b8124ee96591512da
--- /dev/null
+++ b/Waveform/WaveDigiTools/src/WaveformDigitisationTool.h
@@ -0,0 +1,36 @@
+/*
+   Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+*/
+
+/** @file WaveformDigitisationTool.h
+ *  Header file for WaveformDigitisationTool.h
+ *
+ */
+#ifndef WAVEDIGITOOLS_WAVEFORMDIGITISATIONTOOL_H
+#define WAVEDIGITOOLS_WAVEFORMDIGITISATIONTOOL_H
+
+//Athena
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "WaveDigiTools/IWaveformDigitisationTool.h"
+
+//Gaudi
+#include "GaudiKernel/ToolHandle.h"
+
+//STL
+
+class WaveformDigitisationTool: public extends<AthAlgTool, IWaveformDigitisationTool> {
+ public:
+
+  /// Normal constructor for an AlgTool; 'properties' are also declared here
+ WaveformDigitisationTool(const std::string& type, 
+			  const std::string& name, const IInterface* parent);
+
+  /// Retrieve the necessary services in initialize
+  StatusCode initialize();
+
+ private:
+  // None
+
+};
+
+#endif // WAVEDIGITOOLS_WAVEFORMDIGITISATIONTOOL_H
diff --git a/Waveform/WaveDigiTools/src/components/WaveDigiTools_entries.cxx b/Waveform/WaveDigiTools/src/components/WaveDigiTools_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..169476d905bb3e3a56de0d15aac7b7d13c3bdeb1
--- /dev/null
+++ b/Waveform/WaveDigiTools/src/components/WaveDigiTools_entries.cxx
@@ -0,0 +1,3 @@
+#include "../WaveformDigitisationTool.h"
+
+DECLARE_COMPONENT( WaveformDigitisationTool )
diff --git a/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py b/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py
index cc13137d8e59eef7801d0ffa8fa60230721b372f..840a4d0f091a7e195f4e40f818c2b0b99c0b346b 100644
--- a/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py
+++ b/Waveform/WaveEventCnv/WaveByteStream/python/WaveByteStreamConfig.py
@@ -37,6 +37,17 @@ def WaveByteStreamCfg(configFlags, **kwargs):
         waveform_tool.PreshowerChannels = [12, 13]
         acc.addPublicTool(waveform_tool)
 
+    elif configFlags.GeoModel.FaserVersion == "FASER-02":
+        print(" - setting up TI12 with IFT detector")
+
+        # Need to fix this!
+        waveform_tool = CompFactory.RawWaveformDecoderTool("RawWaveformDecoderTool")
+        waveform_tool.CaloChannels = [1, 0, 3, 2]
+        waveform_tool.VetoChannels = [4, 5, 6, 7]
+        waveform_tool.TriggerChannels = [9, 8, 11, 10]
+        waveform_tool.PreshowerChannels = [12, 13]
+        acc.addPublicTool(waveform_tool)
+
     else:
         print(" - unknown version: user must set up Waveform channel mapping by hand!")
 
diff --git a/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py b/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py
index 39a99ed0c39d01e08c5f555e4e3efd98e0bc70a1..64764cd1c30ca7f039a28799dd16cc22bb50720c 100644
--- a/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py
+++ b/Waveform/WaveRecAlgs/python/WaveRecAlgsConfig.py
@@ -11,24 +11,27 @@ WaveformReconstructionTool = CompFactory.WaveformReconstructionTool
 ClockReconstructionTool = CompFactory.ClockReconstructionTool
 
 # One stop shopping for normal FASER data
-def WaveformReconstructionCfg(flags, naive = True):
+def WaveformReconstructionCfg(flags, naive = False):
     """ 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 not "TB" in flags.GeoModel.FaserVersion:
+        if "TB" not in flags.GeoModel.FaserVersion:
             acc.merge(PseudoScintHitToWaveformRecCfg(flags, "PseudoTimingHitWaveformRecAlg", "Trigger"))
         acc.merge(PseudoScintHitToWaveformRecCfg(flags, "PseudoVetoHitToWaveformRecAlg", "Veto"))
         acc.merge(PseudoScintHitToWaveformRecCfg(flags, "PseudoPresehowerHitWaveformRecAlg", "Preshower"))
         acc.merge(PseudoCaloHitToWaveformRecCfg(flags, "PseudoCaloHitWaveformRecAlg"))                
         return acc
 
-    acc.merge(WaveformClockRecCfg(flags, "ClockRecAlg"))
-
+    if "TB" not in flags.GeoModel.FaserVersion:
+        acc.merge(WaveformHitRecCfg(flags, "TimingWaveformRecAlg", "Trigger"))
     acc.merge(WaveformHitRecCfg(flags, "VetoWaveformRecAlg", "Veto"))
-    acc.merge(WaveformHitRecCfg(flags, "TimingWaveformRecAlg", "Trigger"))
     acc.merge(WaveformHitRecCfg(flags, "PreshowerWaveformRecAlg", "Preshower"))
     acc.merge(WaveformHitRecCfg(flags, "CaloWaveformRecAlg", "Calo"))
+
     return acc
 
 # Return configured WaveformClock reconstruction algorithm
@@ -52,7 +55,13 @@ def WaveformHitRecCfg(flags, name="WaveformRecAlg", source="", **kwargs):
 
     acc = ComponentAccumulator()
 
+    if flags.Input.isMC:
+        kwargs.setdefault("PeakThreshold", 5)
+
     tool = WaveformReconstructionTool(name=source+"WaveformRecTool", **kwargs)
+
+    if "PeakThreshold" in kwargs: kwargs.pop("PeakThreshold")
+
     kwargs.setdefault("WaveformContainerKey", source+"Waveforms")
     kwargs.setdefault("WaveformHitContainerKey", source+"WaveformHits")
     kwargs.setdefault("WaveformReconstructionTool", tool)
diff --git a/Waveform/WaveRecAlgs/src/PseudoCaloHitToWaveformRecAlg.cxx b/Waveform/WaveRecAlgs/src/PseudoCaloHitToWaveformRecAlg.cxx
index 5ead65452727c36307d8b4c038812ca9c3683f47..2074b566e044e8bc647237fa7ff5c127f9a94ffe 100644
--- a/Waveform/WaveRecAlgs/src/PseudoCaloHitToWaveformRecAlg.cxx
+++ b/Waveform/WaveRecAlgs/src/PseudoCaloHitToWaveformRecAlg.cxx
@@ -42,12 +42,6 @@ PseudoCaloHitToWaveformRecAlg::execute(const EventContext& ctx) const {
   ATH_CHECK( caloHitHandle.isValid() );
   ATH_MSG_DEBUG("Found ReadHandle for CaloHitCollection " << m_caloHitContainerKey);
 
-  if (caloHitHandle->size() == 0) {
-    ATH_MSG_DEBUG("CaloHitCollection found with zero length!");
-    return StatusCode::SUCCESS;
-  }
-
-
   // Find the output waveform container
   SG::WriteHandle<xAOD::WaveformHitContainer> waveformHitContainerHandle(m_waveformHitContainerKey, ctx);
   ATH_CHECK( waveformHitContainerHandle.record( std::make_unique<xAOD::WaveformHitContainer>(),
@@ -55,6 +49,10 @@ PseudoCaloHitToWaveformRecAlg::execute(const EventContext& ctx) const {
 
   ATH_MSG_DEBUG("WaveformsHitContainer '" << waveformHitContainerHandle.name() << "' initialized");
 
+  if (caloHitHandle->size() == 0) {
+    ATH_MSG_DEBUG("CaloHitCollection found with zero length!");
+    return StatusCode::SUCCESS;
+  }
 
   // "Reconstruct" the hits
   CHECK( m_recoTool->reconstruct<CaloHitCollection>(caloHitHandle.ptr(),
diff --git a/Waveform/WaveRecAlgs/src/PseudoScintHitToWaveformRecAlg.cxx b/Waveform/WaveRecAlgs/src/PseudoScintHitToWaveformRecAlg.cxx
index 2b896e63c8a67b845072b74489ffe5a39b162751..4b8b719781f95a8881391738f076acf8e5c9204b 100644
--- a/Waveform/WaveRecAlgs/src/PseudoScintHitToWaveformRecAlg.cxx
+++ b/Waveform/WaveRecAlgs/src/PseudoScintHitToWaveformRecAlg.cxx
@@ -42,12 +42,6 @@ PseudoScintHitToWaveformRecAlg::execute(const EventContext& ctx) const {
   ATH_CHECK( scintHitHandle.isValid() );
   ATH_MSG_DEBUG("Found ReadHandle for ScintHitCollection " << m_scintHitContainerKey);
 
-  if (scintHitHandle->size() == 0) {
-    ATH_MSG_DEBUG("ScintHitCollection found with zero length!");
-    return StatusCode::SUCCESS;
-  }
-
-
   // Find the output waveform container
   SG::WriteHandle<xAOD::WaveformHitContainer> waveformHitContainerHandle(m_waveformHitContainerKey, ctx);
   ATH_CHECK( waveformHitContainerHandle.record( std::make_unique<xAOD::WaveformHitContainer>(),
@@ -55,6 +49,10 @@ PseudoScintHitToWaveformRecAlg::execute(const EventContext& ctx) const {
 
   ATH_MSG_DEBUG("WaveformsHitContainer '" << waveformHitContainerHandle.name() << "' initialized");
 
+  if (scintHitHandle->size() == 0) {
+    ATH_MSG_DEBUG("ScintHitCollection found with zero length!");
+    return StatusCode::SUCCESS;
+  }
 
   // "Reconstruct" the hits
   CHECK( m_recoTool->reconstruct<ScintHitCollection>(scintHitHandle.ptr(),
diff --git a/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx b/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx
index aaae0861b6fab59b7144afedb72797ad9da91f86..c45ea51007362eecd0ab7827f50123c05ddebab7 100644
--- a/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx
+++ b/Waveform/WaveRecTools/src/ClockReconstructionTool.cxx
@@ -79,8 +79,8 @@ ClockReconstructionTool::reconstruct(const RawWaveform& raw_wave,
   // Get the coefficients
   std::vector<double> re_full(N);
   std::vector<double> im_full(N);
-  std::vector<double> magnitude(N/2); 
-  fftr2c->GetPointsComplex(&re_full[0],&im_full[0]);
+  std::vector<double> magnitude(N/2+1);  // From fftw manual, output array is N/2+1 long 
+  fftr2c->GetPointsComplex(&re_full[0], &im_full[0]);
 
   // Normalize the magnitude (just using the positive complex frequencies)
   unsigned int i=0;
@@ -106,7 +106,7 @@ ClockReconstructionTool::reconstruct(const RawWaveform& raw_wave,
   clockdata->set_dc_offset(magnitude[0]);
   clockdata->set_frequency(imax * freqmult);
   clockdata->set_amplitude(magnitude[imax]);
-  clockdata->set_phase(atan2(im_full[imax], re_full[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:");
   ATH_MSG_DEBUG(*clockdata);
@@ -125,10 +125,11 @@ ClockReconstructionTool::reconstruct(const RawWaveform& raw_wave,
 
   // Improved values
 
+  // Not a bug, atan2(y,x)!
   double phase = atan2(im_full[imax], re_full[imax]) - dm * M_PI;
-  // Fix any overflows
-  if (phase < M_PI) phase += (2*M_PI);
-  if (phase > M_PI) phase -= (2*M_PI);
+  // Fix any overflows caused by adding dm
+  if (phase < -M_PI) phase += (2*M_PI);
+  if (phase > +M_PI) phase -= (2*M_PI);
 
   clockdata->set_frequency( (imax+dm) * freqmult );
   clockdata->set_phase (phase);