From 531a0611b2cd3d39ac4e8fbf4ba9cdb2e9fc37da Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 29 Feb 2024 05:57:02 +0100
Subject: [PATCH] cbm::algo::qa::Histogram is applied to the online QA and
 histogram server

This commit introduces modifications to:
- online digi-event builder QA (replacement of Histo1D with qa::H1D)
- online tracking QA
- histogram server (added 2D histograms)
- histogram server tester (message initialization is replaced with qa::Data)
---
 algo/CMakeLists.txt                      |   6 +-
 algo/base/HistogramSender.h              |   2 +-
 algo/ca/TrackingChain.cxx                |   3 +-
 algo/ca/TrackingChain.h                  |   3 +-
 algo/ca/qa/CaQaBuilder.cxx               |  66 ++++++++----
 algo/ca/qa/CaQaBuilder.h                 |  59 +++++-----
 algo/ca/qa/CaQaConfig.cxx                |  86 ---------------
 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/evbuild/EventbuildChain.cxx         |  11 +-
 algo/qa/CanvasConfig.cxx                 |   4 +-
 algo/qa/CanvasConfig.h                   |   2 +-
 algo/qa/DigiEventQa.cxx                  |  15 +--
 algo/qa/DigiEventQa.h                    |  13 ++-
 algo/qa/Histogram.h                      |  20 +++-
 algo/qa/HistogramContainer.cxx           |  24 +++++
 algo/qa/HistogramContainer.h             |  38 +++++++
 algo/qa/PadConfig.cxx                    |   2 +-
 algo/qa/PadConfig.h                      |   8 +-
 algo/qa/QaData.cxx                       |  40 ++++---
 algo/qa/QaData.h                         |  38 ++++---
 reco/tasks/CbmTaskDigiEventQa.cxx        |  16 ++-
 reco/tasks/CbmTaskDigiEventQa.h          |   6 +-
 services/histserv/app/Application.cxx    |  65 ++++++++----
 services/histserv/tester/Application.cxx | 130 ++++++++++-------------
 services/histserv/tester/Application.h   |  27 ++---
 28 files changed, 354 insertions(+), 551 deletions(-)
 delete mode 100644 algo/ca/qa/CaQaConfig.cxx
 delete mode 100644 algo/ca/qa/CaQaConfig.h
 delete mode 100644 algo/ca/qa/CaQaData.cxx
 delete mode 100644 algo/ca/qa/CaQaData.h
 delete mode 100644 algo/ca/qa/CaQaDefinitions.h
 create mode 100644 algo/qa/HistogramContainer.cxx
 create mode 100644 algo/qa/HistogramContainer.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 26ef7d4404..72869ece11 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -123,13 +123,12 @@ set(SRCS
   global/RecoResultsOutputArchive.cxx
   qa/DigiEventQa.cxx
   qa/Histo1D.cxx
+  qa/HistogramContainer.cxx
   qa/CanvasConfig.cxx
   qa/PadConfig.cxx
   qa/QaData.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)
@@ -248,9 +247,6 @@ install(
     # 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/base/HistogramSender.h b/algo/base/HistogramSender.h
index b44a1bfae4..ec9847312c 100644
--- a/algo/base/HistogramSender.h
+++ b/algo/base/HistogramSender.h
@@ -29,7 +29,7 @@ namespace cbm::algo
     }
 
     /** @brief Serialize object and send it to the histogram server
-       ** @param obj: object to be serialized in the message, e.g. config pairs of strings or Histo1D
+       ** @param obj: object to be serialized in the message, e.g. config pairs of strings or QaData
        ** @param flags: or'ed values from zmq::send_flags, typ. zmq::send_flags::sndmore to indicate multi-parts message
        **/
     template<typename Object>
diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index f84d9f8fe0..b54a1dfc0a 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -30,13 +30,14 @@ using cbm::algo::ca::Framework;
 using cbm::algo::ca::HitTypes_t;
 using cbm::algo::ca::InitManager;
 using cbm::algo::ca::Parameters;
+using cbm::algo::ca::QaBuilder;
 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) : fQaBuilder(ca::qa::Builder(histoSender)) {}
+TrackingChain::TrackingChain(std::shared_ptr<HistogramSender> histoSender) : fQaBuilder(QaBuilder(histoSender)) {}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index 694ed96be2..43cd2b4ec9 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -12,7 +12,6 @@
 #include "CaDataManager.h"
 #include "CaFramework.h"
 #include "CaQaBuilder.h"
-#include "CaQaConfig.h"
 #include "CaTrack.h"
 #include "CaTrackingMonitor.h"
 #include "CaVector.h"
@@ -101,7 +100,7 @@ namespace cbm::algo
     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 builder
+    ca::QaBuilder fQaBuilder;                      ///< CA QA builder
 
     // ************************
     // **  Auxilary variables
diff --git a/algo/ca/qa/CaQaBuilder.cxx b/algo/ca/qa/CaQaBuilder.cxx
index 5a690e7fc7..87e224ef18 100644
--- a/algo/ca/qa/CaQaBuilder.cxx
+++ b/algo/ca/qa/CaQaBuilder.cxx
@@ -2,7 +2,7 @@
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
-/// \file   CaQaBuilder.cxx
+/// \file   CaQaQaBuilder.cxx
 /// \date   20.11.2023
 /// \brief  A QA module for CA tracking (implementation)
 /// \author S.Zharko <s.zharko@gsi.de>
@@ -16,17 +16,18 @@
 
 #include <fmt/format.h>
 
-using cbm::algo::CanvasConfig;
-using cbm::algo::Histo1D;
-using cbm::algo::PadConfig;
-using cbm::algo::QaData;
+using cbm::algo::ca::QaBuilder;
 using cbm::algo::ca::constants::math::Pi;
-using cbm::algo::ca::qa::Builder;
+using cbm::algo::qa::CanvasConfig;
+using cbm::algo::qa::Data;
+using cbm::algo::qa::H1D;
+using cbm::algo::qa::H2D;
+using cbm::algo::qa::PadConfig;
 
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void Builder::Init()
+void QaBuilder::Init()
 {
   using fmt::format;
 
@@ -42,29 +43,35 @@ void Builder::Init()
     {
       auto sName      = format("track_{}_theta", vsPointName[i]);
       auto sTitl      = format("#theta at {} hit; #theta", vsPointName[i]);
-      fvphTrkTheta[i] = fQaData.MakeObj<Histo1D>(62, 0., 90., sName, sTitl);
+      fvphTrkTheta[i] = fQaData.MakeObj<H1D>(sName, sTitl, 62, 0., 90.);
     }
     {
       auto sName    = format("track_{}_phi", vsPointName[i]);
       auto sTitl    = format("#phi at {} hit; #phi", vsPointName[i]);
-      fvphTrkPhi[i] = fQaData.MakeObj<Histo1D>(62, -180., 180., sName, sTitl);
+      fvphTrkPhi[i] = fQaData.MakeObj<H1D>(sName, sTitl, 62, -180., 180.);
+    }
+    {
+      auto sName         = format("track_{}_thata_phi", vsPointName[i]);
+      auto sTitl         = format("#theta vs #phi at {} hit; #phi; #theta", vsPointName[i]);
+      fvphTrkPhiTheta[i] = fQaData.MakeObj<H2D>(sName, sTitl, 62, -180., 180., 62, 0., 90.);
     }
     {
       auto sName        = format("track_{}_chi2_ndf", vsPointName[i]);
       auto sTitl        = format("#chi^{{2}}/NDF at {} hit; #chi^{{2}}/NDF", vsPointName[i]);
-      fvphTrkChi2Ndf[i] = fQaData.MakeObj<Histo1D>(100, 0., 20., sName, sTitl);
+      fvphTrkChi2Ndf[i] = fQaData.MakeObj<H1D>(sName, sTitl, 100, 0., 20.);
     }
     {
       auto sName    = format("track_{}_hit_station", vsPointName[i]);
       auto sTitl    = format("Station of {} hit;ID_{{sta}}", vsPointName[i]);
-      fvphHitSta[i] = fQaData.MakeObj<Histo1D>(100, -.5, 10.5, sName, sTitl);
+      fvphHitSta[i] = fQaData.MakeObj<H1D>(sName, sTitl, 100, -.5, 10.5);
     }
   }
-  fphTrkNofHits = fQaData.MakeObj<Histo1D>(11, -0.5, 10.5, "n_hits", "Number of hits;N_{hit}");
+  fphTrkNofHits = fQaData.MakeObj<H1D>("n_hits", "Number of hits;N_{hit}", 11, -0.5, 10.5);
 
   // ---- Init canvases
   {
     // Track parameters at first/last track hit
+
     for (int i = 0; i < knTrkParPoints; ++i) {
       auto sCanvName = fmt::format("ca_trk_{}_hit", vsPointName[i]);
       auto sCanvTitl = fmt::format("Track parameters at {} hit", vsPointName[i]);
@@ -86,6 +93,20 @@ void Builder::Init()
       fQaData.AddCanvasConfig(canv);
     }
 
+    {
+      auto sCanvName = "ca_trk_theta_phi";
+      auto sCanvTitl = "Track #theta vs #phi";
+      auto canv      = CanvasConfig(sCanvName, sCanvTitl);
+      for (int i = 0; i < knTrkParPoints; ++i) {
+        PadConfig pad;
+        pad.SetLog(false, false, true);
+        pad.SetGrid(true, true);
+        pad.RegisterHistogram(fvphTrkPhiTheta[i], "colz");
+        canv.AddPadConfig(pad);
+      }
+      fQaData.AddCanvasConfig(canv);
+    }
+
     // Number of hits in track
     {
       auto canv = CanvasConfig("ca_nhits_in_trk", "Number of hit in track");
@@ -103,26 +124,26 @@ void Builder::Init()
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void Builder::Build()
+void QaBuilder::Build()
 {
   if (!fpSender.get()) {
     return;
   }
 
   if (!fpParameters) {
-    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): parameters object is undefined";
+    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): parameters object is undefined";
     return;
   }
   if (!fpInputData) {
-    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): input data object is undefined";
+    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): input data object is undefined";
     return;
   }
   if (!fpvTracks) {
-    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): tracks vector is undefined";
+    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): tracks vector is undefined";
     return;
   }
   if (!fpvRecoHits) {
-    LOG(error) << "cbm::algo::ca::qa::Builder::Build(): reco hit indices vector is undefined";
+    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): reco hit indices vector is undefined";
     return;
   }
 
@@ -141,13 +162,14 @@ void Builder::Build()
         int iHit           = (ip == 0 ? iFstHit : iLstHit);
         const auto& trkPar = (ip == 0 ? track.fParFirst : track.fParLast);
         const auto& hit    = fpInputData->GetHit(iHit);
-        fvphTrkTheta[ip]->Add(trkPar.GetTheta() * 180. / Pi);
-        fvphTrkPhi[ip]->Add(trkPar.GetPhi() * 180. / Pi);
-        fvphTrkChi2Ndf[ip]->Add(trkPar.GetChiSq() / trkPar.GetNdf());
-        fvphHitSta[ip]->Add(hit.Station());
+        fvphTrkTheta[ip]->Fill(trkPar.GetTheta() * 180. / Pi);
+        fvphTrkPhi[ip]->Fill(trkPar.GetPhi() * 180. / Pi);
+        fvphTrkPhiTheta[ip]->Fill(trkPar.GetPhi() * 180. / Pi, trkPar.GetTheta() * 180. / Pi);
+        fvphTrkChi2Ndf[ip]->Fill(trkPar.GetChiSq() / trkPar.GetNdf());
+        fvphHitSta[ip]->Fill(hit.Station());
       }
       // Other distributions
-      fphTrkNofHits->Add(nHits);
+      fphTrkNofHits->Fill(nHits);
       trkFirstHit += nHits;
     }
   }
diff --git a/algo/ca/qa/CaQaBuilder.h b/algo/ca/qa/CaQaBuilder.h
index ff3293acdb..5fa1f2321e 100644
--- a/algo/ca/qa/CaQaBuilder.h
+++ b/algo/ca/qa/CaQaBuilder.h
@@ -2,7 +2,7 @@
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
-/// \file   CaQaBuilder.h
+/// \file   CaQaQaBuilder.h
 /// \date   20.11.2023
 /// \brief  A QA module for CA tracking (header)
 /// \author S.Zharko <s.zharko@gsi.de>
@@ -12,52 +12,54 @@
 #include "CaHit.h"  // for HitIndex_t
 #include "CaTimesliceHeader.h"
 #include "CaVector.h"
-#include "QaData.h"
+#include "QaData.h"  // QA data
 
 namespace cbm::algo
 {
-  class Histo1D;
-  namespace ca
+  namespace qa
   {
-    template<typename DataT>
-    class Parameters;
-    class InputData;
-    class Track;
-  }  // namespace ca
-}  // namespace cbm::algo::ca
-
-namespace cbm::algo::ca::qa
+    class H1D;
+    class H2D;
+  }  // namespace qa
+}  // namespace cbm::algo
+
+namespace cbm::algo::ca
 {
-  /// \class cbm::algo::ca::qa::Builder
-  /// \brief Builder class for the CA tracking QA (header)
+  template<typename DataT>
+  class Parameters;
+  class InputData;
+  class Track;
+
+  /// \class cbm::algo::ca::qa::QaBuilder
+  /// \brief QaBuilder class for the CA tracking QA (header)
   ///
-  class Builder {
+  class QaBuilder {
    public:
     using TrackV_t    = ca::Vector<ca::Track>;
     using HitIndexV_t = ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>>;
 
     /// \brief Default destructor
     /// \param pSender  Pointer to the histogram sender
-    Builder(std::shared_ptr<HistogramSender> pSender) : fpSender(pSender){};
+    QaBuilder(std::shared_ptr<HistogramSender> pSender) : fpSender(pSender){};
 
     /// \brief Constructor from the configuration object
     /// \param config  QA configuration object
-    Builder() = default;
+    QaBuilder() = default;
 
     /// \brief Copy constructor
-    Builder(const Builder&) = delete;
+    QaBuilder(const QaBuilder&) = delete;
 
     /// \brief Move constructor
-    Builder(Builder&&) = delete;
+    QaBuilder(QaBuilder&&) = delete;
 
     /// \brief Destructor
-    ~Builder() = default;
+    ~QaBuilder() = default;
 
     /// \brief Copy assignment operator
-    Builder& operator=(const Builder&) = delete;
+    QaBuilder& operator=(const QaBuilder&) = delete;
 
     /// \brief Move assignment operator
-    Builder& operator=(Builder&&) = delete;
+    QaBuilder& operator=(QaBuilder&&) = delete;
 
     /// \brief QA execution function
     void Build();
@@ -85,7 +87,7 @@ namespace cbm::algo::ca::qa
     void RegisterParameters(const Parameters<fvec>* pParameters) { fpParameters = pParameters; }
 
    private:
-    QaData fQaData{"CaQa"};  ///< QA data
+    qa::Data fQaData{"CaQa"};  ///< QA data
 
     std::shared_ptr<HistogramSender> fpSender = nullptr;  ///< Histogram sender
     const Parameters<fvec>* fpParameters      = nullptr;  ///< Pointer to tracking parameters
@@ -96,10 +98,11 @@ namespace cbm::algo::ca::qa
     // ----- List of the histograms ------------------------------------------------------------------------------------
     static constexpr int knTrkParPoints = 2;  ///< Number of track points to build par distributions
 
-    std::array<Histo1D*, knTrkParPoints> fvphTrkTheta   = {{0}};    ///< hist: theta at first/last hit
-    std::array<Histo1D*, knTrkParPoints> fvphTrkPhi     = {{0}};    ///< hist: phi at first/last hit
-    std::array<Histo1D*, knTrkParPoints> fvphTrkChi2Ndf = {{0}};    ///< hist: chi2/NDF at first/last hit
-    std::array<Histo1D*, knTrkParPoints> fvphHitSta     = {{0}};    ///< hist: station of first/last hit
-    Histo1D* fphTrkNofHits                              = nullptr;  ///< hist: number of hits in track
+    std::array<qa::H1D*, knTrkParPoints> fvphTrkTheta    = {{0}};    ///< hist: theta at first/last hit
+    std::array<qa::H1D*, knTrkParPoints> fvphTrkPhi      = {{0}};    ///< hist: phi at first/last hit
+    std::array<qa::H1D*, knTrkParPoints> fvphTrkChi2Ndf  = {{0}};    ///< hist: chi2/NDF at first/last hit
+    std::array<qa::H1D*, knTrkParPoints> fvphHitSta      = {{0}};    ///< hist: station of first/last hit
+    std::array<qa::H2D*, knTrkParPoints> fvphTrkPhiTheta = {{0}};    ///< hist: theta vs. phi at first/last hit
+    qa::H1D* fphTrkNofHits                               = nullptr;  ///< hist: number of hits in track
   };
 }  // namespace cbm::algo::ca::qa
diff --git a/algo/ca/qa/CaQaConfig.cxx b/algo/ca/qa/CaQaConfig.cxx
deleted file mode 100644
index e9c696b7da..0000000000
--- a/algo/ca/qa/CaQaConfig.cxx
+++ /dev/null
@@ -1,86 +0,0 @@
-/* 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
deleted file mode 100644
index 67f93eeae1..0000000000
--- a/algo/ca/qa/CaQaConfig.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* 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 "CaQaDefinitions.h"
-
-#include <string>
-#include <vector>
-
-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
deleted file mode 100644
index da495021f2..0000000000
--- a/algo/ca/qa/CaQaData.cxx
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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
deleted file mode 100644
index 345651740f..0000000000
--- a/algo/ca/qa/CaQaData.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/* 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 "CaQaConfig.h"
-#include "CaQaDefinitions.h"
-#include "Histo1D.h"
-
-#include <string>
-#include <vector>
-
-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
deleted file mode 100644
index 2637d1e790..0000000000
--- a/algo/ca/qa/CaQaDefinitions.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/* 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 "CaEnumArray.h"
-
-#include <iomanip>
-#include <sstream>
-
-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/evbuild/EventbuildChain.cxx b/algo/evbuild/EventbuildChain.cxx
index 2843a98436..79ab194194 100644
--- a/algo/evbuild/EventbuildChain.cxx
+++ b/algo/evbuild/EventbuildChain.cxx
@@ -6,7 +6,7 @@
 
 #include "CbmDigiTimeslice.h"
 #include "DigiData.h"
-#include "Histo1D.h"
+#include "HistogramContainer.h"
 #include "evbuild/Config.h"
 
 #include <sstream>
@@ -51,7 +51,7 @@ EventbuildChain::EventbuildChain(const Config& config, std::shared_ptr<Histogram
     }
 
     /// => (empty) Histograms serialization and emission to close multi-part message
-    fSender->PrepareAndSendMsg(std::vector<Histo1D>{}, zmq::send_flags::none);
+    fSender->PrepareAndSendMsg(qa::HistogramContainer{}, zmq::send_flags::none);
   }
 }
 // ----------------------------------------------------------------------------
@@ -75,11 +75,14 @@ EventbuildChain::ResultType EventbuildChain::Run(const DigiData& timeslice)
 
   /// => Histograms serialization and emission
   if (fSender) {
+    L_(info) << "Running DigiEventQa";
     // --- Run event QA
     DigiEventQaData qaData = fQa(events);
+    L_(info) << "Running DigiEventQa: done";
 
-    fSender->PrepareAndSendMsg(qaData.fVectHistos, zmq::send_flags::none);
-    L_(info) << "Published histograms, nb: " << qaData.fVectHistos.size();
+    fSender->PrepareAndSendMsg(qaData.fHistContainer, zmq::send_flags::none);
+    int nHistograms = std::distance(qaData.fHistContainer.fvH1.begin(), qaData.fHistContainer.fvH1.end());
+    L_(info) << "Published histograms, nb: " << nHistograms;
   }
 
   // --- Some log
diff --git a/algo/qa/CanvasConfig.cxx b/algo/qa/CanvasConfig.cxx
index bfbb99c2f2..e0c3e3cb59 100644
--- a/algo/qa/CanvasConfig.cxx
+++ b/algo/qa/CanvasConfig.cxx
@@ -12,8 +12,8 @@
 #include <log.hpp>
 #include <sstream>
 
-using cbm::algo::CanvasConfig;
-using cbm::algo::PadConfig;
+using cbm::algo::qa::CanvasConfig;
+using cbm::algo::qa::PadConfig;
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
diff --git a/algo/qa/CanvasConfig.h b/algo/qa/CanvasConfig.h
index 0efe885c1c..20088d8b9b 100644
--- a/algo/qa/CanvasConfig.h
+++ b/algo/qa/CanvasConfig.h
@@ -16,7 +16,7 @@
 #include <utility>
 #include <vector>
 
-namespace cbm::algo
+namespace cbm::algo::qa
 {
   /// \class CanvasConfig
   /// \brief A canvas configuration for the histogram server
diff --git a/algo/qa/DigiEventQa.cxx b/algo/qa/DigiEventQa.cxx
index b47b4993fa..3fb21feca3 100644
--- a/algo/qa/DigiEventQa.cxx
+++ b/algo/qa/DigiEventQa.cxx
@@ -4,10 +4,15 @@
 
 #include "DigiEventQa.h"
 
+#include "Histogram.h"
+
+#include <functional>
 #include <iomanip>
+#include <iostream>
 #include <sstream>
 #include <string>
 
+using cbm::algo::qa::H1D;
 using std::string;
 using std::vector;
 
@@ -17,16 +22,14 @@ namespace cbm::algo::evbuild
   // ---   Execution   --------------------------------------------------------
   DigiEventQaData DigiEventQa::operator()(const vector<DigiEvent>& events) const
   {
-
     // --- Instantiate return object
     DigiEventQaData result;
-    result.fVectHistos.reserve(fConfig.fData.size());
     for (const auto& entry : fConfig.fData) {
       ECbmModuleId subsystem = entry.first;
       auto& detConfig        = fConfig.fData.at(subsystem);
-      result.fVectHistos.emplace_back(detConfig.fNumBins, detConfig.fMinValue, detConfig.fMaxValue,
-                                      DigiEventQaConfig::GetDigiTimeHistoName(subsystem));
-      result.fDigiTimeHistos.emplace(subsystem, result.fVectHistos.back());
+      result.fDigiTimeHistos[subsystem] =
+        &(result.fHistContainer.fvH1.emplace_front(DigiEventQaConfig::GetDigiTimeHistoName(subsystem), "",
+                                                   detConfig.fNumBins, detConfig.fMinValue, detConfig.fMaxValue));
     }
 
     // --- Event loop. Fill histograms.
@@ -43,7 +46,7 @@ namespace cbm::algo::evbuild
 
 
   // ---  QA: digi time within event   ----------------------------------------
-  void DigiEventQa::QaDigiTimeInEvent(const DigiEvent& event, ECbmModuleId system, Histo1D& histo) const
+  void DigiEventQa::QaDigiTimeInEvent(const DigiEvent& event, ECbmModuleId system, H1D* histo) const
   {
     switch (system) {
 
diff --git a/algo/qa/DigiEventQa.h b/algo/qa/DigiEventQa.h
index 7df6945795..91961d4649 100644
--- a/algo/qa/DigiEventQa.h
+++ b/algo/qa/DigiEventQa.h
@@ -7,7 +7,7 @@
 
 #include "CbmDefs.h"
 #include "DigiData.h"
-#include "Histo1D.h"
+#include "HistogramContainer.h"
 #include "evbuild/EventBuilderConfig.h"
 
 #include <gsl/span>
@@ -24,9 +24,8 @@ namespace cbm::algo::evbuild
    ** @since 16 June 2023
    **/
   struct DigiEventQaData {
-
-    std::vector<Histo1D> fVectHistos                 = {};
-    std::map<ECbmModuleId, Histo1D&> fDigiTimeHistos = {};
+    qa::HistogramContainer fHistContainer;
+    std::map<ECbmModuleId, qa::H1D*> fDigiTimeHistos = {};
     size_t fNumEvents                                = 0;
   };
 
@@ -142,10 +141,10 @@ namespace cbm::algo::evbuild
      ** The templated class is required to implement the method double GetTime().
      **/
     template<class Digi>
-    void FillDeltaT(gsl::span<const Digi> digis, double eventTime, Histo1D& histo) const
+    void FillDeltaT(gsl::span<const Digi> digis, double eventTime, qa::H1D* histo) const
     {
       for (const Digi& digi : digis)
-        histo.Add(digi.GetTime() - eventTime);
+        histo->Fill(digi.GetTime() - eventTime);
     }
 
     /** @brief Fill histogram with digi time within event
@@ -153,7 +152,7 @@ namespace cbm::algo::evbuild
      ** @param eventTime  Time of event
      ** @param histo  Histogram to be filled
      **/
-    void QaDigiTimeInEvent(const DigiEvent& event, ECbmModuleId system, Histo1D& histo) const;
+    void QaDigiTimeInEvent(const DigiEvent& event, ECbmModuleId system, qa::H1D* histo) const;
 
 
    private:  // members
diff --git a/algo/qa/Histogram.h b/algo/qa/Histogram.h
index 273b6578c0..adb2f5b80a 100644
--- a/algo/qa/Histogram.h
+++ b/algo/qa/Histogram.h
@@ -202,10 +202,16 @@ namespace cbm::algo::qa
     H1D() = default;
 
     /// \brief Copy constructor
-    explicit H1D(const H1D&) = default;
+    H1D(const H1D&) = default;
 
     /// \brief Move constructor
-    explicit H1D(H1D&&) = default;
+    H1D(H1D&&) = default;
+
+    /// \brief Copy assignment operator
+    H1D& operator=(const H1D&) = default;
+
+    /// \brief Move assignment operator
+    H1D& operator=(H1D&&) = default;
 
     /// \brief  Fills histogram
     /// \param  value Value
@@ -262,10 +268,16 @@ namespace cbm::algo::qa
     H2D() = default;
 
     /// \brief Copy constructor
-    explicit H2D(const H2D&) = default;
+    H2D(const H2D&) = default;
 
     /// \brief Move constructor
-    explicit H2D(H2D&&) = default;
+    H2D(H2D&&) = default;
+
+    /// \brief Copy assignment operator
+    H2D& operator=(const H2D&) = default;
+
+    /// \brief Move assignment operator
+    H2D& operator=(H2D&&) = default;
 
     /// \brief  Fills histogram
     /// \param  valueX Value along x-axis
diff --git a/algo/qa/HistogramContainer.cxx b/algo/qa/HistogramContainer.cxx
new file mode 100644
index 0000000000..a0fe588738
--- /dev/null
+++ b/algo/qa/HistogramContainer.cxx
@@ -0,0 +1,24 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   HistogramContainer.cxx
+/// \date   29.02.2024
+/// \brief  A histogram container for the histogram server (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "HistogramContainer.h"
+
+using cbm::algo::qa::HistogramContainer;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void HistogramContainer::Reset()
+{
+  for (auto& h : fvH1) {
+    h.Reset();
+  }
+  for (auto& h : fvH2) {
+    h.Reset();
+  }
+}
diff --git a/algo/qa/HistogramContainer.h b/algo/qa/HistogramContainer.h
new file mode 100644
index 0000000000..11dc4316cd
--- /dev/null
+++ b/algo/qa/HistogramContainer.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   HistogramContainer.h
+/// \date   29.02.2024
+/// \brief  A histogram container for the histogram server (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "Histogram.h"  // for H1D, H2D
+
+#include <boost/serialization/forward_list.hpp>
+
+#include <forward_list>
+
+namespace cbm::algo::qa
+{
+  /// \struct HistogramContainer
+  /// \brief  Structure to keep the histograms for sending them on the histogram server
+  struct HistogramContainer {
+    std::forward_list<qa::H1D> fvH1 = {};  ///< List of 1D-histograms
+    std::forward_list<qa::H2D> fvH2 = {};  ///< List of 2D-histograms
+
+    /// \brief Resets the histograms
+    void Reset();
+
+   private:
+    friend class boost::serialization::access;
+    template<class Archive>
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& fvH1;
+      ar& fvH2;
+    }
+  };
+}  // namespace cbm::algo::qa
diff --git a/algo/qa/PadConfig.cxx b/algo/qa/PadConfig.cxx
index 4b9d1a0394..93cdc8741b 100644
--- a/algo/qa/PadConfig.cxx
+++ b/algo/qa/PadConfig.cxx
@@ -12,7 +12,7 @@
 #include <log.hpp>
 #include <sstream>
 
-using cbm::algo::PadConfig;
+using cbm::algo::qa::PadConfig;
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
diff --git a/algo/qa/PadConfig.h b/algo/qa/PadConfig.h
index d95c1cdc1d..9c2e7bcf1a 100644
--- a/algo/qa/PadConfig.h
+++ b/algo/qa/PadConfig.h
@@ -9,14 +9,14 @@
 
 #pragma once
 
-#include "Histo1D.h"
+#include "Histogram.h"
 
 #include <string>
 #include <string_view>
 #include <utility>
 #include <vector>
 
-namespace cbm::algo
+namespace cbm::algo::qa
 {
   /// \class PadConfig
   /// \brief A pad configuration for the histogram server
@@ -65,7 +65,9 @@ namespace cbm::algo
     /// \brief  Registers a histogram in the pad
     /// \param  hist  Histogram object
     /// \param  opt   Draw options for the histogram
-    void RegisterHistogram(const Histo1D* hist, std::string_view opt) { RegisterObject(hist->Name(), opt); }
+    // TODO: implement a single function
+    void RegisterHistogram(const H1D* hist, std::string_view opt) { RegisterObject(hist->GetName(), opt); }
+    void RegisterHistogram(const H2D* hist, std::string_view opt) { RegisterObject(hist->GetName(), opt); }
 
     /// \brief  Returns message config
     std::string ToString() const;
diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
index 955c1ba27c..90151fe66a 100644
--- a/algo/qa/QaData.cxx
+++ b/algo/qa/QaData.cxx
@@ -2,25 +2,32 @@
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
-/// \file   QaData.cxx
+/// \file   Data.cxx
 /// \date   12.02.2024
 /// \brief  A unified data-structure to handle QA objects for the online reconstruction (implementation)
 /// \author Sergei Zharko <s.zharko@gsi.de>
 
 #include "QaData.h"
 
-using cbm::algo::QaData;
+using cbm::algo::qa::Data;
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void QaData::Init(std::shared_ptr<HistogramSender> histSender)
+void Data::Init(std::shared_ptr<HistogramSender> histSender)
 {
   if (histSender.get()) {
     // Forming a histogram config message
     std::vector<std::pair<std::string, std::string>> vHistCfgs;
-    vHistCfgs.reserve(std::distance(fvH1.begin(), fvH1.end()));
-    for (const auto& hist : fvH1) {
-      vHistCfgs.emplace_back(hist.Name(), fsName);
+    size_t nHistograms = 0;
+    // NOTE: Important to keep the order of filling the histograms: 1D -> 2D -> ..
+    nHistograms += std::distance(fHistograms.fvH1.begin(), fHistograms.fvH1.end());
+    nHistograms += std::distance(fHistograms.fvH2.begin(), fHistograms.fvH2.end());
+    vHistCfgs.reserve(nHistograms);
+    for (const auto& hist : fHistograms.fvH1) {
+      vHistCfgs.emplace_back(hist.GetName(), fsName);
+    }
+    for (const auto& hist : fHistograms.fvH2) {
+      vHistCfgs.emplace_back(hist.GetName(), fsName);
     }
 
     // Forming a canvas config message
@@ -39,26 +46,17 @@ void QaData::Init(std::shared_ptr<HistogramSender> histSender)
       histSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
     }
     // Histograms serialization and emission to close multi-part message
-    histSender->PrepareAndSendMsg(std::vector<Histo1D>{}, zmq::send_flags::none);
-  }
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-//
-void QaData::Reset()
-{
-  // TODO: clear the vectors of other object types here as well
-  for (auto& hist : fvH1) {
-    hist.Clear();
+    histSender->PrepareAndSendMsg(qa::HistogramContainer{}, zmq::send_flags::none);
   }
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void QaData::Send(std::shared_ptr<HistogramSender> histoSender)
+void Data::Send(std::shared_ptr<HistogramSender> histoSender)
 {
-  std::vector<Histo1D> vHisto1D{std::begin(fvH1), std::end(fvH1)};
-  histoSender->PrepareAndSendMsg(vHisto1D, zmq::send_flags::none);
-  L_(info) << fsName << ": Published " << vHisto1D.size() << " 1D-histograms";
+  histoSender->PrepareAndSendMsg(fHistograms, zmq::send_flags::none);
+  auto nH1 = std::distance(fHistograms.fvH1.begin(), fHistograms.fvH1.end());
+  auto nH2 = std::distance(fHistograms.fvH2.begin(), fHistograms.fvH2.end());
+  L_(info) << fsName << ": Published " << nH1 << " 1D- and " << nH2 << " 2D-histograms";
   this->Reset();
 }
diff --git a/algo/qa/QaData.h b/algo/qa/QaData.h
index 263614458c..e4fcc5c170 100644
--- a/algo/qa/QaData.h
+++ b/algo/qa/QaData.h
@@ -2,7 +2,7 @@
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
-/// \file   QaData.cxx
+/// \file   Data.cxx
 /// \date   12.02.2024
 /// \brief  A unified data-structure to handle QA objects for the online reconstruction
 /// \author Sergei Zharko <s.zharko@gsi.de>
@@ -10,40 +10,41 @@
 #pragma once
 
 #include "CanvasConfig.h"
-#include "Histo1D.h"
+#include "HistogramContainer.h"
 #include "HistogramSender.h"
 
-#include <forward_list>
+#include <boost/serialization/forward_list.hpp>
+
 #include <log.hpp>
 #include <memory>
 #include <string_view>
 #include <type_traits>
 #include <vector>
 
-namespace cbm::algo
+namespace cbm::algo::qa
 {
-  /// \class QaData
+  /// \class Data
   /// \brief Class to handle QA-objects in the online reconstruction
-  class QaData {
+  class Data {
    public:
     /// \brief Constructor
     /// \param name  Name of the QA module (appears as the directory name in the output)
-    QaData(std::string_view name) : fsName(name) {}
+    Data(std::string_view name) : fsName(name) {}
 
     /// \brief Copy constructor
-    QaData(const QaData&) = default;
+    Data(const Data&) = default;
 
     /// \brief Move constructor
-    QaData(QaData&&) = default;
+    Data(Data&&) = default;
 
     /// \brief Copy assignment operator
-    QaData& operator=(const QaData&) = default;
+    Data& operator=(const Data&) = default;
 
     /// \brief Move assignment operator
-    QaData& operator=(QaData&&) = default;
+    Data& operator=(Data&&) = default;
 
     /// \brief Destructor
-    ~QaData() = default;
+    ~Data() = default;
 
     /// \brief Adds a canvas to the canvas config list
     /// \param canvas  A CanvasConfig object
@@ -61,7 +62,7 @@ namespace cbm::algo
     void Init(std::shared_ptr<HistogramSender> histoSender);
 
     /// \brief Resets the histograms
-    void Reset();
+    void Reset() { fHistograms.Reset(); }
 
     /// \brief Sends QA data to the HistogramSender
     /// \param histoSender  A pointer to the histogram sender
@@ -70,17 +71,20 @@ namespace cbm::algo
 
    private:
     std::string fsName;                         ///< Name of the QA module (used as a directory name)
-    std::forward_list<Histo1D> fvH1      = {};  ///< List of 1D-histograms
+    qa::HistogramContainer fHistograms;         ///< Histograms container
     std::vector<std::string> fvsCanvCfgs = {};  ///< Vector of canvas configs
   };
 
   // -------------------------------------------------------------------------------------------------------------------
   //
   template<class Obj, typename... Args>
-  Obj* QaData::MakeObj(Args... args)
+  Obj* Data::MakeObj(Args... args)
   {
-    if constexpr (std::is_same_v<Obj, cbm::algo::Histo1D>) {
-      return &(fvH1.emplace_front(args...));
+    if constexpr (std::is_same_v<Obj, cbm::algo::qa::H1D>) {
+      return &(fHistograms.fvH1.emplace_front(args...));
+    }
+    else if constexpr (std::is_same_v<Obj, cbm::algo::qa::H2D>) {
+      return &(fHistograms.fvH2.emplace_front(args...));
     }
     return nullptr;
   }
diff --git a/reco/tasks/CbmTaskDigiEventQa.cxx b/reco/tasks/CbmTaskDigiEventQa.cxx
index 1ea9b16047..f8a71bb955 100644
--- a/reco/tasks/CbmTaskDigiEventQa.cxx
+++ b/reco/tasks/CbmTaskDigiEventQa.cxx
@@ -80,7 +80,7 @@ void CbmTaskDigiEventQa::Exec(Option_t*)
   // which is then added to the member histogram (another data copy). Should implement a method for direct addition TH1D + Histo1D.
   for (const auto& entry : result.fDigiTimeHistos) {
     ECbmModuleId subsystem = entry.first;
-    fDigiTimeHistos[subsystem]->Add(ToTH1D(entry.second));
+    fDigiTimeHistos[subsystem]->Add(ToTH1D(*entry.second));
   }
 
   // --- Timeslice log
@@ -159,19 +159,17 @@ InitStatus CbmTaskDigiEventQa::Init()
 
 
 // -----   Convert CBM histogram to ROOT histogram   --------------------------
-TH1D* CbmTaskDigiEventQa::ToTH1D(const cbm::algo::Histo1D& source)
+TH1D* CbmTaskDigiEventQa::ToTH1D(const cbm::algo::qa::H1D& source)
 {
   bool add = TH1::AddDirectoryStatus();
   TH1::AddDirectory(false);  // Needed to prevent ROOT from adding histogram to its internal registry
-  TH1D* result =
-    new TH1D(source.Name().c_str(), source.Name().c_str(), source.NumBins(), source.MinValue(), source.MaxValue());
+  TH1D* result = new TH1D(source.GetName().c_str(), source.GetName().c_str(), source.GetNbinsX(), source.GetMinX(),
+                          source.GetMaxX());
   TH1::AddDirectory(add);  // Needed to prevent ROOT from adding histogram to its internal registry
-  for (uint32_t bin = 0; bin < source.NumBins(); bin++) {
-    result->SetBinContent(1 + bin, source.Content(bin));
+  for (uint32_t bin = 0; bin <= source.GetNbinsX() + 1; bin++) {
+    result->SetBinContent(bin, source.GetBinContent(bin));
   }
-  result->SetBinContent(0, source.Underflow());
-  result->SetBinContent(source.NumBins(), source.Overflow());
-  result->SetEntries(source.NumEntries());
+  result->SetEntries(source.GetEntries());
   return result;
 }
 // ----------------------------------------------------------------------------
diff --git a/reco/tasks/CbmTaskDigiEventQa.h b/reco/tasks/CbmTaskDigiEventQa.h
index ebd6566753..f338a29379 100644
--- a/reco/tasks/CbmTaskDigiEventQa.h
+++ b/reco/tasks/CbmTaskDigiEventQa.h
@@ -68,14 +68,14 @@ private:  // methods
   virtual InitStatus Init();
 
 
-  /** @brief Create a ROOT TH1D from a Histo1D object
+  /** @brief Create a ROOT TH1D from a H1D object
    ** @param Source histogram
    ** @param ROOT histogram
    */
-  TH1D* ToTH1D(const cbm::algo::Histo1D& source);
+  TH1D* ToTH1D(const cbm::algo::qa::H1D& source);
 
 
-private:                                               // members
+ private:                                              // members
   const std::vector<CbmDigiEvent>* fEvents = nullptr;  //! Input data (events)
   size_t fNumTs                            = 0;        ///< Number of processed timeslices
   size_t fNumEvents                        = 0;        ///< Number of analysed events
diff --git a/services/histserv/app/Application.cxx b/services/histserv/app/Application.cxx
index 8a565c5811..0964416f9f 100644
--- a/services/histserv/app/Application.cxx
+++ b/services/histserv/app/Application.cxx
@@ -5,9 +5,7 @@
 #include "Application.h"
 
 #include "CbmFlesCanvasTools.h"
-
-#include <Logger.h>
-
+#include "HistogramContainer.h"
 #include "TCanvas.h"
 #include "TEnv.h"
 #include "TFile.h"
@@ -20,6 +18,8 @@
 #include "TRootSniffer.h"
 #include "TSystem.h"
 
+#include <Logger.h>
+
 #include <boost/archive/binary_iarchive.hpp>
 #include <boost/iostreams/device/array.hpp>
 #include <boost/iostreams/stream.hpp>
@@ -29,8 +29,6 @@
 #include <mutex>
 #include <zmq_addon.hpp>
 
-#include "Histo1D.h"
-
 std::mutex mtx;
 
 namespace b_io = boost::iostreams;
@@ -163,25 +161,56 @@ namespace cbm::services::histserv
     b_io::stream<b_io::basic_array_source<char>> s(device);
     b_ar::binary_iarchive iarch(s);
 
-    std::vector<cbm::algo::Histo1D> vHist;
+    cbm::algo::qa::HistogramContainer vHist;
     iarch >> vHist;
 
-    for (auto& source : vHist) {
-      /// copied from CbmTaskDigiEventQa::ToTH1D
-      /// FIXME: Should be placed in a tools/interface/whatever library with all similar functions!!
-      /// FIXME: Reverse OP need to be implemented + CI unit tests for back and forth in each direction (ROOT <-> Algo)
-      /// FIXME: Lead to "Warning in <TROOT::Append>: Replacing existing TH1: xxxxxx (Potential memory leak)."
+    /// copied from CbmTaskDigiEventQa::ToTH1D
+    /// FIXME: Should be placed in a tools/interface/whatever library with all similar functions!!
+    /// TODO:  SZh 29.02.2024: Use core/qa/CbmQaOnlineInterface  (for now a fast solution)
+    /// FIXME: Reverse OP need to be implemented + CI unit tests for back and forth in each direction (ROOT <-> Algo)
+    /// FIXME: Lead to "Warning in <TROOT::Append>: Replacing existing TH1: xxxxxx (Potential memory leak)."
+
+    // Collect 1D-histograms
+    for (auto& source : vHist.fvH1) {
+      bool add = TH1::AddDirectoryStatus();
+      TH1::AddDirectory(false);  // Needed to prevent ROOT from adding histogram to its internal registry
+      const char* hName = source.GetName().c_str();
+      const char* hTitl = source.GetTitle().c_str();
+      uint32_t nBinsX   = source.GetNbinsX();
+      double xMin       = source.GetMinX();
+      double xMax       = source.GetMaxX();
+      TH1* result       = new TH1D(hName, hTitl, nBinsX, xMin, xMax);
+      TH1::AddDirectory(add);  // Needed to prevent ROOT from adding histogram to its internal registry
+      for (uint32_t bin = 0; bin <= nBinsX + 1; bin++) {
+        result->SetBinContent(bin, source.GetBinContent(bin));
+      }
+      result->SetEntries(source.GetEntries());
+      if (!ReadHistogram<TH1>(result)) {  //
+        return false;
+      }
+      delete result;
+    }
+
+    // Collect 2D-histograms
+    for (auto& source : vHist.fvH2) {
       bool add = TH1::AddDirectoryStatus();
       TH1::AddDirectory(false);  // Needed to prevent ROOT from adding histogram to its internal registry
-      TH1D* result = new TH1D(source.Name().c_str(), source.Title().c_str(),  // Titles in ROOT convention with ";" sep
-                              source.NumBins(), source.MinValue(), source.MaxValue());
+      const char* hName = source.GetName().c_str();
+      const char* hTitl = source.GetTitle().c_str();
+      uint32_t nBinsX   = source.GetNbinsX();
+      double xMin       = source.GetMinX();
+      double xMax       = source.GetMaxX();
+      uint32_t nBinsY   = source.GetNbinsY();
+      double yMin       = source.GetMinY();
+      double yMax       = source.GetMaxY();
+      TH1* result       = new TH2D(hName, hTitl, nBinsX, xMin, xMax, nBinsY, yMin, yMax);
       TH1::AddDirectory(add);  // Needed to prevent ROOT from adding histogram to its internal registry
-      for (uint32_t bin = 0; bin < source.NumBins(); bin++) {
-        result->SetBinContent(1 + bin, source.Content(bin));
+      for (uint32_t binX = 0; binX <= nBinsX + 1; binX++) {
+        for (uint32_t binY = 0; binY <= nBinsY + 1; binY++) {
+          result->SetBinContent(binX, binY, source.GetBinContent(binX, binY));
+        }
       }
-      result->SetBinContent(0, source.Underflow());
-      result->SetBinContent(source.NumBins(), source.Overflow());
-      result->SetEntries(source.NumEntries());
+      result->SetEntries(source.GetEntries());
       if (!ReadHistogram<TH1>(result)) {  //
         return false;
       }
diff --git a/services/histserv/tester/Application.cxx b/services/histserv/tester/Application.cxx
index 9c2b9fbdbc..f6a675dc72 100644
--- a/services/histserv/tester/Application.cxx
+++ b/services/histserv/tester/Application.cxx
@@ -4,7 +4,12 @@
 
 #include "Application.h"
 
+#include "CanvasConfig.h"
 #include "CbmFlesCanvasTools.h"
+#include "Histogram.h"
+#include "PadConfig.h"
+#include "QaData.h"
+#include "ui_callbacks.h"
 
 #include <Logger.h>
 
@@ -17,9 +22,6 @@
 
 #include <thread>
 
-#include "Histo1D.h"
-#include "ui_callbacks.h"
-
 std::mutex mtx;
 
 namespace b_io = boost::iostreams;
@@ -37,12 +39,13 @@ namespace cbm::services::histserv_tester
     LOG(info) << "Options for Application.";
     LOG(info) << " Output ZMQ channel: " << fOpt.ComChan();
     LOG(info) << " Run time duration:  " << fOpt.Runtime() << " s";
+    fpSender = std::make_shared<cbm::algo::HistogramSender>(fOpt.ComChan());
 
     /// FIXME: SOMETHING_To_Replace_FairMQ!!!!!!!!!!!!!
     /// FIXME: Initialize communication channels of SOMETHING_To_Replace_FairMQ
     /// FIXME: Link channel to method in order to process received messages
-    // fZmqSocket.set(zmq::sockopt::rcvhwm, int(hwm));  // FIXME: need for HWM?
-    fZmqSocket.connect(fOpt.ComChan().c_str());  // This side "connects" to socket => Other side should have "bind"!!!!
+    // fZmqSocket.set(zmq::sockopt::rcvhwm, int(hwm));  // FIXME: need for HWM? (NOTE: SZh 29.02.2024: if needed, move it
+    // do it in the HistogramSender)
   }
   // -------------------------------------------------------------------------------------------------------------------
 
@@ -50,6 +53,11 @@ namespace cbm::services::histserv_tester
   // -----   Main Loop   -----------------------------------------------------------------------------------------------
   void Application::Exec()
   {
+    using cbm::algo::qa::CanvasConfig;
+    using cbm::algo::qa::Data;
+    using cbm::algo::qa::H1D;
+    using cbm::algo::qa::PadConfig;
+
     const std::chrono::milliseconds interval {250};
     const std::chrono::seconds runtime(fOpt.Runtime());
     const std::chrono::seconds pubint(fOpt.PubInterval());
@@ -57,72 +65,66 @@ namespace cbm::services::histserv_tester
     sctp stopTime    = startTime + runtime;
     sctp lastPubTime = startTime;
 
-    std::vector<cbm::algo::Histo1D> vHist;
-    vHist.emplace_back((fOpt.Runtime() + 2), -1.0, fOpt.Runtime() + 1.0, "testHist",
-                       "Tester source; Runtime [s]; Entries to histo [iterations]");
-    vHist.emplace_back(1001, -0.025, 50.025, "transHist",
-                       "Tester histos transmission time; Trans. time [ms]; Messages []");
+    // Init QA data helper
+    Data qaData("Test");
+    auto* pHistTest  = qaData.MakeObj<H1D>("testHist", "Tester source; Runtime [s]; Entries to histo [iterations]",
+                                          fOpt.Runtime() + 2, -1.0, fOpt.Runtime() + 1.0);
+    auto* pHistTrans = qaData.MakeObj<H1D>(
+      "transHist", "Tester histos transmission time; Trans. time [ms]; Messages []", 1001, -0.025, 50.025);
 
     /// Initial emission, including generation and serialization of configs
     /// => Try to evaluate "time cost" of the histo transmission, including serialization
     sctp transStartTime = scsc::now();
 
-    /// => Header for multi-part message with Configuration + data
-    /// => Format: std::pair< Nb histogram configs, Nb canvas configs  >
-    std::pair<uint32_t, uint32_t> pHeader(vHist.size(), 1);
-    PrepareAndSendMsg(pHeader, zmq::send_flags::sndmore);
-
-    /// => Histograms configuration = destination folder in http browser, mandatory but can be empty (= root folder)
-    /// => 1 ZMQ message per histogram (= 1 part)
-    /// => If no (new) histograms declared (e.g. new canvas declaration), has to be en empty message + `0` in the header
-    std::pair<std::string, std::string> pFirstHist(vHist[0].Name(), "TestFolder");
-    std::pair<std::string, std::string> pSecondHist(vHist[1].Name(), "TestFolder/TestSubFolder");
-    PrepareAndSendMsg(pFirstHist, zmq::send_flags::sndmore);
-    PrepareAndSendMsg(pSecondHist, zmq::send_flags::sndmore);
-
-    /// => Canvas configuration = in old code extracted from canvas object, here need to be made by hand
-    /// => 1 ZMQ message per canvas (= 1 part)
-    /// => If no (new) canvas declared (e.g. only histos declaration), has to be en empty message + `0` in the header
-    /// => Format is "CanvasName;Canvas Title;NbPadX(U);NbPadY(U);ConfigPad1(s);....;ConfigPadXY(s)"
-    /// => Format of Pad config is
-    ///    "GrixX(b),GridY(b),LogX(b),LogY(b),LogZ(b),(HistoName1,DrawOptions1),...,(HistoNameZ,DrawOptionsZ)"
-    /// => See core/base/utils/fles/CbmFlesCanvasTools for the full code, especially GenerateCanvasConfigString
-    ///    (CanvasConfig class not used here as library loading would need binding to ROOT library)
-    std::pair<std::string, std::string> pCanvConfig("TestCanvas", "TestCanvas;Canvas configs testing;2;2;");
-    pCanvConfig.second += "0,0,0,0,0,(" + vHist[0].Name() + ",hist);";                                      // Pad 0
-    pCanvConfig.second += "1,1,0,0,0,(" + vHist[0].Name() + ",hist);";                                      // Pad 1
-    pCanvConfig.second += "1,1,1,0,0,(" + vHist[1].Name() + ",hist);";                                      // Pad 2
-    pCanvConfig.second += "1,1,1,1,0,(" + vHist[1].Name() + ",hist),(" + vHist[0].Name() + ",hist same);";  // Pad 3
-    PrepareAndSendMsg(pCanvConfig, zmq::send_flags::sndmore);
-
-    /// => (empty) Histograms serialization and emission
-    PrepareAndSendMsg(vHist, zmq::send_flags::none);
+    {
+      auto canv = CanvasConfig("TestCanvas", "TestCanvas");
+      auto pad1 = PadConfig();
+      pad1.SetGrid(false, false);
+      pad1.SetLog(false, false, false);
+      pad1.RegisterHistogram(pHistTest, "hist");
+      canv.AddPadConfig(pad1);
+      auto pad2 = PadConfig();
+      pad2.SetGrid(true, true);
+      pad2.SetLog(false, false, false);
+      pad2.RegisterHistogram(pHistTest, "hist");
+      canv.AddPadConfig(pad2);
+      auto pad3 = PadConfig();
+      pad3.SetGrid(true, true);
+      pad3.SetLog(true, false, false);
+      pad3.RegisterHistogram(pHistTrans, "hist");
+      canv.AddPadConfig(pad3);
+      auto pad4 = PadConfig();
+      pad4.SetGrid(true, true);
+      pad4.SetLog(true, false, false);
+      pad4.RegisterHistogram(pHistTrans, "hist");
+      pad4.RegisterHistogram(pHistTest, "hist same");
+      canv.AddPadConfig(pad4);
+      qaData.AddCanvasConfig(canv);
+    }
+    qaData.Init(fpSender);  // Init the QA data
+
     lastPubTime = scsc::now();
 
     /// No general references as member/variable bec. simple example, use directly hardcoded vector "array access"
-    vHist[1].Add(std::chrono::duration_cast<std::chrono::microseconds>(lastPubTime - transStartTime).count() / 1e3);
+    pHistTrans->Fill(std::chrono::duration_cast<std::chrono::microseconds>(lastPubTime - transStartTime).count() / 1e3);
 
     while (scsc::now() < stopTime) {  //
       /// No general references as member/variable bec. simple example, use directly hardcoded vector "array access"
-      vHist[0].Add(std::chrono::duration_cast<std::chrono::milliseconds>(scsc::now() - startTime).count() / 1e3);
+      pHistTest->Fill(std::chrono::duration_cast<std::chrono::milliseconds>(scsc::now() - startTime).count() / 1e3);
 
       if (pubint < scsc::now() - lastPubTime) {
-        LOG(info) << "Pub-hist: " << scsc::now().time_since_epoch().count() << " " << vHist[0].NumEntries();
+        LOG(info) << "Pub-hist: " << scsc::now().time_since_epoch().count() << " " << pHistTest->GetEntries();
         /// Try to evaluate "time cost" of the histo transmission, including serialization
         transStartTime = scsc::now();
 
         /// => Histograms serialization and emission
-        PrepareAndSendMsg(vHist, zmq::send_flags::none);
-
-        /// => Reset all histograms as they are added on the other end!
-        for (std::vector<cbm::algo::Histo1D>::iterator itHist = vHist.begin(); itHist != vHist.end(); ++itHist) {
+        qaData.Send(fpSender);
 
-          itHist->Clear();
-        }
         lastPubTime = scsc::now();
 
         /// No general references as member/variable bec. simple example, use directly hardcoded vector "array access"
-        vHist[1].Add(std::chrono::duration_cast<std::chrono::microseconds>(lastPubTime - transStartTime).count() / 1e3);
+        pHistTrans->Fill(std::chrono::duration_cast<std::chrono::microseconds>(lastPubTime - transStartTime).count()
+                         / 1e3);
       }
 
       std::this_thread::sleep_for(interval);
@@ -132,32 +134,8 @@ namespace cbm::services::histserv_tester
     }
 
     /// Final publications
-    LOG(info) << "Pub-hist: " << scsc::now().time_since_epoch().count() << " " << vHist[0].NumEntries();
+    LOG(info) << "Pub-hist: " << scsc::now().time_since_epoch().count() << " " << pHistTest->GetEntries();
     /// => Histograms serialization and emission
-    PrepareAndSendMsg(vHist, zmq::send_flags::none);
-    /// => Reset all histograms as they are added on the other end!
-    for (std::vector<cbm::algo::Histo1D>::iterator itHist = vHist.begin(); itHist != vHist.end(); ++itHist) {
-      itHist->Clear();
-    }
-  }
-  // -------------------------------------------------------------------------------------------------------------------
-
-  template<class Object>
-  void Application::PrepareAndSendMsg(Object& obj, zmq::send_flags flags)
-  {
-    /// Needed ressources (serializd string, boost inserter, boost stream, boost binary output archive)
-    std::string serial_str;
-    b_io::back_insert_device<std::string> inserter(serial_str);
-    b_io::stream<b_io::back_insert_device<std::string>> bstream(inserter);
-    b_ar::binary_oarchive oa(bstream);
-
-    serial_str.clear();
-    oa << obj;
-    bstream.flush();
-
-    zmq::message_t msg(serial_str.size());
-    std::copy_n(static_cast<const char*>(serial_str.data()), msg.size(), static_cast<char*>(msg.data()));
-    fZmqSocket.send(msg, flags);
+    qaData.Send(fpSender);
   }
-
 }  // namespace cbm::services::histserv_tester
diff --git a/services/histserv/tester/Application.h b/services/histserv/tester/Application.h
index 6188785213..ba000e0be4 100644
--- a/services/histserv/tester/Application.h
+++ b/services/histserv/tester/Application.h
@@ -1,20 +1,20 @@
-/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+/* Copyright (C) 2023-2024 Facility for Antiproton and Ion Research in Europe, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Pierre-Alain Loizeau [committer] */
+   Authors: Pierre-Alain Loizeau [committer], Sergei Zharko */
 
 #ifndef CBM_SERVICES_HISTSERV_TESTER_APPLICATION_H
 #define CBM_SERVICES_HISTSERV_TESTER_APPLICATION_H 1
 
-#include <zmq.hpp>
-
+#include "HistogramSender.h"
 #include "ProgramOptions.h"
 
+#include <memory>
+
 namespace cbm::services::histserv_tester
 {
 
   class Application {
 
-
   public:
     /** @brief Standard constructor, initialises the application
      ** @param opt  **/
@@ -23,8 +23,14 @@ namespace cbm::services::histserv_tester
     /** @brief Copy constructor forbidden **/
     Application(const Application&) = delete;
 
-    /** @brief Assignment operator forbidden **/
-    void operator=(const Application&) = delete;
+    /** @brief Move constructor forbidden **/
+    Application(Application&&) = delete;
+
+    /** @brief Copy assignment operator forbidden **/
+    Application& operator=(const Application&) = delete;
+
+    /** @brief Move assignment operator forbidden **/
+    Application& operator=(Application&&) = delete;
 
     /** @brief Destructor **/
     ~Application() = default;
@@ -32,16 +38,11 @@ namespace cbm::services::histserv_tester
     /** @brief Run the application **/
     void Exec();
 
-  private:
-    template<class Object>
-    void PrepareAndSendMsg(Object& obj, zmq::send_flags flags);
-
   private:
     ProgramOptions const& fOpt;  ///< Program options object
 
     /// Interface
-    zmq::context_t fZmqContext {1};
-    zmq::socket_t fZmqSocket {fZmqContext, ZMQ_PUSH};
+    std::shared_ptr<cbm::algo::HistogramSender> fpSender = nullptr;
   };
 
 }  // namespace cbm::services::histserv_tester
-- 
GitLab