From 115e8b2b806876ebe799497ecb4366dd4ff28f0c Mon Sep 17 00:00:00 2001
From: "P.-A. Loizeau" <p.-a.loizeau@gsi.de>
Date: Wed, 27 Mar 2024 16:22:48 +0100
Subject: [PATCH] [Online/Offline] Add online Raw Digis source + inspector task
 + converter macro

---
 algo/base/Definitions.h                    |   6 +-
 algo/base/Options.cxx                      |   2 +-
 algo/global/Reco.cxx                       |   2 +-
 macro/run/run_inspect_digi_timeslice.C     | 116 +++++++++++++++
 reco/steer/CMakeLists.txt                  |   1 +
 reco/steer/CbmRecoSteerLinkDef.h           |   1 +
 reco/steer/CbmSourceDigiTimeslice.cxx      | 156 +++++++++++++++++++++
 reco/steer/CbmSourceDigiTimeslice.h        | 122 ++++++++++++++++
 reco/tasks/CMakeLists.txt                  |   1 +
 reco/tasks/CbmRecoTasksLinkDef.h           |   1 +
 reco/tasks/CbmTaskInspectDigiTimeslice.cxx | 107 ++++++++++++++
 reco/tasks/CbmTaskInspectDigiTimeslice.h   |  75 ++++++++++
 12 files changed, 585 insertions(+), 5 deletions(-)
 create mode 100644 macro/run/run_inspect_digi_timeslice.C
 create mode 100644 reco/steer/CbmSourceDigiTimeslice.cxx
 create mode 100644 reco/steer/CbmSourceDigiTimeslice.h
 create mode 100644 reco/tasks/CbmTaskInspectDigiTimeslice.cxx
 create mode 100644 reco/tasks/CbmTaskInspectDigiTimeslice.h

diff --git a/algo/base/Definitions.h b/algo/base/Definitions.h
index e015b2e23c..a6f3d35505 100644
--- a/algo/base/Definitions.h
+++ b/algo/base/Definitions.h
@@ -40,8 +40,8 @@ namespace cbm::algo
 
   enum class RecoData
   {
-    RawDigi,    //< Raw output from unpackers
-    DigiEvent,  //< Digis after event building
+    DigiTimeslice,  //< Raw output from unpackers
+    DigiEvent,      //< Digis after event building
     Cluster,
     Hit,
     Track,
@@ -80,7 +80,7 @@ CBM_ENUM_DICT(cbm::algo::Step,
 );
 
 CBM_ENUM_DICT(cbm::algo::RecoData,
-  {"RawDigi", RecoData::RawDigi},
+  {"DigiTimeslice", RecoData::DigiTimeslice},
   {"DigiEvent", RecoData::DigiEvent},
   {"Cluster", RecoData::Cluster},
   {"Hit", RecoData::Hit},
diff --git a/algo/base/Options.cxx b/algo/base/Options.cxx
index 62cb3e2690..838430e279 100644
--- a/algo/base/Options.cxx
+++ b/algo/base/Options.cxx
@@ -75,7 +75,7 @@ Options::Options(int argc, char** argv)
     ("log-file,L", po::value(&fLogFile)->value_name("<file>"),
       "write log messages to file")
     ("output-types,O", po::value(&fOutputTypes)->multitoken()->value_name("<types>"),
-      "space seperated list of reconstruction output types (Hit, Tracks, RawDigi, DigiEvent, ...)")
+      "space separated list of reconstruction output types (Hit, Tracks, DigiTimeslice, DigiEvent, ...)")
     ("compress-archive", po::bool_switch(&fCompressArchive)->default_value(false), "Enable compression for output archives")
     ("steps", po::value(&fRecoSteps)->multitoken()->default_value({Step::Unpack, Step::DigiTrigger, Step::LocalReco, Step::Tracking})->value_name("<steps>"),
       "space seperated list of reconstruction steps (unpack, digitrigger, localreco, ...)")
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 76900a35a0..d1cbf1bf94 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -296,7 +296,7 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
       QueueTrackingMetrics(trackingOutput.monitorData);
     }
 
-    if (Opts().HasOutput(RecoData::RawDigi)) {
+    if (Opts().HasOutput(RecoData::DigiTimeslice)) {
       results.bmonDigis  = std::move(digis.fBmon);
       results.stsDigis   = std::move(digis.fSts);
       results.muchDigis  = std::move(digis.fMuch);
diff --git a/macro/run/run_inspect_digi_timeslice.C b/macro/run/run_inspect_digi_timeslice.C
new file mode 100644
index 0000000000..36ed95289c
--- /dev/null
+++ b/macro/run/run_inspect_digi_timeslice.C
@@ -0,0 +1,116 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+/** @file run_inspect_digi_timeslice.C
+ ** @author Pierre-Alain loizeau <p.-a.loizeau@gsi.de>
+ ** @since 21 March 2024
+ **/
+
+
+// --- Includes needed for IDE
+#include <RtypesCore.h>
+#if !defined(__CLING__)
+#include "CbmSourceDigiTimeslice.h"
+#include "CbmTaskInspectDigiEvents.h"
+
+#include <FairRunAna.h>
+#include <FairSystemInfo.h>
+
+#include <TStopwatch.h>
+#endif
+
+
+/** @brief Macro to convert an output from the online binary with raw Digis to a Root file
+ ** @author Pierre-Alain loizeau <p.-a.loizeau
+ ** @since  21 March 2024
+ ** @param input          Name of input file
+ ** @param nTimeSlices    Number of time-slices to process
+ **
+ ** This macro converts raw Digis produced and archived by online data processing to Root format.
+ **/
+void run_inspect_digi_timeslice(TString inputFileName, TString outputFileName, UInt_t runid = 2391,
+                                size_t numTimeslices = -1)
+{
+
+  // ========================================================================
+  //          Adjust this part according to your requirements
+
+  // --- Logger settings ----------------------------------------------------
+  TString logLevel     = "INFO";
+  TString logVerbosity = "LOW";
+  // ------------------------------------------------------------------------
+
+  // -----   Environment   --------------------------------------------------
+  TString myName = "run_convert_online_rawdigis";  // this macro's name for screen output
+  TString srcDir = gSystem->Getenv("VMCWORKDIR");  // top source directory
+  // ------------------------------------------------------------------------
+
+
+  // -----   Timer   --------------------------------------------------------
+  TStopwatch timer;
+  timer.Start();
+  // ------------------------------------------------------------------------
+
+
+  // -----   FairRunAna   ---------------------------------------------------
+  FairRunOnline* run = new FairRunOnline();
+  FairSource* source = new CbmSourceDigiTimeslice(inputFileName);
+  run->SetSource(source);
+  auto sink = new FairRootFileSink(outputFileName);
+  run->SetSink(sink);
+  run->SetGenerateRunInfo(kTRUE);
+  // ------------------------------------------------------------------------
+
+
+  // -----   Logger settings   ----------------------------------------------
+  FairLogger::GetLogger()->SetLogScreenLevel(logLevel.Data());
+  FairLogger::GetLogger()->SetLogVerbosityLevel(logVerbosity.Data());
+  // ------------------------------------------------------------------------
+
+
+  // -----   Event inspection   ---------------------------------------------
+  FairTask* inspect = new CbmTaskInspectDigiTimeslice();
+  LOG(info) << "-I- " << myName << ": Adding task " << inspect->GetName();
+  run->AddTask(inspect);
+  // ------------------------------------------------------------------------
+
+
+  // -----   Run initialisation   -------------------------------------------
+  std::cout << std::endl;
+  std::cout << "-I- " << myName << ": Initialise run" << std::endl;
+  run->Init();
+  // ------------------------------------------------------------------------
+
+
+  // -----   Start run   ----------------------------------------------------
+  std::cout << std::endl << std::endl;
+  std::cout << "-I- " << myName << ": Starting run" << std::endl;
+  if (numTimeslices == -1)
+    run->Run(-1, 0);
+  else
+    run->Run(0, numTimeslices);
+  // ------------------------------------------------------------------------
+
+
+  // -----   Finish   -------------------------------------------------------
+  timer.Stop();
+  FairMonitor::GetMonitor()->Print();
+  Double_t rtime = timer.RealTime();
+  Double_t ctime = timer.CpuTime();
+  std::cout << std::endl << std::endl;
+  std::cout << "Macro finished successfully." << std::endl;
+  std::cout << "Real time " << rtime << " s, CPU time " << ctime << " s" << std::endl;
+  FairSystemInfo sysInfo;
+  Float_t maxMemory = sysInfo.GetMaxMemory();
+  std::cout << "<DartMeasurement name=\"MaxMemory\" type=\"numeric/double\">";
+  std::cout << maxMemory;
+  std::cout << "</DartMeasurement>" << std::endl;
+  Float_t cpuUsage = ctime / rtime;
+  std::cout << "<DartMeasurement name=\"CpuLoad\" type=\"numeric/double\">";
+  std::cout << cpuUsage;
+  std::cout << "</DartMeasurement>" << std::endl;
+  // ------------------------------------------------------------------------
+
+
+}  // End of main macro function
diff --git a/reco/steer/CMakeLists.txt b/reco/steer/CMakeLists.txt
index b74ba5e638..440086429f 100644
--- a/reco/steer/CMakeLists.txt
+++ b/reco/steer/CMakeLists.txt
@@ -8,6 +8,7 @@ set(INCLUDE_DIRECTORIES
 
 set(SRCS
   CbmRecoUnpack.cxx
+  CbmSourceDigiTimeslice.cxx
   CbmSourceDigiEvents.cxx
   CbmSourceTsArchive.cxx
   )
diff --git a/reco/steer/CbmRecoSteerLinkDef.h b/reco/steer/CbmRecoSteerLinkDef.h
index f28876be61..24a33374ea 100644
--- a/reco/steer/CbmRecoSteerLinkDef.h
+++ b/reco/steer/CbmRecoSteerLinkDef.h
@@ -10,6 +10,7 @@
 
 // --- Classes
 #pragma link C++ class CbmRecoUnpack + ;
+#pragma link C++ class CbmSourceDigiTimeslice + ;
 #pragma link C++ class CbmSourceDigiEvents + ;
 #pragma link C++ class CbmSourceTsArchive + ;
 
diff --git a/reco/steer/CbmSourceDigiTimeslice.cxx b/reco/steer/CbmSourceDigiTimeslice.cxx
new file mode 100644
index 0000000000..d01d7d77e2
--- /dev/null
+++ b/reco/steer/CbmSourceDigiTimeslice.cxx
@@ -0,0 +1,156 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese[committer] */
+
+#include "CbmSourceDigiTimeslice.h"
+
+#include <FairRootManager.h>
+#include <Logger.h>
+
+#include <utility>
+
+
+// -----   Constructor   ------------------------------------------------------
+CbmSourceDigiTimeslice::CbmSourceDigiTimeslice(const char* fileName) : fInputFileName(fileName) {}
+// ----------------------------------------------------------------------------
+
+
+// -----   Close   ------------------------------------------------------------
+void CbmSourceDigiTimeslice::Close()
+{
+  LOG(info) << "Source: Closing after " << fNumTs << " timeslices";
+  // Clear output vectors
+  fBmonDigis->clear();
+  fStsDigis->clear();
+  fMuchDigis->clear();
+  fTrdDigis->clear();
+  fTofDigis->clear();
+  fRichDigis->clear();
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Initialisation   ---------------------------------------------------
+template<typename TVecobj>
+Bool_t CbmSourceDigiTimeslice::RegisterVector(FairRootManager* ioman, std::vector<TVecobj>*& vec)
+{
+  if (ioman->GetObject(TVecobj::GetBranchName())) {
+    LOG(fatal) << "Source: Branch " << TVecobj::GetBranchName() << " already exists!";
+    return kFALSE;
+  }
+
+  ioman->RegisterAny(TVecobj::GetBranchName(), vec, kTRUE);
+  LOG(info) << "Source: Registered branch " << TVecobj::GetBranchName() << " at " << vec;
+
+  return kTRUE;
+}
+
+Bool_t CbmSourceDigiTimeslice::Init()
+{
+  // Create input archive
+  fArchive = std::make_unique<cbm::algo::RecoResultsInputArchive>(fInputFileName);
+  LOG(info) << "Source: Reading from input archive " << fInputFileName;
+  auto desc = fArchive->descriptor();
+  LOG(info) << "      - Time created: " << desc.time_created();
+  LOG(info) << "      - Host name   : " << desc.hostname();
+  LOG(info) << "      - User name   : " << desc.username();
+
+  // Create and register the Digi tree branches
+  FairRootManager* ioman = FairRootManager::Instance();
+  assert(ioman);
+
+  fBmonDigis = new std::vector<CbmBmonDigi>();
+  if (kFALSE == RegisterVector<CbmBmonDigi>(ioman, fBmonDigis)) {
+    return kFALSE;
+  }
+
+
+  fStsDigis = new std::vector<CbmStsDigi>();
+  if (kFALSE == RegisterVector<CbmStsDigi>(ioman, fStsDigis)) {
+    return kFALSE;
+  }
+
+
+  fMuchDigis = new std::vector<CbmMuchDigi>();
+  if (kFALSE == RegisterVector<CbmMuchDigi>(ioman, fMuchDigis)) {
+    return kFALSE;
+  }
+
+
+  fTrdDigis = new std::vector<CbmTrdDigi>();
+  if (kFALSE == RegisterVector<CbmTrdDigi>(ioman, fTrdDigis)) {
+    return kFALSE;
+  }
+
+
+  fTofDigis = new std::vector<CbmTofDigi>();
+  if (kFALSE == RegisterVector<CbmTofDigi>(ioman, fTofDigis)) {
+    return kFALSE;
+  }
+
+
+  fRichDigis = new std::vector<CbmRichDigi>();
+  if (kFALSE == RegisterVector<CbmRichDigi>(ioman, fRichDigis)) {
+    return kFALSE;
+  }
+
+
+  return kTRUE;
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Read one time slice from archive   ---------------------------------
+Int_t CbmSourceDigiTimeslice::ReadEvent(UInt_t)
+{
+
+  // Clear output vectors
+  fBmonDigis->clear();
+  fStsDigis->clear();
+  fMuchDigis->clear();
+  fTrdDigis->clear();
+  fTofDigis->clear();
+  fRichDigis->clear();
+
+
+  // Get next timeslice data from archive. Stop run if end of archive is reached.
+  auto results = fArchive->get();
+  if (fArchive->eos()) {
+    LOG(info) << "Source: End of input archive; terminating run";
+    return 1;
+  }
+
+  // Move event data from input archive to ROOT tree
+  if (results == nullptr) {
+    LOG(error) << "Source: Failed to read RecoResults from archive";
+    return 1;
+  }
+  LOG(info) << "Source: Reading TS " << fNumTs << ", index " << results->TsIndex() << ", start "
+            << results->TsStartTime() << ", contains Digis: \n"                                      //
+            << "STS = " << results->StsDigis().size() << " MUCH = " << results->MuchDigis().size()   //
+            << " TOF = " << results->TofDigis().size() << " BMON = " << results->BmonDigis().size()  //
+            << " TRD = " << results->TrdDigis().size() << " TRD2D=" << results->Trd2dDigis().size()  //
+            << " RICH=" << results->RichDigis().size();
+  /// FIXME: Add PSD and FSD to the output of RawDigis in algo/global and reco/app
+  // << " PSD=" << results->StsDigis().size() << " FSD=" << results->StsDigis().size();
+
+  std::move(results->BmonDigis().begin(), results->BmonDigis().end(), std::back_inserter(*fBmonDigis));
+  std::move(results->StsDigis().begin(), results->StsDigis().end(), std::back_inserter(*fStsDigis));
+  std::move(results->MuchDigis().begin(), results->MuchDigis().end(), std::back_inserter(*fMuchDigis));
+  std::move(results->Trd2dDigis().begin(), results->Trd2dDigis().end(), std::back_inserter(*fTrdDigis));
+  std::move(results->TrdDigis().begin(), results->TrdDigis().end(), std::back_inserter(*fTrdDigis));
+  std::move(results->TofDigis().begin(), results->TofDigis().end(), std::back_inserter(*fTofDigis));
+  std::move(results->RichDigis().begin(), results->RichDigis().end(), std::back_inserter(*fRichDigis));
+
+  // Time-sort the TRD vector as we merged TRD1D and TRD2D (needed for compatibility with legacy unpackers)
+  Timesort(fTrdDigis);
+
+  // Update counters
+  fNumTs++;
+
+  return 0;
+}
+// ----------------------------------------------------------------------------
+
+
+ClassImp(CbmSourceDigiTimeslice)
diff --git a/reco/steer/CbmSourceDigiTimeslice.h b/reco/steer/CbmSourceDigiTimeslice.h
new file mode 100644
index 0000000000..e9dc667630
--- /dev/null
+++ b/reco/steer/CbmSourceDigiTimeslice.h
@@ -0,0 +1,122 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#pragma once  // include this header only once per compilation unit
+
+#include "DigiData.h"
+#include "RecoResultsInputArchive.h"
+
+#include <FairSource.h>
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+class FairRootManager;
+
+/** @class CbmSourceDigiTimeslice
+ ** @brief Source class for reading from files resulting from online processing (containing raw Digis)
+ ** @author Pierre-Alain loizeau <p.-a.loizeau
+ ** @since 21 March 2024
+ **
+ ** The online process creates std::vector of Digis for each detector per timeslice.
+ ** These are saved to file using the BOOST streamer.
+ ** This class allows to read such files and get the Digis into the ROOT tree for offline analysis. It creates a
+ ** branches containing the Digis vector with names following established conventions; one tree entry corresponds to one
+ ** timelice.
+ **/
+class CbmSourceDigiTimeslice : public FairSource {
+
+ public:
+  /** @brief Constructor
+   ** @param fileName  Name of (single) input file.
+   **
+   ** More input files can be added by the method AddInputFile.
+   */
+  CbmSourceDigiTimeslice(const char* fileName = "");
+
+  /** @brief Copy constructor - not implemented **/
+  CbmSourceDigiTimeslice(const CbmSourceDigiTimeslice&) = delete;
+
+  /** @brief Assignment operator - not implemented **/
+  CbmSourceDigiTimeslice& operator=(const CbmSourceDigiTimeslice&) = delete;
+
+  /** @brief Destructor **/
+  virtual ~CbmSourceDigiTimeslice() {}
+
+  /** @brief Close source after end of run **/
+  virtual void Close();
+
+  /** @brief Source type
+   ** @return FairSource::Source_Type
+   **/
+  virtual Source_Type GetSourceType() { return fSourceType; }
+
+  /** @brief Initialisation **/
+  virtual Bool_t Init();
+
+  /** @brief Initialise unpackers (forced by base class, not relevant here) **/
+  virtual Bool_t InitUnpackers() { return kTRUE; }
+
+  /** @brief Read one time slice from file **/
+  Int_t ReadEvent(UInt_t = 0);
+
+  /** @brief Re-Initialise unpackers (forced by base class, not relevant here) **/
+  virtual Bool_t ReInitUnpackers() { return kTRUE; }
+
+  /** @brief Reset (forced by base class, not relevant here) **/
+  virtual void Reset() {}
+
+  /** @brief Set unpacker parameters (forced by base class, not relevant here) **/
+  virtual void SetParUnpackers() {}
+
+  /** @brief Set the Source Type
+   ** @param type Source type
+   **/
+  void SetSourceType(Source_Type type) { fSourceType = type; }
+
+  /** @brief Set Run ID (forced by base class, not relevant here) **/
+  Bool_t SpecifyRunId() { return kTRUE; }
+
+
+ private:
+  /** Input file name **/
+  std::string fInputFileName = {};
+
+  /** Input archive **/
+  std::unique_ptr<cbm::algo::RecoResultsInputArchive> fArchive = nullptr;
+
+  /** Branch vectors of Digis **/
+  std::vector<CbmBmonDigi>* fBmonDigis = nullptr;
+  std::vector<CbmStsDigi>* fStsDigis   = nullptr;
+  std::vector<CbmMuchDigi>* fMuchDigis = nullptr;
+  std::vector<CbmTrdDigi>* fTrdDigis   = nullptr;
+  std::vector<CbmTofDigi>* fTofDigis   = nullptr;
+  std::vector<CbmRichDigi>* fRichDigis = nullptr;
+
+  /** @brief type of source that is currently used
+      @remark currently we use kONLINE as default, since, kFILE skipps the first TS probably due to obsolete reasons
+              (to be checked PR072021)
+   **/
+  Source_Type fSourceType = Source_Type::kONLINE;
+
+  /** Time-slice counter **/
+  size_t fNumTs = 0;
+
+  template<typename TVecobj>
+  Bool_t RegisterVector(FairRootManager* ioman, std::vector<TVecobj>*& vec);
+
+  template<typename TVecobj>
+  typename std::enable_if<std::is_member_function_pointer<decltype(&TVecobj::GetTime)>::value, void>::type
+  Timesort(std::vector<TVecobj>* vec = nullptr)
+  {
+    if (vec == nullptr) return;
+    std::sort(vec->begin(), vec->end(),
+              [](const TVecobj& a, const TVecobj& b) -> bool { return a.GetTime() < b.GetTime(); });
+  }
+
+
+  ClassDef(CbmSourceDigiTimeslice, 1)
+};
diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt
index b07e9fea5d..523766f40f 100644
--- a/reco/tasks/CMakeLists.txt
+++ b/reco/tasks/CMakeLists.txt
@@ -13,6 +13,7 @@ set(SRCS
   CbmTaskDigiEventQa.cxx
   CbmTaskEventsCloneInToOut.cxx
   CbmTaskInspectDigiEvents.cxx
+  CbmTaskInspectDigiTimeslice.cxx
   CbmTaskMakeRecoEvents.cxx
   CbmTaskTriggerDigi.cxx
   CbmTaskTofHitFinder.cxx
diff --git a/reco/tasks/CbmRecoTasksLinkDef.h b/reco/tasks/CbmRecoTasksLinkDef.h
index 7242e33ab9..341f9d9065 100644
--- a/reco/tasks/CbmRecoTasksLinkDef.h
+++ b/reco/tasks/CbmRecoTasksLinkDef.h
@@ -17,6 +17,7 @@
 #pragma link C++ class CbmTaskDigiEventQa + ;
 #pragma link C++ class CbmTaskEventsCloneInToOut + ;
 #pragma link C++ class CbmTaskInspectDigiEvents + ;
+#pragma link C++ class CbmTaskInspectDigiTimeslice + ;
 #pragma link C++ class CbmTaskMakeRecoEvents + ;
 #pragma link C++ class CbmTaskTofHitFinder + ;
 #pragma link C++ class CbmTaskTofClusterizer + ;
diff --git a/reco/tasks/CbmTaskInspectDigiTimeslice.cxx b/reco/tasks/CbmTaskInspectDigiTimeslice.cxx
new file mode 100644
index 0000000000..169d3cbf0e
--- /dev/null
+++ b/reco/tasks/CbmTaskInspectDigiTimeslice.cxx
@@ -0,0 +1,107 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#include "CbmTaskInspectDigiTimeslice.h"
+
+#include <FairRootManager.h>
+#include <Logger.h>
+
+
+// -----   Constructor   -----------------------------------------------------
+CbmTaskInspectDigiTimeslice::CbmTaskInspectDigiTimeslice() : FairTask("InspectOnlineRawDigis") {}
+// ---------------------------------------------------------------------------
+
+
+// -----   Destructor   ------------------------------------------------------
+CbmTaskInspectDigiTimeslice::~CbmTaskInspectDigiTimeslice() {}
+// ---------------------------------------------------------------------------
+
+
+// -----   Execution   -------------------------------------------------------
+void CbmTaskInspectDigiTimeslice::Exec(Option_t*)
+{
+
+  // --- Inspect digi data
+  LOG(info) << GetName() << ": timeslice " << fNumTs << " with " << fBmonDigis->size() << " BMon digis "
+            << fStsDigis->size() << " STS digis " << fMuchDigis->size() << " MUCH digis " << fTrdDigis->size()
+            << " TRD digis " << fTofDigis->size() << " TOF digis " << fRichDigis->size() << " RICH digis ";
+
+  // --- Run statistics
+  fNumTs++;
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   End-of-run action   ------------------------------------------------
+void CbmTaskInspectDigiTimeslice::Finish()
+{
+  LOG(info) << "=====================================";
+  LOG(info) << GetName() << ": Run summary";
+  LOG(info) << "Timeslices : " << fNumTs;
+  LOG(info) << "=====================================";
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Initialisation   ---------------------------------------------------
+template<typename TVecobj>
+const std::vector<TVecobj>* CbmTaskInspectDigiTimeslice::InitInput(FairRootManager* ioman)
+{
+  const std::vector<TVecobj>* vec = ioman->InitObjectAs<const std::vector<TVecobj>*>(TVecobj::GetBranchName());
+  if (nullptr == vec) {
+    LOG(error) << GetName() << ": No input branch " << TVecobj::GetBranchName() << " !";
+    return nullptr;
+  }
+  LOG(info) << "--- Found branch " << TVecobj::GetBranchName() << " at " << vec;
+  return vec;
+}
+
+InitStatus CbmTaskInspectDigiTimeslice::Init()
+{
+  // --- Get FairRootManager instance
+  FairRootManager* ioman = FairRootManager::Instance();
+  assert(ioman);
+
+  LOG(info) << "==================================================";
+  LOG(info) << GetName() << ": Initialising...";
+
+  // --- Input data
+
+  fBmonDigis = InitInput<CbmBmonDigi>(ioman);
+  if (!fBmonDigis) {
+    return kFATAL;
+  }
+
+  fStsDigis = InitInput<CbmStsDigi>(ioman);
+  if (!fStsDigis) {
+    return kFATAL;
+  }
+
+  fMuchDigis = InitInput<CbmMuchDigi>(ioman);
+  if (!fMuchDigis) {
+    return kFATAL;
+  }
+
+  fTrdDigis = InitInput<CbmTrdDigi>(ioman);
+  if (!fTrdDigis) {
+    return kFATAL;
+  }
+
+  fTofDigis = InitInput<CbmTofDigi>(ioman);
+  if (!fTofDigis) {
+    return kFATAL;
+  }
+
+  fRichDigis = InitInput<CbmRichDigi>(ioman);
+  if (!fStsDigis) {
+    return kFATAL;
+  }
+
+  LOG(info) << "==================================================";
+  return kSUCCESS;
+}
+// ----------------------------------------------------------------------------
+
+
+ClassImp(CbmTaskInspectDigiTimeslice)
diff --git a/reco/tasks/CbmTaskInspectDigiTimeslice.h b/reco/tasks/CbmTaskInspectDigiTimeslice.h
new file mode 100644
index 0000000000..515e3d5757
--- /dev/null
+++ b/reco/tasks/CbmTaskInspectDigiTimeslice.h
@@ -0,0 +1,75 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau [committer] */
+
+#pragma once
+
+
+#include "CbmBmonDigi.h"
+#include "CbmDefs.h"
+#include "CbmMuchDigi.h"
+#include "CbmRichDigi.h"
+#include "CbmStsDigi.h"
+#include "CbmTofDigi.h"
+#include "CbmTrdDigi.h"
+
+#include <FairTask.h>
+
+#include <vector>
+
+class FairRootManager;
+
+/** @class CbmTaskInspectDigiTimeslice
+ ** @brief Demonstrator class to save online unpacked digis in an output ROOT tree
+ ** @author Pierre-Alain Loizeau <p.-a.loizeau@gsi.de>
+ ** @since 21 March 2024
+ **
+ ** This is a demonstrator of how to convert online unpacked digis into an output ROOT tree.
+ ** It also logs some information to the console as an example on how to look at these digis.
+ **/
+class CbmTaskInspectDigiTimeslice : public FairTask {
+
+ public:
+  /** @brief Constructor **/
+  CbmTaskInspectDigiTimeslice();
+
+
+  /** @brief Copy constructor (disabled) **/
+  CbmTaskInspectDigiTimeslice(const CbmTaskInspectDigiTimeslice&) = delete;
+
+
+  /** @brief Destructor **/
+  virtual ~CbmTaskInspectDigiTimeslice();
+
+
+  /** @brief Task execution **/
+  virtual void Exec(Option_t* opt);
+
+
+  /** @brief Finish timeslice **/
+  virtual void Finish();
+
+
+  /** @brief Assignment operator (disabled) **/
+  CbmTaskInspectDigiTimeslice& operator=(const CbmTaskInspectDigiTimeslice&) = delete;
+
+
+ private:  // methods
+  /** @brief Task initialisation **/
+  virtual InitStatus Init();
+
+  template<typename TVecobj>
+  const std::vector<TVecobj>* InitInput(FairRootManager* ioman);
+
+ private:                                                // members
+  const std::vector<CbmBmonDigi>* fBmonDigis = nullptr;  //! Input data (digis)
+  const std::vector<CbmStsDigi>* fStsDigis   = nullptr;  //! Input data (digis)
+  const std::vector<CbmMuchDigi>* fMuchDigis = nullptr;  //! Input data (digis)
+  const std::vector<CbmTrdDigi>* fTrdDigis   = nullptr;  //! Input data (digis)
+  const std::vector<CbmTofDigi>* fTofDigis   = nullptr;  //! Input data (digis)
+  const std::vector<CbmRichDigi>* fRichDigis = nullptr;  //! Input data (digis)
+  size_t fNumTs                              = 0;        ///< Number of processed timeslices
+
+
+  ClassDef(CbmTaskInspectDigiTimeslice, 1);
+};
-- 
GitLab