From 56819b96292a94732629aeffa6156e992a3e4d13 Mon Sep 17 00:00:00 2001
From: Dominik Smith <smith@th.physik.uni-frankfurt.de>
Date: Thu, 18 Apr 2024 12:08:08 +0200
Subject: [PATCH] - Added online capable TRD hitfinders (1D and 2D) in
 cbm::algo. - Added ROOT-free implementations of CbmTrdCluster and CbmTrdHit.
 - Added class CbmTaskTrdHitFinder as wrapper for new hitfinders. - Added
 class CbmTaskTrdHitFinderParWrite to produce YAML input files. - Added
 example macro trd_hitfinder_run.C in beamtime/mcbm2022 folder to run new
 hitfinder. - Added a required getter function to CbmTrdParModGeo.h. - Updated
 CMakeLists and LinkDef files to enable compilation.

---
 algo/CMakeLists.txt                         |   9 +
 algo/detectors/trd/Cluster.cxx              |  24 +
 algo/detectors/trd/Cluster.h                |  85 ++
 algo/detectors/trd/Cluster2D.cxx            | 149 ++++
 algo/detectors/trd/Cluster2D.h              | 174 ++++
 algo/detectors/trd/Clusterizer.cxx          | 203 +++++
 algo/detectors/trd/Clusterizer.h            |  57 ++
 algo/detectors/trd/Clusterizer2D.cxx        | 119 +++
 algo/detectors/trd/Clusterizer2D.h          |  52 ++
 algo/detectors/trd/DigiRec.cxx              | 123 +++
 algo/detectors/trd/DigiRec.h                |  79 ++
 algo/detectors/trd/Hit.cxx                  |  83 ++
 algo/detectors/trd/Hit.h                    | 172 ++++
 algo/detectors/trd/HitFinder.cxx            | 233 +++++
 algo/detectors/trd/HitFinder.h              |  68 ++
 algo/detectors/trd/HitFinder2D.cxx          | 934 ++++++++++++++++++++
 algo/detectors/trd/HitFinder2D.h            | 242 +++++
 algo/detectors/trd/HitFinder2DPars.h        |  33 +
 algo/detectors/trd/HitFinderPars.h          |  33 +
 algo/detectors/trd/Hitfind.cxx              | 138 +++
 algo/detectors/trd/Hitfind.h                |  84 ++
 algo/detectors/trd/Hitfind2DSetup.h         |  64 ++
 algo/detectors/trd/HitfindSetup.h           |  65 ++
 core/detectors/trd/CbmTrdParModGeo.h        |   6 +-
 macro/beamtime/mcbm2022/trd_hitfinder_run.C | 216 +++++
 reco/tasks/CMakeLists.txt                   |   2 +
 reco/tasks/CbmRecoTasksLinkDef.h            |   2 +
 reco/tasks/CbmTaskTrdHitFinder.cxx          | 153 ++++
 reco/tasks/CbmTaskTrdHitFinder.h            |  93 ++
 reco/tasks/CbmTaskTrdHitFinderParWrite.cxx  | 213 +++++
 reco/tasks/CbmTaskTrdHitFinderParWrite.h    |  63 ++
 31 files changed, 3969 insertions(+), 2 deletions(-)
 create mode 100644 algo/detectors/trd/Cluster.cxx
 create mode 100644 algo/detectors/trd/Cluster.h
 create mode 100644 algo/detectors/trd/Cluster2D.cxx
 create mode 100644 algo/detectors/trd/Cluster2D.h
 create mode 100644 algo/detectors/trd/Clusterizer.cxx
 create mode 100644 algo/detectors/trd/Clusterizer.h
 create mode 100644 algo/detectors/trd/Clusterizer2D.cxx
 create mode 100644 algo/detectors/trd/Clusterizer2D.h
 create mode 100644 algo/detectors/trd/DigiRec.cxx
 create mode 100644 algo/detectors/trd/DigiRec.h
 create mode 100644 algo/detectors/trd/Hit.cxx
 create mode 100644 algo/detectors/trd/Hit.h
 create mode 100644 algo/detectors/trd/HitFinder.cxx
 create mode 100644 algo/detectors/trd/HitFinder.h
 create mode 100644 algo/detectors/trd/HitFinder2D.cxx
 create mode 100644 algo/detectors/trd/HitFinder2D.h
 create mode 100644 algo/detectors/trd/HitFinder2DPars.h
 create mode 100644 algo/detectors/trd/HitFinderPars.h
 create mode 100644 algo/detectors/trd/Hitfind.cxx
 create mode 100644 algo/detectors/trd/Hitfind.h
 create mode 100644 algo/detectors/trd/Hitfind2DSetup.h
 create mode 100644 algo/detectors/trd/HitfindSetup.h
 create mode 100644 macro/beamtime/mcbm2022/trd_hitfinder_run.C
 create mode 100644 reco/tasks/CbmTaskTrdHitFinder.cxx
 create mode 100644 reco/tasks/CbmTaskTrdHitFinder.h
 create mode 100644 reco/tasks/CbmTaskTrdHitFinderParWrite.cxx
 create mode 100644 reco/tasks/CbmTaskTrdHitFinderParWrite.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 7cbf4a011a..aaadf755c8 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -117,6 +117,15 @@ set(SRCS
   detectors/bmon/ReadoutConfig.cxx
   detectors/bmon/Unpack.cxx
   detectors/bmon/UnpackMS.cxx
+  detectors/trd/Cluster.cxx
+  detectors/trd/Clusterizer.cxx
+  detectors/trd/Cluster2D.cxx
+  detectors/trd/Clusterizer2D.cxx
+  detectors/trd/DigiRec.cxx
+  detectors/trd/Hit.cxx
+  detectors/trd/Hitfind.cxx
+  detectors/trd/HitFinder.cxx
+  detectors/trd/HitFinder2D.cxx
   detectors/trd/ReadoutConfig.cxx
   detectors/trd/Unpack.cxx
   detectors/trd/UnpackMS.cxx
diff --git a/algo/detectors/trd/Cluster.cxx b/algo/detectors/trd/Cluster.cxx
new file mode 100644
index 0000000000..867cfe1ef9
--- /dev/null
+++ b/algo/detectors/trd/Cluster.cxx
@@ -0,0 +1,24 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#include "Cluster.h"
+
+#include "CbmTrdDigi.h"
+
+namespace cbm::algo::trd
+{
+  //____________________________________________________________________
+  Cluster::Cluster(const std::vector<int32_t>& indices, const std::vector<const CbmTrdDigi*>& digis, int32_t address,
+                   uint16_t ncols, uint16_t nrows)
+    : fDigiInd()
+    , fDigis()
+    , fAddress(address)
+    , fNCols(ncols)
+  {
+    fDigiInd.assign(indices.begin(), indices.end());
+    fDigis.assign(digis.begin(), digis.end());
+    SetNRows(nrows);
+  }
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Cluster.h b/algo/detectors/trd/Cluster.h
new file mode 100644
index 0000000000..ea92a66a8f
--- /dev/null
+++ b/algo/detectors/trd/Cluster.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+class CbmTrdDigi;
+
+namespace cbm::algo::trd
+{
+  /**
+ * \class Cluster
+ * \author Dominik Smith <d.smith@gsi.de>
+ * \brief Data container for TRD clusters.
+ */
+  class Cluster {
+
+   public:
+    /**
+   * \brief Default constructor.
+   */
+    Cluster() = delete;
+    Cluster(const std::vector<int32_t>& indices, const std::vector<const CbmTrdDigi*>& digis, int32_t address,
+            uint16_t ncols, uint16_t nrows);
+
+    /**
+   * \brief Destructor.
+   */
+    virtual ~Cluster(){};
+
+    /**
+	 * \brief Number of digis in cluster.
+	 * \return Number of digis in cluster.
+	 */
+    size_t GetNofDigis() const { return fDigiInd.size(); }
+
+    /**
+	 * \brief Get digi at position index.
+	 * \param[in] index Position of digi in array.
+	 * \return Digi index in TClonesArray.
+	 */
+    int32_t GetDigi(int32_t index) const { return fDigiInd[index]; }
+
+    /**
+	 * \brief Get array of digi pointers.
+	 * \return Array of digi pointers.
+	 */
+    const std::vector<const CbmTrdDigi*>& GetDigis() const { return fDigis; }
+
+    /**
+	 * \brief Get array of digi indices.
+	 * \return Array of digi indices in TClonesArray.
+	 */
+    const std::vector<int32_t>& GetDigiIndices() const { return fDigiInd; }
+
+    /** Accessors **/
+    int32_t GetAddress() const { return fAddress; }
+    uint16_t GetStartCh() const { return fStartCh; }
+    uint16_t GetNCols() const { return fNCols; }
+    uint16_t GetNRows() const { return fNRows & 0x1f; }
+    uint16_t GetNRowsRaw() const { return fNRows; }
+    uint32_t GetStartTime() const { return fStartTime; }
+    bool HasFaspDigis() const { return false; }
+
+   private:
+    std::vector<int32_t> fDigiInd;          ///< Array of digi indices
+    std::vector<const CbmTrdDigi*> fDigis;  ///< Array of digi pointers
+
+    int32_t fAddress    = 0;     ///< Unique detector ID
+    uint8_t fNCols      = 0;     //< number of columns with charge above threshold
+    uint8_t fNRows      = 0x1f;  //< cluster row info plus extra meta data. Use dedicated getters for the correct value
+    uint16_t fStartCh   = 0xffff;      //< channel address of first channel
+    uint32_t fStartTime = 0xffffffff;  //! start time of cluster in clk units wrt buffer start
+
+    void SetNRows(uint16_t nrows)
+    {
+      fNRows &= (7 << 5);
+      fNRows |= (nrows & 0x1f);
+    }
+  };
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Cluster2D.cxx b/algo/detectors/trd/Cluster2D.cxx
new file mode 100644
index 0000000000..35253c2bfa
--- /dev/null
+++ b/algo/detectors/trd/Cluster2D.cxx
@@ -0,0 +1,149 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#include "Cluster2D.h"
+
+#include "CbmTrdDigi.h"
+
+#include <cmath>
+
+using std::vector;
+
+namespace cbm::algo::trd
+{
+  //____________________________________________________________________
+  Cluster2D::Cluster2D(const Cluster2D& ref)
+    : fDigis(ref.fDigis)
+    , fDigiIndices(ref.fDigiIndices)
+    , fAddress(ref.fAddress)
+    , fNCols(ref.fNCols)
+    , fNRows(ref.fNRows)
+    , fStartCh(ref.fStartCh)
+    , fStartTime(ref.fStartTime)
+  {
+  }
+
+  //____________________________________________________________________
+  Cluster2D::Cluster2D(int32_t address, int32_t idx, const CbmTrdDigi* digi, uint16_t chT, uint16_t chR, int32_t row,
+                       int32_t time)
+    : fAddress(address)
+    , fNCols(0)
+    , fStartCh(0xffff)
+    , fStartTime(time)
+  {
+    SetNRows(row);
+    SetStart(false);
+    SetStop(false);
+    AddDigi(idx, digi, chT, chR);
+  }
+
+  //____________________________________________________________________
+  bool Cluster2D::AddDigi(int32_t idx, const CbmTrdDigi* digi, uint16_t chT, uint16_t chR, int32_t dt)
+  {
+    /** Extend basic functionality of Cluster2D::AddDigi() for the case of 2D.
+ * If chT < 0 use the basic functionality [default].
+ * 
+ * For the 2D the parameters are intergpreted as follows
+ * chT : tilted paired channel [default 0x0fffffff]
+ * chR : rectangular paired channel
+ * dt  : offset in clks of the prompt signal
+ *
+ * if chT and chR positive the (chT, chR) are interpreted as the 2 channels
+ * of the digi specific to the 2D version. The following specific cases 
+ * can be distinguished : 
+ *  - ch == 0 : no data, cluster signal sequence terminator
+ *  - ch == -ch : no data, channel masked in HW 
+ */
+
+    if (chT == 0xffff) {  // basic functionality for rectangular pads
+      AddDigiIdxPair(idx, digi);
+      return true;
+    }
+
+    uint16_t chMin = (chT != 0 ? chT : chR), chMax = (chR != 0 ? chR : chT);
+
+    // assume triangular pads only
+    if (!fNCols) {  // first digi
+      fStartCh = chMin;
+      AddDigiIdxPair(idx, digi);
+    }
+    else if (chMin > GetEndCh()) {  // digi @ end
+      //if (HasStop()) return false;
+      AddDigiIdxPair(idx, digi);
+    }
+    else if (chMax < fStartCh) {  // digi @ beginning
+      //if (HasStart()) return false;
+      fStartCh                           = chMin;
+      vector<int32_t> idx_vec            = GetDigiIndices();
+      vector<const CbmTrdDigi*> digi_vec = GetDigis();
+      ClearDigis();
+      AddDigiIdxPair(idx, digi);
+      AddDigiIdxPairs(idx_vec, digi_vec);
+    }
+    int nch(0);
+    if (chT == 0)
+      SetStart();
+    else
+      nch++;
+    if (chR == 0)
+      SetStop();
+    else
+      nch++;
+
+    fNCols += nch;
+    if (dt > 0) fStartTime -= dt;
+
+    return true;
+  }
+
+  //____________________________________________________________________
+  int32_t Cluster2D::IsChannelInRange(uint16_t chT, uint16_t chR) const
+  {
+    if (!fNCols) return -2;
+    //   if(IsTerminatedLeft() && fAddressCh[0]>ch) return -1;
+    //   if(IsTerminatedRight() && fAddressCh[clSize-1]<ch) return 1;
+
+    uint16_t chMin = (chT != 0 ? chT : chR), chMax = (chR != 0 ? chR : chT);
+    if (fStartCh > chMax + 1) return -1;
+    if (fStartCh + fNCols < chMin) return 1;
+    return 0;
+  }
+
+  //____________________________________________________________________
+  bool Cluster2D::Merge(Cluster2D* second)
+  {
+    if (GetRow() != second->GetRow()) return false;
+    // time difference condition
+    if (fNCols == 1 || second->fNCols == 1) {
+      if (abs(int32_t(second->fStartTime - fStartTime)) > 50) return false;
+    }
+    else if (abs(int32_t(second->fStartTime - fStartTime)) > 20)
+      return false;
+    // look before current
+    if (second->fStartCh + second->fNCols == fStartCh) {
+      fStartCh = second->fStartCh;
+      fNCols += second->fNCols;
+      fStartTime = std::min(fStartTime, second->fStartTime);
+
+      vector<int32_t> idx_vec            = GetDigiIndices();
+      vector<const CbmTrdDigi*> digi_vec = GetDigis();
+      ClearDigis();
+      AddDigiIdxPairs(second->GetDigiIndices(), second->GetDigis());
+      AddDigiIdxPairs(idx_vec, digi_vec);
+      if (second->HasStart()) SetStart();
+      return true;
+    }
+
+    // look after current
+    if (fStartCh + fNCols == second->fStartCh) {
+      fNCols += second->fNCols;
+      fStartTime = std::min(fStartTime, second->fStartTime);
+      AddDigiIdxPairs(second->GetDigiIndices(), second->GetDigis());
+      if (second->HasStop()) SetStop();
+      return true;
+    }
+    return false;
+  }
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Cluster2D.h b/algo/detectors/trd/Cluster2D.h
new file mode 100644
index 0000000000..b2b362e350
--- /dev/null
+++ b/algo/detectors/trd/Cluster2D.h
@@ -0,0 +1,174 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#pragma once
+
+#include <cstdint>
+#include <vector>  // for vector
+
+#define BIT(n) (1ULL << (n))
+#define SETBIT(n, i) ((n) |= BIT(i))
+#define CLRBIT(n, i) ((n) &= ~BIT(i))
+#define TESTBIT(n, i) ((bool) (((n) &BIT(i)) != 0))
+
+class CbmTrdDigi;
+
+namespace cbm::algo::trd
+{
+  /**
+ * \class Cluster2D
+ * \author Dominik Smith <d.smith@gsi.de>
+ * \brief Data Container for TRD clusters.
+ */
+  class Cluster2D {
+
+   public:
+    enum eClusterDef
+    {
+      kFasp = 5  ///< set type of FEE digis contained
+        ,
+      kStart  ///< only for triangular if no T in first col
+        ,
+      kStop  ///< only for triangular if no R in last col
+    };
+
+    /**
+   * \brief Default constructor.
+   */
+    Cluster2D() = delete;
+    Cluster2D(const Cluster2D& ref);
+    /**
+   * \brief Constructor starting from first digit (FASP specific).
+   * \param[in] address global module address
+   * \param[in] idx global digi index in the TClonesArray
+   * \param[in] digi pointer to digi
+   * \param[in] chT RO channel address within the module for tilt pairing
+   * \param[in] chR RO channel address within the module for rect pairing
+   * \param[in] r module row for the RO channel
+   * \param[in] time relative buffer DAQ time
+   */
+    Cluster2D(int32_t address, int32_t idx, const CbmTrdDigi* digi, uint16_t chT, uint16_t chR, int32_t r,
+              int32_t time);
+    /**
+   * \brief Destructor.
+   */
+    virtual ~Cluster2D(){};
+
+    /**
+	 * \brief Add digi to cluster.
+	 * \param[in] index Digi index in TClonesArray.
+	 * \param[in] digi pointer to digi 
+	 */
+    void AddDigiIdxPair(int32_t index, const CbmTrdDigi* digi)
+    {
+      fDigiIndices.push_back(index);
+      fDigis.push_back(digi);
+    }
+
+    /**
+	 * \brief Add array of digi to cluster.
+	 * \param[in] indices Array of digi indices in TClonesArray.
+	 */
+    void AddDigiIdxPairs(const std::vector<int32_t>& indices, const std::vector<const CbmTrdDigi*> digis)
+    {
+      fDigiIndices.insert(fDigiIndices.end(), indices.begin(), indices.end());
+      fDigis.insert(fDigis.end(), digis.begin(), digis.end());
+    }
+
+    /**
+	 * \brief Number of digis in cluster.
+	 * \return Number of digis in cluster.
+	 */
+    int32_t GetNofDigis() const { return fDigiIndices.size(); }
+
+    /**
+	 * \brief Get digi at position index.
+	 * \param[in] index Position of digi in array.
+	 * \return Digi index in TClonesArray.
+	 */
+    int32_t GetDigi(int32_t index) const { return fDigiIndices[index]; }
+
+    /**
+	 * \brief Get array of digi indices.
+	 * \return Array of digi indices in TClonesArray.
+	 */
+    const std::vector<int32_t>& GetDigiIndices() const { return fDigiIndices; }
+
+    /**
+	 * \brief Get array of digi pointers.
+	 * \return Array of digi pointers
+	 */
+    std::vector<const CbmTrdDigi*>& GetDigis() { return fDigis; }
+
+
+    /**
+	 * \brief Remove all digis.
+	 */
+    void ClearDigis()
+    {
+      fDigiIndices.clear();
+      fDigis.clear();
+    }
+
+    /** Accessors **/
+    int32_t GetAddress() const { return fAddress; }
+
+    /** \brief Append digi to cluster
+   * \param[in] idx index of digi in TClonesArray
+   * \param[in] digi pointer to digi 
+   * \param[in] chT RO channel for digi (tilt pairing for FASP) default 0xffff (SPADIC)
+   * \param[in] chR RO channel for rect pairing (only for FASP)
+   * \param[in] dt update start time of cluster if current digi is prompt
+   * \return true if successful
+   */
+    bool AddDigi(int32_t idx, const CbmTrdDigi* digi, uint16_t chT = 0xffff, uint16_t chR = 0, int32_t dt = 0);
+
+    /** Accessors **/
+    uint16_t GetNCols() const { return fNCols; }
+    uint16_t GetNRows() const { return fNRows & 0x1f; }
+    uint16_t GetNRowsRaw() const { return fNRows; }
+    uint16_t GetEndCh() const { return fStartCh + fNCols - 1; }
+    uint16_t GetRow() const { return GetNRows(); }
+    uint16_t GetSize() const { return GetNCols(); }
+    uint16_t GetStartCh() const { return fStartCh; }
+    uint32_t GetStartTime() const { return fStartTime; }
+    bool HasFaspDigis() const { return true; }
+    bool HasStart() const { return TESTBIT(fNRows, kStart); }
+    bool HasStop() const { return TESTBIT(fNRows, kStop); }
+
+    /** \brief Query on RO channels list
+   * \param[in] chT tilted RO channel for digi
+   * \param[in] chR rectangular RO channel for digi
+   * \return -1 before range; 0 in range; 1 after range; -2 cluster empty of digits
+   */
+    int32_t IsChannelInRange(uint16_t chT, uint16_t chR) const;
+
+    /** \brief Merge current cluster with info from second
+   * \param[in] second cluster to be added
+   * \param[in] typ the type of pad-plane of the source chamber; true if Trd2d
+   * \return success or fail
+   */
+    bool Merge(Cluster2D* second);
+
+    /** Setters **/
+    void SetNCols(uint16_t ncols) { fNCols = ncols; }
+    void SetNRows(uint16_t nrows)
+    {
+      fNRows &= (7 << 5);
+      fNRows |= (nrows & 0x1f);
+    }
+    void SetStart(bool set = true) { set ? SETBIT(fNRows, kStart) : CLRBIT(fNRows, kStart); }
+    void SetStop(bool set = true) { set ? SETBIT(fNRows, kStop) : CLRBIT(fNRows, kStop); }
+
+   private:
+    std::vector<const CbmTrdDigi*> fDigis;  ///< Array of digi pointers
+    std::vector<int32_t> fDigiIndices;      ///< Array of digi indices
+
+    int32_t fAddress    = 0;     ///< Unique detector ID
+    uint8_t fNCols      = 0;     //< number of columns with charge above threshold
+    uint8_t fNRows      = 0x1f;  //< cluster row info plus extra meta data. Use dedicated getters for the correct value
+    uint16_t fStartCh   = 0xffff;      //< channel address of first channel
+    uint32_t fStartTime = 0xffffffff;  //! start time of cluster in clk units wrt buffer start
+  };
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Clusterizer.cxx b/algo/detectors/trd/Clusterizer.cxx
new file mode 100644
index 0000000000..3332d6d221
--- /dev/null
+++ b/algo/detectors/trd/Clusterizer.cxx
@@ -0,0 +1,203 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Etienne Bechtel, Florian Uhlig */
+
+#include "Clusterizer.h"
+
+#include <algorithm>
+#include <unordered_map>
+
+namespace cbm::algo::trd
+{
+
+  //_______________________________________________________________________________
+  std::vector<Cluster> Clusterizer::operator()(const std::vector<std::pair<CbmTrdDigi, int32_t>>& inVec) const
+  {
+    std::vector<inputType> inputData;  //digis storage;
+    std::vector<Cluster> clustersOut;
+    std::vector<inputType*> digiPtrVec;                //digis pointer storage (kSelf only);
+    std::vector<std::vector<inputType*>> digiMapSelf;  //channel-wise sorted input
+    std::vector<std::vector<inputType*>> digiMapNeig;  //channel-wise sorted input
+
+    const size_t numCols = fParams.rowPar[0].padPar.size();
+    const size_t numRows = fParams.rowPar.size();
+    const size_t numChan = numCols * numRows;
+
+    for (auto& input : inVec) {
+      inputData.push_back(std::make_tuple(input.second, &input.first, input.first.GetTime()));
+    }
+    ///// DRAW PICTURE HERE (LOGIC OF MAPS)
+
+    // Initialize the digi maps
+    digiMapSelf.resize(numChan);
+    digiMapNeig.resize(numChan);
+
+    for (auto& digidata : inputData) {
+      const CbmTrdDigi* digi = (const CbmTrdDigi*) std::get<1>(digidata);
+      if (!digi) continue;  // Skip invalid digis
+      const CbmTrdDigi::eTriggerType type = static_cast<CbmTrdDigi::eTriggerType>(digi->GetTriggerType());
+      const int channel                   = digi->GetAddressChannel();
+      if (type == CbmTrdDigi::eTriggerType::kSelf) {
+        digiMapSelf[channel].push_back(&digidata);
+        digiPtrVec.push_back(&digidata);
+      }
+      if (type == CbmTrdDigi::eTriggerType::kNeighbor) {
+        digiMapNeig[channel].push_back(&digidata);
+      }
+    }
+
+    //Store last position in input channels to avoid unnecessary checks.
+    std::vector<std::vector<inputType*>::iterator> lastChanPosSelf, lastChanPosNeig;
+    for (size_t chan = 0; chan < numChan; chan++) {
+      lastChanPosSelf.push_back(digiMapSelf[chan].begin());
+      lastChanPosNeig.push_back(digiMapNeig[chan].begin());
+    }
+
+    // search for an unprocessed main triggered digi and then start a subloop to
+    // directly construct the cluster  (search for main-trigger then add the neighbors)
+    for (auto mainit = digiPtrVec.begin(); mainit != digiPtrVec.end(); mainit++) {
+
+      // Skip if digi already processed
+      const CbmTrdDigi* digi = (const CbmTrdDigi*) std::get<1>(**mainit);
+      if (digi == nullptr) continue;
+
+      // get digi time
+      const double time = std::get<2>(**mainit);
+
+      // variety of neccessary address information; uses the "combiId" for the
+      // comparison of digi positions
+      const int digiId  = std::get<0>(**mainit);
+      const int channel = digi->GetAddressChannel();
+      const int ncols   = fParams.rowPar[0].padPar.size();
+
+      // some logic information which is used to process and find the clusters
+      int lowcol  = channel;
+      int highcol = channel;
+
+      // the "seal" bools register when the logical end of the cluster was found
+      bool sealtopcol = false;
+      bool sealbotcol = false;
+
+      // //vector which contains the actual cluster
+      std::vector<std::pair<int, const CbmTrdDigi*>> cluster;
+      cluster.emplace_back(digiId, digi);
+      std::get<1>(**mainit) = nullptr;
+
+      // loop to find the other pads corresponding to the main trigger
+      // is exited either if the implemented trigger logic is fullfilled
+      // or if there are no more adjacend pads due to edges,etc.
+      while (true) {
+
+        // counter which is used to easily break clusters which are at the edge and
+        // therefore do not fullfill the classical look
+        const size_t oldSize = cluster.size();
+
+        const int col = channel % ncols;
+        if (col == ncols) sealtopcol = true;  ///// Axel comment: change to ncols - 1, also for main!!
+        if (col == 0) sealbotcol = true;
+
+        if (!sealbotcol && 0 <= lowcol - 1) {
+          if (TryAddDigi(&digiMapSelf, lowcol - 1, &lastChanPosSelf, &cluster, time)) {
+            lowcol--;
+          }
+        }
+        if (!sealtopcol && highcol + 1 < numChan) {
+          if (TryAddDigi(&digiMapSelf, highcol + 1, &lastChanPosSelf, &cluster, time)) {
+            highcol++;
+          }
+        }
+        if (!sealbotcol && 0 <= lowcol - 1) {
+          if (TryAddDigi(&digiMapNeig, lowcol - 1, &lastChanPosNeig, &cluster, time)) {
+            sealbotcol = true;
+          }
+        }
+        if (!sealtopcol && highcol + 1 < numChan) {
+          if (TryAddDigi(&digiMapNeig, highcol + 1, &lastChanPosNeig, &cluster, time)) {
+            sealtopcol = true;
+          }
+        }
+
+        ////// Row merging was formerly here. To do: Implement as post-processing
+
+        // some finish criteria
+        if (cluster.size() - oldSize == 0) break;
+        if (sealbotcol && sealtopcol) break;
+      }  //!  while (true)
+
+      addClusters(cluster, &clustersOut);
+    }  //! for (auto mainit = inputData.begin(); mainit != inputData.end(); mainit++)
+
+    /// Row merging could be here in principle.
+
+    return clustersOut;
+  }
+
+  bool Clusterizer::TryAddDigi(std::vector<std::vector<inputType*>>* digimap, int chan,
+                               std::vector<std::vector<inputType*>::iterator>* lastPos,
+                               std::vector<std::pair<int, const CbmTrdDigi*>>* cluster, const double digiTime) const
+  {
+    const double interval = CbmTrdDigi::Clk(CbmTrdDigi::eCbmTrdAsicType::kSPADIC);
+
+    // find the FN digis of main trigger or adjacent main triggers
+    for (auto FNit = (*lastPos)[chan]; FNit != (*digimap)[chan].end(); FNit++) {
+
+      // update starting time
+      const double newtime = std::get<2>(**FNit);
+      if (newtime < digiTime - interval) {
+        (*lastPos)[chan]++;
+        continue;
+      }
+
+      // break if out of range
+      if (newtime > digiTime + interval) break;
+
+      // Skip already processed digis
+      const CbmTrdDigi* d = (const CbmTrdDigi*) std::get<1>(**FNit);
+      if (d == nullptr) continue;
+
+      // add digi to cluster
+      const int digiid = std::get<0>(**FNit);
+      cluster->emplace_back(digiid, d);
+      std::get<1>(**FNit) = nullptr;
+      return true;
+    }
+    return false;
+  }
+
+  //_____________________________________________________________________
+  void Clusterizer::addClusters(std::vector<std::pair<int, const CbmTrdDigi*>> cluster,
+                                std::vector<Cluster>* clustersOut) const
+  {
+    // create vector for indice matching
+    std::vector<int> digiIndices;
+    digiIndices.reserve(cluster.size());
+
+    // add digi ids to vector
+    std::transform(cluster.begin(), cluster.end(), std::back_inserter(digiIndices),
+                   [](const auto& pair) { return pair.first; });
+
+    // create vector for pointers
+    std::vector<const CbmTrdDigi*> digis;
+    digis.reserve(cluster.size());
+
+    // add digi ids to vector
+    std::transform(cluster.begin(), cluster.end(), std::back_inserter(digis),
+                   [](const auto& pair) { return pair.second; });
+
+    // Count rows and columns (TO DO: needs to be re-computed / moved if row merging is implemented)
+    std::unordered_map<int, bool> cols, rows;
+    for (const auto& pair : cluster) {
+      const int ncols = fParams.rowPar[0].padPar.size();
+      int digiChannel = pair.second->GetAddressChannel();
+      int colId       = digiChannel % ncols;
+      int globalRow   = digiChannel / ncols;
+      int combiId     = globalRow * ncols + colId;
+      cols[combiId]   = true;
+      rows[globalRow] = true;
+    }
+
+    // add the cluster to the Array
+    clustersOut->emplace_back(digiIndices, digis, fParams.address, cols.size(), rows.size());
+  }
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Clusterizer.h b/algo/detectors/trd/Clusterizer.h
new file mode 100644
index 0000000000..35a203b740
--- /dev/null
+++ b/algo/detectors/trd/Clusterizer.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Etienne Bechtel, Florian Uhlig */
+
+#pragma once
+
+#include "CbmTrdDigi.h"
+#include "Cluster.h"
+#include "HitFinderPars.h"
+
+#include <tuple>
+#include <vector>
+
+namespace cbm::algo::trd
+{
+
+  //// TO DO: Add monitoring data here!!
+
+  /** @class Clusterizer
+ ** @brief Algo class for TRD cluster building
+ ** @author Dominik Smith <d.smith@gsi.de>
+ ** @since 09.04.2024
+ **
+ **/
+
+  class Clusterizer {
+   public:
+    typedef std::tuple<int, const CbmTrdDigi*, double>
+      inputType;  //digi index, pointer to digi (null if processed), digi time
+
+    /** \brief Default constructor.*/
+    Clusterizer() = default;
+
+    /** \brief Default constructor.*/
+    Clusterizer(HitFinderModPar par) : fParams(par){};
+
+    /** @brief Destructor **/
+    virtual ~Clusterizer(){};
+
+    /** @brief Execution
+     ** @param  inVec       Digi data for one module 
+     ** @return Vector of constructed clusters 
+     **/
+    std::vector<Cluster> operator()(const std::vector<std::pair<CbmTrdDigi, int32_t>>& inVec) const;
+
+   protected:
+   private:
+    HitFinderModPar fParams;  ///< Parameter container
+
+    bool TryAddDigi(std::vector<std::vector<inputType*>>* digimap, int chan,
+                    std::vector<std::vector<inputType*>::iterator>* lastPos,
+                    std::vector<std::pair<int, const CbmTrdDigi*>>* cluster, const double digiTime) const;
+
+    void addClusters(std::vector<std::pair<int, const CbmTrdDigi*>> cluster, std::vector<Cluster>* clustersOut) const;
+  };
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Clusterizer2D.cxx b/algo/detectors/trd/Clusterizer2D.cxx
new file mode 100644
index 0000000000..3782ee8681
--- /dev/null
+++ b/algo/detectors/trd/Clusterizer2D.cxx
@@ -0,0 +1,119 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#include "Clusterizer2D.h"
+
+#include <algorithm>
+
+namespace cbm::algo::trd
+{
+
+  //_______________________________________________________________________________
+  std::vector<Cluster2D> Clusterizer2D::operator()(const std::vector<std::pair<CbmTrdDigi, int32_t>>& inVec,
+                                                   const uint64_t t0) const
+  {
+    const size_t numRows = fParams.rowPar.size();
+    const size_t numCols = fParams.rowPar[0].padPar.size();
+
+    std::vector<std::vector<Cluster2D*>> buffer(numRows);  //row-wise organized clusters
+    std::vector<size_t> bufferPos(numRows);                //storage of starting positon in buffers
+    std::vector<Cluster2D> clustersOut;                    //clusters to be returned
+    std::vector<inputType> inputData;                      //formatted input
+
+    // Apply input formatting
+    for (auto& input : inVec) {
+      const CbmTrdDigi* d = &input.first;
+      const size_t index  = input.second;
+      int pad             = d->GetAddressChannel();
+      uint16_t chT        = pad << 1;
+      uint16_t chR        = chT + 1;
+      int dtime;
+      double t;
+      double r      = d->GetCharge(t, dtime);  //subtract timeslice start time. maybe not needed anymore?
+      int tm        = d->GetTimeDAQ() - t0;
+      const int row = pad / numCols;
+      const int col = pad % numCols;
+      if (dtime < 0) tm += dtime;  // correct for the difference between tilt and rect
+      if (r < 1 && !fParams.rowPar[row].padPar[col].chRMasked) chR = 0;
+      if (t < 1 && !fParams.rowPar[row].padPar[col].chTMasked) chT = 0;
+      inputData.emplace_back(chT, chR, tm, row, index, d);
+    }
+
+    // Sort input by time. TO DO: Use insert-sort like in TOF hitfinder
+    std::sort(inputData.begin(), inputData.end(),
+              [](const auto& a, const auto& b) -> bool { return std::get<2>(a) < std::get<2>(b); });
+
+    // Fill row buffers
+    for (auto& in : inputData) {
+      auto& [chT, chR, tm, row, id, digi] = in;
+
+      // check for saved
+      if (buffer[row].empty()) {
+        buffer[row].push_back(new Cluster2D(fParams.address, id, digi, chT, chR, row, tm));
+        bufferPos[row] = 0;
+        continue;
+      }
+      /////// To do: Check whether position storing truly helps, after Cluster2D::Merge() is optimized
+
+      // Get stored starting position
+      auto itc = (buffer[row].begin() + bufferPos[row]);
+
+      //Update cluster
+      for (; itc != buffer[row].end(); itc++) {
+        if ((int64_t)(*itc)->GetStartTime() < tm - 5) {
+          bufferPos[row]++;
+          continue;
+        }
+        if ((*itc)->IsChannelInRange(chT, chR) == 0) {
+          const uint tc = (*itc)->GetStartTime();
+          (*itc)->AddDigi(id, digi, chT, chR, tc - tm);
+          break;
+        }
+      }
+      if (itc == buffer[row].end()) {
+        buffer[row].push_back(new Cluster2D(fParams.address, id, digi, chT, chR, row, tm));
+      }
+    }
+
+    // Build clusters
+    for (auto& clRow : buffer) {
+
+      // TO DO: Process the following remark from original code:
+      // TODO look left and right for masked channels. If they exists add them to cluster.
+      // if (AddClusterEdges(*itc0) && CWRITE(0)) cout << "  edge cl[0] : " << (*itc0)->ToString();
+
+      // Phase 0 : try merge clusters if more than one on a row
+      for (auto itc0 = clRow.begin(); itc0 != clRow.end(); itc0++) {
+        if (*itc0 == nullptr) {
+          continue;
+        }
+        for (auto itc1 = std::next(itc0); itc1 != clRow.end(); itc1++) {
+          if (*itc1 == nullptr) {
+            continue;
+          }
+          if ((*itc1)->GetStartTime() - (*itc0)->GetStartTime() > 50) {
+            break;
+          }
+          if ((*itc0)->Merge((*itc1))) {
+            delete (*itc1);
+            *itc1 = nullptr;
+            itc1  = itc0;
+          }
+        }
+      }
+
+      // Phase 1 : copy older clusters from the buffer to the module wise storage
+      for (auto& pcluster : clRow) {
+        if (pcluster == nullptr) {
+          continue;
+        }
+        clustersOut.emplace_back(*pcluster);
+        delete pcluster;
+        pcluster = nullptr;
+      }
+    }
+    return clustersOut;
+  }
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Clusterizer2D.h b/algo/detectors/trd/Clusterizer2D.h
new file mode 100644
index 0000000000..1fcf3134ed
--- /dev/null
+++ b/algo/detectors/trd/Clusterizer2D.h
@@ -0,0 +1,52 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#pragma once
+
+#include "CbmTrdDigi.h"
+#include "Cluster2D.h"
+#include "HitFinder2DPars.h"
+
+#include <tuple>
+#include <vector>
+
+namespace cbm::algo::trd
+{
+
+  //// TO DO: Add monitoring data here!!
+
+  /** @class Clusterizer2D
+ ** @brief Algo class for TRD2D cluster building
+ ** @author Dominik Smith <d.smith@gsi.de>
+ ** @since 05.04.2024
+ **
+ **/
+
+  class Clusterizer2D {
+   public:
+    typedef std::tuple<uint16_t, uint16_t, int, int, size_t, const CbmTrdDigi*>
+      inputType;  //Tuple: chT, chR, tm, row, id, digi
+
+    /** \brief Default constructor.*/
+    Clusterizer2D() = default;
+
+    /** \brief Default constructor.*/
+    Clusterizer2D(HitFinder2DModPar par) : fParams(par){};
+
+    /** @brief Destructor **/
+    virtual ~Clusterizer2D(){};
+
+    /** @brief Execution
+     ** @param  inVec       Digi data for one module 
+     ** @param  t0          Start time of timeslice
+     ** @return Vector of constructed clusters 
+     **/
+    std::vector<Cluster2D> operator()(const std::vector<std::pair<CbmTrdDigi, int32_t>>& inVec, uint64_t t0) const;
+
+   protected:
+   private:
+    HitFinder2DModPar fParams;  ///< Parameter container
+  };
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/DigiRec.cxx b/algo/detectors/trd/DigiRec.cxx
new file mode 100644
index 0000000000..f74ccbce31
--- /dev/null
+++ b/algo/detectors/trd/DigiRec.cxx
@@ -0,0 +1,123 @@
+/* Copyright (C) 2018-2020 Horia Hulubei National Institute of Physics and Nuclear Engineering, Bucharest
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Alexandru Bercuci[committer] */
+
+#include "DigiRec.h"
+
+#include <cstring>
+
+namespace cbm::algo::trd
+{
+
+  //// Taken from CbmTrdFASP. TO DO: Remove these magic numbers!!
+  float DigiRec::fgBaseline = 0.25;   //[V]  AB : match HW on mCBM22
+  float DigiRec::fgOutGain  = 2.025;  // [V/ 4095 ADC chs]
+
+  //_____________________________________________________________________________
+  DigiRec::DigiRec() : CbmTrdDigi(), fStatus(0)
+  {
+    fG[0] = 1.;
+    fG[1] = 1.;
+    memset(fT, 0, 3 * sizeof(double));
+  }
+
+  //_____________________________________________________________________________
+  DigiRec::DigiRec(const CbmTrdDigi& d, double* G, double* T) : CbmTrdDigi(d), fStatus(0)
+  {
+    if (G) {
+      fG[0] = G[0];
+      fG[1] = G[1];
+    }
+    else {
+      fG[0] = 1.;
+      fG[1] = 1.;
+    }
+    if (T)
+      memcpy(fT, T, 3 * sizeof(double));
+    else
+      memset(fT, 0, 3 * sizeof(double));
+
+    int dt;
+    double t, r = CbmTrdDigi::GetCharge(t, dt);
+    if (t > 4094) SETBIT(fStatus, 0);
+    if (r > 4094) SETBIT(fStatus, 1);
+  }
+
+  //_____________________________________________________________________________
+  DigiRec::DigiRec(const CbmTrdDigi& d, const CbmTrdDigi& dr, double* G, double* T) : CbmTrdDigi(d), fStatus(0)
+  {
+    /** Constructor and RAW digi merger
+*/
+    if (G) {
+      fG[0] = G[0];
+      fG[1] = G[1];
+    }
+    else {
+      fG[0] = 1.;
+      fG[1] = 1.;
+    }
+    if (T)
+      memcpy(fT, T, 3 * sizeof(double));
+    else
+      memset(fT, 0, 3 * sizeof(double));
+
+    int dt;
+    double t, r = dr.GetCharge(t, dt);
+    if (r > 4094) SETBIT(fStatus, 1);
+    CbmTrdDigi::GetCharge(t, dt);
+    if (t > 4094) SETBIT(fStatus, 0);
+    dt = dr.GetTimeDAQ() - GetTimeDAQ();
+    SetCharge(t, r, dt);
+    int rtrg(dr.GetTriggerType() & 2), ttrg(GetTriggerType() & 1);
+    SetTriggerType(rtrg | ttrg);  //merge the triggers
+  }
+
+  //_____________________________________________________________________________
+  double DigiRec::GetCharge(int typ, bool& on) const
+  {
+    int dT;
+    double T, R = CbmTrdDigi::GetCharge(T, dT);
+    on = true;
+    if (typ) {  // rectangular pairing
+      if (R > 0) {
+        R -= GetBaselineCorr();
+        return fG[1] * R;
+      }
+      else
+        on = false;
+    }
+    else {  // tilt pairing
+      if (T > 0) {
+        T -= GetBaselineCorr();
+        return fG[0] * T;
+      }
+      else
+        on = false;
+    }
+    return 0.;
+  }
+
+  //_____________________________________________________________________________
+  double DigiRec::GetTime(int typ) const
+  {
+    int dT;
+    double T, R = CbmTrdDigi::GetCharge(T, dT);
+    if (typ) {  // rectangular pairing
+      double R0 = R - fT[2];
+      return GetTimeDAQ() + dT + fT[0] * R0 * R0 + fT[1] * R0;
+    }
+    else {  // tilt pairing
+      double T0 = T - fT[2];
+      return GetTimeDAQ() + fT[0] * T0 * T0 + fT[1] * T0;
+    }
+  }
+
+  //_____________________________________________________________________________
+  void DigiRec::Init(double g[2], double t[3])
+  {
+    fG[0] = g[0];
+    fG[1] = g[1];
+    memcpy(fT, t, 3 * sizeof(double));
+  }
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/DigiRec.h b/algo/detectors/trd/DigiRec.h
new file mode 100644
index 0000000000..86e7136920
--- /dev/null
+++ b/algo/detectors/trd/DigiRec.h
@@ -0,0 +1,79 @@
+/* Copyright (C) 2018-2020 Horia Hulubei National Institute of Physics and Nuclear Engineering, Bucharest
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Alexandru Bercuci[committer] */
+
+#pragma once
+
+#include "CbmTrdDigi.h"
+
+#define BIT(n) (1ULL << (n))
+#define SETBIT(n, i) ((n) |= BIT(i))
+#define CLRBIT(n, i) ((n) &= ~BIT(i))
+#define TESTBIT(n, i) ((bool) (((n) &BIT(i)) != 0))
+
+/** @class DigiRec
+ ** @brief Extend the TRD(2D) digi class to incorporate FEE calibration. 
+ ** @author Alexandru Bercucic <abercuci@niham.nipne.ro>
+ ** @since 01.10.2021
+ ** @date 01.10.2021
+ **
+ ** The digi class contains the information as it is produced by the FEE (ASIC/GETS)
+ ** The variation from channel to channel is captured by running the pulser on anode wires
+ ** using various signal values, frequencies, etc. The calibrated baselines, gains, jitter, 
+ ** etc. are transported via the parameter files and are applied to the data within the digRec
+ ** class which is in the end used to calculate the TRD hit parameters.
+ **/
+
+namespace cbm::algo::trd
+{
+
+  class DigiRec : public CbmTrdDigi {
+    friend class HitFinder2D;
+
+   public:
+    /** \brief Default constructor*/
+    DigiRec();
+    /** \brief Wrap CbmTrdDigi constructor*/
+    DigiRec(const CbmTrdDigi& d, double* g = NULL, double* t = NULL);
+    virtual ~DigiRec() { ; }
+
+    /** \brief Return calibrated tilt signal
+   * \param[out] on flag signal exists */
+    double GetTiltCharge(bool& on) const { return GetCharge(0, on); }
+    /** \brief Return calibrated tilt time [ns]*/
+    double GetTiltTime() const { return GetTime(0); }
+    /** \brief Return calibrated rect signal
+   * \param[out] on flag signal exists */
+    double GetRectCharge(bool& on) const { return GetCharge(1, on); }
+    /** \brief Return calibrated rect time [ns]*/
+    double GetRectTime() const { return GetTime(1); }
+    /** \brief Return calibrated signal
+   * \param[in] typ tilt [0], rect [1] 
+   * \param[out] on flag signal exists
+   */
+    double GetCharge(int typ, bool& on) const;
+    /** \brief Return calibrated time
+   * \param[in] typ tilt [0], rect [1] 
+  */
+    double GetTime(int typ) const;
+    bool HasRectOvf() const { return TESTBIT(fStatus, 1); }
+    bool HasTiltOvf() const { return TESTBIT(fStatus, 0); }
+    /** \brief Init FEE gain and time walk corrections */
+    void Init(double g[2], double t[3]);
+
+    static float GetBaselineCorr() { return 4095. * fgBaseline / fgOutGain; }
+
+   protected:
+    /** \brief Constructor and merger*/
+    DigiRec(const CbmTrdDigi& dt, const CbmTrdDigi& dr, double* g = NULL, double* t = NULL);
+
+   private:
+    unsigned char fStatus;  //< bit map to store calibration flags
+    double fG[2];           //< FEE gain correction for channel T & R
+    double fT[3];           //< FEE time walk  correction as function of charge
+
+    static float fgBaseline;  ///< FASP baseline [V]
+    static float fgOutGain;   ///< FASP -> ADC gain [V/4095 ADC]
+  };
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Hit.cxx b/algo/detectors/trd/Hit.cxx
new file mode 100644
index 0000000000..363bf40f8b
--- /dev/null
+++ b/algo/detectors/trd/Hit.cxx
@@ -0,0 +1,83 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Matus Kalisky, Florian Uhlig, Andrey Lebedev */
+
+/**
+ * \file Hit.cxx
+ * \author Dominik Smith <d.smith@gsi.de>
+ * \date 2024
+ **/
+#include "Hit.h"
+
+namespace cbm::algo::trd
+{
+
+  Hit::Hit() : Hit(-1, 0., 0., 0., 0., 0., 0., 0., -1, -1., -1.) {}
+
+  Hit::Hit(int32_t address, const ROOT::Math::XYZVector& pos, const ROOT::Math::XYZVector& dpos, double dxy,
+           int32_t refId, double eLoss, double time, double timeError)
+    : Hit(address, pos.X(), pos.Y(), pos.Z(), dpos.X(), dpos.Y(), dpos.Z(), dxy, refId, time, timeError)
+  {
+    fELoss = eLoss;
+  }
+
+  Hit::Hit(int32_t address, double x, double y, double z, double dx, double dy, double dz, double dxy, int32_t refId,
+           double time, double timeError)
+    : fX(x)
+    , fY(y)
+    , fZ(z)
+    , fDx(dx)
+    , fDy(dy)
+    , fDz(dz)
+    , fDxy(dxy)
+    , fRefId(refId)
+    , fAddress(address)
+    , fTime(time)
+    , fTimeError(timeError)
+    , fDefine(0)
+    , fNeighborId(-1)
+    , fELoss(-1.)
+  {
+  }
+
+  Hit& Hit::operator=(const Hit& rhs)
+  {
+    if (this != &rhs) {
+      fX          = rhs.fX;
+      fY          = rhs.fY;
+      fZ          = rhs.fZ;
+      fDx         = rhs.fDx;
+      fDy         = rhs.fDy;
+      fDz         = rhs.fDz;
+      fDxy        = rhs.fDxy;
+      fRefId      = rhs.fRefId;
+      fAddress    = rhs.fAddress;
+      fTime       = rhs.fTime;
+      fTimeError  = rhs.fTimeError;
+      fDefine     = rhs.fDefine;
+      fNeighborId = rhs.fNeighborId;
+      fELoss      = rhs.fELoss;
+    }
+    return *this;
+  }
+
+  void Hit::Position(ROOT::Math::XYZVector& pos) const { pos.SetXYZ(GetX(), GetY(), GetZ()); }
+
+  void Hit::PositionError(ROOT::Math::XYZVector& dpos) const { dpos.SetXYZ(GetDx(), GetDy(), GetDz()); }
+
+  void Hit::SetPosition(const ROOT::Math::XYZVector& pos)
+  {
+    SetX(pos.X());
+    SetY(pos.Y());
+    SetZ(pos.Z());
+  }
+
+  void Hit::SetPositionError(const ROOT::Math::XYZVector& dpos)
+  {
+    SetDx(dpos.X());
+    SetDy(dpos.Y());
+    SetDz(dpos.Z());
+  }
+
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Hit.h b/algo/detectors/trd/Hit.h
new file mode 100644
index 0000000000..2f99d8e6fe
--- /dev/null
+++ b/algo/detectors/trd/Hit.h
@@ -0,0 +1,172 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Matus Kalisky, Florian Uhlig, Andrey Lebedev */
+
+/**
+ * \file Hit.h
+ * \author Dominik Smith <d.smith@gsi.de>
+ * \date 2024
+ * \brief A light-weight TRD hit class for online reconstruction, based on CbmTrdHit. .
+ **/
+
+#pragma once
+
+#include "CbmTrdAddress.h"  // for CbmTrdAddress
+#include "Math/Rotation3D.h"
+#include "Math/Vector3Dfwd.h"
+
+#include <Rtypes.h>      // for CLRBIT, SETBIT, TESTBIT, ClassDef
+#include <RtypesCore.h>  // for Double32_t
+
+#include <cstdint>
+#include <string>  // for string
+
+namespace cbm::algo::trd
+{
+
+  /** \class Hit
+ * \brief A light-weight TRD hit class for online reconstruction, based on CbmTrdHit. .
+ */
+
+  class Hit {
+   public:
+    enum HitDef
+    {
+      kType = 0  ///< set type of pad layout
+        ,
+      kMaxType  ///< set type of pad on which the maximum charge is found
+        ,
+      kRowCross  ///< mark hit defined by 2 clusters
+        ,
+      kOvfl  ///< mark over-flow in the data
+    };
+
+
+    /**
+	 * \brief Default constructor.
+	 */
+    Hit();
+    // Hit(const Hit&);
+    Hit& operator=(const Hit&);
+
+    /**
+	 * \brief Standard constructor.
+	  *\param address Unique detector ID
+	  *\param pos Position in global c.s. [cm]
+         *\param dpos Errors of position in global c.s. [cm]
+	  *\param dxy XY correlation of the hit
+	  *\param refId Index of digi or cluster
+	  *\param eLoss TR + dEdx
+	  **/
+    Hit(int32_t address, const ROOT::Math::XYZVector& pos, const ROOT::Math::XYZVector& dpos, double dxy, int32_t refId,
+        double eLoss, double time = 0., double timeError = 0.);
+
+    /**
+	 * \brief Standard constructor.
+	 * \param[in] address Detector unique identifier.
+	 * \param[in] x X position of the hit [cm].
+	 * \param[in] y Y position of the hit [cm].
+	 * \param[in] z Z position of the hit [cm].
+	 * \param[in] dx X position error of the hit [cm].
+	 * \param[in] dy Y position error of the hit [cm].
+	 * \param[in] dz Z position error of the hit [cm].
+	 * \param[in] dxy X-Y covariance of the hit [cm**2].
+	 * \param[in] refId Some reference ID.
+         * \param[in] time Hit time [ns].   
+         * \param[in] timeError Error of hit time [ns].         
+ 	 **/
+    Hit(int32_t address, double x, double y, double z, double dx, double dy, double dz, double dxy, int32_t refId,
+        double time = -1., double timeError = -1.);
+
+
+    /** \brief Destructor. */
+    ~Hit(){};
+
+    /* Accessors */
+    int32_t GetPlaneId() const { return CbmTrdAddress::GetLayerId(GetAddress()); }
+    double GetELoss() const { return fELoss; }
+    bool GetClassType() const { return TESTBIT(fDefine, kType); }
+    bool GetMaxType() const { return TESTBIT(fDefine, kMaxType); }
+    bool HasOverFlow() const { return TESTBIT(fDefine, kOvfl); }
+    bool IsRowCross() const { return TESTBIT(fDefine, kRowCross); }
+    bool IsUsed() const { return (GetRefId() < 0); }
+    double GetX() const { return fX; }
+    double GetY() const { return fY; }
+    double GetZ() const { return fZ; }
+    double GetDx() const { return fDx; }
+    double GetDy() const { return fDy; }
+    double GetDz() const { return fDz; }
+    double GetDxy() const { return fDxy; }
+    int32_t GetRefId() const { return fRefId; }
+    int32_t GetAddress() const { return fAddress; }
+    double GetTime() const { return fTime; }
+    double GetTimeError() const { return fTimeError; }
+
+    /**
+	 * \brief Copies hit position to pos.
+	 * \param pos Output hit position.
+	 */
+    void Position(ROOT::Math::XYZVector& pos) const;
+
+    /**
+	 * \brief Copies hit position error to pos.
+	 * \param pos Output hit position error.
+	 */
+    void PositionError(ROOT::Math::XYZVector& dpos) const;
+
+    /* Setters */
+    void SetELoss(double loss) { fELoss = loss; }
+    /** \brief Mark overflow in one or more digits which define the hit.*/
+    void SetOverFlow(bool set = true) { set ? SETBIT(fDefine, kOvfl) : CLRBIT(fDefine, kOvfl); }
+    /** \brief Mark hit reconstructed between pad rows.*/
+    void SetRowCross(bool set = true) { set ? SETBIT(fDefine, kRowCross) : CLRBIT(fDefine, kRowCross); }
+    /** \brief Type of pad layout used in reconstruction R[0], T[1]*/
+    void SetClassType(bool set = true) { set ? SETBIT(fDefine, kType) : CLRBIT(fDefine, kType); }
+    /** \brief Extra bool definition for the hit (e.g. the type of maximum for triangular pads).*/
+    void SetMaxType(bool set = true) { set ? SETBIT(fDefine, kMaxType) : CLRBIT(fDefine, kMaxType); }
+    void SetX(double x) { fX = x; }
+    void SetY(double y) { fY = y; }
+    void SetZ(double z) { fZ = z; }
+    void SetDx(double dx) { fDx = dx; }
+    void SetDy(double dy) { fDy = dy; }
+    void SetDz(double dz) { fDz = dz; }
+    void SetDxy(double dxy) { fDxy = dxy; }
+    void SetRefId(int32_t refId) { fRefId = refId; }
+    void SetAddress(int32_t address) { fAddress = address; }
+    void SetTime(double time) { fTime = time; }
+    void SetTime(double time, double error)
+    {
+      fTime      = time;
+      fTimeError = error;
+    }
+    void SetTimeError(double error) { fTimeError = error; }
+
+    /**
+	 * \brief Sets position of the hit.
+	 * \param pos new hit position.
+	 **/
+    void SetPosition(const ROOT::Math::XYZVector& pos);
+
+    /**
+	 * \breif Sets position error of the hit.
+	 * \param dpos new hit position error
+	 **/
+    void SetPositionError(const ROOT::Math::XYZVector& dpos);
+
+
+   private:
+    double fX, fY, fZ;     ///< X, Y, Z positions of hit [cm]
+    double fDx, fDy, fDz;  ///< X, Y, Z errors [cm]
+    double fDxy;           ///< X-Y covariance of the hit [cm**2]
+    int32_t fRefId;        ///< some reference id (usually to cluster, digi or MC point)
+    int32_t fAddress;      ///< detector unique identifier
+    double fTime;          ///< Hit time [ns]
+    double fTimeError;     ///< Error of hit time [ns]
+
+    uint8_t fDefine;      // hit extra info
+    int32_t fNeighborId;  // refId in case of row cross clusters
+    Double32_t fELoss;    // Energy deposit due to TR + dEdx
+  };
+
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/HitFinder.cxx b/algo/detectors/trd/HitFinder.cxx
new file mode 100644
index 0000000000..4faeaf608e
--- /dev/null
+++ b/algo/detectors/trd/HitFinder.cxx
@@ -0,0 +1,233 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Etienne Bechtel, Florian Uhlig */
+
+#include "HitFinder.h"
+
+#include "CbmTrdDigi.h"
+
+namespace cbm::algo::trd
+{
+  constexpr double HitFinder::kxVar_Value[2][5];
+  constexpr double HitFinder::kyVar_Value[2][5];
+
+  //_______________________________________________________________________________
+  HitFinder::HitFinder(HitFinderModPar params) : fParams(params) {}
+
+  //_______________________________________________________________________________
+  std::vector<Hit> HitFinder::operator()(std::vector<Cluster>* clusters)
+  {
+    const int nclusters = clusters->size();
+    std::vector<Hit> hits;
+
+    for (int icluster = 0; icluster < nclusters; icluster++) {
+      Cluster* cluster = &clusters->at(icluster);
+      auto hit         = MakeHit(icluster, cluster, &cluster->GetDigis(), nclusters);
+      if (hit.GetAddress() == -1) continue;
+      hits.push_back(hit);
+    }
+    return hits;
+  }
+
+  //_______________________________________________________________________________
+  Hit HitFinder::MakeHit(int clusterId, const Cluster* cluster, const std::vector<const CbmTrdDigi*>* digis, size_t)
+  {
+    ROOT::Math::XYZVector hit_posV(0.0, 0.0, 0.0);
+
+    double xVar        = 0;
+    double yVar        = 0;
+    double totalCharge = 0;
+    double time        = 0.;
+    int errorclass     = 0.;
+    bool EB            = false;
+    bool EBP           = false;
+
+    size_t skipped = 0;
+    for (auto& digi : *digis) {
+      if (!digi) {
+        skipped++;
+        continue;
+        std::cout << " no digi " << std::endl;
+      }
+
+      const double digiCharge = digi->GetCharge();
+
+      // D.Smith 15.4.24: These get overwritten for each digi. Should be OR?
+      errorclass = digi->GetErrorClass();
+      EB         = digi->IsFlagged(0);
+      EBP        = digi->IsFlagged(1);
+
+      if (digiCharge <= 0.05) {
+        skipped++;
+        continue;
+      }
+
+      time += digi->GetTime();
+      totalCharge += digiCharge;
+
+      int digiCol;
+      int digiRow = GetPadRowCol(digi->GetAddressChannel(), digiCol);
+
+      const ROOT::Math::XYZVector& local_pad_posV  = fParams.rowPar[digiRow].padPar[digiCol].pos;
+      const ROOT::Math::XYZVector& local_pad_dposV = fParams.rowPar[digiRow].padPar[digiCol].pos;
+
+      double xMin = local_pad_posV.X() - local_pad_dposV.X();
+      double xMax = local_pad_posV.X() + local_pad_dposV.X();
+      xVar += (xMax * xMax + xMax * xMin + xMin * xMin) * digiCharge;
+
+      double yMin = local_pad_posV.Y() - local_pad_dposV.Y();
+      double yMax = local_pad_posV.Y() + local_pad_dposV.Y();
+      yVar += (yMax * yMax + yMax * yMin + yMin * yMin) * digiCharge;
+
+      hit_posV.SetX(hit_posV.X() + local_pad_posV.X() * digiCharge);
+      hit_posV.SetY(hit_posV.Y() + local_pad_posV.Y() * digiCharge);
+      hit_posV.SetZ(hit_posV.Z() + local_pad_posV.Z() * digiCharge);
+    }
+    time /= digis->size() - skipped;
+
+    if (totalCharge <= 0) return Hit();
+
+    hit_posV.SetX(hit_posV.X() / totalCharge);
+    hit_posV.SetY(hit_posV.Y() / totalCharge);
+    hit_posV.SetZ(hit_posV.Z() / totalCharge);
+
+    if (EB) {
+      xVar = kxVar_Value[0][errorclass];
+      yVar = kyVar_Value[0][errorclass];
+    }
+    else {
+      if (EBP) time -= 46;  //due to the event time of 0 in the EB mode and the ULong in the the digi time
+      //TODO: move to parameter file
+      xVar = kxVar_Value[1][errorclass];
+      yVar = kyVar_Value[1][errorclass];
+    }
+
+    ROOT::Math::XYZVector global = fParams.translation + fParams.rotation(hit_posV);
+
+    // TO DO: REMOVE MAGIC NUMBERS!
+    if (!EB) {  // preliminary correction for angle dependence in the position
+                // reconsutrction
+      global.SetX(global.X() + (0.00214788 + global.X() * 0.000195394));
+      global.SetY(global.Y() + (0.00370566 + global.Y() * 0.000213235));
+    }
+
+    ROOT::Math::XYZVector cluster_pad_dposV(xVar, yVar, 0);
+    TransformHitError(cluster_pad_dposV);  //Gets overwritten below?
+
+    // TODO: get momentum for more exact spacial error
+    if ((fParams.orientation == 1) || (fParams.orientation == 3)) {
+      cluster_pad_dposV.SetX(sqrt(fParams.padSizeErrY));  // Original version uses padSizeErrY here for some reason
+      //cluster_pad_dposV.SetX( sqrt(fParams.padSizeErrX ) );  // x-component correct?
+    }
+    else {
+      cluster_pad_dposV.SetY(sqrt(fParams.padSizeErrY));
+    }
+
+    // Set charge of incomplete clusters (missing NTs) to -1 (not deleting them because they are still relevant for tracking)
+    if (!IsClusterComplete(cluster)) totalCharge = -1.0;
+
+    return Hit(fParams.address, global, cluster_pad_dposV, 0, clusterId, totalCharge / 1e6, time,
+               double(8.5));  // TODO: move to parameter file
+  }
+
+  void HitFinder::TransformHitError(ROOT::Math::XYZVector& hitErr) const
+  {
+    double x, y;
+    x = hitErr.X();
+    y = hitErr.Y();
+
+    if ((fParams.orientation == 1) || (fParams.orientation == 3)) {  // for orientations 1 or 3
+      hitErr.SetX(y);                                                // swap errors
+      hitErr.SetY(x);                                                // swap errors
+    }
+  }
+
+
+  double HitFinder::GetSpaceResolution(double val)
+  {
+
+    std::pair<double, double> res[12] = {
+      std::make_pair(0.5, 0.4),  std::make_pair(1, 0.35),   std::make_pair(2, 0.3),    std::make_pair(2.5, 0.3),
+      std::make_pair(3.5, 0.28), std::make_pair(4.5, 0.26), std::make_pair(5.5, 0.26), std::make_pair(6.5, 0.26),
+      std::make_pair(7.5, 0.26), std::make_pair(8.5, 0.26), std::make_pair(8.5, 0.26), std::make_pair(9.5, 0.26)};
+
+    double selval = 0.;
+
+    for (int n = 0; n < 12; n++) {
+      if (val < res[0].first) selval = res[0].second;
+      if (n == 11) {
+        selval = res[11].second;
+        break;
+      }
+      if (val >= res[n].first && val <= res[n + 1].first) {
+        double dx    = res[n + 1].first - res[n].first;
+        double dy    = res[n + 1].second - res[n].second;
+        double slope = dy / dx;
+        selval       = (val - res[n].first) * slope + res[n].second;
+        break;
+      }
+    }
+
+    return selval;
+  }
+
+  bool HitFinder::IsClusterComplete(const Cluster* cluster)
+  {
+    int colMin = fParams.rowPar[0].padPar.size();  //numCols
+    int rowMin = fParams.rowPar.size();            //numRows
+
+    for (int i = 0; i < cluster->GetNofDigis(); ++i) {
+      // const CbmTrdDigi* digi = CbmDigiManager::Instance()->Get<CbmTrdDigi>(cluster->GetDigi(i));
+      const CbmTrdDigi* digi = (cluster->GetDigis())[i];
+
+      int digiCol;
+      int digiRow = GetPadRowCol(digi->GetAddressChannel(), digiCol);
+
+      if (digiCol < colMin) colMin = digiCol;
+      if (digiRow < rowMin) rowMin = digiRow;
+    }
+
+    const uint16_t nCols = cluster->GetNCols();
+    const uint16_t nRows = cluster->GetNRows();
+
+    CbmTrdDigi* digiMap[nRows][nCols];                        //create array on stack for optimal performance
+    memset(digiMap, 0, sizeof(CbmTrdDigi*) * nCols * nRows);  //init with nullpointers
+
+    for (int i = 0; i < cluster->GetNofDigis(); ++i) {
+      //    const CbmTrdDigi* digi = CbmDigiManager::Instance()->Get<CbmTrdDigi>(cluster->GetDigi(i));
+      const CbmTrdDigi* digi = (cluster->GetDigis())[i];
+
+      int digiCol;
+      int digiRow = GetPadRowCol(digi->GetAddressChannel(), digiCol);
+
+      if (digiMap[digiRow - rowMin][digiCol - colMin])
+        return false;  // To be investigated why this sometimes happens (Redmin Issue 2914)
+
+      digiMap[digiRow - rowMin][digiCol - colMin] = const_cast<CbmTrdDigi*>(digi);
+    }
+
+    // check if each row of the cluster starts and ends with a kNeighbor digi
+    for (int iRow = 0; iRow < nRows; ++iRow) {
+      int colStart = 0;
+      while (digiMap[iRow][colStart] == nullptr)
+        ++colStart;
+      if (digiMap[iRow][colStart]->GetTriggerType() != static_cast<int>(CbmTrdDigi::eTriggerType::kNeighbor))
+        return false;
+
+      int colStop = nCols - 1;
+      while (digiMap[iRow][colStop] == nullptr)
+        --colStop;
+      if (digiMap[iRow][colStop]->GetTriggerType() != static_cast<int>(CbmTrdDigi::eTriggerType::kNeighbor))
+        return false;
+    }
+
+    return true;
+  }
+
+  int HitFinder::GetPadRowCol(int address, int& c)
+  {
+    c = address % fParams.rowPar[0].padPar.size();
+    return address / fParams.rowPar[0].padPar.size();
+  }
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/HitFinder.h b/algo/detectors/trd/HitFinder.h
new file mode 100644
index 0000000000..3f4475fcc9
--- /dev/null
+++ b/algo/detectors/trd/HitFinder.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Etienne Bechtel, Florian Uhlig */
+
+#pragma once
+
+#include "Cluster.h"
+#include "Hit.h"
+#include "HitFinderPars.h"
+#include "Math/Rotation3D.h"
+#include "Math/Vector3Dfwd.h"
+
+#include <tuple>
+#include <vector>
+
+class CbmTrdDigi;
+
+namespace cbm::algo::trd
+{
+  /**
+  * \brief Rectangular pad module; Cluster finding and hit reconstruction algorithms
+  **/
+  class HitFinder {
+   public:
+    HitFinder(){};
+    /**
+  * \brief Constructor with placement
+  **/
+    HitFinder(HitFinderModPar params);
+    virtual ~HitFinder(){};
+
+    /* \brief Steering routine for building hits */
+    std::vector<Hit> operator()(std::vector<Cluster>* clusters);
+
+    double GetSpaceResolution(double val = 3.0);
+    bool IsClusterComplete(const Cluster* cluster);
+
+
+   protected:
+   private:
+    typedef std::tuple<int, const CbmTrdDigi*, double>
+      inputType;  //digi index, pointer to digi (null if processed), digi time
+
+    HitFinder(const HitFinder& ref);
+    const HitFinder& operator=(const HitFinder& ref);
+
+    Hit MakeHit(int cId, const Cluster* c, const std::vector<const CbmTrdDigi*>* digis, size_t);
+
+    /** \brief Addressing ASIC on module based on id
+   * \param[in] id module wise ASIC identifier
+   * \return ASIC address within experiment
+   */
+    inline int GetPadRowCol(int address, int& c);
+
+    HitFinderModPar fParams;  ///< Parameter container
+
+    void TransformHitError(ROOT::Math::XYZVector& hitErr) const;
+
+    // different error classes for the position resolution based on the simulation results
+    // the error classes are defined for the different module types
+    // TODO: move to parameter file
+    static constexpr double kxVar_Value[2][5] = {{0.0258725, 0.0267693, 0.0344325, 0.0260322, 0.040115},
+                                                 {0.0426313, 0.0426206, 0.0636962, 0.038981, 0.0723851}};
+    static constexpr double kyVar_Value[2][5] = {{0.024549, 0.025957, 0.0250713, 0.0302682, 0.0291146},
+                                                 {0.0401438, 0.0407502, 0.0397242, 0.0519485, 0.0504586}};
+  };
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/HitFinder2D.cxx b/algo/detectors/trd/HitFinder2D.cxx
new file mode 100644
index 0000000000..d3b8a0628f
--- /dev/null
+++ b/algo/detectors/trd/HitFinder2D.cxx
@@ -0,0 +1,934 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#include "HitFinder2D.h"
+
+#include "log.hpp"
+
+#include <numeric>
+
+using std::cout;
+using std::endl;
+using std::vector;
+
+namespace cbm::algo::trd
+{
+
+  //////////// TO DO: fdEdep is not used!!! Will be needed at some point.
+  double HitFinder2D::fgDT[] = {4.181e-6, 1586, 24};
+
+  //_______________________________________________________________________________
+  HitFinder2D::HitFinder2D(HitFinder2DModPar params) : fParams(params), fHit(params.rowPar[0].padPar.size()) {}
+
+  //_______________________________________________________________________________
+  std::vector<Hit> HitFinder2D::operator()(std::vector<Cluster2D>* clusters)
+  {
+    const int nclusters = clusters->size();
+    std::vector<Hit> hits;
+
+    for (int icluster = 0; icluster < nclusters; icluster++) {
+      Cluster2D* cluster = &clusters->at(icluster);
+      auto hit           = MakeHit(icluster, cluster, &cluster->GetDigis(), nclusters);
+      if (hit.GetAddress() == -1) continue;
+      hits.push_back(hit);
+    }
+
+    int a0, nhits = hits.size();
+    float Dx(2 * fParams.padSizeX), Dy(2 * fParams.padSizeY);
+    for (int ih(0); ih < nhits; ih++) {
+      Hit* h0 = &hits[ih];
+      if (h0->IsUsed()) continue;
+
+      for (int jh(ih + 1); jh < nhits; jh++) {
+        Hit* h1 = &hits[jh];
+        if (h1->IsUsed()) continue;
+
+        // basic check on Time
+        if (h1->GetTime() < 4000 - h0->GetTime()) continue;  // TODO check with preoper time calibration
+        // skip next hits for being too far (10us) in the future
+        if (h1->GetTime() > 10000 + h0->GetTime()) break;
+
+        // basic check on Row
+        if (std::abs(h1->GetY() - h0->GetY()) > Dy) continue;
+
+        // basic check on Col
+        if (std::abs(h1->GetX() - h0->GetX()) > Dx) continue;
+
+        // go to topologic checks
+        if (!(a0 = CheckMerge(h0->GetRefId(), h1->GetRefId()))) continue;
+
+        ProjectDigis(a0 < 0 ? h0->GetRefId() : h1->GetRefId(), a0 < 0 ? h1->GetRefId() : h0->GetRefId());
+
+        // call the working algorithm
+        if (MergeHits(h0, a0)) {
+          h0->SetRowCross((bool) h1);
+          h1->SetRefId(-1);
+        }
+      }
+    }
+
+    const auto ret = std::remove_if(hits.begin(), hits.end(), [](const auto& obj) { return obj.IsUsed(); });
+    hits.erase(ret, hits.end());
+
+    // clear all calibrated digis
+    for (auto& vec : fDigis) {
+      for (auto& digiPtr : vec)
+        delete digiPtr;
+      vec.clear();
+    }
+    fDigis.clear();
+
+    return hits;
+  }
+
+  //_______________________________________________________________________________
+  int HitFinder2D::CheckMerge(int cid, int cjd)
+  {
+    /** Check topologic conditions if the 2 clusters (in digi representation) can be merged.
+ * The first pair is always from the bottom row
+ * return the anode candidate wrt boundary 1, 2, 3 for the first 3 anodes of the upper row; -1, -2, -3 for the bottom row (r0) or 0 if the check fails  
+ */
+
+    bool on(kFALSE);
+    int vc[2] = {-1, -1}, vm[2] = {0};
+    double M[2] = {-1., -1.}, S[2] = {0.};
+    vector<DigiRec*>::iterator jp[2];
+
+    for (int rowId(0); rowId < 2; rowId++) {
+      double rtMax      = 0.;
+      double mdMax      = 0.;
+      const int vcid[2] = {cid, cjd};
+      for (auto id = fDigis[vcid[rowId]].begin(); id != fDigis[vcid[rowId]].end(); id++) {
+        int col;
+        GetPadRowCol((*id)->GetAddressChannel(), col);
+
+        // mark max position and type
+        const double t = (*id)->GetTiltCharge(on);
+        if (on && t > rtMax) {
+          vc[rowId] = col;
+          vm[rowId] = 0;
+          rtMax     = t;
+        }
+        const double r = (*id)->GetRectCharge(on);
+        if (on && r > rtMax) {
+          vc[rowId] = col;
+          vm[rowId] = 1;
+          rtMax     = r;
+        }
+
+        double m = 0.;
+        double d = 0.;
+        if (!rowId) {  // compute TR pairs on the bottom row
+          m = 0.5 * (t + r);
+          d = r - t;
+        }
+        else {  // compute RT pairs on the upper row
+          auto jd  = std::next(id);
+          double T = 0;
+          if (jd != fDigis[vcid[rowId]].end()) T = (*jd)->GetTiltCharge(on);
+          m = 0.5 * (r + T);
+          d = r - T;
+        }
+        if (std::abs(m) > 0.) d = 1.e2 * d / m;
+        if (m > mdMax) {
+          mdMax     = m;
+          M[rowId]  = m;
+          S[rowId]  = d;
+          jp[rowId] = id;
+        }
+      }
+    }
+    int rowMax = M[0] > M[1] ? 0 : 1;
+
+    // basic check on col of the max signal
+    const int dc = vc[1] - vc[0];
+    if (dc < 0 || dc > 1) return 0;
+
+    // special care for both tilt maxima :  the TT case
+    // recalculate values on the min row on neighbor column
+    if (!vm[0] && !vm[1]) {
+      if (rowMax == 0) {  // modify r=1
+        double r = 0.;
+        double T = 0.;
+        if (M[1] >= 0) {
+          if (jp[1] != fDigis[cjd].end()) jp[1]++;
+          if (jp[1] != fDigis[cjd].end()) {
+            r = (*jp[1])->GetRectCharge(on);
+            jp[1]++;
+            if (jp[1] != fDigis[cjd].end()) T = (*jp[1])->GetTiltCharge(on);
+          }
+        }
+        M[1] = 0.5 * (r + T);
+        S[1] = r - T;
+      }
+      else {  // modify r=0
+        double r = 0.;
+        double t = 0.;
+        if (M[0] >= 0) {
+          if (jp[0] != fDigis[cid].begin()) jp[0]--;
+          if (jp[0] != fDigis[cid].begin()) {
+            r = (*jp[0])->GetRectCharge(on);
+            t = (*jp[0])->GetTiltCharge(on);
+          }
+        }
+        M[0] = 0.5 * (t + r);
+        S[0] = r - t;
+      }
+    }
+    rowMax = M[0] > M[1] ? 0 : 1;
+
+    // Build the ratio of the charge
+    const float mM = M[rowMax ? 0 : 1] / M[rowMax];
+    const float mS = std::abs(S[rowMax]), mM_l[3] = {0.15, 0.5, 1}, mM_r[3] = {0, 0.28, 1}, mS_r[3] = {43, 27, 20};
+    float dSdM[2], S0[2];
+
+    for (int i(0); i < 2; i++) {
+      dSdM[i] = (mS_r[i + 1] - mS_r[i]) / (mM_r[i + 1] - mM_r[i]);
+      S0[i]   = mS_r[i] - dSdM[i] * mM_r[i];
+    }
+    const int irange = mM < mM_r[1] ? 0 : 1;
+    if (mS > S0[irange] + dSdM[irange] * mM) return 0;
+
+    for (int ia(0); ia < 3; ia++) {
+      if (mM < mM_l[ia]) return (rowMax ? 1 : -1) * (3 - ia);
+    }
+    return 0;
+  }
+
+
+  //_______________________________________________________________________________
+  bool HitFinder2D::MergeHits(Hit* h, int a0)
+  {
+    int n0(fHit.fSignal.size() - 2);
+    auto [dx, dy] = fHit.GetDxDy(n0);
+
+    int typ(fHit.GetHitClass());
+    // get position correction [pw]
+    double xcorr = fHit.GetXcorr(dx, typ, 1) / fParams.padSizeX, xcorrBias(xcorr);
+    if (fHit.IsBiasX()) {
+      typ      = fHit.GetHitRcClass(a0);
+      int xmap = fHit.vyM & 0xff;
+      switch (n0) {
+        case 4:
+          if (dx < 0)
+            xcorrBias += (fHit.IsBiasXleft() ? -0.12 : 0.176);
+          else
+            xcorrBias += (xmap == 53 || xmap == 80 || xmap == 113 || xmap == 117 ? -0.176 : 0.12);
+          break;
+        case 5:
+        case 7:
+          if (typ < 0) break;
+          if (xmap == 50 || xmap == 58 || xmap == 146 || xmap == 154) {
+            if (typ == 2)
+              xcorr += 0.0813;
+            else if (typ == 3) {
+              xcorr -= 0.0813;
+              typ = 2;
+            }
+            dx -= xcorr;
+            fHit.RecenterXoffset(dx);
+            xcorrBias = fHit.GetXcorr(dx, typ, 2) / fParams.padSizeX;
+          }
+          else {
+            dx -= xcorr;
+            fHit.RecenterXoffset(dx);
+            if (typ < 2)
+              xcorrBias = fHit.GetXcorr(dx, typ, 2) / fParams.padSizeX;
+            else if (typ == 2)
+              xcorrBias = 0.12;
+            else if (typ == 3)
+              xcorrBias = -0.12;
+          }
+          break;
+        default:
+          if (typ < 0)
+            break;
+          else if (typ == 2)
+            xcorr += 0.0813;
+          else if (typ == 3) {
+            xcorr -= 0.0813;
+            typ = 2;
+          }
+          dx -= xcorr;
+          fHit.RecenterXoffset(dx);
+          xcorrBias = fHit.GetXcorr(dx, typ, 2) / fParams.padSizeX;
+          break;
+      }
+    }
+    else {
+      if (typ) xcorrBias += (dx < 0 ? 1 : -1) * 0.0293;
+    }
+    std::tie(dx, dy) = CorrectPosition(dx, dy, xcorrBias);
+
+    double edx(1), edy(1), edt(60), time(-21), tdrift(100), e(200);
+    CalibrateHit(h, dx, dy, edx, edy, edt, time, tdrift, e);
+
+    return kTRUE;
+  }
+
+  //_______________________________________________________________________________
+  bool HitFinder2D::BuildHit(Hit* h)
+  {
+    const int n0  = fHit.fSignal.size() - 2;
+    auto [dx, dy] = fHit.GetDxDy(n0);
+
+    // get position correction
+    const double xcorr = fHit.GetXcorr(dx, fHit.GetHitClass()) / fParams.padSizeX;
+    std::tie(dx, dy)   = CorrectPosition(dx, dy, xcorr);
+
+    // get anode wire offset
+    for (int ia = 0; ia <= NANODE; ia++) {
+      const float ya = -1.35 + ia * 0.3;  //anode position in local pad coordinates
+      if (dy > ya + 0.15) continue;
+      break;
+    }
+
+    // Error parametrization X : parabolic model on cl size
+    const double parX[] = {0.713724, -0.318667, 0.0366036};
+    const double parY[] = {0.0886413, 0., 0.0435141};
+    const int nex       = std::min(n0, 7);
+    const double edx    = (n0 < 3) ? 1. : parX[0] + parX[1] * nex + parX[2] * nex * nex;
+    const double edy    = (n0 < 3) ? 1. : parY[0] + parY[2] * dy * dy;
+    const double edt    = (n0 < 3) ? 60. : 26.33;  // should be parametrized as function of da TODO
+
+    // COMPUTE TIME
+    double t_avg = 0.;
+    for (int idx = 1; idx <= n0; idx++) {
+      HitFactory2D::signal& sig = fHit.fSignal[idx];
+      const double dtFEE        = fgDT[0] * (sig.s - fgDT[1]) * (sig.s - fgDT[1]) / fClk;
+      t_avg += (sig.t - dtFEE);
+      if (sig.xe > 0) sig.x += dy / fParams.padSizeY;
+    }
+
+    const double time   = (n0 > 1) ? t_avg / n0 : -21.;
+    const double tdrift = 100.;  // should depend on Ua
+
+    // COMPUTE ENERGY (conversion to GeV)
+    const double e_avg = 1.e-9
+                         * std::accumulate(fHit.fSignal.begin(), fHit.fSignal.end(), 0,
+                                           [](int sum, const auto& sig) { return sum + sig.s; });
+
+    CalibrateHit(h, dx, dy, edx, edy, edt, time, tdrift, e_avg);
+    return kTRUE;
+  }
+
+  //_______________________________________________________________________________
+  std::pair<double, double> HitFinder2D::CorrectPosition(double dx, double dy, const double xcorr)
+  {
+    dx -= xcorr;
+    fHit.RecenterXoffset(dx);
+    dy = dx - dy;
+    fHit.RecenterYoffset(dy);
+
+    const double ycorr = fHit.GetYcorr(dy) / fParams.padSizeY;
+    dy += ycorr;
+    fHit.RecenterYoffset(dy);
+    dx *= fParams.padSizeX;
+    dy *= fParams.padSizeY;
+    return std::make_pair(dx, dy);
+  }
+
+  //_______________________________________________________________________________
+  void HitFinder2D::CalibrateHit(Hit* h, const double dx, const double dy, const double edx, const double edy,
+                                 const double edt, const double time, const double tdrift, const double eloss)
+  {
+    const ROOT::Math::XYZVector& local_pad = fParams.rowPar[fHit.vrM].padPar[fHit.vcM].pos;
+    const ROOT::Math::XYZVector local(local_pad.X() + dx, local_pad.Y() + dy, local_pad.Z());
+    const ROOT::Math::XYZVector global = fParams.translation + fParams.rotation(local);
+    h->SetX(global.X());
+    h->SetY(global.Y());
+    h->SetZ(global.Z());
+
+    h->SetDx(edx);
+    h->SetDy(edy);
+    h->SetDz(0.);
+    h->SetDxy(0.);
+    h->SetTime(fClk * (fHit.vt0 + time) - tdrift + 30.29 + fHitTimeOff, edt);
+    h->SetELoss(eloss);
+    h->SetClassType();
+    h->SetMaxType(fHit.IsMaxTilt());
+    h->SetOverFlow(fHit.HasOvf());
+  }
+
+  //_______________________________________________________________________________
+  Hit HitFinder2D::MakeHit(int ic, const Cluster2D* cl, std::vector<const CbmTrdDigi*>* digis, size_t nclusters)
+  {
+    if (fDigis.empty()) {
+      fDigis.resize(nclusters);
+    }
+
+    Hit hit;
+    if (!LoadDigis(digis, ic)) return hit;
+    if (!ProjectDigis(ic)) return hit;
+
+    hit.SetAddress(fParams.address);
+    hit.SetRefId(ic);
+    BuildHit(&hit);
+    return hit;
+  }
+
+  //_______________________________________________________________________________
+  int HitFinder2D::LoadDigis(vector<const CbmTrdDigi*>* din, int cid)
+  {
+    /** Load RAW digis info in calibration aware strucuture CbmTrdDigiReco
+ * Do basic sanity checks; also incomplete adjacent digi and if found merge them.
+ */
+
+    int last_col(-1);
+    for (auto i = din->begin(); i != din->end(); i++) {
+      if ((*i) == nullptr) continue;
+      int colT(-1), colR(-1);
+      const CbmTrdDigi* dgT = (*i);
+      GetPadRowCol(dgT->GetAddressChannel(), colT);
+
+      // check column order for cluster  /// TO DO: we can probably drop this check
+      if (last_col >= 0 && colT != last_col + 1) {
+        L_(error) << "TrdModuleRec2D::LoadDigis : digis in cluster " << cid << " not in increasing order !";
+        return 0;
+      }
+
+      last_col              = colT;
+      const CbmTrdDigi* dgR = nullptr;
+
+      auto j = std::next(i);
+      if (j != din->end()) {
+        dgR = (*j);
+        GetPadRowCol(dgR->GetAddressChannel(), colR);
+      }
+      if (colR == colT && dgR != nullptr) {
+        fDigis[cid].emplace_back(new DigiRec(*dgT, *dgR));
+        (*j) = nullptr;
+      }
+      else
+        fDigis[cid].emplace_back(new DigiRec(*dgT));
+    }
+    return fDigis[cid].size();
+  }
+
+  //_______________________________________________________________________________
+  int HitFinder2D::ProjectDigis(int cid, int cjd)
+  {
+    /** Load digis information in working vectors Digis are represented in the normal coordinate system of
+   * (pad width [pw], DAQ time [clk], signal [ADC chs]) with rectangular signals at integer 
+   * positions.  
+   */
+
+    if (fDigis[cid].empty()) {
+      L_(debug) << "TrdModuleRec2D::ProjectDigis : Request cl id " << cid << " not found.";
+      return 0;
+    }
+
+    std::vector<HitFactory2D::signal>& hitSig = fHit.fSignal;
+    fHit.reset();
+
+    bool on(0);              // flag signal transmition
+    int n0(0), nsr(0),       // no of signals in the cluster (all/rect)
+      NR(0),                 // no of rect signals/channel in case of RC
+      NT(0),                 // no of tilt signals/channel in case of RC
+      ovf(1);                // over-flow flag for at least one of the digis
+    Char_t dt0(0);           // cluster time offset wrt arbitrary t0
+    double err,              // final error parametrization for signal
+      xc(-0.5),              // running signal-pad position
+      max(0.);               // max signal
+    const double re = 100.;  // rect signal
+    const double te = 100.;  // tilt signal
+
+    int j(0), col(-1), col0(0), col1(0), step(0), row1;
+
+    // link second row if needed
+    vector<DigiRec*>::iterator i1;
+    if (cjd >= 0) {
+      if (fDigis[cjd].empty()) {
+        L_(debug) << "TrdModuleRec2D::ProjectDigis : Request cl id " << cjd << " not found. Skip second row.";
+        cjd = -1;
+      }
+      else
+        i1 = fDigis[cjd].begin();
+    }
+
+    for (auto i = fDigis[cid].begin(); i != fDigis[cid].end(); i++, j++) {
+      const DigiRec* dg  = (*i);
+      const DigiRec* dg0 = nullptr;
+      const DigiRec* dg1 = nullptr;
+
+      //  initialize
+      if (col < 0) {
+        fHit.vrM = GetPadRowCol(dg->GetAddressChannel(), col);
+        fHit.vt0 = dg->GetTimeDAQ();  // set arbitrary t0 to avoid double digis loop
+      }
+      GetPadRowCol(dg->GetAddressChannel(), col0);
+      int nt = 0;  // no of tilt signals/channel in case of RC
+      int nr = 0;  // no of rect signals/channel in case of RC
+
+      // read calibrated signals
+      double t = dg->GetTiltCharge(on);
+      if (on) nt = 1;
+      double r = dg->GetRectCharge(on);
+      if (on) nr = 1;
+      // look for matching neighbor digis in case of pad row cross
+      if (cjd >= 0) {
+        if ((dg0 = (i1 != fDigis[cjd].end()) ? (*i1) : nullptr)) {
+          row1 = GetPadRowCol(dg0->GetAddressChannel(), col1);
+          if (!step) step = fHit.vrM - row1;
+          if (col1 == col0) {
+            r += dg0->GetRectCharge(on);
+            if (on) nr++;
+          }
+          else
+            dg0 = nullptr;
+        }
+        if (step == 1 && i1 != fDigis[cjd].begin()) {
+          dg1 = (*(i1 - 1));
+          GetPadRowCol(dg1->GetAddressChannel(), col1);
+          if (col1 == col0 - 1) {
+            t += dg1->GetTiltCharge(on);
+            if (on) nt++;
+          }
+          else
+            dg1 = nullptr;
+        }
+        if (step == -1 && i1 != fDigis[cjd].end() && i1 + 1 != fDigis[cjd].end()) {
+          dg1 = (*(i1 + 1));
+          GetPadRowCol(dg1->GetAddressChannel(), col1);
+          if (col1 == col0 + 1) {
+            t += dg1->GetTiltCharge(on);
+            if (on) nt++;
+          }
+          else
+            dg1 = nullptr;
+        }
+        if (dg0) i1++;
+      }
+
+      //TO DO: two blocks below might be mergable
+      // process tilt signal/time
+      char ddt = dg->GetTiltTime() - fHit.vt0;  // signal time offset wrt prompt
+      if (ddt < dt0) dt0 = ddt;
+      if (abs(t) > 0) {
+        if (nt > 1) t *= 0.5;
+        err = te * (nt > 1 ? 0.707 : 1);
+        if (dg->HasTiltOvf()) {
+          ovf = -1;
+          err = 150.;
+        }
+        if (t > max) {
+          max      = t;
+          fHit.vcM = j;
+          fHit.SetMaxTilt(1);
+          fHit.viM = hitSig.size();
+        }
+      }
+      else
+        err = 300.;
+
+      hitSig.emplace_back(t, err, ddt, xc, 0.035);
+      xc += 0.5;
+
+      // process rect signal/time
+      ddt = dg->GetRectTime() - fHit.vt0;  // signal time offset wrt prompt
+      if (ddt < dt0) dt0 = ddt;
+      if (abs(r) > 0) {
+        nsr++;
+        if (nr > 1) r *= 0.5;
+        err = re * (nr > 1 ? 0.707 : 1);
+        if (dg->HasRectOvf()) {
+          ovf = -1;
+          err = 150.;
+        }
+        if (r > max) {
+          max      = r;
+          fHit.vcM = j;
+          fHit.SetMaxTilt(0);
+          fHit.viM = hitSig.size();
+        }
+      }
+      else
+        err = 300.;
+
+      hitSig.emplace_back(r, err, ddt, xc, 0.);
+      xc += 0.5;
+      NR += nr;
+      NT += nt;
+    }
+
+    //TO DO: two blocks below might be mergable
+    // add front and back anchor points if needed
+    if (std::abs(hitSig[0].s) > 1.e-3) {
+      xc       = hitSig[0].x;
+      char ddt = hitSig[0].t;
+      hitSig.emplace(hitSig.begin(), 0., 300., ddt, xc - 0.5, 0.);
+      fHit.viM++;
+    }
+    int n(hitSig.size() - 1);
+    if (std::abs(hitSig[n].s) > 1.e-3) {
+      xc       = hitSig[n].x + 0.5;
+      char ddt = hitSig[n].t;
+      hitSig.emplace_back(0., 300., ddt, xc, 0.035);
+    }
+
+    n0 = hitSig.size() - 2;
+    // compute cluster asymmetry
+    int nR = n0 + 1 - fHit.viM;
+    if (nR == fHit.viM) {
+      fHit.SetSymmHit(1);
+      if (hitSig.size() % 2) {
+        double LS(0.), RS(0.);
+        for (UChar_t idx(0); idx < fHit.viM; idx++)
+          LS += hitSig[idx].s;
+        for (uint idx(fHit.viM + 1); idx < hitSig.size(); idx++)
+          RS += hitSig[idx].s;
+        fHit.SetLeftSgn(LS < RS ? 0 : 1);
+      }
+    }
+    else {
+      fHit.SetSymmHit(0);
+      if (fHit.viM < nR)
+        fHit.SetLeftHit(0);
+      else if (fHit.viM > nR)
+        fHit.SetLeftHit(1);
+    }
+    // recenter time and space profile
+    fHit.vt0 += dt0;
+    for (auto& sig : hitSig) {
+      sig.t -= dt0;
+      sig.x -= fHit.vcM;
+    }
+    fHit.vcM += col;
+
+    // check if all signals have same significance
+    int nmiss = 2 * nsr - NR;
+    if (cjd >= 0 && nmiss) {
+      fHit.SetBiasX(1);
+      for (UChar_t idx(1); idx < fHit.viM; idx++) {
+        if (hitSig[idx].xe > 0.) continue;  //  select rect signals
+        if (hitSig[idx].se > re * 0.8) fHit.SetBiasXleft(1);
+      }
+      if (hitSig[fHit.viM].xe <= 0. && hitSig[fHit.viM].se > re * 0.8) fHit.SetBiasXmid(1);
+      for (UChar_t idx(fHit.viM + 1); idx < hitSig.size() - 1; idx++) {
+        if (hitSig[idx].xe > 0.) continue;  //  select rect signals
+        if (hitSig[idx].se > re * 0.8) fHit.SetBiasXright(1);
+      }
+    }
+    else
+      fHit.SetBiasX(0);
+    nmiss = 2 * n0 - 2 * nsr - NT;
+    if (cjd >= 0 && nmiss) {
+      fHit.SetBiasY();
+      for (UChar_t idx(1); idx < fHit.viM; idx++) {
+        if (hitSig[idx].xe > 0. && hitSig[idx].se > te * 0.8) fHit.SetBiasYleft(1);  //  select tilt signals
+      }
+      if (hitSig[fHit.viM].xe > 0. && hitSig[fHit.viM].se > te * 0.8) fHit.SetBiasYmid(1);
+      for (UChar_t idx(fHit.viM + 1); idx < hitSig.size() - 1; idx++) {
+        if (hitSig[idx].xe > 0. && hitSig[idx].se > te * 0.8) fHit.SetBiasYright(1);  //  select tilt signals
+      }
+    }
+    else
+      fHit.SetBiasY(0);
+
+    if (ovf < 0) fHit.SetOvf();
+    return ovf * (hitSig.size() - 2);
+  }
+
+  int HitFinder2D::GetPadRowCol(int address, int& c)
+  {
+    c = address % fParams.rowPar[0].padPar.size();
+    return address / fParams.rowPar[0].padPar.size();
+  }
+
+  ///////////////////////////////////////////
+
+  //_______________________________________________________________________________
+  std::pair<double, double> HitFactory2D::GetDxDy(const int n0)
+  {
+    double dx, dy;
+    switch (n0) {
+      case 1:
+        if (IsMaxTilt()) {  // T
+          dx = -0.5;
+          dy = 0;
+        }
+        else {  // R
+          dx = 0.5;
+          dy = 0;
+        }
+        break;
+      case 2:
+        if (IsOpenLeft() && IsOpenRight()) {  // RT
+          dx = viM == 1 ? 0. : -1;
+          dy = -0.5;
+        }
+        else {  // TR
+          dx = 0.;
+          dy = 0.5;
+        }
+        break;
+      case 3:
+        if (IsMaxTilt() && !IsSymmHit()) {  // TRT asymm
+          dx = viM == 1 ? 0. : -1;
+          dy = GetYoffset();
+        }
+        else if (!IsMaxTilt() && IsSymmHit()) {  // TRT symm
+          dx = 0.;
+          dy = GetYoffset();
+        }
+        else if (IsMaxTilt() && IsSymmHit()) {  // RTR symm
+          dx = GetXoffset();
+          dy = 0.;
+        }
+        else if (!IsMaxTilt() && !IsSymmHit()) {  // RTR asymm
+          dx = GetXoffset();
+          dy = viM == 1 ? -0.5 : 0.5;
+        }
+        break;
+      default:
+        dx = GetXoffset();
+        dy = GetYoffset();
+        break;
+    }
+    RecenterXoffset(dx);
+    return std::make_pair(dx, dy);
+  }
+
+  //_______________________________________________________________________________
+  double HitFactory2D::GetXoffset(int n0) const
+  {
+    double dx(0.), R(0.);
+    int n(n0 ? n0 : fSignal.size());
+    for (int ir(0); ir < n; ir++) {
+      if (fSignal[ir].xe > 0) continue;  // select rectangular coupling
+      R += fSignal[ir].s;
+      dx += fSignal[ir].s * fSignal[ir].x;
+    }
+    if (std::abs(R) > 0) return dx / R;
+    //L_(debug) << "HitFactory2D::GetXoffset : Null total charge for hit size " << n;
+    return 0.;
+  }
+
+  //_______________________________________________________________________________
+  double HitFactory2D::GetYoffset(int n0) const
+  {
+    double dy(0.), T(0.);
+    int n(n0 ? n0 : fSignal.size());
+    for (int it(0); it < n; it++) {
+      if (fSignal[it].xe > 0) {  // select tilted coupling
+        T += fSignal[it].s;
+        dy += fSignal[it].s * fSignal[it].x;
+      }
+    }
+    if (std::abs(T) > 0) return dy / T;
+    // L_(debug) << "HitFactory2D::GetYoffset : Null total charge for hit size " << n;
+    //if (CWRITE(1))
+    return 0.;
+  }
+
+  //_______________________________________________________________________________
+  void HitFactory2D::RecenterXoffset(double& dx)
+  {
+    /** Shift graph representation to fit dx[pw] in [-0.5, 0.5]
+   */
+
+    if (dx >= -0.5 && dx < 0.5) return;
+    int ishift = int(dx - 0.5) + (dx > 0.5 ? 1 : 0);
+    if (vcM + ishift < 0)
+      ishift = -vcM;
+    else if (vcM + ishift >= nCols)
+      ishift = nCols - vcM - 1;
+
+    dx -= ishift;
+    vcM += ishift;
+    for (uint idx(0); idx < fSignal.size(); idx++)
+      fSignal[idx].x -= ishift;
+  }
+
+  //_______________________________________________________________________________
+  void HitFactory2D::RecenterYoffset(double& dy)
+  {
+    /** Shift graph representation to fit dy[ph] in [-0.5, 0.5]
+   */
+
+    if (dy >= -0.5 && dy < 0.5) return;
+    int ishift = int(dy - 0.5) + (dy > 0.5 ? 1 : 0);
+    dy -= ishift;
+  }
+
+  //_______________________________________________________________________________
+  int HitFactory2D::GetHitClass() const
+  {
+    /** Incapsulate hit classification criteria based on signal topology
+ * [0] : center hit type
+ * [1]  : side hit type
+ */
+
+    int n0(fSignal.size() - 2);
+    if ((n0 == 5 && ((IsMaxTilt() && IsSymmHit()) || (!IsMaxTilt() && !IsSymmHit()))) ||  // TRTRT symm/asymm
+        n0 == 4 || (n0 == 3 && ((IsMaxTilt() && IsSymmHit()) || (!IsMaxTilt() && !IsSymmHit()))))
+      return 1;  // RTR symm/asymm
+    else if (n0 > 5 && HasOvf())
+      return 2;
+    return 0;
+  }
+
+  //_______________________________________________________________________________
+  int HitFactory2D::GetHitRcClass(int a0) const
+  {
+    int a0m      = std::abs(a0);
+    uint8_t xmap = vyM & 0xff;
+    if (a0m == 2 && IsBiasXleft() && IsBiasXright() && !IsBiasXmid())
+      return 0;
+    else if (a0m == 3 && ((IsBiasXleft() && IsBiasXright()) || xmap == 116 || xmap == 149 || xmap == 208))
+      return 1;
+    else if (!IsBiasXleft()
+             && (a0m == 2
+                 || (a0m == 3 && ((!IsBiasXright() && IsBiasXmid()) || xmap == 209 || xmap == 212 || xmap == 145))))
+      return 2;
+    else if (!IsBiasXright()
+             && (a0m == 2 || (a0m == 3 && ((!IsBiasXleft() && IsBiasXmid()) || xmap == 112 || xmap == 117))))
+      return 3;
+    else
+      return -1;
+  }
+
+  //_______________________________________________________________________________
+  double HitFactory2D::GetXcorr(double dxIn, int typ, int cls) const
+  {
+    /** Give the linear interpolation of SYS correction for current position offset "dx" based 
+ * on LUT calculated wrt MC EbyE data. The position offset is expresed in [pw] units 
+ * while the output is in [cm]
+ */
+
+    if (typ < 0 || typ > 2) {
+      //L_(error)<< GetName() << "::GetXcorr : type in-param "<<typ<<" out of range.";
+      return 0;
+    }
+    double dx = std::abs(dxIn);
+    int ii    = std::max(0, Nint(dx / fgCorrXdx) - 1), i0;  //  i1;
+
+    if (ii < 0 || ii > NBINSCORRX) {
+      // L_(debug) << GetName() << "::GetXcorr : LUT idx " << ii << " out of range for dx=" << dxIn;
+      return 0;
+    }
+    if (dx < fgCorrXdx * ii) {
+      i0 = std::max(0, ii - 1);
+      /*i1=ii;*/
+    }
+    else {
+      i0 = ii;
+      /*i1=TMath::Min(NBINSCORRX-1,ii+1);*/
+    }
+
+    float* xval = &fgCorrXval[typ][i0];
+    if (cls == 1)
+      xval = &fgCorrRcXval[typ][i0];
+    else if (cls == 2)
+      xval = &fgCorrRcXbiasXval[typ][i0];
+    double DDx = (xval[1] - xval[0]), a = DDx / fgCorrXdx, b = xval[0] - DDx * (i0 + 0.5);
+    return (dxIn > 0 ? 1 : -1) * b + a * dxIn;
+  }
+
+  //_______________________________________________________________________________
+  double HitFactory2D::GetYcorr(double dy, int /* cls*/) const
+  {
+    /** Process y offset. Apply systematic correction for y (MC derived).
+ * The position offset is expresed in [pw] units while the output is in [cm]
+ */
+    float fdy(1.), yoff(0.);
+    int n0(fSignal.size() - 2);
+    switch (n0) {
+      case 3:
+        fdy  = fgCorrYval[0][0];
+        yoff = fgCorrYval[0][1];
+        if (IsMaxTilt() && IsSymmHit()) {
+          fdy  = 0.;
+          yoff = (dy > 0 ? -1 : 1) * 1.56;
+        }
+        else if (!IsMaxTilt() && !IsSymmHit()) {
+          fdy  = 0.;
+          yoff = (dy > 0 ? -1 : 1) * 1.06;
+        }
+        else if (!IsMaxTilt() && IsSymmHit()) {
+          fdy  = 2.114532;
+          yoff = -0.263;
+        }
+        else /*if(IsMaxTilt()&&!IsSymmHit())*/ {
+          fdy  = 2.8016010;
+          yoff = -1.38391;
+        }
+        break;
+      case 4:
+        fdy  = fgCorrYval[1][0];
+        yoff = fgCorrYval[1][1];
+        if ((!IsMaxTilt() && IsLeftHit()) || (IsMaxTilt() && !IsLeftHit())) yoff *= -1;
+        break;
+      case 5:
+      case 7:
+      case 9:
+      case 11:
+        fdy  = fgCorrYval[2][0];
+        yoff = fgCorrYval[2][1];
+        break;
+      case 6:
+      case 8:
+      case 10:
+        fdy  = fgCorrYval[3][0];
+        yoff = fgCorrYval[3][1];
+        if ((!IsMaxTilt() && IsLeftHit()) || (IsMaxTilt() && !IsLeftHit())) yoff *= -1;
+        break;
+    }
+    return dy * fdy + yoff;
+  }
+
+  //_______________________________________________________________________________
+  bool HitFactory2D::IsOpenRight() const
+  {
+    int nR = fSignal.size() - 1 - viM;
+    return (nR % 2 && IsMaxTilt()) || (!(nR % 2) && !IsMaxTilt());
+  }
+
+  float HitFactory2D::fgCorrXdx                 = 0.01;
+  float HitFactory2D::fgCorrXval[3][NBINSCORRX] = {
+    {-0.001, -0.001, -0.002, -0.002, -0.003, -0.003, -0.003, -0.004, -0.004, -0.006, -0.006, -0.006, -0.007,
+     -0.007, -0.008, -0.008, -0.008, -0.009, -0.009, -0.011, -0.011, -0.011, -0.012, -0.012, -0.012, -0.012,
+     -0.013, -0.013, -0.013, -0.013, -0.014, -0.014, -0.014, -0.014, -0.014, -0.016, -0.016, -0.016, -0.016,
+     -0.017, -0.017, -0.017, -0.018, -0.018, -0.018, -0.018, -0.018, 0.000,  0.000,  0.000},
+    {0.467, 0.430, 0.396, 0.364, 0.335, 0.312, 0.291, 0.256, 0.234, 0.219, 0.207, 0.191, 0.172,
+     0.154, 0.147, 0.134, 0.123, 0.119, 0.109, 0.122, 0.113, 0.104, 0.093, 0.087, 0.079, 0.073,
+     0.067, 0.063, 0.058, 0.053, 0.049, 0.046, 0.042, 0.038, 0.036, 0.032, 0.029, 0.027, 0.024,
+     0.022, 0.019, 0.017, 0.014, 0.013, 0.011, 0.009, 0.007, 0.004, 0.003, 0.001},
+    {0.001,  0.001,  0.001,  0.001,  0.002,  0.002,  0.001,  0.002,  0.004,  0.003,  0.002,  0.002,  0.002,
+     0.002,  0.002,  0.002,  0.003,  0.004,  0.003,  0.004,  0.004,  0.007,  0.003,  0.004,  0.002,  0.002,
+     -0.011, -0.011, -0.012, -0.012, -0.012, -0.013, -0.013, -0.013, -0.014, -0.014, -0.014, -0.016, -0.016,
+     -0.016, -0.017, -0.017, -0.017, -0.018, -0.018, -0.018, -0.019, 0.029,  0.018,  0.001}};
+  float HitFactory2D::fgCorrYval[NBINSCORRY][2]   = {{2.421729, 0.},
+                                                   {0.629389, -0.215285},
+                                                   {0.23958, 0.},
+                                                   {0.151913, 0.054404}};
+  float HitFactory2D::fgCorrRcXval[2][NBINSCORRX] = {
+    {-0.00050, -0.00050, -0.00150, -0.00250, -0.00250, -0.00350, -0.00450, -0.00450, -0.00550, -0.00650,
+     -0.00650, -0.00750, -0.00850, -0.00850, -0.00850, -0.00950, -0.00950, -0.00950, -0.01050, -0.01150,
+     -0.01150, -0.01150, -0.01250, -0.01250, -0.01250, -0.01250, -0.01350, -0.01350, -0.01350, -0.01350,
+     -0.01450, -0.01450, -0.01450, -0.01550, -0.01550, -0.01550, -0.01550, -0.01650, -0.01650, -0.01550,
+     -0.01650, -0.01614, -0.01620, -0.01624, -0.01626, -0.01627, -0.01626, -0.01624, -0.01620, -0.01615},
+    {0.36412, 0.34567, 0.32815, 0.31152, 0.29574, 0.28075, 0.26652, 0.25302, 0.24020, 0.22803,
+     0.21647, 0.21400, 0.19400, 0.18520, 0.17582, 0.16600, 0.14600, 0.13800, 0.14280, 0.14200,
+     0.13400, 0.12600, 0.12200, 0.11000, 0.10200, 0.09400, 0.09000, 0.08600, 0.08200, 0.07400,
+     0.07000, 0.06600, 0.06600, 0.06200, 0.05800, 0.05400, 0.05400, 0.05000, 0.04600, 0.04600,
+     0.04200, 0.03800, 0.03800, 0.03400, 0.03400, 0.03000, 0.03000, 0.02600, 0.02200, 0.02200}};
+  float HitFactory2D::fgCorrRcXbiasXval[3][NBINSCORRX] = {
+    {0.00100, 0.00260, 0.00540, 0.00740, 0.00900, 0.01060, 0.01300, 0.01460, 0.01660, 0.01900,
+     0.02060, 0.02260, 0.02420, 0.02700, 0.02860, 0.02980, 0.03220, 0.03340, 0.03540, 0.03620,
+     0.03820, 0.04020, 0.04180, 0.04340, 0.04460, 0.04620, 0.04740, 0.04941, 0.05088, 0.05233,
+     0.05375, 0.05515, 0.05653, 0.05788, 0.05921, 0.06052, 0.06180, 0.06306, 0.06430, 0.06551,
+     0.06670, 0.06786, 0.06901, 0.07012, 0.07122, 0.07229, 0.07334, 0.07436, 0.07536, 0.07634},
+    {0.00100, 0.00380, 0.00780, 0.00900, 0.01220, 0.01460, 0.01860, 0.01940, 0.02260, 0.02540,
+     0.02820, 0.03060, 0.03220, 0.03660, 0.03980, 0.04094, 0.04420, 0.04620, 0.04824, 0.04980,
+     0.05298, 0.05532, 0.05740, 0.05991, 0.06217, 0.06500, 0.06540, 0.06900, 0.07096, 0.07310,
+     0.07380, 0.07729, 0.07935, 0.08139, 0.08340, 0.08538, 0.08734, 0.08928, 0.08900, 0.09307,
+     0.09493, 0.09340, 0.09858, 0.09620, 0.09740, 0.10386, 0.09980, 0.10726, 0.10892, 0.11056},
+    {0.00011, 0.00140, 0.00340, 0.00420, 0.00500, 0.00620, 0.00820, 0.00860, 0.01060, 0.01100,
+     0.01220, 0.01340, 0.01500, 0.01540, 0.01700, 0.01820, 0.01900, 0.02060, 0.02180, 0.02260,
+     0.02340, 0.02420, 0.02500, 0.02500, 0.02660, 0.02740, 0.02820, 0.02900, 0.03020, 0.03180,
+     0.03300, 0.03260, 0.03380, 0.03460, 0.03500, 0.03580, 0.03780, 0.03820, 0.03860, 0.03900,
+     0.04100, 0.04180, 0.04060, 0.04300, 0.04340, 0.04340, 0.04380, 0.04460, 0.04580, 0.04540}};
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/HitFinder2D.h b/algo/detectors/trd/HitFinder2D.h
new file mode 100644
index 0000000000..5398b58d8c
--- /dev/null
+++ b/algo/detectors/trd/HitFinder2D.h
@@ -0,0 +1,242 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#pragma once
+
+#include "CbmTrdDigi.h"
+#include "Cluster2D.h"
+#include "DigiRec.h"
+#include "Hit.h"
+#include "HitFinder2DPars.h"
+#include "Math/Rotation3D.h"
+#include "Math/Vector3Dfwd.h"
+
+#include <cstdint>
+#include <tuple>
+#include <unordered_map>
+#include <vector>
+
+#define NBINSCORRX 50  //! no of bins in the discretized correction LUT
+#define NBINSCORRY 4   //! no of bins in the parametrization correction
+
+#define NANODE 9
+
+using std::vector;
+class DigiRec;
+class CbmTrdParFaspChannel;
+
+
+namespace cbm::algo::trd
+{
+
+  // working representation of a hit on which the reconstruction is performed
+  class HitFactory2D {
+
+   public:
+    struct signal {
+      double s;   //! working copy of signals from cluster
+      double se;  //! working copy of signal errors from cluster
+      char t;     //! working copy of signal relative timing
+      double x;   //! working copy of signal relative positions
+      double xe;  //! working copy of signal relative position errors
+      signal(double _s, double _se, char _t, double _x, double _xe) : s(_s), se(_se), t(_t), x(_x), xe(_xe) {}
+      signal() : s(0), se(0), t(0), x(0), xe(0) {}
+    };
+
+    std::vector<signal> fSignal;
+    int nCols;
+
+    uint64_t vt0 = 0;  //! start time of current hit [clk]
+    uint8_t vcM  = 0;  //! maximum col
+    uint8_t vrM  = 0;  //! maximum row
+    uint8_t viM  = 0;  //! index of maximum signal in the projection
+    uint16_t vyM = 0;  //! bit map for cluster topology classification
+
+    HitFactory2D(int ncols) : nCols(ncols), vt0(0), vcM(0), vrM(0), viM(0), vyM(0) {}
+
+    void reset()
+    {
+      fSignal.clear();
+      vt0 = 0;
+      vcM = 0;
+      vrM = 0;
+      viM = 0;
+      vyM = 0;
+    }
+
+    bool HasLeftSgn() const { return TESTBIT(vyM, 3); }
+    bool HasOvf() const { return TESTBIT(vyM, 12); }
+    bool IsBiasX() const { return TESTBIT(vyM, 4); }
+    bool IsBiasXleft() const { return TESTBIT(vyM, 5); }
+    bool IsBiasXmid() const { return TESTBIT(vyM, 6); }
+    bool IsBiasXright() const { return TESTBIT(vyM, 7); }
+    bool IsBiasY() const { return TESTBIT(vyM, 8); }
+    bool IsBiasYleft() const { return TESTBIT(vyM, 9); }
+    bool IsLeftHit() const { return TESTBIT(vyM, 2); }
+    bool IsMaxTilt() const { return TESTBIT(vyM, 0); }
+    bool IsOpenLeft() const { return (viM % 2 && !IsMaxTilt()) || (!(viM % 2) && IsMaxTilt()); }
+    inline bool IsOpenRight() const;
+    bool IsSymmHit() const { return TESTBIT(vyM, 1); }
+    void SetBiasX(bool set = 1) { set ? SETBIT(vyM, 4) : CLRBIT(vyM, 4); }
+    void SetBiasXleft(bool set = 1) { set ? SETBIT(vyM, 5) : CLRBIT(vyM, 5); }
+    void SetBiasXmid(bool set = 1) { set ? SETBIT(vyM, 6) : CLRBIT(vyM, 6); }
+    void SetBiasXright(bool set = 1) { set ? SETBIT(vyM, 7) : CLRBIT(vyM, 7); }
+    void SetBiasY(bool set = 1) { set ? SETBIT(vyM, 8) : CLRBIT(vyM, 8); }
+    void SetBiasYleft(bool set = 1) { set ? SETBIT(vyM, 9) : CLRBIT(vyM, 9); }
+    void SetBiasYmid(bool set = 1) { set ? SETBIT(vyM, 10) : CLRBIT(vyM, 10); }
+    void SetBiasYright(bool set = 1) { set ? SETBIT(vyM, 11) : CLRBIT(vyM, 11); }
+    void SetLeftSgn(bool set = 1) { set ? SETBIT(vyM, 3) : CLRBIT(vyM, 3); }
+    void SetLeftHit(bool set = 1) { set ? SETBIT(vyM, 2) : CLRBIT(vyM, 2); }
+    void SetSymmHit(bool set = 1) { set ? SETBIT(vyM, 1) : CLRBIT(vyM, 1); }
+    void SetMaxTilt(bool set = 1) { set ? SETBIT(vyM, 0) : CLRBIT(vyM, 0); }
+    void SetOvf(bool set = 1) { set ? SETBIT(vyM, 12) : CLRBIT(vyM, 12); }
+    uint16_t GetHitMap() const { return vyM; }
+
+    std::pair<double, double> GetDxDy(const int n0);
+    double GetXoffset(int n0 = 0) const;
+    double GetYoffset(int n0 = 0) const;
+
+    void RecenterXoffset(double& dx);
+    /** \brief Shift graph representation to [-0.5, 0.5]
+   * \param[in] dy offset wrt center pad [ph]
+   */
+    void RecenterYoffset(double& dy);
+    /** \brief Hit classification wrt center pad
+   * \return hit type : center hit [0]; side hit [1]
+   */
+
+    int GetHitClass() const;
+    /** \brief Hit classification wrt signal bias
+   * \return hit type : center hit [0]; side hit [1]
+   */
+    int GetHitRcClass(int a0) const;
+
+    double GetXcorr(double dx, int typ, int cls = 0) const;
+    /** \brief y position correction based on LUT
+   * \param[in] dy offset computed on charge sharing expressed in [ph]
+   * \param[in] cls correction class
+   * \return correction expresed in [cm]
+   */
+    double GetYcorr(double dy, int cls = 0) const;
+    /** \brief Shift graph representation to [-0.5, 0.5]
+   * \param[in] dx offset wrt center pad [pw]
+   */
+
+    static float fgCorrXdx;                         //! step of the discretized correction LUT
+    static float fgCorrXval[3][NBINSCORRX];         //! discretized correction LUT
+    static float fgCorrYval[NBINSCORRY][2];         //! discretized correction params
+    static float fgCorrRcXval[2][NBINSCORRX];       //! discretized correction LUT
+    static float fgCorrRcXbiasXval[3][NBINSCORRX];  //! discretized correction LUT
+
+    // Nint function copied from TMath until better option is available
+    template<typename T>
+    inline int Nint(T x) const
+    {
+      int i;
+      if (x >= 0) {
+        i = int(x + 0.5);
+        if (i & 1 && x + 0.5 == T(i)) i--;
+      }
+      else {
+        i = int(x - 0.5);
+        if (i & 1 && x - 0.5 == T(i)) i++;
+      }
+      return i;
+    }
+  };
+
+
+  /** @class HitFinder2D
+ ** @brief Cluster finding and hit reconstruction algorithms for the TRD(2D) module.
+ ** @author Alexandru Bercucic <abercuci@niham.nipne.ro>
+ ** @since 01.02.2019
+ ** @date 01.10.2021
+ **
+ ** Extend the TRD module reconstructor for the particular case of inner TRD-2D modules.
+ ** The class is a collection of algorithms to :
+ ** - identify time and spatially correlated digis and build clusters
+ ** - identify the type of clusters and apply further merging/deconvolution
+ ** - apply FEE (channel gain, baseline) and detector (energy gain, maps, etc) calibration
+ ** - steer the calculation of hit 4D parameters (x, y, t, E)
+ **/
+
+  class HitFinder2D {
+   public:
+    typedef std::tuple<uint16_t, uint16_t, int, int, size_t> inputType;  //Tuple: chT, chR, tm, row, id
+
+    /** \brief Default constructor.*/
+    HitFinder2D() : fHit(0){};
+    /** \brief Constructor with placement */
+    HitFinder2D(HitFinder2DModPar params);
+
+    virtual ~HitFinder2D(){};
+
+    /** \brief Steering routine for building hits **/
+    std::vector<Hit> operator()(std::vector<Cluster2D>* clusters);
+
+    /** \brief Load RAW digis into working array of RECO digis
+   * \param[in] din list of RAW digis in increasing order of column no
+   * \param[in] cid cluster index in the cluster array
+   * \return no of digis loaded
+   */
+
+    int LoadDigis(vector<const CbmTrdDigi*>* din, int cid);
+    int ProjectDigis(int cid, int cjd = -1);
+    /** \brief Implement topologic cuts for hit merging
+   * \return index of anode wire wrt to boundary or 0 if check fails
+   */
+    int CheckMerge(int cid, int cjd);
+    /** \brief Algorithm for hit merging
+   * \param[in] h hit to be modified by addition of hp.
+   * \param[in] a0 anode hypothesis around boundary (see CheckMerge function).
+   * \return TRUE if succesful.
+   */
+    bool MergeHits(Hit* h, int a0);
+    bool BuildHit(Hit* h);
+    /** \brief x position correction based on LUT
+   * \param[in] dx offset computed on charge sharing expressed in [pw]
+   * \param[in] typ hit type central pad [0] or edge [1]
+   * \param[in] cls correction class x wrt center [0] row-cross [1] row-cross biassed x [2]
+   * \return correction expresed in [cm]
+   */
+
+    /** \brief Time offset to synchronize TRD2D hits to the rest of detectors
+   * \param dt offset in [ns]
+   */
+    void SetHitTimeOffset(int dt) { fHitTimeOff = dt; }
+
+   protected:
+   private:
+    HitFinder2D(const HitFinder2D& ref);
+    const HitFinder2D& operator=(const HitFinder2D& ref);
+
+    Hit MakeHit(int cId, const Cluster2D* c, std::vector<const CbmTrdDigi*>* digis, size_t nclusters);
+
+    const float fClk = CbmTrdDigi::Clk(CbmTrdDigi::eCbmTrdAsicType::kFASP);
+
+    HitFinder2DModPar fParams;  ///< Parameter container
+
+    /** \brief Addressing ASIC on module based on id
+   * \param[in] id module wise ASIC identifier
+   * \return ASIC address within experiment
+   */
+    inline int GetPadRowCol(int address, int& c);
+
+    std::pair<double, double> CorrectPosition(double dx, double dy, const double xcorr);
+
+    void CalibrateHit(Hit* h, const double dx, const double dy, const double edx, const double edy, const double edt,
+                      const double time, const double tdrift, const double eloss);
+
+    std::vector<vector<DigiRec*>> fDigis;  //!cluster-wise organized calibrated digi
+
+    int fHitTimeOff = 0;  //! hit time offset for synchronization
+
+    // working representation of a hit on which the reconstruction is performed
+    HitFactory2D fHit;
+
+    static Double_t fgDT[3];  //! FASP delay wrt signal
+  };
+
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/HitFinder2DPars.h b/algo/detectors/trd/HitFinder2DPars.h
new file mode 100644
index 0000000000..142c7bb3b3
--- /dev/null
+++ b/algo/detectors/trd/HitFinder2DPars.h
@@ -0,0 +1,33 @@
+/* Copyright (C) 2022 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Pierre-Alain Loizeau, Felix Weiglhofer */
+#pragma once
+
+#include "Math/Rotation3D.h"
+#include "Math/Vector3Dfwd.h"
+
+#include <vector>
+
+namespace cbm::algo::trd
+{
+
+  struct HitFinder2DPadPar {
+    ROOT::Math::XYZVector pos;
+    ROOT::Math::XYZVector posErr;  /// TO DO: probably not needed
+    bool chRMasked = false;
+    bool chTMasked = false;
+  };
+
+  struct HitFinder2DRowPar {
+    std::vector<HitFinder2DPadPar> padPar;
+  };
+
+  struct HitFinder2DModPar {
+    double padSizeX  = 0;
+    double padSizeY  = 0;
+    uint16_t address = 0;
+    std::vector<HitFinder2DRowPar> rowPar;
+    ROOT::Math::XYZVector translation;
+    ROOT::Math::Rotation3D rotation;
+  };
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/HitFinderPars.h b/algo/detectors/trd/HitFinderPars.h
new file mode 100644
index 0000000000..404d6e5f4c
--- /dev/null
+++ b/algo/detectors/trd/HitFinderPars.h
@@ -0,0 +1,33 @@
+/* Copyright (C) 2022 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Pierre-Alain Loizeau, Felix Weiglhofer */
+#pragma once
+
+#include "Math/Rotation3D.h"
+#include "Math/Vector3Dfwd.h"
+
+#include <vector>
+
+namespace cbm::algo::trd
+{
+  struct HitFinderPadPar {
+    ROOT::Math::XYZVector pos;
+    ROOT::Math::XYZVector posErr;  /// TO DO: probably not needed
+  };
+
+  struct HitFinderRowPar {
+    std::vector<HitFinderPadPar> padPar;
+  };
+
+  struct HitFinderModPar {
+    double padSizeX    = 0;
+    double padSizeY    = 0;
+    double padSizeErrX = 0;
+    double padSizeErrY = 0;
+    uint16_t address   = 0;
+    int orientation    = 0;
+    std::vector<HitFinderRowPar> rowPar;
+    ROOT::Math::XYZVector translation;
+    ROOT::Math::Rotation3D rotation;
+  };
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Hitfind.cxx b/algo/detectors/trd/Hitfind.cxx
new file mode 100644
index 0000000000..f1328a68ec
--- /dev/null
+++ b/algo/detectors/trd/Hitfind.cxx
@@ -0,0 +1,138 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#include "Hitfind.h"
+
+#include "compat/OpenMP.h"
+#include "log.hpp"
+#include "util/TimingsFormat.h"
+
+#include <chrono>
+
+using namespace std;
+using fles::Subsystem;
+
+namespace cbm::algo::trd
+{
+  // -----   Constructor   ------------------------------------------------------
+  Hitfind::Hitfind(trd::HitfindSetup setup, trd::Hitfind2DSetup setup2D)  // : fStorDigi(fNbSm.size())
+  {
+
+    // Create one algorithm per module for TRD and configure it with parameters
+    for (size_t mod = 0; mod < setup.modules.size(); mod++) {
+
+      cbm::algo::trd::HitfindSetup::Mod& module = setup.modules[mod];
+      cbm::algo::trd::HitFinderModPar params;
+      params.rowPar.resize(module.rowPar.size());
+
+      const double* tra_ptr = module.translation.data();
+      const double* rot_ptr = module.rotation.data();
+      params.translation    = ROOT::Math::XYZVector(tra_ptr[0], tra_ptr[1], tra_ptr[2]);
+      params.rotation       = ROOT::Math::Rotation3D(&rot_ptr[0], &rot_ptr[9]);
+      params.address        = module.address;
+      params.padSizeX       = module.padSizeX;
+      params.padSizeY       = module.padSizeY;
+      params.padSizeErrX    = module.padSizeErrX;
+      params.padSizeErrY    = module.padSizeErrY;
+      params.orientation    = module.orientation;
+
+      for (size_t row = 0; row < module.rowPar.size(); row++) {
+        cbm::algo::trd::HitfindSetup::Row& rowPar = module.rowPar[row];
+        params.rowPar[row].padPar.resize(rowPar.padPar.size());
+
+        for (size_t col = 0; col < rowPar.padPar.size(); col++) {
+          cbm::algo::trd::HitfindSetup::Pad& pad  = rowPar.padPar[col];
+          cbm::algo::trd::HitFinderPadPar& padPar = params.rowPar[row].padPar[col];
+
+          const double* pos_ptr    = pad.position.data();
+          const double* posErr_ptr = pad.positionError.data();
+          padPar.pos               = ROOT::Math::XYZVector(pos_ptr[0], pos_ptr[1], pos_ptr[2]);
+          padPar.posErr            = ROOT::Math::XYZVector(posErr_ptr[0], posErr_ptr[1], posErr_ptr[2]);
+        }
+      }
+      fHitFind[module.address]      = std::make_unique<cbm::algo::trd::HitFinder>(params);
+      fClusterBuild[module.address] = std::make_unique<cbm::algo::trd::Clusterizer>(params);
+    }
+
+    // Create one algorithm per module for TRD2D and configure it with parameters
+    for (size_t mod = 0; mod < setup2D.modules.size(); mod++) {
+
+      cbm::algo::trd::Hitfind2DSetup::Mod& module = setup2D.modules[mod];
+      cbm::algo::trd::HitFinder2DModPar params;
+      params.rowPar.resize(module.rowPar.size());
+
+      const double* tra_ptr = module.translation.data();
+      const double* rot_ptr = module.rotation.data();
+      params.translation    = ROOT::Math::XYZVector(tra_ptr[0], tra_ptr[1], tra_ptr[2]);
+      params.rotation       = ROOT::Math::Rotation3D(&rot_ptr[0], &rot_ptr[9]);
+      params.address        = module.address;
+      params.padSizeX       = module.padSizeX;
+      params.padSizeY       = module.padSizeY;
+
+      for (size_t row = 0; row < module.rowPar.size(); row++) {
+        cbm::algo::trd::Hitfind2DSetup::Row& rowPar = module.rowPar[row];
+        params.rowPar[row].padPar.resize(rowPar.padPar.size());
+
+        for (size_t col = 0; col < rowPar.padPar.size(); col++) {
+          cbm::algo::trd::Hitfind2DSetup::Pad& pad  = rowPar.padPar[col];
+          cbm::algo::trd::HitFinder2DPadPar& padPar = params.rowPar[row].padPar[col];
+
+          const double* pos_ptr    = pad.position.data();
+          const double* posErr_ptr = pad.positionError.data();
+          padPar.pos               = ROOT::Math::XYZVector(pos_ptr[0], pos_ptr[1], pos_ptr[2]);
+          padPar.posErr            = ROOT::Math::XYZVector(posErr_ptr[0], posErr_ptr[1], posErr_ptr[2]);
+          padPar.chRMasked         = pad.chRMasked;
+          padPar.chTMasked         = pad.chTMasked;
+        }
+      }
+      fHitFind2d[module.address]      = std::make_unique<cbm::algo::trd::HitFinder2D>(params);
+      fClusterBuild2d[module.address] = std::make_unique<cbm::algo::trd::Clusterizer2D>(params);
+    }
+
+    L_(info) << "--- Configured hitfinder algorithms for TRD.";
+    L_(info) << "==================================================";
+  }
+  // ----------------------------------------------------------------------------
+
+
+  // -----   Execution   -------------------------------------------------------
+  Hitfind::resultType Hitfind::operator()(gsl::span<CbmTrdDigi> digiIn)
+  {
+    // --- Output data
+    resultType result         = {};
+    std::vector<Hit>& hitsOut = result.first;
+
+    // Intermediate storage variables (digi, index) per module
+    std::unordered_map<int, std::vector<std::pair<CbmTrdDigi, int32_t>>> digiBuffer;  //[modAddress]
+
+    const size_t maxdigis = 100000000001;
+
+    // Sort digis by module address
+    for (size_t idigi = 0; idigi < digiIn.size(); idigi++) {
+      const CbmTrdDigi* digi = &digiIn[idigi];
+      digiBuffer[digi->GetAddressModule()].emplace_back(*digi, idigi);
+      if (idigi == maxdigis) {
+        break;
+      }
+    }
+
+    // Process 1D digis
+    for (auto imod = fHitFind.begin(); imod != fHitFind.end(); imod++) {
+      auto clusters = (*fClusterBuild[imod->first])(digiBuffer[imod->first]);
+      auto hits     = (*fHitFind[imod->first])(&clusters);
+      hitsOut.insert(hitsOut.end(), hits.begin(), hits.end());
+    }
+
+    // Process 2D digis
+    for (auto imod = fHitFind2d.begin(); imod != fHitFind2d.end(); imod++) {
+      auto clusters = (*fClusterBuild2d[imod->first])(digiBuffer[imod->first], 0.);  // Number is TS start time (T0)
+      auto hits     = (*fHitFind2d[imod->first])(&clusters);
+      hitsOut.insert(hitsOut.end(), hits.begin(), hits.end());
+    }
+
+    return result;
+  }
+  // ----------------------------------------------------------------------------
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Hitfind.h b/algo/detectors/trd/Hitfind.h
new file mode 100644
index 0000000000..c3fcfa2745
--- /dev/null
+++ b/algo/detectors/trd/Hitfind.h
@@ -0,0 +1,84 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#pragma once
+
+#include "CbmTrdDigi.h"
+#include "PODVector.h"
+#include "PartitionedVector.h"
+#include "trd/Clusterizer.h"
+#include "trd/Clusterizer2D.h"
+#include "trd/HitFinder.h"
+#include "trd/HitFinder2D.h"
+#include "trd/Hitfind2DSetup.h"
+#include "trd/HitfindSetup.h"
+
+#include <gsl/span>
+#include <optional>
+#include <sstream>
+#include <vector>
+
+#include <xpu/host.h>
+
+
+namespace cbm::algo::trd
+{
+
+  /** @struct HitfindMonitorData
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 17 Apr 2024
+   ** @brief Monitoring data for hitfinding
+   **/
+  struct HitfindMonitorData {
+    xpu::timings fTime;
+    xpu::timings fSortTime;
+    size_t fNumDigis = 0;
+    size_t fNumHits  = 0;
+
+    std::string print() const
+    {
+      std::stringstream ss;
+      ss << "Hitfind stats: num digis " << fNumDigis << ", time " << fTime.wall() << " ms ( " << fTime.throughput()
+         << " GB/s ), sort time " << fSortTime.wall() << " ms, num hits " << fNumHits << std::endl;
+      return ss.str();
+    }
+  };
+
+  /** @class Hitfind
+ ** @brief Algo class for hitfinding
+ ** @author Dominik Smith <d.smith@gsi.de>
+ ** @since 17.04.2024
+ **
+ **/
+  class Hitfind {
+
+   public:
+    //typedef std::tuple<PartitionedVector<Hit>, HitfindMonitorData, PODVector<i32>> resultType;
+    typedef std::pair<std::vector<Hit>, HitfindMonitorData> resultType;
+
+
+    /** @brief Algorithm execution
+     ** @param fles timeslice to hitfind
+     ** @return pair: digi timeslice, monitoring data
+     **
+     ** @note Modifies input digis for time calibration
+     **/
+    resultType operator()(gsl::span<CbmTrdDigi> digiIn);
+
+    /** @brief Constructor **/
+    explicit Hitfind(trd::HitfindSetup, trd::Hitfind2DSetup);
+
+   private:  // members
+    /** @brief Cluster building algorithms per module */
+    std::unordered_map<int, std::unique_ptr<cbm::algo::trd::Clusterizer2D>> fClusterBuild2d;
+    std::unordered_map<int, std::unique_ptr<cbm::algo::trd::Clusterizer>> fClusterBuild;
+
+    /** @brief Hit finding algorithms per module */
+    std::unordered_map<int, std::unique_ptr<cbm::algo::trd::HitFinder2D>> fHitFind2d;
+    std::unordered_map<int, std::unique_ptr<cbm::algo::trd::HitFinder>> fHitFind;
+
+    /** @brief Intermediate storage variables (digi, index) **/
+    std::vector<std::vector<std::vector<std::pair<CbmTrdDigi, int32_t>>>> fStorDigi;  //[nbType][nbSm*nbRpc][nDigis]
+  };
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/Hitfind2DSetup.h b/algo/detectors/trd/Hitfind2DSetup.h
new file mode 100644
index 0000000000..a045abbc75
--- /dev/null
+++ b/algo/detectors/trd/Hitfind2DSetup.h
@@ -0,0 +1,64 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+#pragma once
+
+#include "Definitions.h"
+#include "yaml/Property.h"
+
+#include <array>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace cbm::algo::trd
+{
+
+  /**
+   * @brief Hitfind setup / Hardware cabling for TRD2D
+   * Used to create the hardware mapping for the TRD2D hitfinder.
+   */
+  struct Hitfind2DSetup {
+
+    struct Pad {
+      std::array<double, 3> position;
+      std::array<double, 3> positionError;
+      bool chRMasked;
+      bool chTMasked;
+
+      CBM_YAML_PROPERTIES(yaml::Property(&Pad::position, "position", "Local position", YAML::Flow),
+			yaml::Property(&Pad::positionError, "positionError", "Local position error", YAML::Flow),
+		        yaml::Property(&Pad::chRMasked, "chRMasked", "Is channel R component masked"),
+                        yaml::Property(&Pad::chTMasked, "chTMasked", "Is channel T component masked"));
+    };
+
+    struct Row {
+      std::vector<Pad> padPar;
+
+      CBM_YAML_PROPERTIES(yaml::Property(&Row::padPar, "padPar", "pad parameters"));
+    };
+
+
+    struct Mod {
+      double padSizeX;
+      double padSizeY;
+      u16 address;
+      std::vector<Row> rowPar;
+      std::array<double, 3> translation;
+      std::array<double, 9> rotation;
+
+      CBM_YAML_PROPERTIES(yaml::Property(&Mod::padSizeX, "padSizeX", "X size of pads"),
+			yaml::Property(&Mod::padSizeY, "padSizeY", "Y size of pads"),
+                        yaml::Property(&Mod::address, "address", "module address"),
+                        yaml::Property(&Mod::translation, "translation", "Module position", YAML::Flow),
+			yaml::Property(&Mod::rotation, "rotation", "Module rotation", YAML::Flow),
+			yaml::Property(&Mod::rowPar, "rowPar", "row parameters"));
+    };
+
+    std::vector<Mod> modules;
+
+    CBM_YAML_PROPERTIES(yaml::Property(&Hitfind2DSetup::modules, "modules", "Parameters of modules"));
+  };
+
+
+}  // namespace cbm::algo::trd
diff --git a/algo/detectors/trd/HitfindSetup.h b/algo/detectors/trd/HitfindSetup.h
new file mode 100644
index 0000000000..68214432c7
--- /dev/null
+++ b/algo/detectors/trd/HitfindSetup.h
@@ -0,0 +1,65 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+#pragma once
+
+#include "Definitions.h"
+#include "yaml/Property.h"
+
+#include <array>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace cbm::algo::trd
+{
+
+  /**
+   * @brief Hitfind setup / Hardware cabling for TRD
+   * Used to create the hardware mapping for the TRD hitfinder.
+   */
+  struct HitfindSetup {
+
+    struct Pad {
+      std::array<double, 3> position;
+      std::array<double, 3> positionError;
+
+      CBM_YAML_PROPERTIES(yaml::Property(&Pad::position, "position", "Local position", YAML::Flow),
+			yaml::Property(&Pad::positionError, "positionError", "Local position error", YAML::Flow));
+    };
+
+    struct Row {
+      std::vector<Pad> padPar;
+
+      CBM_YAML_PROPERTIES(yaml::Property(&Row::padPar, "padPar", "pad parameters"));
+    };
+
+
+    struct Mod {
+      double padSizeX;
+      double padSizeY;
+      double padSizeErrX;
+      double padSizeErrY;
+      u16 address;
+      int orientation;
+      std::vector<Row> rowPar;
+      std::array<double, 3> translation;
+      std::array<double, 9> rotation;
+
+      CBM_YAML_PROPERTIES(yaml::Property(&Mod::padSizeX, "padSizeX", "X size of pads"),
+			yaml::Property(&Mod::padSizeY, "padSizeY", "Y size of pads"),
+			yaml::Property(&Mod::padSizeErrX, "padSizeErrX", "error of X size of pads"),
+			yaml::Property(&Mod::padSizeErrY, "padSizeErrY", "error of Y size of pads"),
+                        yaml::Property(&Mod::address, "address", "module address"),
+                        yaml::Property(&Mod::orientation, "orientation", "module orientation"),
+                        yaml::Property(&Mod::translation, "translation", "Module position", YAML::Flow),
+			yaml::Property(&Mod::rotation, "rotation", "Module rotation", YAML::Flow),
+			yaml::Property(&Mod::rowPar, "rowPar", "row parameters"));
+    };
+
+    std::vector<Mod> modules;
+
+    CBM_YAML_PROPERTIES(yaml::Property(&HitfindSetup::modules, "modules", "Parameters of modules"));
+  };
+
+}  // namespace cbm::algo::trd
diff --git a/core/detectors/trd/CbmTrdParModGeo.h b/core/detectors/trd/CbmTrdParModGeo.h
index 42a4da3d66..49559f66ff 100644
--- a/core/detectors/trd/CbmTrdParModGeo.h
+++ b/core/detectors/trd/CbmTrdParModGeo.h
@@ -14,7 +14,7 @@ class TGeoPhysicalNode;
 
 /** \brief Definition of geometry for one TRD module **/
 class CbmTrdParModGeo : public CbmTrdParMod {
-public:
+ public:
   CbmTrdParModGeo(const char* name = "CbmTrdParModGeo", const char* title = "TRD module geometry");
   virtual ~CbmTrdParModGeo();
   virtual Double_t GetDX() const;
@@ -43,7 +43,9 @@ public:
   virtual void LocalToMaster(Double_t in[3], Double_t out[3]) const;
   bool SetNode();
 
-private:
+  TGeoPhysicalNode* GetNode() { return fNode; }
+
+ private:
   CbmTrdParModGeo(const CbmTrdParModGeo&);
   const CbmTrdParModGeo& operator=(const CbmTrdParModGeo&);
 
diff --git a/macro/beamtime/mcbm2022/trd_hitfinder_run.C b/macro/beamtime/mcbm2022/trd_hitfinder_run.C
new file mode 100644
index 0000000000..0b7cb3fb6c
--- /dev/null
+++ b/macro/beamtime/mcbm2022/trd_hitfinder_run.C
@@ -0,0 +1,216 @@
+/* Copyright (C) 2020 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau, Adrian Weber [committer], Dominik Smith */
+
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+/// FIXME: Disable clang formatting to keep easy parameters overview
+/* clang-format off */
+Bool_t trd_hitfinder_run(UInt_t uRunId                   = 2457,
+                       Int_t nTimeslices               = 5,
+ //                      Int_t nTimeslices               = 1,
+                       TString sInpDir                 = "/home/dsmith/cbmroot/macro/beamtime/mcbm2022/data/",
+                       TString sOutDir                 = "rec/",
+                       Int_t iUnpFileIndex             = -1)
+{
+  /// FIXME: Re-enable clang formatting after parameters initial values setting
+  /* clang-format on */
+
+  // --- Logger settings ----------------------------------------------------
+  TString logLevel     = "INFO";
+  TString logVerbosity = "LOW";
+  // ------------------------------------------------------------------------
+
+
+  // -----   Environment   --------------------------------------------------
+  TString myName = "trd_hitfinder_run";            // this macro's name for screen output
+  TString srcDir = gSystem->Getenv("VMCWORKDIR");  // top source directory
+  // ------------------------------------------------------------------------
+
+
+  // -----   In- and output file names   ------------------------------------
+  /// Standardized RUN ID
+  TString sRunId = TString::Format("%04u", uRunId);
+
+  /// Initial pattern
+  //TString inFile = sInpDir + "/" + sRunId + "_faspMixed_10ts.digi";
+  TString inFile = sInpDir + "/" + sRunId + ".digi";
+
+  TString parFileOut = sOutDir + "/reco_event_mcbm_params_" + sRunId;
+  TString outFile    = sOutDir + "/reco_event_mcbm_" + sRunId;
+
+  // Your folder with the Tof Calibration files;
+  TString TofFileFolder = "/home/dsmith/cbmroot/macro/run/data/";
+
+  /// Add index of splitting at unpacking level if needed
+  if (0 <= iUnpFileIndex) {
+    inFile += TString::Format("_%02u", iUnpFileIndex);
+    // the input par file is not split during unpacking!
+    parFileOut += TString::Format("_%02u", iUnpFileIndex);
+    outFile += TString::Format("_%02u", iUnpFileIndex);
+  }  // if ( 0 <= uUnpFileIndex )
+  /// Add ROOT file suffix
+  inFile += ".root";
+  parFileOut += ".root";
+  outFile += ".root";
+  // ---------------------------------------------
+
+
+  // --- Load the geometry setup ----
+  // This is currently only required by the TRD (parameters)
+  cbm::mcbm::ToForceLibLoad dummy;  /// Needed to trigger loading of the library as no fct dict in ROOT6 and CLING
+  TString geoSetupTag = "";
+  try {
+    geoSetupTag = cbm::mcbm::GetSetupFromRunId(uRunId);
+  }
+  catch (const std::invalid_argument& e) {
+    std::cout << "Error in mapping from runID to setup name: " << e.what() << std::endl;
+    return kFALSE;
+  }
+
+  TString geoFile    = srcDir + "/macro/mcbm/data/" + geoSetupTag + ".geo.root";
+  CbmSetup* geoSetup = CbmSetup::Instance();
+  geoSetup->LoadSetup(geoSetupTag);
+
+  // You can modify the pre-defined setup by using
+  geoSetup->SetActive(ECbmModuleId::kTof, kTRUE);
+
+  //-----  Load Parameters --------------------------------------------------
+  TList* parFileList = new TList();
+
+  // ----- TRD digitisation parameters -------------------------------------
+  TString geoTagTrd;
+  if (geoSetup->IsActive(ECbmModuleId::kTrd)) {
+    if (geoSetup->GetGeoTag(ECbmModuleId::kTrd, geoTagTrd)) {
+      TString paramFilesTrd(Form("%s/parameters/trd/trd_%s", srcDir.Data(), geoTagTrd.Data()));
+      std::vector<TString> paramFilesVecTrd = {"asic", "digi", "gas", "gain"};
+      for (auto parIt : paramFilesVecTrd) {
+        parFileList->Add(new TObjString(Form("%s.%s.par", paramFilesTrd.Data(), parIt.Data())));
+      }
+    }
+    for (auto parFileVecIt : *parFileList) {
+      std::cout << Form("TrdParams - %s - added to parameter file list", parFileVecIt->GetName()) << std::endl;
+    }
+  }
+
+  // -----   Timer   --------------------------------------------------------
+  TStopwatch timer;
+  timer.Start();
+  // ------------------------------------------------------------------------
+
+
+  // -----   FairRunAna   ---------------------------------------------------
+  FairRunAna* run             = new FairRunAna();
+  FairFileSource* inputSource = new FairFileSource(inFile);
+  run->SetSource(inputSource);
+
+  FairRootFileSink* outputSink = new FairRootFileSink(outFile);
+  run->SetSink(outputSink);
+  run->SetGeomFile(geoFile);
+
+  // Define output file for FairMonitor histograms
+  TString monitorFile{outFile};
+  monitorFile.ReplaceAll("reco", "reco.monitor");
+  FairMonitor::GetMonitor()->EnableMonitor(kTRUE, monitorFile);
+  // ------------------------------------------------------------------------
+
+
+  // -----   Logger settings   ----------------------------------------------
+  FairLogger::GetLogger()->SetLogScreenLevel(logLevel.Data());
+  FairLogger::GetLogger()->SetLogVerbosityLevel(logVerbosity.Data());
+
+
+  // =========================================================================
+  // ===                 local TRD Reconstruction                          ===
+  // =========================================================================
+
+  bool bTRD   = true;
+  bool bTRD2d = true;
+  if ((bTRD && geoSetup->IsActive(ECbmModuleId::kTrd))
+      || (bTRD2d && (geoSetup->IsActive(ECbmModuleId::kTrd2d) || geoSetup->IsActive(ECbmModuleId::kTrd)))) {
+    //Double_t triggerThreshold = 0.5e-6;  // SIS100
+
+    CbmTaskTrdHitFinderParWrite* trdHitfinderPar = new CbmTaskTrdHitFinderParWrite();
+    run->AddTask(trdHitfinderPar);
+
+    CbmTaskTrdHitFinder* trdHitfinder = new CbmTaskTrdHitFinder();
+    run->AddTask(trdHitfinder);
+
+    // CbmTrdClusterFinder* trdCluster;
+    // trdCluster = new CbmTrdClusterFinder();
+    // trdCluster->SetNeighbourEnable(true, false);
+    // trdCluster->SetMinimumChargeTH(triggerThreshold);
+    // trdCluster->SetRowMerger(true);
+    // run->AddTask(trdCluster);
+    //  std::cout << "-I- : Added task " << trdCluster->GetName() << std::endl;
+
+    // CbmTrdHitProducer* trdHit = new CbmTrdHitProducer();
+    // run->AddTask(trdHit);
+    // std::cout << "-I- : Added task " << trdHit->GetName() << std::endl;
+  }
+
+  // -----  Parameter database   --------------------------------------------
+  std::cout << std::endl << std::endl;
+  std::cout << "-I- " << myName << ": Set runtime DB" << std::endl;
+  FairRuntimeDb* rtdb        = run->GetRuntimeDb();
+  FairParRootFileIo* parIo1  = new FairParRootFileIo();
+  FairParAsciiFileIo* parIo2 = new FairParAsciiFileIo();
+  FairParRootFileIo* parIo3  = new FairParRootFileIo();
+  parIo2->open(parFileList, "in");
+  rtdb->setSecondInput(parIo2);
+  parIo3->open(parFileOut.Data(), "RECREATE");
+  // ------------------------------------------------------------------------
+  rtdb->setOutput(parIo3);
+  rtdb->saveOutput();
+  rtdb->print();
+
+  // -----   Run initialisation   -------------------------------------------
+  std::cout << std::endl;
+  std::cout << "-I- " << myName << ": Initialise run" << std::endl;
+  run->Init();
+
+
+  // -----   Start run   ----------------------------------------------------
+  std::cout << std::endl << std::endl;
+  std::cout << "-I- " << myName << ": Starting run" << std::endl;
+  run->Run(0, nTimeslices);
+  // ------------------------------------------------------------------------
+
+  // -----   Finish   -------------------------------------------------------
+  timer.Stop();
+  FairMonitor::GetMonitor()->Print();
+  Double_t rtime = timer.RealTime();
+  Double_t ctime = timer.CpuTime();
+  std::cout << std::endl << std::endl;
+  std::cout << "Macro finished successfully." << std::endl;
+  std::cout << "Output file is " << outFile << std::endl;
+  std::cout << "Parameter file is " << parFileOut << std::endl;
+  std::cout << "Real time " << rtime << " s, CPU time " << ctime << " s" << std::endl;
+  std::cout << std::endl;
+  // ------------------------------------------------------------------------
+
+
+  // -----   Resource monitoring   ------------------------------------------
+  // Extract the maximal used memory an add is as Dart measurement
+  // This line is filtered by CTest and the value send to CDash
+  FairSystemInfo sysInfo;
+  Float_t maxMemory = sysInfo.GetMaxMemory();
+  std::cout << "<DartMeasurement name=\"MaxMemory\" type=\"numeric/double\">";
+  std::cout << maxMemory;
+  std::cout << "</DartMeasurement>" << std::endl;
+
+  Float_t cpuUsage = ctime / rtime;
+  std::cout << "<DartMeasurement name=\"CpuLoad\" type=\"numeric/double\">";
+  std::cout << cpuUsage;
+  std::cout << "</DartMeasurement>" << std::endl;
+  // ------------------------------------------------------------------------
+
+  /// --- Screen output for automatic tests
+  std::cout << " Test passed" << std::endl;
+  std::cout << " All ok " << std::endl;
+
+  return kTRUE;
+}
diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt
index 533e37764f..b07e9fea5d 100644
--- a/reco/tasks/CMakeLists.txt
+++ b/reco/tasks/CMakeLists.txt
@@ -18,6 +18,8 @@ set(SRCS
   CbmTaskTofHitFinder.cxx
   CbmTaskTofClusterizer.cxx
   CbmTaskTofClusterizerParWrite.cxx
+  CbmTaskTrdHitFinder.cxx
+  CbmTaskTrdHitFinderParWrite.cxx
   CbmTaskUnpack.cxx
 )
 
diff --git a/reco/tasks/CbmRecoTasksLinkDef.h b/reco/tasks/CbmRecoTasksLinkDef.h
index fbff7c46c3..7242e33ab9 100644
--- a/reco/tasks/CbmRecoTasksLinkDef.h
+++ b/reco/tasks/CbmRecoTasksLinkDef.h
@@ -21,6 +21,8 @@
 #pragma link C++ class CbmTaskTofHitFinder + ;
 #pragma link C++ class CbmTaskTofClusterizer + ;
 #pragma link C++ class CbmTaskTofClusterizerParWrite + ;
+#pragma link C++ class CbmTaskTrdHitFinderParWrite + ;
+#pragma link C++ class CbmTaskTrdHitFinder + ;
 #pragma link C++ class CbmTaskTriggerDigi + ;
 #pragma link C++ class CbmTaskUnpack + ;
 //#pragma link C++ class cbm::algo::MainConfig + ;
diff --git a/reco/tasks/CbmTaskTrdHitFinder.cxx b/reco/tasks/CbmTaskTrdHitFinder.cxx
new file mode 100644
index 0000000000..9e505e9e3b
--- /dev/null
+++ b/reco/tasks/CbmTaskTrdHitFinder.cxx
@@ -0,0 +1,153 @@
+/* Copyright (C) 2010-2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Florian Uhlig [committer], Pascal Raisig, Alexandru Bercuci */
+
+#include "CbmTaskTrdHitFinder.h"
+
+#include "CbmDigiManager.h"
+
+#include <FairRootManager.h>
+#include <FairRunAna.h>
+#include <FairRuntimeDb.h>
+#include <Logger.h>
+
+// C++ Classes and includes
+#include "compat/Filesystem.h"
+#include "yaml/Yaml.h"
+
+#include <TStopwatch.h>
+#include <TVector3.h>
+
+#include <cmath>
+#include <iomanip>
+#include <iostream>
+
+using std::fixed;
+using std::left;
+using std::right;
+using std::setprecision;
+using std::setw;
+using std::stringstream;
+
+//_____________________________________________________________________
+CbmTaskTrdHitFinder::CbmTaskTrdHitFinder() : FairTask("TrdClusterFinder", 1) {}
+
+// ---- Destructor ----------------------------------------------------
+CbmTaskTrdHitFinder::~CbmTaskTrdHitFinder()
+{
+  if (fClusters) {
+    fClusters->clear();
+    delete fClusters;
+  }
+  if (fHits) {
+    fHits->clear();
+    delete fHits;
+  }
+}
+
+//_____________________________________________________________________
+void CbmTaskTrdHitFinder::SetParContainers()
+{
+  // FairRuntimeDb* rtdb = FairRunAna::Instance()->GetRuntimeDb();
+}
+
+//_____________________________________________________________________
+InitStatus CbmTaskTrdHitFinder::Init()
+{
+  CbmDigiManager::Instance()->Init();
+  if (!CbmDigiManager::Instance()->IsPresent(ECbmModuleId::kTrd)) LOG(fatal) << GetName() << "Missing Trd digi branch.";
+
+  fClusters              = new std::vector<CbmTrdCluster>();
+  FairRootManager* ioman = FairRootManager::Instance();
+  ioman->RegisterAny("TrdCluster", fClusters, true);
+
+  fHits = new std::vector<CbmTrdHit>();
+  ioman->RegisterAny("TrdHit", fHits, true);
+
+  // Read hitfinder parameters and initialize algo
+  fAlgo = std::make_unique<cbm::algo::trd::Hitfind>(
+    cbm::algo::yaml::ReadFromFile<cbm::algo::trd::HitfindSetup>("TrdHitfinderPar.yaml"),
+    cbm::algo::yaml::ReadFromFile<cbm::algo::trd::Hitfind2DSetup>("TrdHitfinder2DPar.yaml"));
+
+  return kSUCCESS;
+}
+
+//_____________________________________________________________________
+void CbmTaskTrdHitFinder::Exec(Option_t* /*option*/)
+{
+
+  size_t numHitsCall = 0;
+  fClusters->clear();
+  fHits->clear();
+
+  TStopwatch timerTs;
+  timerTs.Start();
+
+  std::vector<CbmTrdDigi> digiVec;
+
+  for (int32_t iDigi = 0; iDigi < CbmDigiManager::Instance()->GetNofDigis(ECbmModuleId::kTrd); iDigi++) {
+    const CbmTrdDigi* tDigi = CbmDigiManager::Instance()->Get<CbmTrdDigi>(iDigi);
+    digiVec.push_back(CbmTrdDigi(*tDigi));
+  }
+
+  auto [hits, monitor] = (*fAlgo)(digiVec);
+  AddHits(&hits);
+
+  timerTs.Stop();
+  fProcessTime += timerTs.RealTime();
+
+  stringstream logOut;
+  logOut << setw(20) << left << GetName() << " [";
+  logOut << fixed << setw(8) << setprecision(1) << right << timerTs.RealTime() * 1000. << " ms] ";
+  logOut << "TS " << fNrTs;
+  LOG(info) << logOut.str();
+}
+
+
+//_____________________________________________________________________
+void CbmTaskTrdHitFinder::AddHits(std::vector<cbm::algo::trd::Hit>* hits)
+{
+  for (auto& hit : *hits) {
+    TVector3 pos(hit.GetX(), hit.GetY(), hit.GetZ());
+    TVector3 dpos(hit.GetDx(), hit.GetDy(), hit.GetDz());
+
+    CbmTrdHit& hitSave =
+      fHits->emplace_back(hit.GetAddress(), pos, dpos, hit.GetDxy(), hit.GetRefId(), hit.GetELoss(), hit.GetTime(),
+                          hit.GetTimeError());  // TODO implement copy constructor
+    hitSave.SetOverFlow(hit.HasOverFlow());
+    hitSave.SetRowCross(hit.IsRowCross());
+    hitSave.SetClassType(hit.GetClassType());
+    hitSave.SetMaxType(hit.GetMaxType());
+  }
+}
+
+
+//_____________________________________________________________________
+void CbmTaskTrdHitFinder::Finish()
+{
+  std::cout << std::endl;
+  LOG(info) << "=====================================";
+  LOG(info) << GetName() << ": Finish run";
+  LOG(info) << GetName() << ": Run summary ";
+  LOG(info) << GetName() << ": Processing time         : " << std::fixed << std::setprecision(3) << fProcessTime;
+  LOG(info) << GetName() << ": Nr of events            : " << fNrEvents;
+  LOG(info) << GetName() << ": Nr of input digis       : " << fNrDigis;
+  LOG(info) << GetName() << ": Nr of produced clusters : " << fNrClusters;
+  LOG(info) << GetName() << ": Nr of clusters / event  : " << std::fixed << std::setprecision(2)
+            << (fNrEvents > 0 ? fNrClusters / (Double_t) fNrEvents : 0);
+  LOG(info) << GetName() << ": Nr of digis / cluster   : " << std::fixed << std::setprecision(2)
+            << (fNrClusters > 0 ? fNrDigis / (Double_t) fNrClusters : 0);
+  LOG(info) << "=====================================";
+
+  LOG(info) << GetName() << ": Nr of events         : " << fNrEvents;
+  LOG(info) << GetName() << ": Nr of input clusters : " << fNrClusters;
+  LOG(info) << GetName() << ": Nr of produced hits  : " << fNrHits;
+  LOG(info) << GetName() << ": Nr of hits / event   : " << std::fixed << std::setprecision(2)
+            << (fNrEvents > 0 ? fNrHits / (Double_t) fNrEvents : 0);
+  LOG(info) << GetName() << ": Nr of hits / clusters: " << std::fixed << std::setprecision(2)
+            << (fNrClusters > 0 ? fNrHits / (Double_t) fNrClusters : 0);
+  LOG(info) << "=====================================";
+  std::cout << std::endl;
+}
+
+ClassImp(CbmTaskTrdHitFinder)
diff --git a/reco/tasks/CbmTaskTrdHitFinder.h b/reco/tasks/CbmTaskTrdHitFinder.h
new file mode 100644
index 0000000000..15ca0995f4
--- /dev/null
+++ b/reco/tasks/CbmTaskTrdHitFinder.h
@@ -0,0 +1,93 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer], Alexandru Bercuci */
+
+#ifndef CBMTASKTRDHITFINDER_H
+#define CBMTASKTRDHITFINDER_H
+
+#include "CbmTrdCluster.h"
+#include "CbmTrdDigi.h"
+#include "CbmTrdHit.h"
+#include "FairTask.h"
+#include "trd/Hit.h"
+#include "trd/Hitfind.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+/** CbmTaskTrdHitFinder.h
+ *@author Dominik Smith <d.smith@gsi.de>
+ **
+ ** Task to build TRD and TRD2D hits from yaml configuratin. 
+ **/
+class CbmTaskTrdHitFinder : public FairTask {
+
+ public:
+  /**
+   * \brief Default constructor.
+   */
+  CbmTaskTrdHitFinder();
+
+  /**
+   * \brief Default destructor.
+   */
+  ~CbmTaskTrdHitFinder();
+
+  /** Initialisation **/
+  virtual InitStatus Init();
+  virtual void SetParContainers();
+
+  /** \brief Executed task **/
+  virtual void Exec(Option_t* option);
+
+  /** Finish task **/
+  virtual void Finish();
+
+ private:
+  CbmTaskTrdHitFinder(const CbmTaskTrdHitFinder&);
+  CbmTaskTrdHitFinder& operator=(const CbmTaskTrdHitFinder&);
+
+  /**
+   * @brief Build hits from clusters for a given module
+   */
+  template<class TModule, class TCluster>
+  void BuildHits(TModule* mod, std::vector<TCluster>* clusters);
+
+  template<class TCluster>
+  void AddClusters(std::vector<TCluster>* clusters);
+  void AddHits(std::vector<cbm::algo::trd::Hit>* hits);
+
+  /** @brief Create one algo object for each RPC **/
+  bool InitAlgos();
+
+  /** Output array of CbmTrdCluster **/
+  std::vector<CbmTrdCluster>* fClusters = nullptr;
+
+  /** @brief Output array of CbmTrdHit */
+  std::vector<CbmTrdHit>* fHits = nullptr;
+
+  /** @brief Hit finding algorithm */
+  std::unique_ptr<cbm::algo::trd::Hitfind> fAlgo;
+
+  /** @brief Number of processed time slices */
+  UInt_t fNrTs = 0;
+
+  /** @brief Number of processed events (without CbmEvent corresponds to nr of exec calls) */
+  UInt_t fNrEvents = 0;
+
+  /** @brief Number of digis as input for the hit production. */
+  UInt_t fNrDigis = 0;
+
+  /** @brief Number of produced clusters. */
+  UInt_t fNrClusters = 0;
+
+  /** @brief Number of produced hits. */
+  UInt_t fNrHits = 0;
+
+  /** @brief Total processing time [RealTime]. */
+  Float_t fProcessTime = 0;
+
+  ClassDef(CbmTaskTrdHitFinder, 1);
+};
+#endif
diff --git a/reco/tasks/CbmTaskTrdHitFinderParWrite.cxx b/reco/tasks/CbmTaskTrdHitFinderParWrite.cxx
new file mode 100644
index 0000000000..9d23f6aa99
--- /dev/null
+++ b/reco/tasks/CbmTaskTrdHitFinderParWrite.cxx
@@ -0,0 +1,213 @@
+/* Copyright (C) 2010-2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Florian Uhlig [committer], Pascal Raisig, Alexandru Bercuci */
+
+#include "CbmTaskTrdHitFinderParWrite.h"
+
+#include "CbmTrdParAsic.h"
+#include "CbmTrdParFasp.h"
+#include "CbmTrdParModAsic.h"
+#include "CbmTrdParModDigi.h"
+#include "CbmTrdParModGeo.h"
+#include "CbmTrdParSetAsic.h"
+#include "CbmTrdParSetDigi.h"
+#include "CbmTrdParSetGeo.h"
+#include "TGeoPhysicalNode.h"
+#include "TVector3.h"
+#include "trd/Hitfind2DSetup.h"
+#include "trd/HitfindSetup.h"
+#include "yaml/Yaml.h"
+
+#include <FairRootManager.h>
+#include <FairRunAna.h>
+#include <FairRuntimeDb.h>
+#include <Logger.h>
+
+#include <iomanip>
+#include <iostream>
+#include <vector>
+
+using std::fixed;
+using std::left;
+using std::right;
+using std::setprecision;
+using std::setw;
+using std::stringstream;
+
+//_____________________________________________________________________
+CbmTaskTrdHitFinderParWrite::CbmTaskTrdHitFinderParWrite() : FairTask("TrdClusterFinder", 1) {}
+
+//_____________________________________________________________________
+void CbmTaskTrdHitFinderParWrite::SetParContainers()
+{
+  FairRuntimeDb* rtdb = FairRunAna::Instance()->GetRuntimeDb();
+  fAsicPar            = static_cast<CbmTrdParSetAsic*>(rtdb->getContainer("CbmTrdParSetAsic"));
+  fDigiPar            = static_cast<CbmTrdParSetDigi*>(rtdb->getContainer("CbmTrdParSetDigi"));
+  fGeoPar             = static_cast<CbmTrdParSetGeo*>(rtdb->getContainer("CbmTrdParSetGeo"));
+}
+
+//_____________________________________________________________________
+InitStatus CbmTaskTrdHitFinderParWrite::Init()
+{
+  // Get the full geometry information of the detector gas layers and store
+  // them with the CbmTrdModuleRec. This information can then be used for
+  // transformation calculations
+  if (!fGeoPar->LoadAlignVolumes()) {
+    LOG(error) << GetName() << "::Init: GEO info for modules unavailable !";
+    return kFATAL;
+  }
+  if (fDigiPar->GetNrOfModules() != fGeoPar->GetNrOfModules()) {
+    LOG(fatal) << "CbmTaskTrdHitFinderParWrite::Init() - Geometry and parameter files"
+               << " have different number of modules.";
+  }
+
+  // Sets of module IDs
+  std::vector<uint16_t> trdModules;
+  std::vector<uint16_t> trd2DModules;
+
+  // Store IDs of 1D and 2D modules
+  for (auto entry : fDigiPar->GetModuleMap()) {
+    const auto moduleId = entry.first;
+
+    // Get ASIC parameters for this module
+    const CbmTrdParModAsic* asicPar = static_cast<const CbmTrdParModAsic*>(fAsicPar->GetModulePar(moduleId));
+    if (!asicPar) continue;
+
+    if (asicPar->GetAsicType() == CbmTrdDigi::eCbmTrdAsicType::kFASP) {
+      trd2DModules.push_back(entry.first);
+    }
+    else {
+      trdModules.push_back(entry.first);
+    }
+  }
+
+  // Create setup files for each type
+  cbm::algo::trd::HitfindSetup setup;
+  cbm::algo::trd::Hitfind2DSetup setup2D;
+  setup.modules.resize(trdModules.size());
+  setup2D.modules.resize(trd2DModules.size());
+
+  // Fill 2D setup files
+  for (size_t mod; mod < trd2DModules.size(); mod++) {
+    const uint16_t moduleId = trd2DModules[mod];
+
+    // Get Geometry parameters for module
+    CbmTrdParModGeo* pGeo = static_cast<CbmTrdParModGeo*>(fGeoPar->GetModulePar(moduleId));
+
+    // Get ASIC parameters for this module
+    const CbmTrdParModAsic* asicPar = static_cast<const CbmTrdParModAsic*>(fAsicPar->GetModulePar(moduleId));
+
+    if (!asicPar) continue;
+    const CbmTrdParModDigi* digiPar = static_cast<const CbmTrdParModDigi*>(fDigiPar->GetModulePar(moduleId));
+
+    cbm::algo::trd::Hitfind2DSetup::Mod& module = setup2D.modules[mod];
+    module.padSizeX                             = digiPar->GetPadSizeX(0);
+    module.padSizeY                             = digiPar->GetPadSizeY(0);
+    module.address                              = moduleId;
+    module.rowPar.resize(digiPar->GetNofRows());
+
+    const double* tra_ptr = pGeo->GetNode()->GetMatrix()->GetTranslation();
+    const double* rot_ptr = pGeo->GetNode()->GetMatrix()->GetRotationMatrix();
+    memcpy(module.translation.data(), tra_ptr, 3 * sizeof(double));
+    memcpy(module.rotation.data(), rot_ptr, 9 * sizeof(double));
+
+    for (int row = 0; row < digiPar->GetNofRows(); row++) {
+
+      cbm::algo::trd::Hitfind2DSetup::Row& rowPar = module.rowPar[row];
+      rowPar.padPar.resize(digiPar->GetNofColumns());
+
+      int rowInSector  = 0;
+      const int sector = digiPar->GetSectorRow(row, rowInSector);  // Second is ref. TO DO: Return std::pair instead.
+
+      for (int col = 0; col < digiPar->GetNofColumns(); col++) {
+        cbm::algo::trd::Hitfind2DSetup::Pad& padPar = rowPar.padPar[col];
+        TVector3 pos, posErr;
+        digiPar->GetPadPosition(sector, col, rowInSector, pos, posErr);
+        const std::array<double, 3> pos_ptr    = {pos[0], pos[1], pos[2]};
+        const std::array<double, 3> posErr_ptr = {posErr[0], posErr[1], posErr[2]};
+        memcpy(padPar.position.data(), pos_ptr.data(), 3 * sizeof(double));
+        memcpy(padPar.positionError.data(), posErr_ptr.data(), 3 * sizeof(double));
+
+        const size_t chan = row * digiPar->GetNofColumns() + col;
+        const CbmTrdParFaspChannel *daqFaspChT(nullptr), *daqFaspChR(nullptr);
+        asicPar->GetFaspChannelPar(chan, daqFaspChT, daqFaspChR);
+        if (!daqFaspChR) {
+          padPar.chRMasked = false;  // TODO implement case for not installed
+        }
+        else {
+          padPar.chRMasked = daqFaspChR->IsMasked();
+        }
+        if (!daqFaspChT) {
+          padPar.chTMasked = false;  // TODO implement case for not installed
+        }
+        else {
+          padPar.chTMasked = daqFaspChT->IsMasked();
+        }
+      }
+    }
+  }  // for (size_t mod; mod < trd2DModules.size(); mod++) {
+
+
+  // Fill 1D setup files
+  for (size_t mod; mod < trdModules.size(); mod++) {
+    const uint16_t moduleId = trdModules[mod];
+
+    // Get Geometry parameters for module
+    CbmTrdParModGeo* pGeo = static_cast<CbmTrdParModGeo*>(fGeoPar->GetModulePar(moduleId));
+
+    // Get ASIC parameters for this module
+    const CbmTrdParModAsic* asicPar = static_cast<const CbmTrdParModAsic*>(fAsicPar->GetModulePar(moduleId));
+
+    if (!asicPar) continue;
+    const CbmTrdParModDigi* digiPar = static_cast<const CbmTrdParModDigi*>(fDigiPar->GetModulePar(moduleId));
+
+    cbm::algo::trd::HitfindSetup::Mod& module = setup.modules[mod];
+    module.padSizeX                           = digiPar->GetPadSizeX(0);
+    module.padSizeY                           = digiPar->GetPadSizeY(0);
+    module.padSizeErrX                        = digiPar->GetPadSizeX(1);
+    module.padSizeErrY                        = digiPar->GetPadSizeY(1);
+    module.orientation                        = digiPar->GetOrientation();
+    module.address                            = moduleId;
+    module.rowPar.resize(digiPar->GetNofRows());
+
+    const double* tra_ptr = pGeo->GetNode()->GetMatrix()->GetTranslation();
+    const double* rot_ptr = pGeo->GetNode()->GetMatrix()->GetRotationMatrix();
+    memcpy(module.translation.data(), tra_ptr, 3 * sizeof(double));
+    memcpy(module.rotation.data(), rot_ptr, 9 * sizeof(double));
+
+    for (int row = 0; row < digiPar->GetNofRows(); row++) {
+
+      cbm::algo::trd::HitfindSetup::Row& rowPar = module.rowPar[row];
+      rowPar.padPar.resize(digiPar->GetNofColumns());
+
+      int rowInSector  = 0;
+      const int sector = digiPar->GetSectorRow(row, rowInSector);  // Second is ref. TO DO: Return std::pair instead.
+
+      for (int col = 0; col < digiPar->GetNofColumns(); col++) {
+        cbm::algo::trd::HitfindSetup::Pad& padPar = rowPar.padPar[col];
+        TVector3 pos, posErr;
+        digiPar->GetPadPosition(sector, col, rowInSector, pos, posErr);
+        const std::array<double, 3> pos_ptr    = {pos[0], pos[1], pos[2]};
+        const std::array<double, 3> posErr_ptr = {posErr[0], posErr[1], posErr[2]};
+        memcpy(padPar.position.data(), pos_ptr.data(), 3 * sizeof(double));
+        memcpy(padPar.positionError.data(), posErr_ptr.data(), 3 * sizeof(double));
+      }
+    }
+  }  // for (size_t mod; mod < trdModules.size(); mod++) {
+
+
+  /* Write Yaml files */
+
+  cbm::algo::yaml::Dump dump;
+  std::ofstream fout("TrdHitfinderPar.yaml");
+  fout << dump(setup);
+  fout.close();
+
+  std::ofstream f2Dout("TrdHitfinder2DPar.yaml");
+  f2Dout << dump(setup2D);
+  f2Dout.close();
+
+  return kSUCCESS;
+}
+
+ClassImp(CbmTaskTrdHitFinderParWrite)
diff --git a/reco/tasks/CbmTaskTrdHitFinderParWrite.h b/reco/tasks/CbmTaskTrdHitFinderParWrite.h
new file mode 100644
index 0000000000..24ff1fbce5
--- /dev/null
+++ b/reco/tasks/CbmTaskTrdHitFinderParWrite.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#ifndef CBMTRDHITFINDERPARWRITE_H
+#define CBMTRDHITFINDERPARWRITE_H
+
+#include "FairTask.h"
+#include "trd/Clusterizer.h"
+#include "trd/Clusterizer2D.h"
+#include "trd/Hit.h"
+#include "trd/HitFinder.h"
+#include "trd/HitFinder2D.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+class CbmTrdParSetAsic;
+class CbmTrdParSetGeo;
+class CbmTrdParSetDigi;
+
+/** CbmTaskTrdHitFinderParWrite.h
+ *@author Dominik Smith <d.smith@gsi.de>
+ **
+ ** Task to create YAML files for TRD hitfinders
+ **
+ **/
+class CbmTaskTrdHitFinderParWrite : public FairTask {
+
+ public:
+  /**
+   * \brief Default constructor.
+   */
+  CbmTaskTrdHitFinderParWrite();
+
+  /**
+   * \brief Default destructor.
+   */
+  ~CbmTaskTrdHitFinderParWrite(){};
+
+  /** Initialisation **/
+  virtual InitStatus Init();
+  virtual void SetParContainers();
+
+  /** \brief Executed task **/
+  virtual void Exec(Option_t* option){};
+
+  /** Finish task **/
+  virtual void Finish(){};
+
+ private:
+  CbmTaskTrdHitFinderParWrite(const CbmTaskTrdHitFinderParWrite&);
+  CbmTaskTrdHitFinderParWrite& operator=(const CbmTaskTrdHitFinderParWrite&);
+
+  //==================================================================
+  CbmTrdParSetAsic* fAsicPar = nullptr;  ///< parameter list for ASIC characterization
+  CbmTrdParSetDigi* fDigiPar = nullptr;  ///< parameter list for read-out geometry
+  CbmTrdParSetGeo* fGeoPar   = nullptr;  ///< parameter list for modules geometry
+
+  ClassDef(CbmTaskTrdHitFinderParWrite, 1);
+};
+#endif
-- 
GitLab