diff --git a/core/qa/CMakeLists.txt b/core/qa/CMakeLists.txt
index 456a3b7adf1dd2b256972f835ee8655cf342ca97..65783ad554c6cdead7ada990c35f83bc33c80c3b 100644
--- a/core/qa/CMakeLists.txt
+++ b/core/qa/CMakeLists.txt
@@ -1,5 +1,6 @@
 set(INCLUDE_DIRECTORIES
   ${CMAKE_CURRENT_SOURCE_DIR}/checker
+  ${CMAKE_CURRENT_SOURCE_DIR}/report
   ${CMAKE_CURRENT_SOURCE_DIR}
   )
 
@@ -22,10 +23,21 @@ set(SRCS
   checker/CbmQaCheckerObjectHandler.cxx
   checker/CbmQaCheckerProfile1DHandler.cxx
   checker/CbmQaCheckerObjectDB.cxx
+  report/CbmQaReportBuilder.cxx 
+  report/CbmQaReportLatexEngine.cxx
+  report/CbmQaReportHtmlEngine.cxx
+  report/CbmQaReportSection.cxx 
+  report/CbmQaReportTable.cxx
   )
 
 set(HEADERS
   CbmQaConstants.h
+  report/CbmQaReportDefines.h 
+  report/CbmQaReportElement.h 
+  report/CbmQaReportEngine.h 
+  report/CbmQaReportFigure.h 
+  report/CbmQaReportHeader.h 
+  report/CbmQaReportTail.h
   )
 
 set(LIBRARY_NAME CbmQaBase)
@@ -38,6 +50,8 @@ set(PUBLIC_DEPENDENCIES
   ROOT::Core
   ROOT::Gpad
   ROOT::Hist
+  Boost::filesystem
+  fmt::fmt
   )
 
 set(PRIVATE_DEPENDENCIES
@@ -65,6 +79,18 @@ Install(FILES
         CbmQaConstants.h
         CbmQaCmpDrawer.h
         checker/CbmQaCheckerTypedefs.h
+      
+        report/CbmQaReportDefines.h 
+        report/CbmQaReportBuilder.h
+        report/CbmQaReportLatexEngine.h
+        report/CbmQaReportHtmlEngine.h
+        report/CbmQaReportSection.h
+        report/CbmQaReportTable.h
+        report/CbmQaReportElement.h 
+        report/CbmQaReportEngine.h 
+        report/CbmQaReportFigure.h 
+        report/CbmQaReportHeader.h 
+        report/CbmQaReportTail.h
         DESTINATION include
         )
 
diff --git a/core/qa/CbmQaBaseLinkDef.h b/core/qa/CbmQaBaseLinkDef.h
index 2d33214ac1ed04ddfd13d9b57853a683aacdbfd3..e0b53833439f0432f0eb984883f89be29c6a0a53 100644
--- a/core/qa/CbmQaBaseLinkDef.h
+++ b/core/qa/CbmQaBaseLinkDef.h
@@ -31,6 +31,7 @@
 #pragma link C++ class CbmQaTable + ;
 #pragma link C++ class CbmQaTask + ;
 #pragma link C++ class CbmQaManager + ;
+
 #pragma link C++ class cbm::qa::checker::Core + ;
 #pragma link C++ class cbm::qa::checker::FileHandler + ;
 #pragma link C++ class cbm::qa::checker::Hist1DHandler + ;
@@ -39,4 +40,15 @@
 #pragma link C++ class cbm::qa::checker::ObjectHandler + ;
 #pragma link C++ class cbm::qa::checker::ObjectDB + ;
 
+#pragma link C++ class cbm::qa::report::Builder + ;
+#pragma link C++ class cbm::qa::report::Element + ;
+#pragma link C++ class cbm::qa::report::CollapsibleElement + ;
+#pragma link C++ class cbm::qa::report::Engine + ;
+#pragma link C++ class cbm::qa::report::Figure + ;
+#pragma link C++ class cbm::qa::report::Header + ;
+#pragma link C++ class cbm::qa::report::Section + ;
+#pragma link C++ class cbm::qa::report::Table + ;
+#pragma link C++ class cbm::qa::report::Tail + ;
+#pragma link C++ class cbm::qa::report::LatexEngine + ;
+
 #endif
diff --git a/core/qa/CbmQaIO.cxx b/core/qa/CbmQaIO.cxx
index e0635994de7933262d51e7d8e6babb654aa7eb1d..a5f91d42c7c823ca9352b7a791232ad83c397f40 100644
--- a/core/qa/CbmQaIO.cxx
+++ b/core/qa/CbmQaIO.cxx
@@ -30,11 +30,13 @@ CbmQaIO::CbmQaIO(TString prefixName, std::shared_ptr<ObjList_t> pObjList) : fsPr
 //
 CbmQaIO::~CbmQaIO() {}
 
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmQaIO::SetTH1Properties(TH1* pHist)
+void CbmQaIO::SetTH1Properties(TH1* pHist) const
 {
   // Set default histo properties
+  pHist->GetYaxis()->SetLabelOffset(0.95);
 
   TPaveStats* stats = cbm::qa::util::GetHistStats(pHist);
   assert(stats);
@@ -45,12 +47,13 @@ void CbmQaIO::SetTH1Properties(TH1* pHist)
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmQaIO::SetTH2Properties(TH2* pHist)
+void CbmQaIO::SetTH2Properties(TH2* pHist) const
 {
   // Set default histo properties
 
   pHist->SetOption("colz");
   pHist->SetStats(false);
+  pHist->GetYaxis()->SetLabelOffset(0.95);
   /*
   TPaveStats* stats = cbm::qa::util::GetHistStats(pHist);
   assert(stats);
@@ -61,10 +64,11 @@ void CbmQaIO::SetTH2Properties(TH2* pHist)
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmQaIO::SetTProfile2DProperties(TProfile2D* pHist)
+void CbmQaIO::SetTProfile2DProperties(TProfile2D* pHist) const
 {
   // Set default histo properties
 
+  pHist->GetYaxis()->SetLabelOffset(0.95);
   pHist->SetOption("colz");
   pHist->SetStats(false);
   /*
@@ -77,9 +81,9 @@ void CbmQaIO::SetTProfile2DProperties(TProfile2D* pHist)
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmQaIO::SetCanvasProperties(TCanvas* pCanv)
+void CbmQaIO::SetCanvasProperties(TCanvas* pCanv) const
 {
-  constexpr double left   = 0.15;
+  constexpr double left   = 0.18;
   constexpr double bottom = 0.15;
   constexpr double right  = 0.10;
   constexpr double top    = 0.10;
diff --git a/core/qa/CbmQaIO.h b/core/qa/CbmQaIO.h
index ab6f83317d7db3025328a7c3f12656c1ebe68b8a..146df13b95e57fbea7bb596e88f48f26265f4eba 100644
--- a/core/qa/CbmQaIO.h
+++ b/core/qa/CbmQaIO.h
@@ -121,19 +121,19 @@ class CbmQaIO {
 
   /// \brief Applies properties on the histogram created with the MakeQaObject function
   /// \param pHist  Pointer to the histogram
-  virtual void SetTH1Properties(TH1* pHist);
+  virtual void SetTH1Properties(TH1* pHist) const;
 
   /// \brief Applies properties on the histogram created with the MakeQaObject function
   /// \param pHist  Pointer to the histogram
-  virtual void SetTH2Properties(TH2* pHist);
+  virtual void SetTH2Properties(TH2* pHist) const;
 
   /// \brief Applies properties on the profile 2D  created with the MakeQaObject function
   /// \param pHist  Pointer to the profile
-  void SetTProfile2DProperties(TProfile2D* pHist);
+  virtual void SetTProfile2DProperties(TProfile2D* pHist) const;
 
   /// \brief Applies properties on the canvas created with the MakeQaObject funciton
   /// \param pCanv  Pointer to the canvas
-  virtual void SetCanvasProperties(TCanvas* pCanv);
+  virtual void SetCanvasProperties(TCanvas* pCanv) const;
 
   /// \brief Writes objects into file
   /// \param pOutFile Pointer to output ROOT file
@@ -200,7 +200,7 @@ T* CbmQaIO::ConstructAndRegisterQaObject(TString sName, Args... args)
   }
 
   // apply user-defined properties
-  if constexpr (std::is_same_v<TH1F, T> || std::is_same_v<TH1D, T>) {  // histograms
+  if constexpr (std::is_same_v<TH1F, T> || std::is_same_v<TH1D, T> || std::is_same_v<TProfile, T>) {  // histograms
     SetTH1Properties(pObj);
   }
   else if constexpr (std::is_same_v<TH2F, T> || std::is_same_v<TH2D, T>) {  // histograms
diff --git a/core/qa/CbmQaTable.h b/core/qa/CbmQaTable.h
index ecbaa171ccae5482f38ad3dc0a650380239581c7..3f17907761c6e85cab3b1abc248c3c07cc69beba 100644
--- a/core/qa/CbmQaTable.h
+++ b/core/qa/CbmQaTable.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2022-2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergey Gorbunov, Sergei Zharko [committer] */
 
@@ -10,8 +10,8 @@
 /// Provides a ROOT Class to handle a numeric table
 ///
 
-#ifndef CbmQaTable_h
-#define CbmQaTable_h 1
+#pragma once
+
 #include "TH2D.h"
 #include "TROOT.h"
 
@@ -101,5 +101,3 @@ class CbmQaTable : public TH2D {
 
   ClassDef(CbmQaTable, 1);
 };
-
-#endif  // CbmQaTable_h
diff --git a/core/qa/report/CbmQaReportBuilder.cxx b/core/qa/report/CbmQaReportBuilder.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5990142ff434c46e1d44221f3ccb3514726534db
--- /dev/null
+++ b/core/qa/report/CbmQaReportBuilder.cxx
@@ -0,0 +1,76 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportBuilder.h
+/// \brief  Base class for the report builder (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#include "CbmQaReportBuilder.h"
+
+#include "CbmQaReportFigure.h"
+#include "Logger.h"
+#include "TCanvas.h"
+
+#include <fstream>
+
+
+using cbm::qa::report::Builder;
+using cbm::qa::report::Engine;
+using cbm::qa::report::Figure;
+using cbm::qa::report::Header;
+using cbm::qa::report::Tail;
+
+namespace fs = cbm::qa::report::fs;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Builder::Builder(std::string_view title, fs::path outPath)
+  : fpHeader(std::make_shared<Header>())
+  , fpTail(std::make_shared<Tail>())
+{
+  fScriptsPath = fs::weakly_canonical(outPath);
+  fFiguresPath = fs::weakly_canonical(fScriptsPath / "figures");
+  fs::create_directories(fScriptsPath);
+  fs::create_directories(fFiguresPath);
+
+  LOG(info) << "Initializing report builder for a document " << title << ". The scripts path is "
+            << fScriptsPath.string();
+  fpHeader->SetTitle(title);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Builder::CreateScript(Engine& engine, bool bCompile)
+{
+  std::string outName = (fScriptsPath / (fmt::format("{}.{}", ksSourceName, engine.ScriptExtention()))).string();
+  LOG(info) << "\nCreating source script \"" << outName << "\" using " << engine.MyName();
+
+  std::ofstream script(outName);
+
+  script << fpHeader->GetBody(engine);
+  for (const auto& pSection : fvpSections) {
+    script << pSection->GetBody(engine);
+  }
+  script << fpTail->GetBody(engine);
+  script.close();
+
+  if (bCompile) {
+    engine.Compile(outName);
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string Builder::SaveCanvas(const TCanvas* canv, const std::string& relPath) const
+{
+  if (!canv) {
+    LOG(fatal) << "cbm::qa::report::Builder::SaveCanvas(): a nullptr canvas is passed to the function. The provided "
+               << "relative path is \"" << relPath << "\"";
+  }
+  fs::path absPath = this->AbsFigurePath(relPath + "." + fsFigureExtention);
+  fs::create_directories(absPath.parent_path());
+  canv->SaveAs(absPath.string().c_str());
+  return this->PathInSource(absPath);
+}
diff --git a/core/qa/report/CbmQaReportBuilder.h b/core/qa/report/CbmQaReportBuilder.h
new file mode 100644
index 0000000000000000000000000000000000000000..aae766ebdbac86bd3383ba94d9c4a866b88294f6
--- /dev/null
+++ b/core/qa/report/CbmQaReportBuilder.h
@@ -0,0 +1,76 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportBuilder.h
+/// \brief  Base class for the report builder
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#pragma once
+
+#include "CbmQaReportDefines.h"
+#include "CbmQaReportEngine.h"
+#include "CbmQaReportHeader.h"
+#include "CbmQaReportSection.h"
+#include "CbmQaReportTail.h"
+
+class TCanvas;
+
+namespace cbm::qa::report
+{
+  /// \class Builder
+  /// \brief Report builder
+  class Builder {
+   public:
+    /// \brief Constructor
+    /// \param title    Report title
+    /// \param outPath  Output path
+    Builder(std::string_view title, fs::path outPath);
+
+    /// \brief Destructor
+    virtual ~Builder() = default;
+
+    /// \brief Adds section
+    void AddSection(std::shared_ptr<Section> pSection) { fvpSections.push_back(pSection); }
+
+    /// \brief Gets header
+    std::shared_ptr<Header> GetHeader() { return fpHeader; }
+
+    /// \brief Gets tail
+    std::shared_ptr<Tail> GetTail() { return fpTail; }
+
+    /// \brief Saves script
+    /// \param engine   Engine of the document
+    /// \param bCompile Flag: true - script will be compiled, if the compilation rule is defined by engine
+    void CreateScript(Engine& engine, bool bCompile = true);
+
+    /// \brief Converts path of the object in the code to one in the document source
+    /// \param path  Path to the object
+    std::string PathInSource(fs::path path) const { return fs::relative(path, fScriptsPath).string(); }
+
+    /// \brief Returns absolute path to the figure
+    /// \param relPath  Relative path to the figure
+    std::string AbsFigurePath(fs::path relPath) const { return (fFiguresPath / relPath).string(); }
+
+    /// \brief  Saves canvas
+    /// \param  canv    Pointer to canvas
+    /// \param  relPath Relative path to the canvas image
+    /// \return Path to canvas in source
+    /// \note   Creates the directory, if it does not exist
+    std::string SaveCanvas(const TCanvas* canv, const std::string& relPath) const;
+
+    /// \brief  Sets figure extention
+    void SetFigureExtention(std::string_view figExtention) { fsFigureExtention = figExtention; }
+
+   private:
+    static constexpr std::string_view ksSourceName = "source";  ///< Name of source
+
+    std::vector<std::shared_ptr<Section>> fvpSections;  ///< List of report sections
+    std::string fsFigureExtention = "pdf";              ///< Figure extention
+    fs::path fScriptsPath;                              ///< Scripts path
+    fs::path fFiguresPath;                              ///< Figures path
+    std::shared_ptr<Header> fpHeader = nullptr;         ///< Header of the report
+    std::shared_ptr<Tail> fpTail     = nullptr;         ///< Tail of the report
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportDefines.h b/core/qa/report/CbmQaReportDefines.h
new file mode 100644
index 0000000000000000000000000000000000000000..8ef47aa1745ca8a0acb1cd66cdccdc587f388bb3
--- /dev/null
+++ b/core/qa/report/CbmQaReportDefines.h
@@ -0,0 +1,20 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportDefines.h
+/// \brief  Common definitions for cbm::qa::report
+/// \author S. Zharko <s.zharko@gsi.de>
+/// \since  25.02.2024
+
+#pragma once
+
+//#include <filesystem>
+#include <boost/filesystem.hpp>
+
+namespace cbm::qa::report
+{
+  // Filesystem
+  //namespace fs = std::filesystem;
+  namespace fs = boost::filesystem;
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportElement.h b/core/qa/report/CbmQaReportElement.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ce9595a73acdd09fd682678a6caac4f7511bd42
--- /dev/null
+++ b/core/qa/report/CbmQaReportElement.h
@@ -0,0 +1,72 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportElement.h
+/// \brief  Base class for the report element (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  21.02.2024
+
+#pragma once
+
+#include "CbmQaReportDefines.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace cbm::qa::report
+{
+  class Engine;
+
+  /// \class Element
+  /// \brief Interface for the report element
+  ///
+  class Element {
+   public:
+    /// \brief Constructor
+    /// \param label  Label of section
+    Element(std::string_view label) : fsLabel(label) {}
+
+    /// \brief Default constructor
+    Element() = default;
+
+    /// \brief Destructor
+    virtual ~Element() = default;
+
+    /// \brief Gets body of the element
+    /// \param engine  A concrete implementation of the Engine to get the element body
+    virtual std::string GetBody(const Engine& engine) const = 0;
+
+    /// \brief Gets label
+    const std::string& GetLabel() const { return fsLabel; }
+
+    /// \brief Sets label
+    void SetLabel(std::string_view label) { fsLabel = label; }
+
+   private:
+    // TODO: Add check for multiple label definition
+    std::string fsLabel = "";  ///< Label for referencing
+  };
+
+  /// \class CollapsibleElement
+  /// \brief Interface to the element, which can contain daughter elements
+  ///
+  class CollapsibleElement : public Element {
+   public:
+    using Element::Element;
+
+    /// \brief Destructor
+    virtual ~CollapsibleElement() = default;
+
+    /// \brief Adds element
+    // TODO: Check for label
+    virtual void Add(std::shared_ptr<Element> pElement) { fvDaughterElements.push_back(pElement); }
+
+    /// \brief Get daughter elements
+    const std::vector<std::shared_ptr<Element>> GetDaughterElements() const { return fvDaughterElements; }
+
+   private:
+    std::vector<std::shared_ptr<Element>> fvDaughterElements;  ///< Daughter elements
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportEngine.h b/core/qa/report/CbmQaReportEngine.h
new file mode 100644
index 0000000000000000000000000000000000000000..2e11a0a3e063210fd8be5ce224314cc5eaf1e0aa
--- /dev/null
+++ b/core/qa/report/CbmQaReportEngine.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportElementFactory.h
+/// \brief  An abstract factory for the report elements
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#pragma once
+
+#include <string>
+#include <string_view>
+
+namespace cbm::qa::report
+{
+  class Figure;
+  class Header;
+  class Section;
+  class Table;
+  class Tail;
+
+  /// \class Engine
+  /// \brief A base abstract class to provide an interface for element body (a visitor in the Visitor pattern)
+  ///
+  /// Each method of the class provides interface to create the body of the document element. The body
+  /// is returned as a string.
+  class Engine {
+   public:
+    /// \brief  Destructor
+    virtual ~Engine() = default;
+
+    /// \brief  Creates a body for figure
+    /// \param  figure  Reference to figure
+    /// \return Figure body
+    virtual std::string FigureBody(const Figure& figure) const = 0;
+
+    /// \brief  Creates a body for header
+    /// \param  header  Reference to header
+    /// \return Header body
+    virtual std::string HeaderBody(const Header& header) const = 0;
+
+    /// \brief  Creates a body for section
+    /// \param  section  Reference to section
+    /// \return Section body
+    virtual std::string SectionBody(const Section& section) const = 0;
+
+    /// \brief  Creates a body for table
+    /// \param  table  Reference to table
+    /// \return Table body
+    virtual std::string TableBody(const Table& table) const = 0;
+
+    /// \brief  Creates a body for tail
+    /// \param  tail  Reference to tail
+    /// \return Figure body
+    virtual std::string TailBody(const Tail& tail) const = 0;
+
+    /// \brief  Returns script extention
+    virtual std::string ScriptExtention() const = 0;
+
+    /// \brief  Returns engine name
+    virtual std::string MyName() const = 0;
+
+    /// \brief Defines the compilation rule (can be omitted)
+    /// \param source  Path to the source
+    virtual void Compile(const std::string& /*source*/) const {};
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportFigure.h b/core/qa/report/CbmQaReportFigure.h
new file mode 100644
index 0000000000000000000000000000000000000000..03d93018be6a72afa4e6772e5152d31147758990
--- /dev/null
+++ b/core/qa/report/CbmQaReportFigure.h
@@ -0,0 +1,49 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportFigure.h
+/// \brief  Base class for the report figure (header)
+/// \author S. Zharko <s.zharko@gsi.de>
+/// \since  21.02.2024
+
+#pragma once
+
+#include "CbmQaReportElement.h"
+#include "CbmQaReportEngine.h"
+
+namespace cbm::qa::report
+{
+  /// \class Figure
+  /// \brief Figure in the report
+  class Figure : public Element {
+   public:
+    /// \brief Constructor
+    /// \param label  Label of the element
+    Figure(std::string_view label) : Element(std::string("fig:") + std::string(label)) {}
+
+    /// \brief Destructor
+    virtual ~Figure() = default;
+
+    /// \brief Gets body of the element
+    /// \param engine  A concrete implementation of the Engine to get the element body
+    std::string GetBody(const Engine& engine) const override { return engine.FigureBody(*this); }
+
+    /// \brief Gets caption
+    const std::string& GetCaption() const { return fsCaption; }
+
+    /// \brief Gets path suffix to the figure
+    const std::string& GetPath() const { return fsPath; }
+
+    /// \brief Sets path to the figure
+    /// \param path to the figure, which will be represented in
+    void SetPath(std::string_view path) { fsPath = path; }
+
+    /// \brief Sets caption
+    void SetCaption(std::string_view caption) { fsCaption = caption; }
+
+   private:
+    std::string fsPath    = "";  ///< Relative path
+    std::string fsCaption = "";  ///< Figure caption
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportHeader.h b/core/qa/report/CbmQaReportHeader.h
new file mode 100644
index 0000000000000000000000000000000000000000..7b457f7ee2e38d1f5662fd747e11b4ce02622913
--- /dev/null
+++ b/core/qa/report/CbmQaReportHeader.h
@@ -0,0 +1,67 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportHeader.h
+/// \brief  Base class for the report header (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#pragma once
+
+#include "CbmQaReportEngine.h"
+
+#include <string>
+#include <string_view>
+
+namespace cbm::qa::report
+{
+  /// \class  Header
+  /// \brief  Header of the report
+  class Header {
+   public:
+    /// \brief Destructor
+    virtual ~Header() = default;
+
+    /// \brief Gets body of the element
+    /// \param engine  A concrete implementation of the Engine to get the element body
+    std::string GetBody(const Engine& engine) const { return engine.HeaderBody(*this); }
+
+    /// \brief Gets author
+    const std::string& GetAuthor() const { return fsAuthor; }
+
+    /// \brief Gets page header
+    const std::string& GetPageHeader() const { return fsPageHeader; }
+
+    /// \brief Gets setup
+    const std::string& GetSetup() const { return fsSetup; }
+
+    /// \brief Gets subtitle
+    const std::string& GetSubtitle() const { return fsSubtitle; }
+
+    /// \brief Gets title
+    const std::string& GetTitle() const { return fsTitle; }
+
+    /// \brief Sets author
+    void SetAuthor(std::string_view author) { fsAuthor = author; }
+
+    /// \brief Sets page header
+    void SetPageHeader(std::string_view pageHeader) { fsPageHeader = pageHeader; }
+
+    /// \brief Sets setup
+    void SetSetup(std::string_view setup) { fsSetup = setup; }
+
+    /// \brief Sets subtitle
+    void SetSubtitle(std::string_view subtitle) { fsSubtitle = subtitle; }
+
+    /// \brief Sets title
+    void SetTitle(std::string_view title) { fsTitle = title; }
+
+   private:
+    std::string fsAuthor     = "";
+    std::string fsSetup      = "";
+    std::string fsSubtitle   = "";
+    std::string fsTitle      = "";
+    std::string fsPageHeader = "";  ///< Placed on top/bottom of the page
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportHtmlEngine.cxx b/core/qa/report/CbmQaReportHtmlEngine.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f4be356eb8bba9357da7c49733f2c94f0d311199
--- /dev/null
+++ b/core/qa/report/CbmQaReportHtmlEngine.cxx
@@ -0,0 +1,152 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportHtmlEngine.h
+/// \brief  HTML document engine for the cbm::qa::report (source)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#include "CbmQaReportHtmlEngine.h"
+
+#include "CbmQaReportFigure.h"
+#include "CbmQaReportHeader.h"
+#include "CbmQaReportSection.h"
+#include "CbmQaReportTable.h"
+#include "CbmQaReportTail.h"
+
+using cbm::qa::report::Figure;
+using cbm::qa::report::Header;
+using cbm::qa::report::HtmlEngine;
+using cbm::qa::report::Section;
+using cbm::qa::report::Table;
+using cbm::qa::report::Tail;
+
+#include <algorithm>
+#include <chrono>
+#include <ctime>
+#include <iomanip>
+#include <sstream>
+//#include <regex>
+
+#include <fmt/format.h>
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string HtmlEngine::FigureBody(const Figure& figure) const
+{
+  std::stringstream out;
+  out << "<figure>\n";
+  out << "  <embed src=\"" << figure.GetPath() << "\" style=\"width:" << kFigureWidth << "\">\n";
+  if (!figure.GetCaption().empty()) {
+    out << "  <figcaption>Fig.[" << figure.GetLabel() << "]: " << figure.GetCaption() << "</figcaption>\n";
+  }
+  out << "</figure>\n";
+
+  return out.str();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string HtmlEngine::HeaderBody(const Header& header) const
+{
+  std::stringstream out;
+
+  // TODO: Move these definitions to text-file (?)
+  // Settings
+  out << "<!DOCTYPE html>\n";
+  out << "<html>\n";
+  out << "<style>\n";
+  out << "table {\n  boarder-collapse: collapse;\n  width: 100%;\n"
+      << "}\n";
+  out << "th, td {\n  text-align: " << kTableTextAlign << ";\n  padding: " << kTablePadding << "px;\n"
+      << "}\n";
+  out << "tr:nth-child(even) {\n  background-color: #EEEEEE;\n}\n";
+  out << "</style>\n";
+  out << "<head>\n";
+  out << "  <title>" << header.GetTitle() << "</title>\n";
+  out << "</head>\n\n";
+  out << "<body>\n";
+  out << "<h1>" << header.GetTitle() << "</h1>\n\n";
+  if (!header.GetSubtitle().empty()) {
+    out << "<h2>" << header.GetSubtitle() << "</h2>\n\n";
+  }
+  auto timeNow = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+  out << "<table style=\"width:1\">\n";
+  out << "  <tr>\n";
+  out << "    <td>Generated on:</td>\n";
+  out << "    <td>" << std::put_time(std::localtime(&timeNow), "%a %d.%m.%Y, %X") << "</td>\n";
+  out << "  </tr>\n";
+  out << "  <tr>\n";
+  out << "    <td>Author:</td>\n";
+  out << "    <td>" << header.GetAuthor() << "</td>\n";
+  out << "  </tr>\n";
+  if (!header.GetSetup().empty()) {
+    out << "  <tr>\n";
+    out << "    <td>Setup:</td>\n";
+    out << "    <td>" << header.GetSetup() << "</td>\n";
+    out << "  </tr>\n";
+  }
+  out << "</table>\n\n";
+
+  return out.str();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string HtmlEngine::SectionBody(const Section& section) const
+{
+  std::stringstream out;
+  std::string sSectionTag = fmt::format("h{}", std::min((section.GetLevel() + 2), 6));
+
+  out << '<' << sSectionTag << '>' << section.GetTitle() << "</" << sSectionTag << ">\n\n";
+  for (const auto& pElement : section.GetDaughterElements()) {
+    out << pElement->GetBody(*this) << '\n';
+  }
+  out << '\n';
+  return out.str();
+}
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string HtmlEngine::TableBody(const Table& table) const
+{
+  std::stringstream out;
+  int nRows = table.GetNofRows();
+  int nCols = table.GetNofCols();
+
+  out << "<table>\n";
+  // tabular header
+  if (!table.GetCaption().empty()) {
+    out << "  <tablecaption>Table: " << table.GetCaption() << "</tablecaption>\n";
+  }
+  // table header
+  out << "  <tr>\n";
+  for (int iCol = 0; iCol < nCols; ++iCol) {
+    out << "    <th>" << table.GetColumnTitle(iCol) << "</th>\n";
+  }
+  out << "  </tr>\n";
+  // table body
+  for (int iRow = 0; iRow < nRows; ++iRow) {
+    out << "  <tr>\n";
+    for (int iCol = 0; iCol < nCols; ++iCol) {
+      out << "    <td>" << table(iRow, iCol) << "</td>\n";
+    }
+    out << "  </tr>\n";
+  }
+  out << "</table>\n";
+  return out.str();
+}
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string HtmlEngine::TailBody(const Tail&) const
+{
+  std::stringstream out;
+  out << '\n';
+  out << "</body>\n";
+  out << "</html>\n";
+  return out.str();
+}
diff --git a/core/qa/report/CbmQaReportHtmlEngine.h b/core/qa/report/CbmQaReportHtmlEngine.h
new file mode 100644
index 0000000000000000000000000000000000000000..0d06dffa2ea74d074006677f0c196a743fc15e04
--- /dev/null
+++ b/core/qa/report/CbmQaReportHtmlEngine.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportHtmlEngine.h
+/// \brief  HTML document engine for the cbm::qa::report
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  24.02.2024
+
+#pragma once
+
+#include "CbmQaReportEngine.h"
+
+#include <string>
+#include <string_view>
+
+namespace cbm::qa::report
+{
+  /// \class HtmlEngine
+  /// \brief HTML document engine
+  class HtmlEngine : public Engine {
+   public:
+    /// \brief  Creates a body for figure
+    /// \param  figure  Reference to figure
+    /// \return Figure body
+    std::string FigureBody(const Figure& figure) const override;
+
+    /// \brief  Creates a body for header
+    /// \param  header  Reference to header
+    /// \return Header body
+    std::string HeaderBody(const Header& header) const override;
+
+    /// \brief  Creates a body for section
+    /// \param  section  Reference to section
+    /// \return Section body
+    std::string SectionBody(const Section& section) const override;
+
+    /// \brief  Creates a body for table
+    /// \param  table  Reference to table
+    /// \return Table body
+    std::string TableBody(const Table& table) const override;
+
+    /// \brief  Creates a body for tail
+    /// \param  tail  Reference to tail
+    /// \return Figure body
+    std::string TailBody(const Tail& tail) const override;
+
+    /// \brief  Returns script extention
+    std::string ScriptExtention() const override { return "html"; };
+
+    /// \brief  Returns engine name
+    std::string MyName() const override { return "HTML engine"; }
+
+   private:
+    // TODO: control with config
+    static constexpr double kFigureWidth              = 0.9;     ///< Figure width [in page width]
+    static constexpr std::string_view kTableTextAlign = "left";  ///< Table: align
+    static constexpr int kTablePadding                = 4;       ///< Table: rows padding [px]
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportLatexEngine.cxx b/core/qa/report/CbmQaReportLatexEngine.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..9b5214189ba7ea23b9e8b8c4efa1cf63a79908cd
--- /dev/null
+++ b/core/qa/report/CbmQaReportLatexEngine.cxx
@@ -0,0 +1,233 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportLatexEngine.h
+/// \brief  LaTeX plain document engine for the cbm::qa::report (source)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#include "CbmQaReportLatexEngine.h"
+
+#include "CbmQaReportFigure.h"
+#include "CbmQaReportHeader.h"
+#include "CbmQaReportSection.h"
+#include "CbmQaReportTable.h"
+#include "CbmQaReportTail.h"
+#include "Logger.h"
+
+#include <chrono>
+#include <ctime>
+#include <iomanip>
+#include <regex>
+#include <sstream>
+
+using cbm::qa::report::Figure;
+using cbm::qa::report::Header;
+using cbm::qa::report::LatexEngine;
+using cbm::qa::report::LatexFormat;
+using cbm::qa::report::Section;
+using cbm::qa::report::Table;
+using cbm::qa::report::Tail;
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string LatexFormat::Apply(std::string_view input)
+{
+  auto output = std::string(input);
+
+  // Replace all underscores with "\_"
+  // TODO: ignore replacing in LaTeX formulas
+  {
+    std::regex rex("_");
+    output = std::regex_replace(output, rex, "\\_");
+  }
+
+  return output;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string LatexEngine::FigureBody(const Figure& figure) const
+{
+  std::stringstream out;
+  out << "\\begin{figure}[H]\n";
+  out << "  \\centering\n";
+  out << "  \\includegraphics[width=" << LatexEngine::kFigureWidth << "\\textwidth]{" << figure.GetPath() << "}\n";
+  if (!figure.GetCaption().empty()) {
+    out << "  \\caption{" << LatexFormat::Apply(figure.GetCaption()) << "}\n";
+  }
+  out << "  \\label{" << figure.GetLabel() << "}\n";
+  out << "\\end{figure}\n";
+  return out.str();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string LatexEngine::HeaderBody(const Header& header) const
+{
+  std::stringstream out;
+
+  // TODO: Move these definitions to text-file (?)
+  // Settings
+  out << "\\documentclass[12pt]{article}\n";
+  out << "\\usepackage[english]{babel}\n";
+  out << "\\usepackage{array}\n";
+  out << "\\usepackage{graphicx}\n";
+  out << "\\usepackage{float}\n";
+  out << "\\usepackage{longtable}\n";
+  out << "\\usepackage{helvet}\n";
+  out << "\\usepackage{sansmath}\n\\sansmath\n";
+  out << "\\usepackage{geometry}\n";
+  out << "\\geometry{a4paper, total={170mm,257mm}, left=25mm, right=25mm, top=30mm, bottom=30mm}\n";
+  //out << "\\setlength{\\parindent}{0cm}\n";
+  out << "\\usepackage{hyperref}\n";
+  //out << "\\usepackage{color}\n";
+  //out << "\\definecolor{hrefcolor}{rgb}{0.2, 0.2, 0.6}\n";
+  //out << "\\hypersetup{color}{colorlinks=true, urlcolor=hrefcolor,linkcolor=black,citecolor=hrefcolor}\n";
+  if (!header.GetPageHeader().empty()) {
+    //out << "\\usepackage{fancyhdr}\n\\pagestyle{fancy}\n\\lhead{"
+    //    << LatexFormat::Apply(header.GetPageHeader()) << "}\n";
+  }
+  out << "\n";
+  out << "\\newcolumntype{L}[1]{>{\\flushleft\\arraybackslash}p{#1}}\n";
+
+  // Title page settings
+  out << "\\title{" << LatexFormat::Apply(header.GetTitle()) << "\\\\ \\vspace{1cm} \\Large "
+      << LatexFormat::Apply(header.GetSubtitle()) << "}\n";
+  out << "\\author{Author: " << LatexFormat::Apply(header.GetAuthor()) << "}\n";
+  auto timeNow = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+  out << "\\date{Generated on " << std::put_time(std::localtime(&timeNow), "%a %d.%m.%Y, %X") << "}\n";
+
+
+  // Title page
+  out << "\\begin{document}\n";
+  out << "\\begin{titlepage}\n";
+  out << "  \\maketitle\n";
+  out << "  \\thispagestyle{empty}\n";
+  out << "  \\vfill\n";
+  //out << "  \\centering\n";
+  out << "  Setup: " << LatexFormat::Apply(header.GetSetup()) << "\\\\\n";
+  out << "\\end{titlepage}\n";
+  out << "\\newpage\n";
+  out << "\\tableofcontents\n";
+  out << "\\newpage\n";
+
+  out << '\n';
+  return out.str();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string LatexEngine::SectionBody(const Section& section) const
+{
+  std::stringstream out;
+  std::string sSectionType = "";
+  if (section.GetLevel() == 0) {
+    sSectionType = "section";
+  }
+  else if (section.GetLevel() == 1) {
+    sSectionType = "subsection";
+  }
+  else {
+    sSectionType = "subsubsection";
+  }
+
+  if (section.GetLevel() == 0) {
+    out << "\\newpage\n";
+  }
+  out << "\\" << sSectionType << "{" << section.GetTitle() << "}\n\n";
+  for (const auto& pElement : section.GetDaughterElements()) {
+    out << pElement->GetBody(*this) << '\n';
+  }
+  out << '\n';
+  return out.str();
+}
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string LatexEngine::TableBody(const Table& table) const
+{
+  std::stringstream out;
+  int nRows = table.GetNofRows();
+  int nCols = table.GetNofCols();
+
+
+  out << "\\begin{table}\n";
+  out << "\\footnotesize\n";
+  // tabular header
+  if (!table.GetCaption().empty()) {
+    out << "  \\caption{" << LatexFormat::Apply(table.GetCaption()) << "}\n";
+  }
+  out << "  \\begin{longtable}{" << std::string(nCols, 'c') << "}\n";
+  // table header
+  out << "    \\hline\n";
+  out << "    ";
+  for (int iCol = 0; iCol < nCols; ++iCol) {
+    out << table.GetColumnTitle(iCol) << ((iCol < nCols - 1) ? " & " : " \\\\\n");
+  }
+  out << "    \\hline\n";
+  // table body
+  for (int iRow = 0; iRow < nRows; ++iRow) {
+    out << "    ";
+    for (int iCol = 0; iCol < nCols; ++iCol) {
+      out << table(iRow, iCol) << ((iCol < nCols - 1) ? " & " : " \\\\\n");
+    }
+  }
+  out << "    \\hline\n";
+
+  out << "  \\end{longtable}\n";
+  out << "  \\label{" << table.GetLabel() << "}\n";
+  out << "\\end{table}\n";
+  return out.str();
+}
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string LatexEngine::TailBody(const Tail&) const
+{
+  std::stringstream out;
+  out << "\\end{document}\n";
+  return out.str();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void LatexEngine::Compile(const std::string& source) const
+{
+  // Search for compiler program in path
+  std::string compiler = fsLatexCompiler.substr(0, fsLatexCompiler.find_first_of(' '));  // compiler name without opts
+  int bCompilerFonud   = false;
+  {
+    std::stringstream stream(getenv("PATH"));
+    std::string dir;
+    while (!bCompilerFonud && std::getline(stream, dir, ':')) {
+      bCompilerFonud = fs::exists(fs::path(dir) / compiler);
+    }
+  }
+
+  if (!bCompilerFonud) {
+    LOG(error) << "cbm::qa::report::LatexEngine::Compile(): compiler \"" << fsLatexCompiler << "\" not found in PATH. "
+               << "Please use another compiler or compile the sourec in external program";
+    return;
+  }
+
+  // Execute compiler
+  fs::path sourceDir = fs::path(source).parent_path();
+  fs::path currPath  = fs::current_path();
+  fs::current_path(sourceDir);
+
+  std::string logFile = compiler + ".log";
+  std::string command = fmt::format("{} {} > {}", fsLatexCompiler, source, logFile);
+  command             = command + ";" + command;  // compile two times to get contents
+
+  LOG(info) << "cbm::qa::report::LatexEngine::Compile(): executing command: \n\t" << command;
+  int res = std::system(command.c_str());
+  fs::current_path(currPath);
+
+  LOG(info) << "cbm::qa::report::LatexEngine::Compile(): compillation " << (res == 0 ? "succeed" : "failed")
+            << "(compiler log: \"" << logFile << "\")";
+}
diff --git a/core/qa/report/CbmQaReportLatexEngine.h b/core/qa/report/CbmQaReportLatexEngine.h
new file mode 100644
index 0000000000000000000000000000000000000000..0eeeb5573191fae04c39e97eda9fcc41c09a1e50
--- /dev/null
+++ b/core/qa/report/CbmQaReportLatexEngine.h
@@ -0,0 +1,76 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportLatexEngine.h
+/// \brief  LaTeX document engine for the cbm::qa::report
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#pragma once
+
+#include "CbmQaReportEngine.h"
+
+#include <string>
+#include <string_view>
+
+namespace cbm::qa::report
+{
+  // Latex formatting collection
+  /// \class LatexFormat
+  class LatexFormat {
+   public:
+    /// \brief Applies a LaTeX-friendly formatting
+    static std::string Apply(std::string_view input);
+  };
+
+  /// \class LatexEngine
+  /// \brief Plain LaTeX document engine
+  class LatexEngine : public Engine {
+   public:
+    /// \brief  Creates a body for figure
+    /// \param  figure  Reference to figure
+    /// \return Figure body
+    std::string FigureBody(const Figure& figure) const override;
+
+    /// \brief  Creates a body for header
+    /// \param  header  Reference to header
+    /// \return Header body
+    std::string HeaderBody(const Header& header) const override;
+
+    /// \brief  Creates a body for section
+    /// \param  section  Reference to section
+    /// \return Section body
+    std::string SectionBody(const Section& section) const override;
+
+    /// \brief  Creates a body for table
+    /// \param  table  Reference to table
+    /// \return Table body
+    std::string TableBody(const Table& table) const override;
+
+    /// \brief  Creates a body for tail
+    /// \param  tail  Reference to tail
+    /// \return Figure body
+    std::string TailBody(const Tail& tail) const override;
+
+    /// \brief  Returns script extention
+    std::string ScriptExtention() const override { return "tex"; };
+
+    /// \brief  Returns engine name
+    std::string MyName() const override { return "LaTeX plain document engine"; }
+
+    /// \brief  Compiles source
+    /// \param  source  Source path
+    void Compile(const std::string& source) const override;
+
+    /// \brief  Sets the LaTeX compilation program name
+    /// \param  latexCompiler A LaTeX compiler
+    /// \note   The compiler can contain options
+    void SetLatexCompiler(std::string_view latexCompiler) { fsLatexCompiler = latexCompiler; }
+
+   private:
+    static constexpr double kFigureWidth = 1.0;  ///< Figure width [in textwidth]
+
+    std::string fsLatexCompiler = "pdflatex -interaction=nonstopmode";
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportLatexFormat.h b/core/qa/report/CbmQaReportLatexFormat.h
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/core/qa/report/CbmQaReportLinkDef.h b/core/qa/report/CbmQaReportLinkDef.h
new file mode 100644
index 0000000000000000000000000000000000000000..54de0d4e8381caa3b11973b6e4f266e84a0ddacb
--- /dev/null
+++ b/core/qa/report/CbmQaReportLinkDef.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   CbmQaReportLinkDef.h
+/// \brief  Linkage definitions for using in ROOT
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  24.02.2024
+
+#ifdef __CINT__
+
+#pragma link off all globals;
+#pragma link off all classes;
+#pragma link off all functions;
+
+#pragma link C++ nestedclasses;
+#pragma link C++ nestedtypedef;
+
+#pragma link C++ class cbm::qa::report::Builder + ;
+#pragma link C++ class cbm::qa::report::Element + ;
+#pragma link C++ class cbm::qa::report::CollapsibleElement + ;
+#pragma link C++ class cbm::qa::report::ElementFactory + ;
+#pragma link C++ class cbm::qa::report::Figure + ;
+#pragma link C++ class cbm::qa::report::Header + ;
+#pragma link C++ class cbm::qa::report::Section + ;
+#pragma link C++ class cbm::qa::report::Table + ;
+#pragma link C++ class cbm::qa::report::Tail + ;
+#pragma link C++ class cbm::qa::report::LatexFactory + ;
+#pragma link C++ class cbm::qa::report::LatexFormat + ;
+#pragma link C++ class cbm::qa::report::LatexFigure + ;
+#pragma link C++ class cbm::qa::report::LatexHeader + ;
+#pragma link C++ class cbm::qa::report::LatexSection + ;
+#pragma link C++ class cbm::qa::report::LatexTable + ;
+#pragma link C++ class cbm::qa::report::LatexTail + ;
+
+
+#endif
+
diff --git a/core/qa/report/CbmQaReportSection.cxx b/core/qa/report/CbmQaReportSection.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f55ea983bd8938943c714858fdf79a7e2b2639d1
--- /dev/null
+++ b/core/qa/report/CbmQaReportSection.cxx
@@ -0,0 +1,33 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportSection.h
+/// \brief  Base class for the report section (header)
+/// \author S. Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#include "CbmQaReportSection.h"
+
+using cbm::qa::report::CollapsibleElement;
+using cbm::qa::report::Element;
+using cbm::qa::report::Section;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Section::Section(std::string_view label, std::string_view title)
+  : CollapsibleElement(std::string("section") + std::string(label))
+  , fsTitle(title)
+{
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Section::Add(std::shared_ptr<Element> pElement)
+{
+  // Add level to the section
+  if (auto* pSubSection = dynamic_cast<Section*>(pElement.get())) {
+    pSubSection->SetLevel(this->fLevel + 1);
+  }
+  this->CollapsibleElement::Add(pElement);
+}
diff --git a/core/qa/report/CbmQaReportSection.h b/core/qa/report/CbmQaReportSection.h
new file mode 100644
index 0000000000000000000000000000000000000000..0f50ab69b7e02229ac79a2c37ec200a412009ab5
--- /dev/null
+++ b/core/qa/report/CbmQaReportSection.h
@@ -0,0 +1,61 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportSection.h
+/// \brief  Base class for the report section (header)
+/// \author S. Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#pragma once
+
+#include "CbmQaReportElement.h"
+#include "CbmQaReportEngine.h"
+
+#include <string>
+#include <string_view>
+
+namespace cbm::qa::report
+{
+  /// \class Section
+  /// \brief Section of the report
+  class Section : public CollapsibleElement {
+   public:
+    /// \brief Constructor
+    /// \param label  Label of section
+    /// \param title  Title
+    Section(std::string_view label, std::string_view title);
+
+    /// \brief Default constructor
+    Section() = default;
+
+    /// \brief Gets body of the element
+    /// \param engine  A concrete implementation of the Engine to get the element body
+    std::string GetBody(const Engine& engine) const override { return engine.SectionBody(*this); }
+
+    /// \brief Destructor
+    virtual ~Section() = default;
+
+    /// \brief Adds daughter element
+    void Add(std::shared_ptr<Element> pElement) override;
+
+    /// \brief Gets level of the section
+    int GetLevel() const { return fLevel; }
+
+    /// \brief Gets title
+    const std::string& GetTitle() const { return fsTitle; }
+
+    /// \brief Sets title
+    void SetTitle(std::string_view title) { fsTitle = title; }
+
+   protected:
+    /// \brief Sets level of the section
+    /// \param level  Level of the section (is it section, subsection etc.)
+    /// \note  The level is assigned in the adding a new section
+    void SetLevel(int level) { fLevel = level; }
+
+   private:
+    std::string fsTitle = "";  ///< Title of the section
+    int fLevel          = 0;   ///< Level of the section
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportTable.cxx b/core/qa/report/CbmQaReportTable.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..581ee5462ac7747f907a5d4b4706a112ff359ef0
--- /dev/null
+++ b/core/qa/report/CbmQaReportTable.cxx
@@ -0,0 +1,106 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportTable.cxx
+/// \brief  Base class for the report table (source)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#include "CbmQaReportTable.h"
+
+#include "CbmQaTable.h"
+#include "Logger.h"
+
+using cbm::qa::report::Table;
+
+#include <algorithm>
+#include <sstream>
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Table::Table(std::string_view label) : Table(label, 0, 0) {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Table::Table(std::string_view label, int nRows, int nCols)
+  : Element(std::string("tab:") + std::string(label))
+  , fNofRows(nRows)
+  , fNofCols(nCols)
+{
+  fvsTable.clear();
+  fvsTable.resize(fNofRows * fNofCols);
+  fvsTableHeader.clear();
+  fvsTableHeader.resize(fNofCols);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Table::Set(const CbmQaTable* pQaTable, const std::string& cellFormat)
+{
+  if (!pQaTable) {
+    LOG(fatal) << "cbm::qa::report::Table::Set(): A nullptr QA-table is passed to the function. The table label is \""
+               << GetLabel() << '\"';
+  }
+
+  // Define table size
+  fvsTable.clear();
+  fvsTableHeader.clear();
+  int nRowsQa = pQaTable->GetNrows();
+  int nColsQa = pQaTable->GetNcols();
+  fsCaption   = pQaTable->GetTitle();
+
+  bool bRowsHaveTitles = false;
+  for (int iRow = 0; iRow < nRowsQa; ++iRow) {
+    if (!std::string(pQaTable->GetXaxis()->GetBinLabel(nRowsQa - iRow)).empty()) {
+      bRowsHaveTitles = true;
+    }
+  }
+  fNofRows = nRowsQa;
+  fNofCols = nColsQa + static_cast<int>(bRowsHaveTitles);  // Extra column comes from row names
+
+  // Parse row format
+  // NOTE: If the sequence of formats was passed, and it is shorter, than number of columns, then the last
+  //       columns will be formated with default format, which is {:.3}.
+  bool bCommonRowFormat = cellFormat.find_first_of('|') == std::string::npos;
+  std::vector<std::string> vCellFormat(nColsQa, bCommonRowFormat ? cellFormat : std::string(kDefaultFormat));
+  if (!bCommonRowFormat) {
+    std::stringstream stream(cellFormat);
+    int iCol = 0;
+    while (iCol < nColsQa && std::getline(stream, vCellFormat[iCol], '|')) {
+      if (vCellFormat.empty()) {
+        vCellFormat[iCol] = "{}";
+      }
+      ++iCol;
+    }
+  }
+
+  // Test formatting:
+  for (auto& entry : vCellFormat) {
+    try {
+      std::string sTest = fmt::format(entry, 0.);
+    }
+    catch (const std::runtime_error& err) {
+      LOG(error) << "cbm::qa::report::Table(): Inacceptable format \"" << entry << "\" is passed, using default";
+      entry = kDefaultFormat;
+    }
+  }
+
+  // Filling the table
+  fvsTable.resize(fNofCols * fNofRows);
+  fvsTableHeader.resize(fNofCols);
+  for (int iCol = static_cast<int>(bRowsHaveTitles); iCol < fNofCols; ++iCol) {
+    this->SetColumnTitle(iCol, std::string(pQaTable->GetXaxis()->GetBinLabel(iCol)));
+  }
+  for (int iRow = 0; iRow < fNofRows; ++iRow) {
+    // Print row title
+    if (bRowsHaveTitles) {
+      this->SetCell(iRow, 0, pQaTable->GetYaxis()->GetBinLabel(fNofRows - iRow));
+    }
+    for (int iColQa = 0; iColQa < nColsQa; ++iColQa) {
+      double entry = pQaTable->GetCell(iRow, iColQa);
+      int iCol     = iColQa + static_cast<int>(bRowsHaveTitles);
+      this->SetCell(iRow, iCol, fmt::format(vCellFormat[iColQa], entry));
+    }
+  }
+}
diff --git a/core/qa/report/CbmQaReportTable.h b/core/qa/report/CbmQaReportTable.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d65d3bdb51e89c0cdaa42f46362538121122ec8
--- /dev/null
+++ b/core/qa/report/CbmQaReportTable.h
@@ -0,0 +1,109 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportTable.h
+/// \brief  Base class for the report table (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  21.02.2024
+
+#pragma once
+
+#include "CbmQaReportElement.h"
+#include "CbmQaReportEngine.h"
+
+class CbmQaTable;
+
+namespace cbm::qa::report
+{
+  /// \class Table
+  /// \brief Table element in the report
+  class Table : public Element {
+   private:
+    static constexpr std::string_view kDefaultFormat = "{:.3}";  ///< Default format of double entries
+
+   public:
+    /// \brief Constructor
+    /// \param label  Label of the element
+    explicit Table(std::string_view label);
+
+    /// \brief Constructor
+    /// \param label  Label of the element
+    /// \param nRows  Number of rows
+    /// \param nCols  Number of columns
+    Table(std::string_view label, int nRows, int nCols);
+
+    /// \brief Destructor
+    virtual ~Table(){};
+
+    /// \brief Cell access operator
+    /// \param iRow  Index of row
+    /// \param iCol  Index of column
+    const std::string& operator()(int iRow, int iCol) const { return fvsTable[GetIndex(iRow, iCol)]; }
+
+    /// \brief Cell access operator
+    /// \param iRow  Index of row
+    /// \param iCol  Index of column
+    std::string& operator()(int iRow, int iCol) { return fvsTable[GetIndex(iRow, iCol)]; }
+
+    /// \brief Gets body of the element
+    /// \param engine  A concrete implementation of the Engine to get the element body
+    std::string GetBody(const Engine& engine) const override { return engine.TableBody(*this); }
+
+    /// \brief Gets caption
+    const std::string& GetCaption() const { return fsCaption; }
+
+    /// \brief Gets cell
+    /// \param iRow  Index of row
+    /// \param iCol  Index of column
+    const std::string& GetCell(int iRow, int iCol) const { return fvsTable[GetIndex(iRow, iCol)]; }
+
+    /// \brief Gets column title
+    /// \param iCol  Index of column
+    const std::string& GetColumnTitle(int iCol) const { return fvsTableHeader[iCol]; }
+
+    /// \brief Gets number of rows
+    int GetNofRows() const { return fNofRows; }
+
+    /// \brief Gets number of columns
+    int GetNofCols() const { return fNofCols; }
+
+    /// \brief Sets caption
+    /// \param caption Table caption
+    void SetCaption(std::string_view caption) { fsCaption = caption; }
+
+    /// \brief Sets a table from CbmQaTable
+    /// \param pQaTable   Pointer to the QA-table object
+    /// \param cellFormat Defines format of the rows in fmt::format formating scheme
+    ///
+    /// The parameter format provides ether a common format for all cells of the CbmQaTabl, or individual
+    /// formats for each  column (excluding the first row, if it is defined with the QA-table row names).
+    /// For latter one have to pass a sequence of the formats, separated by the bar symbol:
+    ///    "<format_0>|<format_1>|<...>|<format_n>".
+    /// Each of the <format_i> entries represents a fmt::format formatting line. For example, "{:.2}"
+    /// formats a double into two digits after floating point, "{:.2e}" converts the number in
+    /// the scientific format.
+    void Set(const CbmQaTable* pQaTable, const std::string& cellFormat = std::string(kDefaultFormat));
+
+    /// \brief Sets cell
+    /// \param iRow  Index of row
+    /// \param iCol  Index of column
+    /// \param cell  Cell
+    void SetCell(int iRow, int iCol, std::string_view cell) { fvsTable[GetIndex(iRow, iCol)] = cell; }
+
+    /// \brief Sets column title
+    /// \param iCol  Index of column
+    void SetColumnTitle(int iCol, std::string_view title) { fvsTableHeader[iCol] = title; };
+
+   protected:
+    /// \brief Gets index of cell in the table vector
+    inline int GetIndex(int iRow, int iCol) const { return iRow * fNofCols + iCol; }
+
+   private:
+    std::vector<std::string> fvsTable;
+    std::vector<std::string> fvsTableHeader;
+    std::string fsCaption = "";
+    int fNofRows          = 0;
+    int fNofCols          = 0;
+  };
+}  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportTail.h b/core/qa/report/CbmQaReportTail.h
new file mode 100644
index 0000000000000000000000000000000000000000..125775f9f019a165705a770416adee7d43240ff9
--- /dev/null
+++ b/core/qa/report/CbmQaReportTail.h
@@ -0,0 +1,30 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaReportHeader.h
+/// \brief  Base class for the report tail (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  23.02.2024
+
+#pragma once
+
+#include "CbmQaReportEngine.h"
+
+#include <string>
+#include <string_view>
+
+namespace cbm::qa::report
+{
+  /// \class  Tail
+  /// \brief  Tail of the report
+  class Tail {
+   public:
+    /// \brief Destructor
+    virtual ~Tail() = default;
+
+    /// \brief Gets body of the element
+    /// \param engine  A concrete implementation of the Engine to get the element body
+    std::string GetBody(const Engine& engine) const { return engine.TailBody(*this); }
+  };
+}  // namespace cbm::qa::report
diff --git a/macro/qa/report_example.C b/macro/qa/report_example.C
new file mode 100644
index 0000000000000000000000000000000000000000..750281b1d1011986acdaaa7102d547f712420cb2
--- /dev/null
+++ b/macro/qa/report_example.C
@@ -0,0 +1,211 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   report_example.h
+/// \brief  Macro to illustrate usage of CbmQaReport library
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  24.02.2024
+
+/* clang-format off */
+void report_example(
+  TString qaFile = "",
+  TString setup  = ""
+)
+/* clang-format on */
+{
+  using cbm::qa::report::Builder;
+  using cbm::qa::report::Figure;
+  using cbm::qa::report::Section;
+  using cbm::qa::report::Table;
+
+  if (qaFile.Length() == 0) {
+    return;
+  }
+
+  TFile* pFile = TFile::Open(qaFile.Data(), "READONLY");
+  if (!pFile->IsOpen()) {
+    std::cerr << "- E: Input file \"" << qaFile << "\" cannot be opened\n";
+  }
+
+  // Report header
+  auto pReport = std::make_unique<Builder>("CA Tracking QA Status", "./report");
+  pReport->GetHeader()->SetSubtitle("The CBM Collaboration");
+  pReport->GetHeader()->SetAuthor(gSystem->Getenv("USER"));
+  pReport->GetHeader()->SetSetup(setup.Data());
+  pReport->SetFigureExtention("png");
+
+  // ----- Setup
+  {
+    std::string sTaskName = "CbmCaInputQaSetup";
+
+    auto pSection = std::make_shared<Section>(sTaskName, "CA Tracking Setup");
+    pReport->AddSection(pSection);
+    {
+      auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/c_setup_hits").c_str());
+      auto pFig   = std::make_shared<Figure>("setup");
+      pFig->SetPath(pReport->SaveCanvas(pCanv, sTaskName + "/setup_hits"));
+      pSection->Add(pFig);
+    }
+  }
+
+  // ----- Input QA
+  {
+    std::vector<std::pair<std::string, std::string>> vDetNames = {{"Mvd", "MVD"},
+                                                                  {"Sts", "STS"},
+                                                                  {"Much", "MuCh"},
+                                                                  {"Trd", "TRD"},
+                                                                  {"Tof", "TOF"}};
+    for (const auto& [detTag, detName] : vDetNames) {
+      std::string sTaskName = Form("CbmCaInputQa%s", detTag.c_str());
+      if (pFile->Get(sTaskName.c_str())) {
+        int nStations = 0;
+        while (pFile->Get(Form("%s/Station %d", sTaskName.c_str(), nStations))) {
+          ++nStations;
+        }
+        std::string sectionName = Form("CA Tracking Input QA for %s", detName.c_str());
+        auto pSection           = std::make_shared<Section>(sTaskName, sectionName);
+        pReport->AddSection(pSection);
+
+        // Occupancy
+        {
+          std::string secLabel = Form("%s:occupancy", sTaskName.c_str());
+          auto pSubSection     = std::make_shared<Section>(secLabel, "Hits and MC Points occupancy");
+          pSection->Add(pSubSection);
+          std::vector<std::pair<std::string, std::string>> vFigAtts = {
+            {"hit_xy", Form("%s hit occupancy in xy-plane.", detName.c_str())},
+            {"hit_zx", Form("%s hit occupancy in xz-plane.", detName.c_str())},
+            {"hit_zy", Form("%s hit occupancy in yz-plane.", detName.c_str())},
+            {"point_xy", Form("%s MC-point occupancy in xy-plane.", detName.c_str())},
+            {"point_zx", Form("%s MC-point occupancy in xz-plane.", detName.c_str())},
+            {"point_zy", Form("%s MC-point occupancy in yz-plane.", detName.c_str())}};
+          for (const auto& [name, caption] : vFigAtts) {
+            auto* pCanv       = pFile->Get<TCanvas>((sTaskName + "/Summary/vs Station/" + name).c_str());
+            std::string label = Form("%s:%s", sTaskName.c_str(), name.c_str());
+            auto pFig         = std::make_shared<Figure>(label);
+            pFig->SetPath(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+            pFig->SetCaption(caption);
+            pSubSection->Add(pFig);
+          }
+        }
+        // Efficiencies
+        {
+          std::string secLabel = Form("%s:efficiency", sTaskName.c_str());
+          auto pSubSection     = std::make_shared<Section>(secLabel, "Detector efficiency");
+          pSection->Add(pSubSection);
+
+          {
+            std::string name    = "reco_eff_vs_xy";
+            std::string caption = "Hit efficency in xy-plane.";
+            std::string label   = Form("%s:%s", sTaskName.c_str(), name.c_str());
+            auto* pCanv         = pFile->Get<TCanvas>((sTaskName + "/Summary/vs Station/" + name).c_str());
+            auto pFig           = std::make_shared<Figure>(label);
+            pFig->SetPath(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+            pFig->SetCaption(caption);
+            pSubSection->Add(pFig);
+          }
+
+          {
+            std::string name  = "eff_table";
+            std::string label = Form("%s:%s", sTaskName.c_str(), name.c_str());
+            auto* pQaTable    = pFile->Get<CbmQaTable>((sTaskName + "/Summary/vs Station/" + name).c_str());
+            auto pTable       = std::make_shared<Table>(label);
+            pTable->Set(pQaTable);
+            pSubSection->Add(pTable);
+          }
+        }
+        // Residuals and pulls
+        {
+          std::string secLabel = Form("%s:pulls", sTaskName.c_str());
+          auto pSubSection     = std::make_shared<Section>(secLabel, "Residuals and pulls");
+          pSection->Add(pSubSection);
+
+          std::vector<std::pair<std::string, std::string>> vFigAtts = {
+            {"res_x", Form("Hit residuals for hit x-coordinate in %s", detName.c_str())},
+            {"res_y", Form("Hit residuals for hit y-coordinate in %s", detName.c_str())},
+            {"res_t", Form("Hit residuals for hit time in %s", detName.c_str())},
+            {"pull_x", Form("Hit pulls for hit x-coordinate in %s", detName.c_str())},
+            {"pull_y", Form("Hit pulls for hit y-coordinate in %s", detName.c_str())},
+            {"pull_t", Form("Hit pulls for hit time in %s", detName.c_str())}};
+          for (const auto& [name, caption] : vFigAtts) {
+            auto* pCanv       = pFile->Get<TCanvas>((sTaskName + "/Summary/vs Station/" + name).c_str());
+            std::string label = Form("%s:%s", sTaskName.c_str(), name.c_str());
+            auto pFig         = std::make_shared<Figure>(label);
+            pFig->SetPath(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+            pFig->SetCaption(caption);
+            pSubSection->Add(pFig);
+          }
+
+          for (const std::string& name : {"residuals_mean", "pulls_rms"}) {
+            std::string label = Form("%s:%s", sTaskName.c_str(), name.c_str());
+            auto* pQaTable    = pFile->Get<CbmQaTable>((sTaskName + "/Summary/vs Station/" + name).c_str());
+            auto pTable       = std::make_shared<Table>(label);
+            pTable->Set(pQaTable);
+            pSubSection->Add(pTable);
+          }
+        }
+      }
+    }
+  }
+
+  // ----- Output QA
+  if (pFile->Get("CbmCaOutputQa")) {
+    std::string sTaskName = "CbmCaOutputQa";
+    auto pSection         = std::make_shared<Section>(sTaskName, "CA Tracking Output QA");
+    pReport->AddSection(pSection);
+
+    // Tracking summary table
+    {
+      auto pSubSection = std::make_shared<Section>("ca::Summary", "Tracking summary");
+      pSection->Add(pSubSection);
+      auto* pSummaryTable = pFile->Get<CbmQaTable>((sTaskName + "/Summary/summary_table").c_str());
+      auto pTable         = std::make_shared<Table>("casummary");
+      pTable->Set(pSummaryTable, "{:.04}|{:.02}|{:.02}|{:.02}|{:.02}|{:.02}|{:.02}|{:.02}");
+      pSubSection->Add(pTable);
+    }
+    // Track distributions
+    {
+      auto pSubSection = std::make_shared<Section>("ca::trkDistr", "Track Distributions");
+      pSection->Add(pSubSection);
+      std::vector<std::pair<std::string, std::string>> vFigAtts = {
+        {"reco_eta", "Pseudorapidity distributions of reconstructed tracks for different track groups."},
+        {"reco_etaMC", "MC pseudorapidity distributions of reconstructed tracks for different track groups."},
+        {"reco_pMC", "MC momentum distributions of reconstructed tracks for different track groups."},
+        {"reco_yMC", "MC rapidity distributions of reconstructed tracks for different track groups."},
+        {"mc_pMC", "MC momentum distributions of MC-tracks for different track groups."},
+        {"mc_yMC", "MC rapidity distributions of MC-tracks for different track groups."},
+        {"mc_pMC_yMC", "MC tracks vs. MC momentum and MC rapidity."}};
+      for (const auto& [name, caption] : vFigAtts) {
+        auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/" + name).c_str());
+        auto pFig   = std::make_shared<Figure>(name);
+        pFig->SetPath(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+        pFig->SetCaption(caption);
+        pSubSection->Add(pFig);
+      }
+    }
+    // Track distributions
+    {
+      auto pSubSection = std::make_shared<Section>("ca::Eff", "Tracking Efficiency");
+      pSection->Add(pSubSection);
+      std::vector<std::pair<std::string, std::string>> vFigAtts = {
+        {"eff_pMC", "Tracking efficiency vs. MC momentum for different track groups."},
+        {"eff_yMC", "Tracking efficiency vs. MC rapidity for different track groups."},
+        {"eff_thetaMC", "Tracking efficiency vs. MC theta for different track groups."},
+        {"eff_phiMC", "Tracking efficiency vs. MC azimuthal angle for different track groups."},
+        {"eff_etaMC", "Tracking efficiency vs. MC pseudorapidity for different track groups."}};
+      for (const auto& [name, caption] : vFigAtts) {
+        auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/" + name).c_str());
+        auto pFig   = std::make_shared<Figure>(name);
+        pFig->SetPath(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+        pFig->SetCaption(caption);
+        pSubSection->Add(pFig);
+      }
+    }
+  }
+
+  cbm::qa::report::LatexEngine latex;
+  pReport->CreateScript(latex);
+
+  cbm::qa::report::HtmlEngine html;
+  pReport->CreateScript(html);
+}
diff --git a/reco/L1/qa/CbmCaInputQaBase.cxx b/reco/L1/qa/CbmCaInputQaBase.cxx
index a6802c860a444768354988cb4d0d732134fec509..80a15ad140e379f636dfa4c3b7fcd33af4a133a9 100644
--- a/reco/L1/qa/CbmCaInputQaBase.cxx
+++ b/reco/L1/qa/CbmCaInputQaBase.cxx
@@ -20,7 +20,6 @@
 #include "CbmMuchPoint.h"
 #include "CbmMvdHit.h"
 #include "CbmMvdPoint.h"
-#include "CbmQaCanvas.h"
 #include "CbmQaTable.h"
 #include "CbmQaUtil.h"
 #include "CbmStsCluster.h"
@@ -37,6 +36,7 @@
 #include "FairRootManager.h"
 #include "Logger.h"
 #include "TBox.h"
+#include "TCanvas.h"
 #include "TClonesArray.h"
 #include "TEllipse.h"
 #include "TF1.h"
@@ -163,14 +163,14 @@ void CbmCaInputQaBase<DetID>::Check()
     {
       LOG(info) << "-- Hit efficiency integrated over hit distance from station center";
 
-      auto* pEffTable = MakeQaObject<CbmQaTable>("vs Station/eff_table", "Efficiency table", nSt, 2);
-      pEffTable->SetNamesOfCols({"Station ID", "Efficiency"});
+      auto* pEffTable = MakeQaObject<CbmQaTable>("vs Station/eff_table", "Efficiency table", nSt, 1);
+      pEffTable->SetNamesOfCols({"Efficiency"});
       pEffTable->SetColWidth(20);
 
       for (int iSt = 0; iSt < nSt; ++iSt) {
         auto eff = fvph_reco_eff[iSt]->GetMean();
-        pEffTable->SetCell(iSt, 0, iSt);
-        pEffTable->SetCell(iSt, 1, eff);
+        pEffTable->SetRowName(iSt, Form("station %d", iSt));
+        pEffTable->SetCell(iSt, 0, eff);
         bool res = CheckRange("Hit finder efficiency in station " + std::to_string(iSt), eff, fEffThrsh, 1.000);
         StoreCheckResult(Form("hit_efficiency_station_%d", iSt), res);
       }
@@ -181,8 +181,8 @@ void CbmCaInputQaBase<DetID>::Check()
     // Check hits for potential biases from central values
     {
       auto* pResidualsTable =
-        MakeQaObject<CbmQaTable>("vs Station/residuals_mean", "Residual mean values in different stations", nSt, 4);
-      pResidualsTable->SetNamesOfCols({"Station ID", "Residual(x) [cm]", "Residual(y) [cm]", "Residual(t) [ns]"});
+        MakeQaObject<CbmQaTable>("vs Station/residuals_mean", "Residual mean values in different stations", nSt, 3);
+      pResidualsTable->SetNamesOfCols({"Residual(x) [cm]", "Residual(y) [cm]", "Residual(t) [ns]"});
       pResidualsTable->SetColWidth(20);
 
       // Fit residuals
@@ -192,10 +192,10 @@ void CbmCaInputQaBase<DetID>::Check()
         cbm::qa::util::SetLargeStats(fvph_res_y[iSt]);
         cbm::qa::util::SetLargeStats(fvph_res_t[iSt]);
 
-        pResidualsTable->SetCell(iSt, 0, iSt);
-        pResidualsTable->SetCell(iSt, 1, fvph_res_x[iSt]->GetStdDev());
-        pResidualsTable->SetCell(iSt, 2, fvph_res_y[iSt]->GetStdDev());
-        pResidualsTable->SetCell(iSt, 3, fvph_res_t[iSt]->GetStdDev());
+        pResidualsTable->SetRowName(iSt, Form("station %d", iSt));
+        pResidualsTable->SetCell(iSt, 0, fvph_res_x[iSt]->GetStdDev());
+        pResidualsTable->SetCell(iSt, 1, fvph_res_y[iSt]->GetStdDev());
+        pResidualsTable->SetCell(iSt, 2, fvph_res_t[iSt]->GetStdDev());
       }
       LOG(info) << '\n' << pResidualsTable->ToString(8);
     }
@@ -209,7 +209,7 @@ void CbmCaInputQaBase<DetID>::Check()
     {
       auto* pPullsTable =
         MakeQaObject<CbmQaTable>("vs Station/pulls_rms", "Pulls std. dev. values in different stations", nSt, 4);
-      pPullsTable->SetNamesOfCols({"Station ID", "Pull(x) sigm", "Pull(y) sigm", "Pull(t) sigm"});
+      pPullsTable->SetNamesOfCols({"Pull(x) sigm", "Pull(y) sigm", "Pull(t) sigm"});
       pPullsTable->SetColWidth(20);
 
       for (int iSt = 0; iSt < nSt + 1; ++iSt) {
@@ -225,10 +225,10 @@ void CbmCaInputQaBase<DetID>::Check()
         StoreCheckResult(Form("pull_y_station_%d", iSt), CheckRangePull(fvph_pull_y[iSt]));
         StoreCheckResult(Form("pull_t_station_%d", iSt), CheckRangePull(fvph_pull_t[iSt]));
 
-        pPullsTable->SetCell(iSt, 0, iSt);
-        pPullsTable->SetCell(iSt, 1, fvph_pull_x[iSt]->GetStdDev());
-        pPullsTable->SetCell(iSt, 2, fvph_pull_y[iSt]->GetStdDev());
-        pPullsTable->SetCell(iSt, 3, fvph_pull_t[iSt]->GetStdDev());
+        pPullsTable->SetRowName(iSt, Form("station %d", iSt));
+        pPullsTable->SetCell(iSt, 0, fvph_pull_x[iSt]->GetStdDev());
+        pPullsTable->SetCell(iSt, 1, fvph_pull_y[iSt]->GetStdDev());
+        pPullsTable->SetCell(iSt, 2, fvph_pull_t[iSt]->GetStdDev());
       }
 
       LOG(info) << '\n' << pPullsTable->ToString(3);
@@ -1070,6 +1070,23 @@ InitStatus CbmCaInputQaBase<DetID>::InitHistograms()
 template<ca::EDetectorID DetID>
 InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
 {
+  /// TODO: Move this fuction somewhere on top of the class
+  auto DivideCanvas = [](TCanvas* pCanv, int nPads) {
+    if (nPads < 1) {
+      nPads = 1;
+    }
+    int rows = static_cast<int>(std::sqrt(nPads));
+    int cols = nPads / rows;
+    if (cols * rows < nPads) {
+      cols++;
+    }
+    int newSizeX = cols * 600;
+    int newSizeY = rows * 600;
+    pCanv->SetCanvasSize(newSizeX, newSizeY);
+    pCanv->SetWindowSize(newSizeX, newSizeY);
+    pCanv->Divide(cols, rows, 0, 0);
+  };
+
   gStyle->SetOptFit(1);
   int nSt = fpDetInterface->GetNtrackingStations();
 
@@ -1086,8 +1103,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   // ** summary for all stations
 
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("occ_hit", "Hit Occupancy", 1600, 800);
-    canv->Divide2D(3);
+    auto* canv = MakeQaObject<TCanvas>("occ_hit", "Hit Occupancy", 1600, 800);
+    DivideCanvas(canv, 3);
     canv->cd(1);
     fvph_hit_xy[nSt]->DrawCopy("colz", "");
     canv->cd(2);
@@ -1097,8 +1114,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("occ_point", "Point Occupancy", 1600, 800);
-    canv->Divide2D(3);
+    auto* canv = MakeQaObject<TCanvas>("occ_point", "Point Occupancy", 1600, 800);
+    DivideCanvas(canv, 3);
 
     canv->cd(1);
     fvph_point_xy[nSt]->DrawCopy("colz", "");
@@ -1109,8 +1126,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("residual", "Hit Residuals", 1600, 800);
-    canv->Divide2D(6);
+    auto* canv = MakeQaObject<TCanvas>("residual", "Hit Residuals", 1600, 800);
+    DivideCanvas(canv, 6);
     canv->cd(1);
     fvph_res_x[nSt]->DrawCopy("colz", "");
     canv->cd(2);
@@ -1124,8 +1141,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("pull", "Hit Pulls", 1600, 800);
-    canv->Divide2D(6);
+    auto* canv = MakeQaObject<TCanvas>("pull", "Hit Pulls", 1600, 800);
+    DivideCanvas(canv, 6);
     canv->cd(1);
     fvph_pull_x[nSt]->DrawCopy("colz", "");
     canv->cd(2);
@@ -1139,8 +1156,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("eff", "Hit Reconstruction Efficiency", 1600, 800);
-    canv->Divide2D(2);
+    auto* canv = MakeQaObject<TCanvas>("eff", "Hit Reconstruction Efficiency", 1600, 800);
+    DivideCanvas(canv, 2);
     canv->cd(1);
     fvpe_reco_eff_vs_xy[nSt]->DrawCopy("colz", "");
     canv->cd(2);
@@ -1148,8 +1165,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("err", "Hit Errors", 1600, 800);
-    canv->Divide2D(6);
+    auto* canv = MakeQaObject<TCanvas>("err", "Hit Errors", 1600, 800);
+    DivideCanvas(canv, 6);
     canv->cd(1);
     fvph_hit_dx[nSt]->DrawCopy();
     canv->cd(2);
@@ -1165,8 +1182,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("other", "Other histograms", 1600, 800);
-    canv->Divide2D(3);
+    auto* canv = MakeQaObject<TCanvas>("other", "Other histograms", 1600, 800);
+    DivideCanvas(canv, 3);
     canv->cd(1);
     fvph_n_points_per_hit[nSt]->DrawCopy("colz", "");
     canv->cd(2);
@@ -1180,8 +1197,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   // ** Hit occupancies
 
   {  // ** Occupancy in XY plane
-    auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/hit_xy", "", 1600, 800);
-    canv->Divide2D(nSt);
+    auto* canv = MakeQaObject<TCanvas>("vs Station/hit_xy", "", 1600, 800);
+    DivideCanvas(canv, nSt);
     for (int iSt = 0; iSt < nSt; ++iSt) {
       canv->cd(iSt + 1);
       fvph_hit_xy[iSt]->DrawCopy("colz", "");
@@ -1202,7 +1219,7 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {  // ** Occupancy in XZ plane
-    auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/hit_zx", "", 1600, 800);
+    auto* canv = MakeQaObject<TCanvas>("vs Station/hit_zx", "", 1600, 800);
     canv->cd();
     fvph_hit_zx[nSt]->DrawCopy("colz", "");
     for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -1222,7 +1239,7 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
   }
 
   {  // ** Occupancy in YZ plane
-    auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/hit_zy", "", 1600, 800);
+    auto* canv = MakeQaObject<TCanvas>("vs Station/hit_zy", "", 1600, 800);
     canv->cd();
     fvph_hit_zy[nSt]->DrawCopy("colz", "");
     for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -1250,8 +1267,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     // ** Point occupancies
 
     {  // ** Occupancy in XY plane
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/point_xy", "", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/point_xy", "", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_point_xy[iSt]->DrawCopy("colz", "");
@@ -1272,7 +1289,7 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // ** Occupancy in XZ plane
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/point_zx", "", 1600, 800);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/point_zx", "", 1600, 800);
       canv->cd();
       fvph_point_zx[nSt]->DrawCopy("colz", "");
       for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -1292,7 +1309,7 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // ** Occupancy in YZ plane
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/point_zy", "", 1600, 800);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/point_zy", "", 1600, 800);
       canv->cd();
       fvph_point_zy[nSt]->DrawCopy("colz", "");
       for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -1315,8 +1332,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     // ** Residuals
 
     {  // x-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/res_x", "Residuals for x coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/res_x", "Residuals for x coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_res_x[iSt]->DrawCopy("", "");
@@ -1324,8 +1341,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // y-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/res_y", "Residuals for y coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/res_y", "Residuals for y coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_res_y[iSt]->DrawCopy("", "");
@@ -1333,8 +1350,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // u-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/res_u", "Residuals for u coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/res_u", "Residuals for u coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_res_u[iSt]->DrawCopy("", "");
@@ -1342,8 +1359,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // v-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/res_v", "Residuals for v coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/res_v", "Residuals for v coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_res_v[iSt]->DrawCopy("", "");
@@ -1351,8 +1368,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // t-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/res_t", "Residuals for time", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/res_t", "Residuals for time", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_res_t[iSt]->DrawCopy("", "");
@@ -1364,8 +1381,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     // ** Pulls
 
     {  // x-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/pull_x", "Pulls for x coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/pull_x", "Pulls for x coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_pull_x[iSt]->DrawCopy("", "");
@@ -1373,8 +1390,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // y-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/pull_y", "Pulls for y coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/pull_y", "Pulls for y coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_pull_y[iSt]->DrawCopy("", "");
@@ -1382,8 +1399,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // u-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/pull_u", "Pulls for u coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/pull_u", "Pulls for u coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_pull_u[iSt]->DrawCopy("", "");
@@ -1391,8 +1408,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // v-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/pull_v", "Pulls for v coordinate", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/pull_v", "Pulls for v coordinate", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_pull_v[iSt]->DrawCopy("", "");
@@ -1400,8 +1417,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {  // t-coordinate
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/pull_t", "Pulls for time", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/pull_t", "Pulls for time", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvph_pull_t[iSt]->DrawCopy("", "");
@@ -1412,8 +1429,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     // ** Hit reconstruction efficiencies
 
     {
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/reco_eff", "Hit efficiencies in xy bins", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/reco_eff", "Hit efficiencies in xy bins", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         //fvph_reco_eff[iSt]->SetMarkerStyle(20);
@@ -1422,8 +1439,8 @@ InitStatus CbmCaInputQaBase<DetID>::InitCanvases()
     }
 
     {
-      auto* canv = MakeQaObject<CbmQaCanvas>("vs Station/reco_eff_vs_xy", "Hit efficiencies wrt x and y", 1600, 800);
-      canv->Divide2D(nSt);
+      auto* canv = MakeQaObject<TCanvas>("vs Station/reco_eff_vs_xy", "Hit efficiencies wrt x and y", 1600, 800);
+      DivideCanvas(canv, nSt);
       for (int iSt = 0; iSt < nSt; ++iSt) {
         canv->cd(iSt + 1);
         fvpe_reco_eff_vs_xy[iSt]->DrawCopy("colz", "");
diff --git a/reco/L1/qa/CbmCaInputQaSetup.cxx b/reco/L1/qa/CbmCaInputQaSetup.cxx
index 64d5f5e833e6432f64c6cc515c1812ded8376d92..4078b7df27758bf9e3396ca288c98ebabcc9c8cf 100644
--- a/reco/L1/qa/CbmCaInputQaSetup.cxx
+++ b/reco/L1/qa/CbmCaInputQaSetup.cxx
@@ -128,7 +128,7 @@ InitStatus InputQaSetup::InitCanvases()
 {
   // Tracking setup by hits
   {
-    auto* canv = MakeQaObject<CbmQaCanvas>("c_setup_hits", "Setup by hits", 1000, 1000);
+    auto* canv = MakeQaObject<TCanvas>("c_setup_hits", "Setup by hits", 1000, 1000);
     canv->Divide(1, 2, 0.000001, 0.000001);
     canv->cd(1);
     gPad->SetLogz();
diff --git a/reco/L1/qa/CbmCaOutputQa.cxx b/reco/L1/qa/CbmCaOutputQa.cxx
index bd946878538cd0f48c9dc14f373e7387b107e86d..52c2372af1ee05fe925f4b269e772880b0c796cb 100644
--- a/reco/L1/qa/CbmCaOutputQa.cxx
+++ b/reco/L1/qa/CbmCaOutputQa.cxx
@@ -644,8 +644,8 @@ InitStatus OutputQa::InitCanvases()
   std::vector<ETrackType> vCmpTypesProtons = {kAllPPBAR, kPrimP, kPrimPBAR, kSecP, kSecPBAR};
 
   /// @brief Function to draw generic canvas of histogram comparison
-  auto DrawTrackDistributions = [&](CbmQaCanvas* pCanv, std::function<TH1F*(ETrackType)> Hist) {
-    pCanv->Divide2D(6);
+  auto DrawTrackDistributions = [&](TCanvas* pCanv, std::function<TH1F*(ETrackType)> Hist) {
+    pCanv->Divide(3, 2);
     pCanv->cd(1);
     gPad->SetLogy();
     DrawSetOf<TH1F>(vCmpTypesGeneral, Hist);
@@ -667,8 +667,8 @@ InitStatus OutputQa::InitCanvases()
   };
 
   /// @brief Function to draw generic canvas of efficiencies comparison
-  auto DrawTrackEfficiens = [&](CbmQaCanvas* pCanv, std::function<TProfile*(ETrackType)> Prof) {
-    pCanv->Divide2D(3);
+  auto DrawTrackEfficiens = [&](TCanvas* pCanv, std::function<TProfile*(ETrackType)> Prof) {
+    pCanv->Divide(3, 1);
     pCanv->cd(1);
     DrawSetOf<TProfile>(vCmpTypesGeneral, Prof);
     pCanv->cd(2);
@@ -682,61 +682,61 @@ InitStatus OutputQa::InitCanvases()
     // **  Reconstructed track distributions  **
     // Reconstructed pseudorapidity
     auto* pc_reco_eta =
-      MakeQaObject<CbmQaCanvas>("reco_eta", "Reconstructed track pseudorapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
+      MakeQaObject<TCanvas>("reco_eta", "Reconstructed track pseudorapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
     DrawTrackDistributions(pc_reco_eta, [&](ETrackType t) -> TH1F* { return fvpTrackHistograms[t]->fph_reco_eta; });
 
     // MC pseudorapidity
     auto* pc_reco_etaMC =
-      MakeQaObject<CbmQaCanvas>("reco_etaMC", "Reconstructed track MC pseudorapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
+      MakeQaObject<TCanvas>("reco_etaMC", "Reconstructed track MC pseudorapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
     DrawTrackDistributions(pc_reco_etaMC, [&](ETrackType t) -> TH1F* { return fvpTrackHistograms[t]->fph_reco_etaMC; });
 
     // MC momentum
     auto* pc_reco_pMC =
-      MakeQaObject<CbmQaCanvas>("reco_pMC", "Reconstructed track MC momentum", kCXSIZEPX * 3, kCYSIZEPX * 2);
+      MakeQaObject<TCanvas>("reco_pMC", "Reconstructed track MC momentum", kCXSIZEPX * 3, kCYSIZEPX * 2);
     DrawTrackDistributions(pc_reco_pMC, [&](ETrackType t) -> TH1F* { return fvpTrackHistograms[t]->fph_reco_pMC; });
 
     // MC rapidity
     auto* pc_reco_yMC =
-      MakeQaObject<CbmQaCanvas>("reco_yMC", "Reconstructed track MC rapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
+      MakeQaObject<TCanvas>("reco_yMC", "Reconstructed track MC rapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
     DrawTrackDistributions(pc_reco_yMC, [&](ETrackType t) -> TH1F* { return fvpTrackHistograms[t]->fph_reco_yMC; });
 
     // **  MC track distributions  **
 
     // MC momentum
     auto* pc_mc_pMC =
-      MakeQaObject<CbmQaCanvas>("mc_pMC", "MC reconstructable track MC momentum", kCXSIZEPX * 3, kCYSIZEPX * 2);
+      MakeQaObject<TCanvas>("mc_pMC", "MC reconstructable track MC momentum", kCXSIZEPX * 3, kCYSIZEPX * 2);
     DrawTrackDistributions(pc_mc_pMC, [&](ETrackType t) -> TH1F* { return fvpTrackHistograms[t]->fph_mc_pMC; });
 
     // MC rapidity
     auto* pc_mc_yMC =
-      MakeQaObject<CbmQaCanvas>("mc_yMC", "MC reconstructable track MC rapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
+      MakeQaObject<TCanvas>("mc_yMC", "MC reconstructable track MC rapidity", kCXSIZEPX * 3, kCYSIZEPX * 2);
     DrawTrackDistributions(pc_mc_yMC, [&](ETrackType t) -> TH1F* { return fvpTrackHistograms[t]->fph_mc_yMC; });
 
     // MC rapidity vs. MC momentum
     // auto* pc_mc_pMC_yMC =
-    MakeQaObject<CbmQaCanvas>("mc_ptMC_yMC", "MC track MC transverse mom. vs. rapidity ", kCXSIZEPX * 3, kCYSIZEPX * 2);
+    MakeQaObject<TCanvas>("mc_ptMC_yMC", "MC track MC transverse mom. vs. rapidity ", kCXSIZEPX * 3, kCYSIZEPX * 2);
     DrawSetOf<TH2F>(vCmpTypesGeneral, [&](ETrackType t) -> TH2F* { return fvpTrackHistograms[t]->fph_reco_ptMC_yMC; });
 
     // **  Efficiencies  **
 
     // MC momentum
-    auto* pc_eff_pMC = MakeQaObject<CbmQaCanvas>("eff_pMC", "Tracking Eff. vs. MC momentum", kCXSIZEPX * 3, kCYSIZEPX);
+    auto* pc_eff_pMC = MakeQaObject<TCanvas>("eff_pMC", "Tracking Eff. vs. MC momentum", kCXSIZEPX * 3, kCYSIZEPX);
     DrawTrackEfficiens(pc_eff_pMC, [&](ETrackType t) -> TProfile* { return fvpTrackHistograms[t]->fph_eff_pMC; });
 
-    auto* pc_eff_yMC = MakeQaObject<CbmQaCanvas>("eff_yMC", "Tracking Eff. vs. MC rapidity", kCXSIZEPX * 3, kCYSIZEPX);
+    auto* pc_eff_yMC = MakeQaObject<TCanvas>("eff_yMC", "Tracking Eff. vs. MC rapidity", kCXSIZEPX * 3, kCYSIZEPX);
     DrawTrackEfficiens(pc_eff_yMC, [&](ETrackType t) -> TProfile* { return fvpTrackHistograms[t]->fph_eff_yMC; });
 
     auto* pc_eff_thetaMC =
-      MakeQaObject<CbmQaCanvas>("eff_thetaMC", "Tracking Eff. vs. MC polar angle", kCXSIZEPX * 3, kCYSIZEPX);
+      MakeQaObject<TCanvas>("eff_thetaMC", "Tracking Eff. vs. MC polar angle", kCXSIZEPX * 3, kCYSIZEPX);
     DrawTrackEfficiens(pc_eff_thetaMC,
                        [&](ETrackType t) -> TProfile* { return fvpTrackHistograms[t]->fph_eff_thetaMC; });
 
     auto* pc_eff_phiMC =
-      MakeQaObject<CbmQaCanvas>("eff_phiMC", "Tracking Eff. vs. MC azimuthal angle", kCXSIZEPX * 3, kCYSIZEPX);
+      MakeQaObject<TCanvas>("eff_phiMC", "Tracking Eff. vs. MC azimuthal angle", kCXSIZEPX * 3, kCYSIZEPX);
     DrawTrackEfficiens(pc_eff_phiMC, [&](ETrackType t) -> TProfile* { return fvpTrackHistograms[t]->fph_eff_phiMC; });
 
     auto* pc_eff_etaMC =
-      MakeQaObject<CbmQaCanvas>("eff_etaMC", "Tracking Eff. vs. MC pseudorapidity", kCXSIZEPX * 3, kCYSIZEPX);
+      MakeQaObject<TCanvas>("eff_etaMC", "Tracking Eff. vs. MC pseudorapidity", kCXSIZEPX * 3, kCYSIZEPX);
     DrawTrackEfficiens(pc_eff_etaMC, [&](ETrackType t) -> TProfile* { return fvpTrackHistograms[t]->fph_eff_etaMC; });
 
 
diff --git a/reco/L1/qa/CbmCaTrackTypeQa.cxx b/reco/L1/qa/CbmCaTrackTypeQa.cxx
index f5497a8dd10fbdbbc5df9ffc3e89767541d9d05a..a12ce6b99b1e5fffbe0493de4481d8ad05525516 100644
--- a/reco/L1/qa/CbmCaTrackTypeQa.cxx
+++ b/reco/L1/qa/CbmCaTrackTypeQa.cxx
@@ -387,14 +387,12 @@ void TrackTypeQa::SetDrawAtt(Color_t markerCol, Style_t markerSty, Color_t lineC
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void TrackTypeQa::SetHistoProperties(TH1* pHist)
+void TrackTypeQa::SetTH1Properties(TH1* pHist) const
 {
   pHist->SetStats(true);
   pHist->Sumw2();
-  if (!pHist->InheritsFrom("TH2") && !pHist->InheritsFrom("TH3")) {
-    pHist->SetMarkerStyle(fMarkerStyle);
-    pHist->SetLineStyle(fLineStyle);
-  }
+  pHist->SetMarkerStyle(fMarkerStyle);
+  pHist->SetLineStyle(fLineStyle);
   pHist->SetMarkerColor(fMarkerColor);
   pHist->SetLineColor(fLineColor);
 }
diff --git a/reco/L1/qa/CbmCaTrackTypeQa.h b/reco/L1/qa/CbmCaTrackTypeQa.h
index 38171e3a8db4d8c5dcffc9af3a971b630e88d57f..8b4ab81f5f5e11405efc5267705f079f19056e57 100644
--- a/reco/L1/qa/CbmCaTrackTypeQa.h
+++ b/reco/L1/qa/CbmCaTrackTypeQa.h
@@ -269,7 +269,7 @@ namespace cbm::ca
    private:
     /// @brief Overrided virtual function of the CbmQaIO class, defines properties of the histograms
     /// @param pHist Pointer to a histogram, which properties are to be set
-    virtual void SetHistoProperties(TH1* pHist);
+    virtual void SetTH1Properties(TH1* pHist) const override;
 
     // ** Technical profiles and histograms (counters) **
     TProfile* fph_rate_reco      = nullptr;  ///< Rate of reconstructed tracks / mc