From a8ecffeabb898fd923a1b44f64d82103d85b900f Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 18 Apr 2024 18:46:18 +0200
Subject: [PATCH] qa-report: subfigures support

---
 algo/ca/qa/CaOutputQa.cxx                 |   8 +-
 core/qa/report/CbmQaReportFigure.h        |  54 +++++-
 core/qa/report/CbmQaReportHtmlEngine.cxx  |   4 +-
 core/qa/report/CbmQaReportLatexEngine.cxx |  75 +++++++-
 macro/qa/CMakeLists.txt                   |   2 +-
 macro/qa/qa_report_ca_offline.C           | 211 ++++++++++++++++++++++
 macro/qa/report_example.C                 | 100 +++++-----
 7 files changed, 395 insertions(+), 59 deletions(-)
 create mode 100644 macro/qa/qa_report_ca_offline.C

diff --git a/algo/ca/qa/CaOutputQa.cxx b/algo/ca/qa/CaOutputQa.cxx
index a23de73d2e..ee728fed70 100644
--- a/algo/ca/qa/CaOutputQa.cxx
+++ b/algo/ca/qa/CaOutputQa.cxx
@@ -64,8 +64,8 @@ void OutputQa::Init()
     double xMin = -0.5;
     double xMax = double(knStaMax) - 0.5;
     {
-      auto sName  = "track_fst_lst_sta";
-      auto sTitl  = "First vs. last station index;ID^{last}_{station};ID^{first}_{station}";
+      auto sName      = "track_fst_lst_sta";
+      auto sTitl      = "First vs. last station index;ID^{last}_{station};ID^{first}_{station}";
       fphTrkFstLstSta = fQaData.MakeObj<H2D>(sName, sTitl, nBins, xMin, xMax, nBins, xMin, xMax);
     }
     fphTrkNofHits = fQaData.MakeObj<H1D>("n_hits", "Number of hits;N_{hit}", nBins, xMin, xMax);
@@ -114,14 +114,14 @@ void OutputQa::Init()
     {
       auto canv = CanvasConfig("ca_nhits_in_trk", "Number of hit in track");
       {
-        auto pad  = PadConfig();
+        auto pad = PadConfig();
         pad.SetGrid(true, true);
         pad.SetLog(false, true);
         pad.RegisterHistogram(fphTrkNofHits, "hist");
         canv.AddPadConfig(pad);
       }
       {
-        auto pad  = PadConfig();
+        auto pad = PadConfig();
         pad.SetGrid(true, true);
         pad.SetLog(false, false, true);
         pad.RegisterHistogram(fphTrkFstLstSta, "colz");
diff --git a/core/qa/report/CbmQaReportFigure.h b/core/qa/report/CbmQaReportFigure.h
index b152ae9820..fc6037df0d 100644
--- a/core/qa/report/CbmQaReportFigure.h
+++ b/core/qa/report/CbmQaReportFigure.h
@@ -18,6 +18,24 @@ namespace cbm::qa::report
   /// \brief Figure in the report
   class Figure : public Element {
    public:
+    /// \struct Plot
+    /// \brief  A structure to handle the plot details
+    struct Plot {
+      Plot() = default;
+
+      /// \brief Constructor
+      Plot(std::string_view path, std::string_view caption, std::string_view label)
+        : fsPath(path)
+        , fsCaption(caption)
+        , fsLabel(label)
+      {
+      }
+
+      std::string fsPath    = "";
+      std::string fsCaption = "";
+      std::string fsLabel   = "";
+    };
+
     /// \brief Constructor
     /// \param label  Label of the element
     /// \param title  Title
@@ -29,6 +47,15 @@ namespace cbm::qa::report
     /// \brief Destructor
     virtual ~Figure() = default;
 
+    /// \brief Add plot
+    /// \param path to the plot
+    /// \param caption caption of the plot
+    /// \param label label of the plot
+    void AddPlot(std::string_view path, std::string_view caption = "", std::string_view label = "")
+    {
+      fvsPlots.emplace_back(path, caption, label);
+    }
+
     /// \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); }
@@ -36,18 +63,31 @@ namespace cbm::qa::report
     /// \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 Gets plot grid
+    const std::vector<size_t>& GetPlotGrid() const { return fvGrid; }
 
-    /// \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 Gets plot vector
+    const std::vector<Plot>& GetPlots() const { return fvsPlots; }
 
     /// \brief Sets caption
     void SetCaption(std::string_view caption) { fsCaption = caption; }
 
+    /// \brief Sets plot grid
+    /// \param nX  Number of plots along the horizontal direction
+    /// \param nY  Number of plots along the vertical direction
+    void SetPlotGrid(size_t nX, size_t nY)
+    {
+      fvGrid.clear();
+      fvGrid.resize(nY, nX);
+    }
+
+    /// \brief Set plot grid
+    /// \param grid  Grid {X1, X2, ..., Xn}, where Xi -- number of plots in the ith line
+    void SetPlotGrid(const std::vector<size_t>& grid) { fvGrid = grid; }
+
    private:
-    std::string fsPath    = "";  ///< Relative path
-    std::string fsCaption = "";  ///< Figure caption
+    std::vector<Plot> fvsPlots{};  ///< Vector of plots (subfigures)
+    std::vector<size_t> fvGrid{};  ///< Plot grid
+    std::string fsCaption{};       ///< Figure caption
   };
 }  // namespace cbm::qa::report
diff --git a/core/qa/report/CbmQaReportHtmlEngine.cxx b/core/qa/report/CbmQaReportHtmlEngine.cxx
index f4be356eb8..1a0c32d16b 100644
--- a/core/qa/report/CbmQaReportHtmlEngine.cxx
+++ b/core/qa/report/CbmQaReportHtmlEngine.cxx
@@ -37,7 +37,9 @@ std::string HtmlEngine::FigureBody(const Figure& figure) const
 {
   std::stringstream out;
   out << "<figure>\n";
-  out << "  <embed src=\"" << figure.GetPath() << "\" style=\"width:" << kFigureWidth << "\">\n";
+  for (const auto& plot : figure.GetPlots()) {
+    out << "  <embed src=\"" << plot.fsPath << "\" style=\"width:" << kFigureWidth << "\">\n";
+  }
   if (!figure.GetCaption().empty()) {
     out << "  <figcaption>Fig.[" << figure.GetLabel() << "]: " << figure.GetCaption() << "</figcaption>\n";
   }
diff --git a/core/qa/report/CbmQaReportLatexEngine.cxx b/core/qa/report/CbmQaReportLatexEngine.cxx
index b2e95e6262..d010a78ca4 100644
--- a/core/qa/report/CbmQaReportLatexEngine.cxx
+++ b/core/qa/report/CbmQaReportLatexEngine.cxx
@@ -20,6 +20,7 @@
 #include <chrono>
 #include <ctime>
 #include <iomanip>
+#include <numeric>
 #include <regex>
 #include <sstream>
 
@@ -36,10 +37,81 @@ using cbm::qa::report::Tail;
 //
 std::string LatexEngine::FigureBody(const Figure& figure) const
 {
+  size_t nPlots = figure.GetPlots().size();
+  if (nPlots == 0) {
+    LOG(warn) << "No plots provided for figure " << figure.GetLabel() << ". Ignoring.";
+    return "";
+  }
+
   std::stringstream out;
   out << "\\begin{figure}[H]\n";
   out << "  \\centering\n";
-  out << "  \\includegraphics[width=" << LatexEngine::kFigureWidth << "\\textwidth]{" << figure.GetPath() << "}\n";
+  if (nPlots == 1) {
+    out << "  \\includegraphics[width=" << LatexEngine::kFigureWidth << "\\textwidth]{" << figure.GetPlots()[0].fsPath
+        << "}\n";
+  }
+  else {
+    std::vector<size_t> vPlotGrid;
+    bool bDefineDefaultGrid = figure.GetPlotGrid().empty();
+    if (!bDefineDefaultGrid) {
+      size_t nPlotsByGrid = std::accumulate(figure.GetPlotGrid().begin(), figure.GetPlotGrid().end(), 0);
+      if (nPlotsByGrid < nPlots) {
+        LOG(warn) << "Figure " << figure.GetLabel()
+                  << ": provided grid does not fit the subfigures, define the default one";
+        bDefineDefaultGrid = true;
+      }
+    }
+
+    if (bDefineDefaultGrid) {
+      constexpr size_t nX = 3;
+      auto nY             = static_cast<size_t>(std::ceil(static_cast<double>(nPlots) / nX));
+      vPlotGrid.resize(nY, nX);
+    }
+    else {
+      vPlotGrid = figure.GetPlotGrid();
+    }
+
+    for (auto& i : vPlotGrid) {
+      LOG(info) << "- " << i;
+    }
+
+    size_t iPlot = 0;
+    for (size_t iY = 0; iY < vPlotGrid.size(); ++iY) {
+      if (iPlot >= nPlots) {
+        break;
+      }
+      size_t nX = vPlotGrid[iY];
+      if (nX == 0) {
+        continue;
+      }
+      double wSize = 0.90 / std::min(nX, nPlots - iPlot);
+      for (size_t iX = 0; iX < nX; ++iX) {
+        if (iPlot >= nPlots) {
+          break;
+        }
+        const auto& plot = figure.GetPlots()[iPlot];
+        out << "  \\begin{subfigure}[t]{" << wSize << "\\textwidth}\n";
+        out << "    \\centering\n";
+        out << "    \\includegraphics[width=\\linewidth]{" << plot.fsPath << "}\n";
+        out << "    \\caption{" << plot.fsCaption << "}\n";
+        if (!plot.fsLabel.empty()) {
+          out << "    \\label{" << figure.GetLabel() << ":" << plot.fsLabel << "}\n";
+        }
+        out << "  \\end{subfigure}\n";
+        if (iX == nX - 1) {
+          out << '\n';
+          if (iY != vPlotGrid.size() - 1) {
+            out << "\\vspace{3mm}\n";
+          }
+        }
+        else {
+          out << "\\quad\n";
+        }
+        ++iPlot;
+      }
+    }
+  }
+
   if (!figure.GetCaption().empty()) {
     out << "  \\caption{" << LatexFormat::Apply(figure.GetCaption()) << "}\n";
   }
@@ -65,6 +137,7 @@ std::string LatexEngine::HeaderBody(const Header& header) const
   out << "\\usepackage{helvet}\n";
   out << "\\usepackage{sansmath}\n\\sansmath\n";
   out << "\\usepackage{geometry}\n";
+  out << "\\usepackage{subcaption}\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";
diff --git a/macro/qa/CMakeLists.txt b/macro/qa/CMakeLists.txt
index 5fab9636a0..3f283fd157 100644
--- a/macro/qa/CMakeLists.txt
+++ b/macro/qa/CMakeLists.txt
@@ -99,7 +99,7 @@ foreach(setup IN LISTS cbm_setup)
 endforeach(setup IN LISTS cbm_setup)
 # ============================================================================
 
-Install(FILES ../run/.rootrc qa_compare.C
+Install(FILES ../run/.rootrc qa_compare.C ./qa_report_ca_offline.C
         DESTINATION share/cbmroot/macro/qa
        )
 #Install(CODE "FILE(MAKE_DIRECTORY \${CMAKE_INSTALL_PREFIX}/share/cbmroot/macro/run/data)")
diff --git a/macro/qa/qa_report_ca_offline.C b/macro/qa/qa_report_ca_offline.C
new file mode 100644
index 0000000000..353ac5ba11
--- /dev/null
+++ b/macro/qa/qa_report_ca_offline.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   qa_report_ca_offline.C
+/// \brief  QA report for CA tracking (offline)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+/// \since  18.04.2024
+
+/* clang-format off */
+void qa_report_ca_offline(
+  TString qaFile = "",
+  TString setup  = "",
+  TString report = "./report"
+)
+/* 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.Data());
+  pReport->GetHeader()->SetSubtitle("The CBM Collaboration");
+  pReport->GetHeader()->SetAuthor(gSystem->Getenv("USER"));
+  pReport->GetHeader()->SetSetup(setup.Data());
+  pReport->SetFigureExtention("pdf");
+
+  // ----- 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->AddPlot(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->AddPlot(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->AddPlot(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->AddPlot(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_ptMC_yMC", "MC tracks vs. MC transverse momentum and MC rapidity."}};
+      for (const auto& [name, caption] : vFigAtts) {
+        if (auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/" + name).c_str())) {
+          auto pFig = std::make_shared<Figure>(name);
+          pFig->AddPlot(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) {
+        if (auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/" + name).c_str())) {
+          auto pFig = std::make_shared<Figure>(name);
+          pFig->AddPlot(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+          pFig->SetCaption(caption);
+          pSubSection->Add(pFig);
+        }
+      }
+    }
+  }
+
+  cbm::qa::report::LatexEngine latex;
+  pReport->CreateScript(latex);
+}
diff --git a/macro/qa/report_example.C b/macro/qa/report_example.C
index 750281b1d1..515479074f 100644
--- a/macro/qa/report_example.C
+++ b/macro/qa/report_example.C
@@ -10,7 +10,8 @@
 /* clang-format off */
 void report_example(
   TString qaFile = "",
-  TString setup  = ""
+  TString setup  = "",
+  TString report = "./report",
 )
 /* clang-format on */
 {
@@ -29,7 +30,7 @@ void report_example(
   }
 
   // Report header
-  auto pReport = std::make_unique<Builder>("CA Tracking QA Status", "./report");
+  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());
@@ -42,10 +43,11 @@ void report_example(
     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);
+      if (auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/c_setup_hits").c_str())) {
+        auto pFig = std::make_shared<Figure>("setup");
+        pFig->AddPlot(pReport->SaveCanvas(pCanv, sTaskName + "/setup_hits"));
+        pSection->Add(pFig);
+      }
     }
   }
 
@@ -80,12 +82,13 @@ void report_example(
             {"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);
+            if (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->AddPlot(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+              pFig->SetCaption(caption);
+              pSubSection->Add(pFig);
+            }
           }
         }
         // Efficiencies
@@ -98,20 +101,22 @@ void report_example(
             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);
+            if (auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/vs Station/" + name).c_str())) {
+              auto pFig = std::make_shared<Figure>(label);
+              pFig->AddPlot(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);
+            if (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
@@ -128,20 +133,22 @@ void report_example(
             {"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);
+            if (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->AddPlot(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);
+            if (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);
+            }
           }
         }
       }
@@ -158,10 +165,11 @@ void report_example(
     {
       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);
+      if (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
     {
@@ -176,11 +184,12 @@ void report_example(
         {"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);
+        if (auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/" + name).c_str())) {
+          auto pFig = std::make_shared<Figure>(name);
+          pFig->AddPlot(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+          pFig->SetCaption(caption);
+          pSubSection->Add(pFig);
+        }
       }
     }
     // Track distributions
@@ -194,11 +203,12 @@ void report_example(
         {"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);
+        if (auto* pCanv = pFile->Get<TCanvas>((sTaskName + "/Summary/" + name).c_str())) {
+          auto pFig = std::make_shared<Figure>(name);
+          pFig->AddPlot(pReport->SaveCanvas(pCanv, sTaskName + "/" + name));
+          pFig->SetCaption(caption);
+          pSubSection->Add(pFig);
+        }
       }
     }
   }
-- 
GitLab