From e3d000c155b613b2781c3ebe14a7378d1b4ed3f3 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 20 Nov 2023 22:01:26 +0100
Subject: [PATCH] ca: QA module for online reconstruction

---
 algo/CMakeLists.txt          | 11 +++++
 algo/ca/TrackingChain.cxx    | 38 ++++++++++++++++
 algo/ca/TrackingChain.h      | 18 ++++++--
 algo/ca/core/CMakeLists.txt  |  3 ++
 algo/ca/core/data/CaHit.h    |  1 +
 algo/ca/qa/CaQaBuilder.cxx   | 67 ++++++++++++++++++++++++++++
 algo/ca/qa/CaQaBuilder.h     | 85 ++++++++++++++++++++++++++++++++++++
 algo/ca/qa/CaQaConfig.cxx    | 84 +++++++++++++++++++++++++++++++++++
 algo/ca/qa/CaQaConfig.h      | 61 ++++++++++++++++++++++++++
 algo/ca/qa/CaQaData.cxx      | 38 ++++++++++++++++
 algo/ca/qa/CaQaData.h        | 63 ++++++++++++++++++++++++++
 algo/ca/qa/CaQaDefinitions.h | 59 +++++++++++++++++++++++++
 algo/global/Reco.cxx         |  9 ++--
 algo/global/Reco.h           |  2 +-
 14 files changed, 530 insertions(+), 9 deletions(-)
 create mode 100644 algo/ca/qa/CaQaBuilder.cxx
 create mode 100644 algo/ca/qa/CaQaBuilder.h
 create mode 100644 algo/ca/qa/CaQaConfig.cxx
 create mode 100644 algo/ca/qa/CaQaConfig.h
 create mode 100644 algo/ca/qa/CaQaData.cxx
 create mode 100644 algo/ca/qa/CaQaData.h
 create mode 100644 algo/ca/qa/CaQaDefinitions.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 01de74a9e6..b57bfe43b8 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -111,6 +111,9 @@ set(SRCS
   qa/DigiEventQa.cxx
   qa/Histo1D.cxx
   ca/TrackingChain.cxx
+  ca/qa/CaQaBuilder.cxx
+  ca/qa/CaQaConfig.cxx
+  ca/qa/CaQaData.cxx
 )
 
 set(BUILD_INFO_CXX ${CMAKE_CURRENT_BINARY_DIR}/base/BuildInfo.cxx)
@@ -142,6 +145,7 @@ target_include_directories(Algo
          ${CMAKE_CURRENT_SOURCE_DIR}/unpack
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors
          ${CMAKE_CURRENT_SOURCE_DIR}/qa
+         ${CMAKE_CURRENT_SOURCE_DIR}/ca/qa
          ${CMAKE_CURRENT_SOURCE_DIR}
          ${CMAKE_SOURCE_DIR}/core/data/global
 )
@@ -202,6 +206,7 @@ install(DIRECTORY detectors/sts TYPE INCLUDE FILES_MATCHING PATTERN "*.h")
 install(DIRECTORY detectors/tof TYPE INCLUDE FILES_MATCHING PATTERN "*.h")
 install(DIRECTORY detectors/trd TYPE INCLUDE FILES_MATCHING PATTERN "*.h")
 install(DIRECTORY detectors/trd2d TYPE INCLUDE FILES_MATCHING PATTERN "*.h")
+install(DIRECTORY ca/qa TYPE INCLUDE FILES_MATCHING PATTERN "*.h")
 install(DIRECTORY ca TYPE INCLUDE FILES_MATCHING PATTERN "*.h")
 
 
@@ -221,6 +226,12 @@ install(
     global/RecoResultsOutputArchive.h
     global/StorableRecoResults.h
     ca/TrackingChain.h
+    # NOTE: SZh 20.11.2023: 
+    #       The ca/qa directory depends on the online qa classes, so for now it has to be a part of the Algo library.
+    ca/qa/CaQaBuilder.h
+    ca/qa/CaQaConfig.h
+    ca/qa/CaQaData.h
+    ca/qa/CaQaDefinitions.h
   DESTINATION
     include/
 )
diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index e38cf8977a..ee7774c52b 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -33,6 +33,27 @@ using cbm::algo::ca::Track;
 using cbm::algo::ca::constants::clrs::CL;   // clear text
 using cbm::algo::ca::constants::clrs::GNb;  // grin bald text
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+TrackingChain::TrackingChain(std::shared_ptr<HistogramSender> histoSender) : fpSender(histoSender)
+{
+  // ------ Initialize the histogram sender
+  if (fpSender) {
+    auto histCfgs = fQaBuilder.GetConfig().GetHistogramConfigs();
+    auto canvCfgs = fQaBuilder.GetConfig().GetCanvasConfigs();
+    fpSender->PrepareAndSendMsg(std::pair<uint32_t, uint32_t>(histCfgs.size(), canvCfgs.size()),
+                                zmq::send_flags::sndmore);
+    for (const auto& cfg : histCfgs) {
+      fpSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
+    }
+    for (const auto& cfg : canvCfgs) {
+      fpSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
+    }
+    // Histograms serialization and emission to close multi-part message
+    fpSender->PrepareAndSendMsg(std::vector<Histo1D> {}, zmq::send_flags::none);
+  }
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void TrackingChain::Init()
@@ -51,6 +72,9 @@ void TrackingChain::Init()
   fCaMonitor.Reset();
   fCaFramework.Init(ca::Framework::TrackingMode::kMcbm);
   fCaFramework.ReceiveParameters(std::move(parameters));
+
+  // ------ Initialize QA builder
+  fQaBuilder.RegisterParameters(&fCaFramework.GetParameters());
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -133,6 +157,19 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
 
   fCaMonitor.AddMonitorData(fCaMonitorData);
   output.monitorData = fCaMonitorData;
+
+  // QA
+  if (fpSender) {
+    fQaBuilder.RegisterInputData(&fCaFramework.GetInputData());
+    fQaBuilder.RegisterTracks(&output.tracks);
+    fQaBuilder.RegisterRecoHitIndices(&fCaFramework.fRecoHits);
+    auto qaData = fQaBuilder.Build();
+    L_(info) << "TrackingChain: " << qaData.ToString();
+    fpSender->PrepareAndSendMsg(qaData.VectorOfH1(), zmq::send_flags::none);
+    L_(info) << "TrackingChain: Published " << qaData.VectorOfH1().size() << " 1D-histograms";
+  }
+
+
   return output;
 }
 
@@ -199,6 +236,7 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
       caHit.SetRangeX(3.5 * hit.Dx());
       caHit.SetRangeY(3.5 * hit.Dy());
       caHit.SetRangeT(3.5 * hit.TimeError());
+      //L_(info) << ">>>>>>>>>>>> " << iStActive;
       caHit.SetStation(iStActive);
       caHit.SetId(fCaDataManager.GetNofHits());
       if (caHit.Check()) {
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index b85ff45614..c750d9d7ce 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -12,13 +12,17 @@
 #include "TrackingDefs.h"
 #include "tof/Hit.h"
 
+#include <memory>
 #include <vector>
 
 #include "CaDataManager.h"
 #include "CaFramework.h"
+#include "CaQaBuilder.h"
+#include "CaQaConfig.h"
 #include "CaTrack.h"
 #include "CaTrackingMonitor.h"
 #include "CaVector.h"
+#include "HistogramSender.h"
 #include "PartitionedSpan.h"
 #include "RecoResults.h"
 #include "SubChain.h"
@@ -32,6 +36,10 @@ namespace cbm::algo
   /// The class executes a tracking algorithm in the online data reconstruction chain.
   class TrackingChain : public SubChain {
   public:
+    /// \brief Constructor from parameters
+    /// \param histoSender  A shared pointer to a histogram sender
+    TrackingChain(std::shared_ptr<HistogramSender> histoSender = nullptr);
+
     /// \struct Input_t
     /// \brief  Input to the TrackingChain
     struct Input_t {
@@ -94,10 +102,12 @@ namespace cbm::algo
     // *************************
     // **  Framework variables
 
-    ca::TrackingMonitor fCaMonitor {};          ///< CA internal monitor (debug purposes)
-    ca::TrackingMonitorData fCaMonitorData {};  ///< CA monitor data object
-    ca::Framework fCaFramework {};              ///< CA framework instance
-    ca::DataManager fCaDataManager {};          ///< CA data manager
+    ca::TrackingMonitor fCaMonitor {};              ///< CA internal monitor (debug purposes)
+    ca::TrackingMonitorData fCaMonitorData {};      ///< CA monitor data object
+    ca::Framework fCaFramework {};                  ///< CA framework instance
+    ca::DataManager fCaDataManager {};              ///< CA data manager
+    ca::qa::Builder fQaBuilder {ca::qa::Config()};  ///< CA QA builder
+    std::shared_ptr<HistogramSender> fpSender;      ///< Histogram sender
 
     // ************************
     // **  Auxilary variables
diff --git a/algo/ca/core/CMakeLists.txt b/algo/ca/core/CMakeLists.txt
index 544b54bd64..36d86f93bc 100644
--- a/algo/ca/core/CMakeLists.txt
+++ b/algo/ca/core/CMakeLists.txt
@@ -2,6 +2,7 @@ set(INCLUDE_DIRECTORIES
   ${CMAKE_CURRENT_SOURCE_DIR}
   ${CMAKE_CURRENT_SOURCE_DIR}/utils
   ${CMAKE_CURRENT_SOURCE_DIR}/pars
+  ${CMAKE_CURRENT_SOURCE_DIR}/qa
   ${CMAKE_CURRENT_SOURCE_DIR}/data
   ${CMAKE_CURRENT_SOURCE_DIR}/tracking
 )
@@ -45,6 +46,7 @@ target_include_directories(CaCore
   PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/data
          ${CMAKE_CURRENT_SOURCE_DIR}/utils
          ${CMAKE_CURRENT_SOURCE_DIR}/pars
+         ${CMAKE_CURRENT_SOURCE_DIR}/qa
          ${CMAKE_CURRENT_SOURCE_DIR}/tracking
          ${CMAKE_CURRENT_SOURCE_DIR}
 )
@@ -91,6 +93,7 @@ install(
     pars/CaSearchWindow.h
     pars/CaStation.h
     pars/CaStationInitializer.h
+
     utils/CaTrackingMonitor.h
     utils/CaEnumArray.h
     utils/CaMonitor.h
diff --git a/algo/ca/core/data/CaHit.h b/algo/ca/core/data/CaHit.h
index 0020fd809c..1a9c17e6a6 100644
--- a/algo/ca/core/data/CaHit.h
+++ b/algo/ca/core/data/CaHit.h
@@ -17,6 +17,7 @@
 
 namespace cbm::algo::ca
 {
+  // FIXME: Move typedefs to another header (potential problems with dependencies)
   using HitIndex_t    = unsigned int;  ///< Index of ca::Hit
   using HitKeyIndex_t = unsigned int;  ///< Index of the hit key (e.g. front / back cluster id for STS)
 
diff --git a/algo/ca/qa/CaQaBuilder.cxx b/algo/ca/qa/CaQaBuilder.cxx
new file mode 100644
index 0000000000..38b3651594
--- /dev/null
+++ b/algo/ca/qa/CaQaBuilder.cxx
@@ -0,0 +1,67 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaBuilder.cxx
+/// \date   20.11.2023
+/// \brief  A QA module for CA tracking (implementation)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#include "CaQaBuilder.h"
+
+#include "CaConstants.h"
+#include "CaInputData.h"
+#include "CaParameters.h"
+#include "CaTrack.h"
+
+using cbm::algo::ca::constants::math::Pi;
+using cbm::algo::ca::qa::Builder;
+using cbm::algo::ca::qa::Config;
+using cbm::algo::ca::qa::Data;
+using cbm::algo::ca::qa::H1Key;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Data Builder::Build()
+{
+  Data res(fConfig);
+
+  if (!fpParameters) {
+    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): parameters object is undefined";
+    return res;
+  }
+  if (!fpInputData) {
+    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): input data object is undefined";
+    return res;
+  }
+  if (!fpvTracks) {
+    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): tracks vector is undefined";
+    return res;
+  }
+  if (!fpvRecoHits) {
+    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): reco hit indices vector is undefined";
+    return res;
+  }
+
+  // Fill track histograms
+  int trkFirstHit = 0;  // Index of hit in fpvRecoHits
+  int iTr         = 0;
+  for (auto& track : (*fpvTracks)) {
+    int nHits = track.fNofHits;
+    // Indices of hits in fpInputData->GetHits()
+    int iFstHit = (*fpvRecoHits)[trkFirstHit];
+    int iLstHit = (*fpvRecoHits)[trkFirstHit + nHits - 1];
+
+    //res.H1(H1Key::TrackFirstTx).Add(track.fParFirst.GetTx());
+    //res.H1(H1Key::TrackFirstTy).Add(track.fParFirst.GetTy());
+    res.H1(H1Key::FstTrkTheta).Add(track.fParFirst.GetTheta() * 180. / Pi);
+    res.H1(H1Key::FstTrkPhi).Add(track.fParFirst.GetPhi() * 180. / Pi);
+    res.H1(H1Key::FstChi2Ndf).Add(track.fParFirst.GetChiSq() / track.fParFirst.GetNdf());
+    res.H1(H1Key::FstHitSta).Add(fpInputData->GetHit(iFstHit).Station());
+    res.H1(H1Key::LstHitSta).Add(fpInputData->GetHit(iLstHit).Station());
+    res.H1(H1Key::TrkNofHits).Add(nHits);
+    trkFirstHit += nHits;
+  }
+
+  return res;
+}
diff --git a/algo/ca/qa/CaQaBuilder.h b/algo/ca/qa/CaQaBuilder.h
new file mode 100644
index 0000000000..26615bc76e
--- /dev/null
+++ b/algo/ca/qa/CaQaBuilder.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaBuilder.h
+/// \date   20.11.2023
+/// \brief  A QA module for CA tracking (header)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaHit.h"  // for HitIndex_t
+#include "CaQaBuilder.h"
+#include "CaQaData.h"
+#include "CaVector.h"
+
+namespace cbm::algo::ca
+{
+  class Parameters;
+  class InputData;
+  class Track;
+}  // namespace cbm::algo::ca
+
+namespace cbm::algo::ca::qa
+{
+  /// \class cbm::algo::ca::qa::Builder
+  /// \brief Builder class for the CA tracking QA (header)
+  ///
+  class Builder {
+  public:
+    using TrackV_t    = ca::Vector<ca::Track>;
+    using HitIndexV_t = ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>>;
+
+    /// \brief Default destructor
+    Builder() = delete;
+
+    /// \brief Constructor from the configuration object
+    /// \param config  QA configuration object
+    Builder(const Config& config) : fConfig(config) {}
+
+    /// \brief Copy constructor
+    Builder(const Builder&) = delete;
+
+    /// \brief Move constructor
+    Builder(Builder&&) = delete;
+
+    /// \brief Destructor
+    ~Builder() = default;
+
+    /// \brief Copy assignment operator
+    Builder& operator=(const Builder&) = delete;
+
+    /// \brief Move assignment operator
+    Builder& operator=(Builder&&) = delete;
+
+    /// \brief QA execution function
+    Data Build();
+
+    /// \brief Gets QA config
+    const Config& GetConfig() const { return fConfig; }
+
+    /// \brief Registers tracking input data
+    /// \note  Call per TS
+    void RegisterInputData(const InputData* pInputData) { fpInputData = pInputData; }
+
+    /// \brief Registers track vector
+    /// \note  Call per TS
+    void RegisterTracks(const Vector<Track>* pvTracks) { fpvTracks = pvTracks; }
+
+    /// \brief Registers reco hits indices
+    /// \note  Call per TS
+    void RegisterRecoHitIndices(const Vector<HitIndex_t>* pvRecoHits) { fpvRecoHits = pvRecoHits; }
+
+    /// \brief Registers tracking parameters object
+    /// \note  Call per run
+    void RegisterParameters(const Parameters* pParameters) { fpParameters = pParameters; }
+
+  private:
+    Config fConfig;                                   ///< QA configuration
+    const Parameters* fpParameters        = nullptr;  ///< Pointer to tracking parameters
+    const Vector<Track>* fpvTracks        = nullptr;  ///< Pointer to tracks vector
+    const InputData* fpInputData          = nullptr;  ///< Pointer to input data
+    const Vector<HitIndex_t>* fpvRecoHits = nullptr;  ///< Pointer to reco hit indices
+  };
+}  // namespace cbm::algo::ca::qa
diff --git a/algo/ca/qa/CaQaConfig.cxx b/algo/ca/qa/CaQaConfig.cxx
new file mode 100644
index 0000000000..bea2d565e6
--- /dev/null
+++ b/algo/ca/qa/CaQaConfig.cxx
@@ -0,0 +1,84 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaConfig.cxx
+/// \date   20.11.2023
+/// \brief  A configuration class for the QA module of the CA tracking (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "CaQaConfig.h"
+
+using cbm::algo::ca::qa::Config;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Config::Config()
+{
+  // Parameters initialization
+  // TODO: SZh 20.11.2023: Read from YAML
+  /* clang-format off */
+  fvH1Pars[H1Key::FstTrkTheta] = { "track_first_theta",    "#theta at first hit;#theta"            ,  62,    0.,  +90. };
+  fvH1Pars[H1Key::FstTrkPhi]   = { "track_first_phi"  ,    "#phi at first hit;#phi"                ,  62, -180., +180. }; 
+  fvH1Pars[H1Key::FstChi2Ndf]  = { "track_first_chi2_ndf", "#chi^{2}/NDF at first hit;#chi^{2}/NDF", 100,    0.,  +20. }; 
+  fvH1Pars[H1Key::FstHitSta]   = { "first_hit_station",    "Station of first hit;ID_{sta}"         ,  11,  -0.5,  10.5 }; 
+  fvH1Pars[H1Key::LstHitSta]   = { "last_hit_station",     "Station of last hit;ID_{sta}"          ,  11,  -0.5,  10.5 }; 
+  fvH1Pars[H1Key::TrkNofHits]  = { "n_hits",               "Number of hits;N_{hit}"                ,  11,  -0.5,  10.5 };
+  /* clang-format on */
+
+  // QA Canvas
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::vector<std::pair<std::string, std::string>> Config::GetHistogramConfigs() const
+{
+  std::vector<std::pair<std::string, std::string>> res;
+  auto nHistos = fvH1Pars.size();
+  res.reserve(nHistos);
+  for (const auto& par : fvH1Pars) {
+    res.emplace_back(par.fName, fsQaName);
+  }
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::vector<std::pair<std::string, std::string>> Config::GetCanvasConfigs() const
+{
+  std::vector<std::pair<std::string, std::string>> res;
+  res.reserve(1);
+  {
+    std::string sConfig = "caQaSummary;CA QA Summary;3;2;";
+    //sConfig += GetPadConfig({fvH1Pars[H1Key::FstTrkTx].fName}, 0, 1);
+    //sConfig += GetPadConfig({fvH1Pars[H1Key::FstTrkTy].fName}, 0, 1);
+    sConfig += GetPadConfig({fvH1Pars[H1Key::FstTrkTheta].fName}, 0, 1);
+    sConfig += GetPadConfig({fvH1Pars[H1Key::FstTrkPhi].fName}, 0, 1);
+    sConfig += GetPadConfig({fvH1Pars[H1Key::FstChi2Ndf].fName}, 0, 1);
+    sConfig += GetPadConfig({fvH1Pars[H1Key::FstHitSta].fName}, 0, 1);
+    sConfig += GetPadConfig({fvH1Pars[H1Key::LstHitSta].fName}, 0, 1);
+    sConfig += GetPadConfig({fvH1Pars[H1Key::TrkNofHits].fName}, 0, 1);
+    res.emplace_back("caQaSummary", sConfig);
+  }
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string Config::GetPadConfig(const std::vector<std::string>& vHistNames, bool bLogX, bool bLogY)
+{
+  constexpr bool bGridX = true;
+  constexpr bool bGridY = true;
+  constexpr bool bLogZ  = false;
+
+  std::stringstream config;
+  config << bGridX << ',' << bGridY << ',' << bLogX << ',' << bLogY << ',' << bLogZ;
+  if (vHistNames.empty()) { config << ','; }
+  else {
+    for (auto& name : vHistNames) {
+      config << ",(" << name << ",hist)";
+    }
+  }
+  config << ';';
+  return config.str();
+}
diff --git a/algo/ca/qa/CaQaConfig.h b/algo/ca/qa/CaQaConfig.h
new file mode 100644
index 0000000000..7f59533574
--- /dev/null
+++ b/algo/ca/qa/CaQaConfig.h
@@ -0,0 +1,61 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaConfig.h
+/// \date   20.11.2023
+/// \brief  A configuration class for the QA module of the CA tracking (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "CaQaDefinitions.h"
+
+namespace cbm::algo::ca::qa
+{
+  /// \brief  Configuration structure for QA of the tracking
+  /// \struct cbm::algo::ca::qa::Config
+  class Config {
+  public:
+    /// \brief Default constructor
+    Config();
+
+    /// \brief Copy constructor
+    Config(const Config&) = default;
+
+    /// \brief Move constructor
+    Config(Config&&) = default;
+
+    /// \brief Destructor
+    ~Config() = default;
+
+    /// \brief Access to 1D-histogram parameters
+    const H1KeyArray_t<H1Pars>& GetH1Pars() const { return fvH1Pars; }
+
+    /// \brief  Gets histograms configuration vector
+    /// \return A histogram configuration vector
+    ///
+    /// The returned configuration vector is a std::vector of std::pair of two strings. The first string is the
+    /// histogram name and the second string is the QA name (common for all the histograms within the QA module).
+    std::vector<std::pair<std::string, std::string>> GetHistogramConfigs() const;
+
+    /// \brief  Gets canvas configuration vector
+    /// \return A canvas configuration vector
+    ///
+    /// The returned configuration vector is a std::vector of std::pair of two strings. The first string is the
+    /// histogram name and the second string is the QA name (common for all the histograms within the QA module).
+    std::vector<std::pair<std::string, std::string>> GetCanvasConfigs() const;
+
+  private:
+    /// \brief Gets pad configuration string
+    static std::string GetPadConfig(const std::vector<std::string>& vHistNames, bool bLogX, bool bLogY);
+
+    std::string fsQaName = "CaQa";  ///< Name of the QA module
+
+    H1KeyArray_t<H1Pars> fvH1Pars;    ///< Parameters of 1D-histograms
+    std::string fsCanvasConfig = "";  ///< QA canvas configuration
+  };
+}  // namespace cbm::algo::ca::qa
diff --git a/algo/ca/qa/CaQaData.cxx b/algo/ca/qa/CaQaData.cxx
new file mode 100644
index 0000000000..da495021f2
--- /dev/null
+++ b/algo/ca/qa/CaQaData.cxx
@@ -0,0 +1,38 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaData.h
+/// \date   20.11.2023
+/// \brief  A data structure for the QA module of the CA tracking (header)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#include "CaQaData.h"
+
+#include <sstream>
+
+using cbm::algo::ca::qa::Config;
+using cbm::algo::ca::qa::Data;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Data::Data(const Config& config)
+{
+  // 1D-histograms initialization
+  fvH1.reserve(static_cast<size_t>(H1Key::kEND));
+  for (auto& par : config.GetH1Pars()) {
+    fvH1.emplace_back(par.fNumBins, par.fMinValue, par.fMaxValue, par.fName, par.fTitle);
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string Data::ToString() const
+{
+  std::stringstream msg;
+  msg << "CA QA histogram status:";
+  for (const auto& hist : fvH1) {
+    msg << '\n' << hist.ToString();
+  }
+  return msg.str();
+}
diff --git a/algo/ca/qa/CaQaData.h b/algo/ca/qa/CaQaData.h
new file mode 100644
index 0000000000..d44a8b8a5b
--- /dev/null
+++ b/algo/ca/qa/CaQaData.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaData.h
+/// \date   20.11.2023
+/// \brief  A data structure for the QA module of the CA tracking (header)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "CaQaConfig.h"
+#include "CaQaDefinitions.h"
+#include "Histo1D.h"
+
+namespace cbm::algo::ca::qa
+{
+  /// \class cbm::algo::ca::qa::Data
+  /// \brief QA data structure for CA tracking
+  class Data {
+  public:
+    /// \brief Default constructor
+    Data() = delete;
+
+    /// \brief Constructor from parameters
+    /// \param config  QA configuration object
+    Data(const Config& config);
+
+    /// \brief Copy constructor
+    Data(const Data&) = default;
+
+    /// \brief Move constructor
+    Data(Data&&) = default;
+
+    /// \brief Copy assignment operator
+    Data& operator=(const Data&) = default;
+
+    /// \brief Move assignment operator
+    Data& operator=(Data&&) = default;
+
+    /// \brief Destructor
+    ~Data() = default;
+
+    /// \brief Accesses 1D-histogram
+    const Histo1D& H1(H1Key key) const { return fvH1[static_cast<size_t>(key)]; }
+
+    /// \brief Accesses 1D-histogram (mutable)
+    Histo1D& H1(H1Key key) { return fvH1[static_cast<size_t>(key)]; }
+
+    /// \brief Accesses the full histogram vector
+    const std::vector<Histo1D>& VectorOfH1() const { return fvH1; }
+
+    /// \brief String representation of the data status
+    std::string ToString() const;
+
+  private:
+    std::vector<Histo1D> fvH1 = {};  ///< Vector of 1D-histograms, indexed by H1Key
+  };
+
+}  // namespace cbm::algo::ca::qa
diff --git a/algo/ca/qa/CaQaDefinitions.h b/algo/ca/qa/CaQaDefinitions.h
new file mode 100644
index 0000000000..d5129dd506
--- /dev/null
+++ b/algo/ca/qa/CaQaDefinitions.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaDefinitions.h
+/// \date   20.11.2023
+/// \brief  Definitions (enums and structures) for the cbm::algo::ca::qa namespace
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include <iomanip>
+#include <sstream>
+
+#include "CaEnumArray.h"
+
+namespace cbm::algo::ca::qa
+{
+  /// \struct H1Pars
+  /// \brief  Parameters of the 1D-histograms
+  ///
+  struct H1Pars {
+    std::string fName;   ///< Histogram name
+    std::string fTitle;  ///< Histogram title
+    uint32_t fNumBins;   ///< Number of bins in histogram
+    double fMinValue;    ///< Minimal value
+    double fMaxValue;    ///< Maximal value
+
+    /// \brief String representation of the object
+    std::string ToString() const
+    {
+      using std::setw;
+      std::stringstream msg;
+      msg << "nbins " << setw(8) << fNumBins << ", min " << setw(15) << fMinValue << ", max " << setw(15) << fMaxValue;
+      return msg.str();
+    };
+  };
+
+  /// \enum  H1Key
+  /// \brief Keys for the 1D-histograms
+  ///
+  enum class H1Key
+  {
+    //TrackFstTx,     ///< First hit: Slope of the track along x-axis
+    //TrackFstTy,     ///< First hit: Slope of the track along y-axis
+    FstTrkTheta,  ///< First hit: Polar angle of the track
+    FstTrkPhi,    ///< First hit: Azimuthal angle of the track
+    FstChi2Ndf,   ///< First hit: chi2 / NDF
+    FstHitSta,    ///< Station index of first hit
+    LstHitSta,    ///< Station index of last hit
+    TrkNofHits,   ///< Number of hits in track
+    kEND          ///< END
+  };
+
+  /// \brief  Alias to array, indexed by H1Key
+  template<typename T>
+  using H1KeyArray_t = EnumArray<H1Key, T>;
+
+}  // namespace cbm::algo::ca::qa
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 3e6badfdaf..56be1fbbc7 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -54,7 +54,6 @@ void Reco::Init(const Options& opts)
   fStsHitFinder.SetContext(&fContext);
   fTofCalibrator.SetContext(&fContext);
   fTofHitFinder.SetContext(&fContext);
-  fTracking.SetContext(&fContext);
 
   if (Opts().HistogramUri() != "") {
     fSender = std::make_shared<HistogramSender>(Opts().HistogramUri());
@@ -98,7 +97,9 @@ void Reco::Init(const Options& opts)
   fTofCalibrator.Init();
 
   // Tracking
-  fTracking.Init();
+  fTracking = std::make_unique<TrackingChain>(fSender);
+  fTracking->SetContext(&fContext);
+  fTracking->Init();
 
   fInitialized = true;
 
@@ -175,7 +176,7 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
         .stsHits = stsHits,
         .tofHits = tofHits,
       };
-      TrackingChain::Output_t trackingOutput = fTracking.Run(input);
+      TrackingChain::Output_t trackingOutput = fTracking->Run(input);
       if (Opts().HasOutput(RecoData::Track)) {
         results.tracks             = std::move(trackingOutput.tracks);
         results.trackStsHitIndices = std::move(trackingOutput.stsHitIndices);
@@ -200,7 +201,7 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
 void Reco::Finalize()
 {
   fStsHitFinder.Finalize();
-  fTracking.Finalize();
+  fTracking->Finalize();
 
   // Pop timer that was started in Init()
   xpu::timings t = xpu::pop_timer();
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index f0a7c2b6d6..e34c9d8836 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -65,7 +65,7 @@ namespace cbm::algo
     std::unique_ptr<evbuild::EventbuildChain> fEventBuild;
 
     // Tracking
-    TrackingChain fTracking;
+    std::unique_ptr<TrackingChain> fTracking;
 
     void Validate(const Options& opts);
 
-- 
GitLab