From ba851e511dbca8ddf37198ed4668fc9c630190c7 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 9 Jan 2023 14:47:36 +0100
Subject: [PATCH] L1: a separate histogrammer for CA tracking

---
 reco/L1/CbmCaMCModule.cxx          |  39 +++++-
 reco/L1/CbmCaMCModule.h            | 202 +++++++++++++--------------
 reco/L1/CbmL1.cxx                  |   8 +-
 reco/L1/CbmL1.h                    |   2 +-
 reco/L1/CbmL1Hit.h                 |  94 +++++++++++--
 reco/L1/CbmL1Performance.cxx       |  13 +-
 reco/L1/CbmL1ReadEvent.cxx         |  12 +-
 reco/L1/CbmL1Track.h               |  90 +++++++++---
 reco/L1/CbmL1TrackPar.h            |   8 +-
 reco/L1/catools/CaToolsMCData.cxx  |   2 +-
 reco/L1/catools/CaToolsMCData.h    |   4 +-
 reco/L1/catools/CaToolsMCTrack.cxx |   2 +-
 reco/L1/catools/CaToolsMCTrack.h   |  32 ++---
 reco/L1/catools/CaToolsQa.cxx      | 212 ++++++++++++++++++++++++++++-
 reco/L1/catools/CaToolsQa.h        | 186 +++++++++++++++++++++++--
 15 files changed, 705 insertions(+), 201 deletions(-)

diff --git a/reco/L1/CbmCaMCModule.cxx b/reco/L1/CbmCaMCModule.cxx
index e76e9e51a8..01b7c99506 100644
--- a/reco/L1/CbmCaMCModule.cxx
+++ b/reco/L1/CbmCaMCModule.cxx
@@ -30,12 +30,13 @@
 #include "TLorentzVector.h"
 #include "TVector3.h"
 
+#include <boost/filesystem.hpp>
+
 #include <algorithm>
 #include <cassert>
 #include <fstream>    // TODO: SZh 07.12.2022: For debug, should be removed!
 #include <stdexcept>  // for std::logic_error
 
-
 // *********************************
 // ** Action definition functions **
 // *********************************
@@ -121,6 +122,23 @@ try {
   LOG(info) << "\tTRD:  " << (fbUseTrd ? "ON" : "OFF");
   LOG(info) << "\tTOF:  " << (fbUseTof ? "ON" : "OFF");
 
+  // Init QA module
+  fpQaModule = std::make_unique<ca::tools::Qa>();
+  fpQaModule->SetParameters(fpParameters);
+  fpQaModule->SetMCData(&fMCData);
+  fpQaModule->SetRecoTrackContainer(fpvRecoTracks);
+  fpQaModule->SetHitContainer(fpvHits);
+
+  // Setup output names
+  // Names are defined using the name for main reconstruction output file, defined from the FairRunAna class
+  boost::filesystem::path sOutPath = FairRunAna::Instance()->GetUserOutputFileName().Data();
+  std::string sOutDir              = sOutPath.parent_path().string();
+  if (sOutDir.empty()) { sOutDir = "."; }
+  std::string sOutHistoQa = sOutDir + "/CaHistoQa." + sOutPath.filename().string();
+  fpQaModule->SetOutputFilename(sOutHistoQa.c_str());
+
+  fpQaModule->InitHistograms();
+
   return true;
 }
 catch (const std::logic_error& error) {
@@ -165,7 +183,11 @@ void CbmCaMCModule::InitEvent(CbmEvent* /*pEvent*/)
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmCaMCModule::InitTrackInfo(const L1Vector<CbmL1Hit>& vHits)
+void CbmCaMCModule::ProcessEvent(CbmEvent*) { fpQaModule->FillHistograms(); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmCaMCModule::InitTrackInfo(const L1Vector<CbmL1HitDebugInfo>& vHits)
 {
   // ----- Initialize stations arrangement and hit indexes
   fMCData.InitTrackInfo(vHits);
@@ -208,7 +230,11 @@ void CbmCaMCModule::InitTrackInfo(const L1Vector<CbmL1Hit>& vHits)
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmCaMCModule::Finish() { std::cout << "\033[1;32mFinishing performance\033[0m\n"; }
+void CbmCaMCModule::Finish()
+{
+  std::cout << "\033[1;32mFinishing performance\033[0m\n";
+  fpQaModule->SaveHistograms();
+}
 
 
 // **********************************
@@ -217,7 +243,7 @@ void CbmCaMCModule::Finish() { std::cout << "\033[1;32mFinishing performance\033
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmCaMCModule::MatchPointsWithHits(L1Vector<CbmL1Hit>& vInputExtHits)
+void CbmCaMCModule::MatchPointsWithHits(L1Vector<CbmL1HitDebugInfo>& vInputExtHits)
 {
   // FIXME: Cleaning up makes it safer, but probably is not needed after old code removal
   for (auto& hit : vInputExtHits) {
@@ -383,7 +409,7 @@ int CbmCaMCModule::MatchHitWithMc<L1DetectorID::kTof>(int iHit) const
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmCaMCModule::MatchRecoAndMCTracks(L1Vector<CbmL1Track>& vRecoTracks, L1Vector<CbmL1Hit>& vInputExtHits)
+void CbmCaMCModule::MatchRecoAndMCTracks(L1Vector<CbmL1Track>& vRecoTracks, L1Vector<CbmL1HitDebugInfo>& vInputExtHits)
 {
   // NOTE: Potentially there is a case, when
 
@@ -550,8 +576,7 @@ void CbmCaMCModule::ReadMCPointsForDetector<L1DetectorID::kTof>(CbmMCDataArray*
       if (!vbTofPointMatched[iP]) { continue; }  // iP was not matched
 
       auto* pExtPoint = L1_DYNAMIC_CAST<CbmTofPoint*>(pPoints->Get(iFile, iEvent, iP));
-      LOG_IF(fatal, !pExtPoint) << "NO POINT: TOF: file, event, index = " << iFile << ' ' << iEvent << ' ' << iP
-                                << '\n';
+      LOG_IF(fatal, !pExtPoint) << "NO POINT: TOF: file, event, index = " << iFile << ' ' << iEvent << ' ' << iP;
 
       if (pExtPoint->GetTrackID() < 0) { continue; }  // Point does not have a track
 
diff --git a/reco/L1/CbmCaMCModule.h b/reco/L1/CbmCaMCModule.h
index 1c7f4ea633..56266d0606 100644
--- a/reco/L1/CbmCaMCModule.h
+++ b/reco/L1/CbmCaMCModule.h
@@ -2,10 +2,10 @@
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
-/// \file   CbmCaMCModule.h
-/// \brief  CA Tracking performance interface for CBM (header)
-/// \since  23.09.2022
-/// \author S.Zharko <s.zharko@gsi.de>
+/// @file   CbmCaMCModule.h
+/// @brief  CA Tracking performance interface for CBM (header)
+/// @since  23.09.2022
+/// @author S.Zharko <s.zharko@gsi.de>
 
 #ifndef CbmCaMCModule_h
 #define CbmCaMCModule_h 1
@@ -41,173 +41,161 @@
 
 class CbmEvent;
 class CbmMCDataObject;
-class CbmL1Hit;
+class CbmL1HitDebugInfo;
 class CbmL1Track;
 
 enum class L1DetectorID;
 
-/// Class CbmCaPerformcance is an interface to communicate between
+/// Class CbmCaPerformance is an interface to communicate between
+///
 class CbmCaMCModule {
 public:
-  // *****************************************
-  // **     Constructors and destructor     **
-  // *****************************************
-
-  /// Constructor
-  /// \param verbosity Verbosity level
+  /// @brief Constructor
+  /// @param verbosity  Verbosity level
   CbmCaMCModule(int verb = 0, int perfMode = 3) : fVerbose(verb), fPerformanceMode(perfMode) {}
 
-  /// Destructor
+  /// @brief Destructor
   ~CbmCaMCModule() = default;
 
-  /// Copy constructor
+  /// @brief Copy constructor
   CbmCaMCModule(const CbmCaMCModule&) = delete;
 
-  /// Move constructor
+  /// @brief Move constructor
   CbmCaMCModule(CbmCaMCModule&&) = delete;
 
-  /// Copy assignment operator
+  /// @brief Copy assignment operator
   CbmCaMCModule& operator=(const CbmCaMCModule&) = delete;
 
-  /// Move assignment operator
+  /// @brief Move assignment operator
   CbmCaMCModule& operator=(CbmCaMCModule&&) = delete;
 
-
-  // *****************************************
-  // **     Action definition functions     **
-  // *****************************************
-
-  /// Defines performance action in the beginning of the run
-  /// \return Success flag
-  bool InitRun();
-
-  /// \brief Defines performance action in the beginning of each event or timeslice
-  /// \note This function should be called before hits initialization
-  /// \param  pEvent Pointer to a current CbmEvent
-  void InitEvent(CbmEvent* pEvent);
-
-  /// \brief Processes event
-  /// Fills histograms and tables, should be called after the tracking done
-  void ProcessEvent(CbmEvent* pEvent);
-
-  /// \brief Initialize information about arrangement of points and hits of MC tracks within stations and the status
-  ///        of track reconstructability
-  /// \param vHits  Vector of hit objects
-  /// Calculates max number of points and hits on a station, number of consecutive stations containing a hit or point
-  /// and number of stations and points with hits. Provides the determination of track reconstructablity status
-  void InitTrackInfo(const L1Vector<CbmL1Hit>& vHits);
-
-  /// Defines performance action in the end of the run
+  /// @brief Defines performance action in the end of the run
   void Finish();
 
-  /// Gets the first point index for a given detector subsystem
+  /// @brief Gets the first point index for a given detector subsystem
   int GetFirstPointIndex(L1DetectorID detID) const
   {
     return std::accumulate(fvNofPointsUsed.cbegin(), fvNofPointsUsed.cbegin() + int(detID), 0);
   }
 
-  /// Gets the first point index for a given detector subsystem
+  /// @brief Gets the first point index for a given detector subsystem
   int GetLastPointIndex(L1DetectorID detID) const
   {
     return std::accumulate(fvNofPointsUsed.cbegin(), fvNofPointsUsed.cbegin() + int(detID) + 1, 0) - 1;
   }
 
-  /// \brief Matches hit with MC point
-  /// \tparam  DetId Detector ID
-  /// \param   iHit  External index of hit
-  /// \return        MC-point index in fvMCPoints array
+  /// @brief Gets MC data object
+  /// @return  Constant reference to MC data object
+  const ca::tools::MCData& GetMCData() const { return fMCData; }
+
+  /// @brief Gets mutable pointer to MC data object
+  /// @return  Mutable reference to MC data object
+  ca::tools::MCData* GetMCData() { return &fMCData; }
+
+  /// @brief Defines performance action in the beginning of each event or time slice
+  /// @note This function should be called before hits initialization
+  /// @param  pEvent Pointer to a current CbmEvent
+  void InitEvent(CbmEvent* pEvent);
+
+  /// @brief Defines action on the module in the beginning of the run
+  /// @return Success flag
+  bool InitRun();
+
+  /// @brief  Initializes MC track
+  /// @param  vHits  Vector of hit objects
+  ///
+  /// Initializes information about arrangement of points and hits of MC tracks within stations and the status
+  /// of track reconstructability, calculates max number of points and hits on a station, number of consecutive
+  /// stations containing a hit or point and number of stations and points with hits. Provides the determination
+  /// of track reconstructability status.
+  void InitTrackInfo(const L1Vector<CbmL1HitDebugInfo>& vHits);
+
+  /// @brief Processes event
+  ///
+  /// Fills histograms and tables, should be called after the tracking done
+  void ProcessEvent(CbmEvent* pEvent);
+
+  /// @brief Matches hit with MC point
+  /// @tparam  DetId Detector ID
+  /// @param   iHit  External index of hit
+  /// @return        MC-point index in fvMCPoints array
+  ///
   /// This method finds a match for a given hit or matches for hits clusters (in case of STS), finds the best
   /// link in the match and returns the corresponding global ID of the MC points
   template<L1DetectorID DetId>
   int MatchHitWithMc(int iHit) const;
 
-  /// Assigns MC point indexes to each hit and hit indexes to each MC point
-  /// \param  vInputExtHits  Vector of external input hits
-  void MatchPointsWithHits(L1Vector<CbmL1Hit>& vInputExtHits);
+  /// @brief Assigns MC point indexes to each hit and hit indexes to each MC point
+  /// @param  vInputExtHits  Vector of external input hits
+  void MatchPointsWithHits(L1Vector<CbmL1HitDebugInfo>& vInputExtHits);
 
-  /// \brief Matches reconstructed tracks with MC tracks
-  /// \param  vRecoTracks    Vector of reconstructed tracks
-  /// \param  vInputExtHits  Vector of external input hits
+  /// @brief Matches reconstructed tracks with MC tracks
+  /// @param  vRecoTracks    Vector of reconstructed tracks
+  /// @param  vInputExtHits  Vector of external input hits
+  ///
   /// In the procedure, the maps of associated MC track indexes to corresponding number of hits are filled out and the
   /// max purity is calculated for each reconstructed track in the TS/event. If the value of purity for a given MC track
   /// is larger then a threshold, the corresponding track index is saved to the reconstructed track object, in the same
   /// time the index of the reconstructed track object is saved to this MC track object. If purity for the MC track does
   /// not pass the threshold, its index will not be accounted as a matched track, and the index of reconstructed track
   /// will be added as an index of "touched" track.
-  void MatchRecoAndMCTracks(L1Vector<CbmL1Track>& vRecoTracks, L1Vector<CbmL1Hit>& vInputExtHits);
+  void MatchRecoAndMCTracks(L1Vector<CbmL1Track>& vRecoTracks, L1Vector<CbmL1HitDebugInfo>& vInputExtHits);
 
+  /// @brief Sets used detector subsystems
+  /// @param  detID  Id of detector
+  /// @param  flag   Flag: true - detector is used
+  /// @note Should be called before this->Init()
+  void SetDetector(L1DetectorID detID, bool flag);
 
-  // ***********************
-  // **     Accessors     **
-  // ***********************
+  /// @brief Sets pointer to the vector of hits from reference
+  void SetHitContainer(const L1Vector<CbmL1HitDebugInfo>& vHits) { fpvHits = &vHits; }
 
-  /// Gets reference to MC data object
-  const ca::tools::MCData& GetMCData() const { return fMCData; }
-
-  /// Gets mutable pointer to MC data object
-  ca::tools::MCData* GetMCData() { return &fMCData; }
+  /// @brief Sets legacy event mode:
+  /// @param flag Flag:
+  ///              - true:  runs on events base,
+  ///              - false: runs on time-slice base
+  void SetLegacyEventMode(bool flag) { fbLegacyEventMode = flag; }
 
-  /// Registers pointer to the tracking parameters object
+  /// @brief Registers pointer to the tracking parameters object
   void SetParameters(const L1Parameters* pParameters) { fpParameters = pParameters; }
 
-  /// Sets used detector subsystems
-  /// \note Should be called before this->Init()
-  /// \param  detID  Id of detector
-  /// \param  flag   Flag: true - detector is used
-  void SetDetector(L1DetectorID detID, bool flag);
-
-  /// Sets legacy event mode:
-  /// \param flag: true - runs on events base, false - runs on time-slice base
-  void SetLegacyEventMode(bool flag) { fbLegacyEventMode = flag; }
-
-  /// Sets pointer to the vector of reconstructed tracks from reference
+  /// @brief Sets pointer to the vector of reconstructed tracks from reference
   void SetRecoTrackContainer(const L1Vector<CbmL1Track>& vRecoTracks) { fpvRecoTracks = &vRecoTracks; }
 
-  /// Sets pointer to the vector of hits from reference
-  void SetHitContainer(const L1Vector<CbmL1Hit>& vHits) { fpvHits = &vHits; }
-
 private:
-  // ****************************************
-  // **     Local constant expressions     **
-  // ****************************************
-
-  static constexpr int kNdetectors = L1Constants::size::kMaxNdetectors;
-
-  // *******************************
-  // **     Utility functions     **
-  // *******************************
-
+  /// @brief Calculates global index of MC point
+  /// @param  iPointLocal  Local index of MC point
+  /// @param  detID        Detector ID
+  ///
   /// Calculates global index of MC point as a sum of a given local index and total provided number of points in previous
   /// detector subsystem
-  /// \param  iPointLocal   Local index of MC point
-  /// \param  detID         Detector ID
   int CalcGlobMCPointIndex(int iPointLocal, L1DetectorID detID) const
   {
     return iPointLocal + std::accumulate(fvNofPointsOrig.cbegin(), fvNofPointsOrig.cbegin() + int(detID), 0);
   }
 
-  /// Checks class initialization. Throws std::logic_error, if initialization is incomplete at initialization call
+  /// @brief Check class initialization
+  /// @note The function throws std::logic_error, if initialization is incomplete
   void CheckInit() const;
 
-  /// Reads MC tracks from external trees and saves them to MCDataObject
+  /// @brief Reads MC tracks from external trees and saves them to MCDataObject
   void ReadMCTracks();
 
-  /// Reads MC points from external trees and saves them to MCDataObject
+  /// @brief Reads MC points from external trees and saves them to MCDataObject
   void ReadMCPoints();
 
-  /// Reads MC points in particular detector
+  /// @brief Reads MC points in particular detector
   template<L1DetectorID DetID>
   void ReadMCPointsForDetector(CbmMCDataArray* pPoints);
 
-  /// Fills a single detector-specific MC point
-  /// \tparam     DetID      Detector subsystem ID
-  /// \param[in]  iExtId     Index of point in the external points container
-  /// \param[in]  iEvent     EventID of point in the external points container
-  /// \param[in]  iFile      FileID of point int the external points container
-  /// \param[out] intMCPoint Reference to the internal tracking MC point object (ca::tools::MCData)
-  /// \return                Success flag:
-  ///                        - #true:  Point is correct and is to be saved to the MCData object
-  ///                        - #false: Point is incorrect and will be ignored
+  /// @brief Fills a single detector-specific MC point
+  /// @tparam     DetID      Detector subsystem ID
+  /// @param[in]  iExtId     Index of point in the external points container
+  /// @param[in]  iEvent     EventID of point in the external points container
+  /// @param[in]  iFile      FileID of point int the external points container
+  /// @param[out] intMCPoint Reference to the internal tracking MC point object (ca::tools::MCData)
+  /// @return true   Point is correct and is to be saved to the MCData object
+  /// @return false  Point is incorrect and will be ignored
   template<L1DetectorID DetID>
   bool FillMCPoint(int iExtId, int iEvent, int iFile, ca::tools::MCPoint& point);
 
@@ -248,8 +236,8 @@ private:
   CbmMCDataArray* fpTrdPoints  = nullptr;  ///< TRD MC-points input container
   CbmMCDataArray* fpTofPoints  = nullptr;  ///< TOF MC-points input container
 
-  std::array<int, kNdetectors> fvNofPointsOrig = {};  ///< Number of points by detector provided
-  std::array<int, kNdetectors> fvNofPointsUsed = {};  ///< Number of points used in performance
+  std::array<int, L1Constants::size::kMaxNdetectors> fvNofPointsOrig = {};  ///< Number of points by detector provided
+  std::array<int, L1Constants::size::kMaxNdetectors> fvNofPointsUsed = {};  ///< Number of points used in performance
 
   // ------ Matches
   TClonesArray* fpMvdHitMatches  = nullptr;  ///< Array of MVD hit matches
@@ -277,7 +265,7 @@ private:
   // ********************************************
 
   const L1Vector<CbmL1Track>* fpvRecoTracks = nullptr;  ///< Pointer to reconstructed track container
-  const L1Vector<CbmL1Hit>* fpvHits         = nullptr;  ///< Pointer to hits container
+  const L1Vector<CbmL1HitDebugInfo>* fpvHits = nullptr;  ///< Pointer to hits container
 };
 
 // **********************************************
diff --git a/reco/L1/CbmL1.cxx b/reco/L1/CbmL1.cxx
index 2a66c06daf..1e533d2c86 100644
--- a/reco/L1/CbmL1.cxx
+++ b/reco/L1/CbmL1.cxx
@@ -978,7 +978,7 @@ void CbmL1::Reconstruct(CbmEvent* event)
       t.Tpv[i]   = it->Tpv[i];
     }
 
-    for (int i = 0; i < L1TrackPar::kNparCov; i++) {
+    for (int i = 0; i < L1TrackPar::kNparCov; ++i) {
       t.C[i]     = it->CFirst[i];
       t.CLast[i] = it->CLast[i];
       t.Cpv[i]   = it->Cpv[i];
@@ -1009,7 +1009,8 @@ void CbmL1::Reconstruct(CbmEvent* event)
   if (fPerformance) {
     if (fVerbose > 1) { cout << "Performance..." << endl; }
     TrackMatch();
-    fpMCModule->MatchRecoAndMCTracks(fvRecoTracks, fvExternalHits);
+    //fpMCModule->MatchRecoAndMCTracks(fvRecoTracks, fvHitDebugInfo);
+    //fpMCModule->ProcessEvent(event);
     EfficienciesPerformance();
     HistoPerformance();
     TrackFitPerformance();
@@ -1024,9 +1025,12 @@ void CbmL1::Reconstruct(CbmEvent* event)
 // -----   Finish CbmStsFitPerformanceTask task   -----------------------------
 void CbmL1::Finish()
 {
+  //if (fPerformance) { fpMCModule->Finish(); }
+
   TDirectory* curr   = gDirectory;
   TFile* currentFile = gFile;
 
+
   // Open output file and write histograms
   boost::filesystem::path p = (FairRunAna::Instance()->GetUserOutputFileName()).Data();
   std::string dir           = p.parent_path().string();
diff --git a/reco/L1/CbmL1.h b/reco/L1/CbmL1.h
index 5cd6d31c20..2a45101085 100644
--- a/reco/L1/CbmL1.h
+++ b/reco/L1/CbmL1.h
@@ -449,7 +449,7 @@ private:
   L1InitManager fInitManager;      ///< Tracking parameters data manager
   L1IODataManager fIODataManager;  ///< Input-output data manager
 
-  std::unique_ptr<CbmCaMCModule> fpMCModule = nullptr;  ///< MC-module for tracking
+  //std::unique_ptr<CbmCaMCModule> fpMCModule = nullptr;  ///< MC-module for tracking
 
 
 public:
diff --git a/reco/L1/CbmL1Hit.h b/reco/L1/CbmL1Hit.h
index 02007d3c92..3fabd44e62 100644
--- a/reco/L1/CbmL1Hit.h
+++ b/reco/L1/CbmL1Hit.h
@@ -5,8 +5,16 @@
 #ifndef _CbmL1Hit_h_
 #define _CbmL1Hit_h_
 
+#include <iomanip>
+#include <sstream>
+#include <string>
+
 #include "L1Vector.h"
 
+/// TODO: SZh: Complete the rule of five
+/// TODO: SZh: Make variables private
+/// TODO; SZh: Move class to ca::tools (ca::tools::Hit)
+
 ///
 /// Identificator for cbm hits with their detector and index in cbm arrays
 ///
@@ -25,16 +33,82 @@ public:
 ///
 class CbmL1HitDebugInfo {  // TODO: SZh 21.09.2022: Replace instances of this class with L1Hit
 public:
-  int ExtIndex;  ///< index of hit in the external branch
-  int iStation;  ///< index of station in active stations array
-  double x;      ///< x coordinate of position [cm]
-  double y;      ///< y coordinate of position [cm]
-  double time;   ///< hit time [ns]
-  double dx;     ///< x coordinate error [cm]
-  double dy;     ///< y coordinate error [cm]
-  double dt;     ///< time error [ns]
-  double dxy;    ///< covariance between x and y [cm2]
-  int Det;       ///< detector subsystem ID
+  /// Gets detector type
+  /// 0 - MVD
+  /// 1 - STS
+  /// 2 - MuCh
+  /// 3 - TRD
+  /// 4 - TOF
+  // TODO: SZh 02.03.2023: Replace det. ID with L1DetectorID
+  int GetDetectorType() const { return Det; }
+
+  /// Gets distance from z-axis [cm]
+  float GetR() const { return std::sqrt(x * x + y * y); }
+
+  /// Gets index of the hit in the external container
+  int GetExternalHitId() const { return ExtIndex; }
+
+  /// Gets MC point index container
+  const auto& GetMCPointIndexes() const { return mcPointIds; }
+
+  /// Gets index of matched MC point
+  int GetMCPointIndex() const { return mcPointIds.size() ? mcPointIds[0] : -1; }
+
+  /// Gets global index of active tracking station
+  int GetStationId() const { return iStation; }
+
+  /// Gets time measurement [ns]
+  float GetT() const { return time; }
+
+  /// Gets x component of position [cm]
+  float GetX() const { return x; }
+
+  /// Gets y component of position [cm]
+  float GetY() const { return y; }
+
+  /// @brief String representation of the object
+  /// @param verbose  Verbosity level
+  /// @param header   If true, header will be printed
+  std::string ToString(int verbose = 10, bool header = false) const
+  {
+    using std::setfill;
+    using std::setw;
+    std::stringstream msg;
+    if (header) {
+      msg << setw(8) << setfill(' ') << "ext. ID" << ' ';
+      msg << setw(8) << setfill(' ') << "st. ID" << ' ';
+      msg << setw(12) << setfill(' ') << "x [cm]" << ' ';
+      msg << setw(12) << setfill(' ') << "y [cm]" << ' ';
+      msg << setw(12) << setfill(' ') << "time [ns]" << ' ';
+      msg << setw(8) << setfill(' ') << "Det. ID" << ' ';
+    }
+    else {
+      msg << setw(8) << setfill(' ') << ExtIndex << ' ';
+      msg << setw(8) << setfill(' ') << iStation << ' ';
+      msg << setw(12) << setfill(' ') << x << ' ';
+      msg << setw(12) << setfill(' ') << y << ' ';
+      msg << setw(12) << setfill(' ') << time << ' ';
+      msg << setw(8) << setfill(' ') << Det << ' ';
+      if (verbose > 0) {
+        msg << "\n\tMC point indexes: ";
+        for (int iP : mcPointIds) {
+          msg << iP << ' ';
+        }
+      }
+    }
+    return msg.str();
+  }
+
+  int ExtIndex;                                                ///< index of hit in the external branch
+  int iStation;                                                ///< index of station in active stations array
+  double x;                                                    ///< x coordinate of position [cm]
+  double y;                                                    ///< y coordinate of position [cm]
+  double time;                                                 ///< hit time [ns]
+  double dx;                                                   ///< x coordinate error [cm]
+  double dy;                                                   ///< y coordinate error [cm]
+  double dt;                                                   ///< time error [ns]
+  double dxy;                                                  ///< covariance between x and y [cm2]
+  int Det;                                                     ///< detector subsystem ID
   L1Vector<int> mcPointIds {"CbmL1HitDebugInfo::mcPointIds"};  ///< indices of CbmL1MCPoint in L1->fvMCPoints array
 };
 
diff --git a/reco/L1/CbmL1Performance.cxx b/reco/L1/CbmL1Performance.cxx
index 38432f6307..a7b56af548 100644
--- a/reco/L1/CbmL1Performance.cxx
+++ b/reco/L1/CbmL1Performance.cxx
@@ -265,14 +265,16 @@ struct TL1PerfEfficiencies : public TL1Efficiencies {
   {
     L1_assert(nEvents != 0);
     int NCounters = mc.GetNcounters();
-    std::vector<std::string> rowNames(20);
+    std::vector<std::string> rowNames(NCounters + 2);
     for (int iC = 0; iC < NCounters; ++iC) {
       rowNames[iC] = std::string(names[iC].Data());
     }
+    rowNames[NCounters]               = "Ghost prob.";
+    rowNames[NCounters + 1]           = "N ghosts";
     std::vector<std::string> colNames = {"Eff.",     "Killed", "Length",    "Fakes",    "Clones",
                                          "All Reco", "All MC", "MCl(hits)", "MCl(MCps)"};
 
-    CbmQaTable* aTable = new CbmQaTable(nameOfTable.c_str(), "Track Efficiency", 20, 9);
+    CbmQaTable* aTable = new CbmQaTable(nameOfTable.c_str(), "Track Efficiency", NCounters + 2, 9);
     aTable->SetNamesOfRows(rowNames);
     aTable->SetNamesOfCols(colNames);
     for (int iC = 0; iC < NCounters; iC++) {
@@ -286,6 +288,9 @@ struct TL1PerfEfficiencies : public TL1Efficiencies {
       aTable->SetCell(iC, 7, mc_length_hits.counters[iC] / double(mc.counters[iC]));
       aTable->SetCell(iC, 8, mc_length.counters[iC] / double(mc.counters[iC]));
     }
+    aTable->SetCell(NCounters, 0, ratio_ghosts);
+    aTable->SetCell(NCounters + 1, 0, ghosts);
+
     if (ifPrintTableToLog) {
       cout << *aTable;  // print a table to log
     }
@@ -293,8 +298,6 @@ struct TL1PerfEfficiencies : public TL1Efficiencies {
     else {
       delete aTable;
     }
-
-    cout << "Ghost     probability  : " << ratio_ghosts << "  | " << ghosts << endl;
   };
 
   TL1TracksCatCounters<double> ratio_killed;
@@ -386,7 +389,7 @@ void CbmL1::EfficienciesPerformance()
     int mc_length = mtra.NMCStations();
     if (reco) {
       for (unsigned int irt = 0; irt < rTracks.size(); irt++) {
-        ratio_length += static_cast<double>(rTracks[irt]->GetNOfHits()) * rTracks[irt]->GetMaxPurity() / mc_length_hits;
+        ratio_length += static_cast<double>(rTracks[irt]->GetNofHits()) * rTracks[irt]->GetMaxPurity() / mc_length_hits;
         ratio_fakes += 1 - rTracks[irt]->GetMaxPurity();
       }
     }
diff --git a/reco/L1/CbmL1ReadEvent.cxx b/reco/L1/CbmL1ReadEvent.cxx
index 5b7b083f9c..4925155135 100644
--- a/reco/L1/CbmL1ReadEvent.cxx
+++ b/reco/L1/CbmL1ReadEvent.cxx
@@ -1153,7 +1153,7 @@ void CbmL1::ReadEvent(CbmEvent* event)
   fIODataManager.ResetInputData();
   fIODataManager.ReserveNhits(nHits);
   fIODataManager.SetNhitKeys(NStrips);
-  if (fPerformance) { fpMCModule->GetMCData()->ReserveNofHits(nHits); }
+  //if (fPerformance) { fpMCModule->GetMCData()->ReserveNofHits(nHits); }
 
   // ----- Fill
   for (int iHit = 0; iHit < nHits; ++iHit) {
@@ -1196,10 +1196,10 @@ void CbmL1::ReadEvent(CbmEvent* event)
 
     fvHitDebugInfo.push_back(s);
     fvHitPointIndexes.push_back(th.iMC);
-    fpMCModule->GetMCData()->RegisterPointIndexForHit(iHit, th.iMC);
+    //if (fPerformance) { fpMCModule->GetMCData()->RegisterPointIndexForHit(iHit, th.iMC); }
   }
-  if (fPerformance) { HitMatch(); }                                       /// OLD
-  if (fPerformance) { fpMCModule->MatchPointsWithHits(fvExternalHits); }  /// NEW
+  if (fPerformance) { HitMatch(); }  /// OLD
+  //if (fPerformance) { fpMCModule->MatchPointsWithHits(fvHitDebugInfo); }  /// NEW
 
   if (fVerbose >= 2) cout << "ReadEvent: mvd and sts are saved." << endl;
 
@@ -1468,7 +1468,7 @@ bool CbmL1::ReadMCPoint(CbmL1MCPoint* MC, int iPoint, int file, int event, int i
 void CbmL1::HitMatch()
 {
   // Clear contents
-  for (auto& hit : fvExternalHits) {
+  for (auto& hit : fvHitDebugInfo) {
     hit.mcPointIds.clear();
   }
 
@@ -1477,7 +1477,7 @@ void CbmL1::HitMatch()
   }
 
   // Fill new contents
-  const int NHits = fvExternalHits.size();
+  const int NHits = fvHitDebugInfo.size();
   for (int iH = 0; iH < NHits; iH++) {
     CbmL1HitDebugInfo& hit = fvHitDebugInfo[iH];
     int iP                 = fvHitPointIndexes[iH];
diff --git a/reco/L1/CbmL1Track.h b/reco/L1/CbmL1Track.h
index b7434e01bc..6ea035c7ed 100644
--- a/reco/L1/CbmL1Track.h
+++ b/reco/L1/CbmL1Track.h
@@ -1,6 +1,6 @@
-/* Copyright (C) 2006-2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2006-2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Ivan Kisel,  Sergey Gorbunov, Denis Bertini [committer], Igor Kulakov, Maksym Zyzak */
+   Authors: Ivan Kisel,  Sergey Gorbunov, Denis Bertini [committer], Igor Kulakov, Maksym Zyzak, Sergei Zharko */
 
 /*
  *====================================================================
@@ -26,6 +26,8 @@
 #include "CbmL1MCTrack.h"
 #include "CbmL1TrackPar.h"
 
+#include "TMath.h"
+
 #include <map>
 #include <vector>
 using std::map;
@@ -37,8 +39,7 @@ class CbmL1Track : public CbmL1TrackPar {
 public:
   CbmL1Track() : Hits(), nStations(0), index(0), fTrackTime(0.), hitMap(), mcTracks(), maxPurity(-1) {}
 
-  int GetNOfHits() { return Hits.size(); }
-
+  /// Adds pointer to MC track (TODO: remove this and related methods)
   void AddMCTrack(CbmL1MCTrack* mcTr) { mcTracks.push_back(mcTr); }
 
   /// Adds an index of MC track index
@@ -51,17 +52,69 @@ public:
     fvMcTrackIndexes.clear();
   }
 
-  vector<CbmL1MCTrack*>& GetMCTracks() { return mcTracks; }
-  CbmL1MCTrack* GetMCTrack() { return mcTracks[0]; }
-  int GetNMCTracks() { return mcTracks.size(); }
-  bool IsGhost() { return (maxPurity < CbmL1Constants::MinPurity); }
+  /// Gets Chi-square of track fit model
+  double GetChiSq() const { return chi2; }
+
+  /// Gets a reference to hit indexes array
+  const vector<int>& GetHitIndexes() const { return Hits; }
+
+  bool IsGhost() const { return (maxPurity < CbmL1Constants::MinPurity); }
+
+  /// Gets NDF of track fit model
+  int GetNDF() const { return NDF; }
 
+  /// Gets number of MC tracks
+  // TODO: Remove this method
+  int GetNMCTracks() const { return mcTracks.size(); }
 
   /// Gets a reference to MC track indexes
   const auto& GetMCTrackIndexes() const { return fvMcTrackIndexes; }
 
   /// Gets max purity
-  double GetMaxPurity() { return maxPurity; }
+  double GetMaxPurity() const { return maxPurity; }
+
+  /// Gets container of pointers to MC tracks
+  // TODO: this function is to be replaced with GetMCTrackIndexes()
+  vector<CbmL1MCTrack*>& GetMCTracks() { return mcTracks; }
+
+  /// Gets pointer of the associated MC track
+  CbmL1MCTrack* GetMCTrack() { return mcTracks[0]; }
+
+  /// Gets number of hits of the track
+  int GetNofHits() const { return Hits.size(); }
+
+  /// Gets number of associated MC tracks
+  int GetNofMCTracks() const { return fvMcTrackIndexes.size(); }
+
+  /// Gets number of stations
+  int GetNofStations() const { return nStations; }
+
+  /// Gets absolute value of momentum divided by the charge (absolute value) of particle [GeV/ec]
+  double GetP() const { return fabs(T[4]) > 1.e-10 ? 1. / fabs(T[4]) : 1.e-10; }
+
+  /// Gets transverse momentum
+  double GetPt() const { return sqrt(GetP() * GetP() - GetPz() * GetPz()); }
+
+  /// Gets azimuthal angle of the track
+  double GetPhi() const { return TMath::ATan2(-GetTy(), -GetTx()); }
+
+  /// Gets probability of track fit model
+  double GetProb() const { return TMath::Prob(chi2, NDF); }
+
+  /// Gets x-component of momentum divided by the charge (absolute value) of particle [GeV/ec]
+  double GetPx() const { return GetPz() * GetTx(); }
+
+  /// Gets y-component of momentum divided by the charge (absolute value) of particle [GeV/ec]
+  double GetPy() const { return GetPz() * GetTy(); }
+
+  /// Gets z-component of momentum divided by the charge (absolute value) of particle [GeV/ec]
+  double GetPz() const { return std::sqrt(GetP() * GetP() / (GetTx() * GetTx() + GetTy() * GetTy() + 1)); }
+
+  /// Gets track slope along x-axis
+  double GetTx() const { return T[2]; }
+
+  /// Gets track slope along y-axis
+  double GetTy() const { return T[3]; }
 
   /// Sets max purity
   /// NOTE: max purity is calculated as a ratio of max number of hits left by an actual track and the
@@ -70,21 +123,26 @@ public:
 
 
   static bool compareChi2(const CbmL1Track& a, const CbmL1Track& b) { return (a.chi2 < b.chi2); }
+
   static bool comparePChi2(const CbmL1Track* a, const CbmL1Track* b) { return (a->chi2 < b->chi2); }
 
 
-  double Tpv[L1TrackPar::kNparTr], Cpv[L1TrackPar::kNparCov];
+  double Tpv[L1TrackPar::kNparTr];   ///< Track parameters at primary vertex
+  double Cpv[L1TrackPar::kNparCov];  ///< Track covariance matrix at primary vertex
 
+  double TLast[L1TrackPar::kNparTr];   ///< Track parameters in the end of the track
+  double CLast[L1TrackPar::kNparCov];  ///< Track covariance matrix at the end of the track
 
-  double TLast[L1TrackPar::kNparTr], CLast[L1TrackPar::kNparCov];
-  vector<int> Hits;
-  int nStations;
-  int index;
+  vector<int> Hits;  ///< Indexes of hits of this track
+  int nStations;     ///< Number of stations with hits of this track
+  int index;         ///< Index of this track (TODO: it seems to be not initialized)
 
-  double fTrackTime;
+  double fTrackTime;  ///< Time of the track [ns] ???
 
   map<int, int> hitMap;  // N hits (second) from each mcTrack (first is a MC track ID) belong to current recoTrack
   // FIXME: SZh 14.12.2022:  map => unordered_map
+
+
 private:
   // next members filled and used in Performance
   vector<CbmL1MCTrack*> mcTracks;  // array of assosiated recoTracks. Should be only one.
@@ -92,7 +150,7 @@ private:
   L1Vector<int> fvMcTrackIndexes = {"CbmL1Track::fvMcTrackIndexes"};  // global indexes of MC tracks
   // NOTE: mcTracks should be replaced with fvMcTrackIndexes
 
-  double maxPurity;                // maximum persent of hits, which belong to one mcTrack.
+  double maxPurity;  ///< Maximum persent of hits, which belong to one mcTrack.
 };
 
 #endif
diff --git a/reco/L1/CbmL1TrackPar.h b/reco/L1/CbmL1TrackPar.h
index 57fe79607b..421d53e03c 100644
--- a/reco/L1/CbmL1TrackPar.h
+++ b/reco/L1/CbmL1TrackPar.h
@@ -16,10 +16,12 @@ public:
   double* GetCovMatrix() { return C; }
   double& GetRefChi2() { return chi2; }
   int& GetRefNDF() { return NDF; }
-  double GetMass() { return mass; }
-  bool IsElectron() { return is_electron; }
+  double GetMass() const { return mass; }
+  bool IsElectron() const { return is_electron; }
 
-  double T[L1TrackPar::kNparTr], C[L1TrackPar::kNparCov], chi2;
+  double T[L1TrackPar::kNparTr];
+  double C[L1TrackPar::kNparCov];
+  double chi2;
   int NDF;
   double mass;  // mass hypothesis
   bool is_electron;
diff --git a/reco/L1/catools/CaToolsMCData.cxx b/reco/L1/catools/CaToolsMCData.cxx
index 64573973b6..c01fe96611 100644
--- a/reco/L1/catools/CaToolsMCData.cxx
+++ b/reco/L1/catools/CaToolsMCData.cxx
@@ -95,7 +95,7 @@ void MCData::Clear()
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void MCData::InitTrackInfo(const L1Vector<CbmL1Hit>& vHits)
+void MCData::InitTrackInfo(const L1Vector<CbmL1HitDebugInfo>& vHits)
 {
   for (auto& aTrk : fvTracks) {
     // Assign hits to tracks
diff --git a/reco/L1/catools/CaToolsMCData.h b/reco/L1/catools/CaToolsMCData.h
index 1fab4a3125..48733204dc 100644
--- a/reco/L1/catools/CaToolsMCData.h
+++ b/reco/L1/catools/CaToolsMCData.h
@@ -128,7 +128,7 @@ namespace ca::tools
     /// Initialize tracks: defines indexes of hits and points related to the track, calculates max number of points and
     /// hits on a station, number of consecutive stations containing a hit or point and number of stations and points
     /// with hits.
-    void InitTrackInfo(const L1Vector<CbmL1Hit>& vHits);
+    void InitTrackInfo(const L1Vector<CbmL1HitDebugInfo>& vHits);
 
     /// Registers index of point for a given index of hit
     /// \param  iHit    Index of hit
@@ -146,7 +146,7 @@ namespace ca::tools
     void ReserveNofPoints(int nPoints) { fvPoints.reserve(nPoints); }
 
     /// Reserves total number of used hits in the event
-    void ReserveNofHits(int nHits) { fvPointIndexOfHit.reset(nHits); }
+    void ReserveNofHits(int nHits) { fvPointIndexOfHit.reserve(nHits); }
 
     /// Prints an example of tracks and points
     /// \param verbose  Verbose level:
diff --git a/reco/L1/catools/CaToolsMCTrack.cxx b/reco/L1/catools/CaToolsMCTrack.cxx
index 1a07b81dee..0163c48c92 100644
--- a/reco/L1/catools/CaToolsMCTrack.cxx
+++ b/reco/L1/catools/CaToolsMCTrack.cxx
@@ -29,7 +29,7 @@ void MCTrack::Clear()
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void MCTrack::InitHitsInfo(const L1Vector<CbmL1Hit>& vHits)
+void MCTrack::InitHitsInfo(const L1Vector<CbmL1HitDebugInfo>& vHits)
 {
   // NOTE: vHits must be sorted over stations!
   fMaxNofHitsOnStation    = 0;
diff --git a/reco/L1/catools/CaToolsMCTrack.h b/reco/L1/catools/CaToolsMCTrack.h
index da2a1bcef8..602883d474 100644
--- a/reco/L1/catools/CaToolsMCTrack.h
+++ b/reco/L1/catools/CaToolsMCTrack.h
@@ -10,13 +10,16 @@
 #ifndef CaToolsMCTrack_h
 #define CaToolsMCTrack_h 1
 
+#include "TMath.h"
+
 #include <functional>
 
 #include "CaToolsLinkKey.h"
+#include "L1Constants.h"
 #include "L1Undef.h"
 #include "L1Vector.h"
 
-class CbmL1Hit;
+class CbmL1HitDebugInfo;
 
 namespace ca::tools
 {
@@ -142,7 +145,7 @@ namespace ca::tools
     int GetPdgCode() const { return fPdgCode; }
 
     /// Gets azimuthal angle [rad]
-    double GetPhi() const;
+    double GetPhi() const { return TMath::ATan2(-fMom[1], -fMom[0]); }
 
     /// Gets a reference to associated point indexes
     const auto& GetPointIndexes() const { return fvPointIndexes; }
@@ -189,6 +192,12 @@ namespace ca::tools
     /// Gets total number of stations with MC points
     int GetTotNofStationsWithPoint() const { return fTotNofStationsWithPoint; }
 
+    /// Gets track slope along x-axis
+    double GetTx() const { return fMom[0] / fMom[2]; }
+
+    /// Gets track slope along y-axis
+    double GetTy() const { return fMom[1] / fMom[2]; }
+
     /// Gets a reference to vector of reconstructed track indexes, not associated with this MC track but containing some
     /// hits, produced by this MC track
     const auto& GetTouchTrackIndexes() const { return fvTouchTrackIndexes; }
@@ -199,7 +208,7 @@ namespace ca::tools
     ///   #2) Maximal number of hits within one station
     ///   #3) Number of consecutive stations with a hit in MC track
     /// \param  vHits  Vector of hits for a given TS
-    void InitHitsInfo(const L1Vector<CbmL1Hit>& vHits);
+    void InitHitsInfo(const L1Vector<CbmL1HitDebugInfo>& vHits);
 
     /// \brief Initializes information about MC track points arrangement within stations
     /// Defines:
@@ -345,21 +354,4 @@ namespace ca::tools
 
 }  // namespace ca::tools
 
-
-// ************************************
-// **     Inline implementations     **
-// ************************************
-
-inline double ca::tools::MCTrack::GetPhi() const
-{
-  // NOTE: Implementation was taken from ROOT TMath::Atan2
-  constexpr double kRootPi = 3.14159265358979323846;  // Value of Pi, used in ROOT TMath
-  if (fMom[0] != 0) { return std::atan2(-fMom[1], -fMom[0]); }
-  if (fMom[1] == 0) { return 0; }
-  if (fMom[1] < 0) { return kRootPi / 2; }
-  else {
-    return -kRootPi / 2;
-  }
-}
-
 #endif  // CaToolsMCTrack_h
diff --git a/reco/L1/catools/CaToolsQa.cxx b/reco/L1/catools/CaToolsQa.cxx
index 1f693a8995..9bd4715326 100644
--- a/reco/L1/catools/CaToolsQa.cxx
+++ b/reco/L1/catools/CaToolsQa.cxx
@@ -1,6 +1,6 @@
-/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2022-2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Sergey Gorbunov, Sergei Zharko [committer] */
+   Authors: Sergei Zharko [committer] */
 
 /// \file   CaToolsQa.h
 /// \brief  Tracking performance class (implementation)
@@ -9,18 +9,220 @@
 
 #include "CaToolsQa.h"
 
+#include "CbmL1Hit.h"    // TODO: SZh: move CbmL1Hit to ca::tools::Hit
+#include "CbmL1Track.h"  // TODO: SZh: move CbmL1Track to ca::tools::RecoTrack
+
+#include "FairRunAna.h"
+
 #include <utility>  // for std::move
 
+#include "CaToolsMCData.h"
+#include "L1Parameters.h"
+
 using namespace ca::tools;
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void Qa::InitHistograms() {}
+void Qa::InitHistograms()
+{
+  LOG_IF(fatal, !fpOutFile) << "CA QA: Output file was not defined";
+
+  TDirectory* currentDir = gDirectory;
+  gDirectory             = fpHistoDir;
+
+  // ----- Initialize histograms and profiles --------------------------------------------------------------------------
+  // Distributions of reconstructed tracks by different quantities
+  fph_reco_p        = MakeHisto<TH1F>("ca_reco_p", "", kNbinsRecoP, 0., kAxMaxRecoP);
+  fph_reco_pt       = MakeHisto<TH1F>("ca_reco_pt", "", kNbinsRecoPt, 0., kAxMaxRecoPt);
+  fph_reco_phi      = MakeHisto<TH1F>("ca_reco_phi", "", kNbinsRecoPhi, -kAxMaxRecoPhi, kAxMaxRecoPhi);
+  fph_reco_nhits    = MakeHisto<TH1F>("ca_reco_nhits", "", kNbinsNofHits, -0.5, kNbinsNofHits - 0.5);
+  fph_reco_fsta     = MakeHisto<TH1F>("ca_reco_fsta", "", kNbinsNofSta, -0.5, kNbinsNofSta - 0.5);
+  fph_reco_purity   = MakeHisto<TH1F>("ca_reco_purity", "", kNbinsPurity, -0.5, 100.5);
+  fph_reco_chi2_ndf = MakeHisto<TH1F>("ca_reco_chi2_ndf", "", kNbinsChi2NDF, -0.5, kAxMaxChi2NDF);
+  fph_reco_prob     = MakeHisto<TH1F>("ca_reco_prob", "", kNbinsProb, 0., 1.01);
+  fph_rest_chi2_ndf = MakeHisto<TH1F>("ca_rest_chi2_ndf", "", kNbinsChi2NDF, -0.5, kAxMaxChi2NDF);
+  fph_rest_prob     = MakeHisto<TH1F>("ca_rest_prob", "", kNbinsProb, 0., 1.01);
+
+  fph_reco_p->SetTitle("Momentum of reconstructed track;(p/q)_{reco} [GeV/ec]");
+  fph_reco_pt->SetTitle("Transverse momentum of reconstructed track;(p_{T}/q)_{reco} [GeV/ec]");
+  fph_reco_phi->SetTitle("Azimuthal angle of reconstructed track;#phi_{reco} [rad]");
+  fph_reco_nhits->SetTitle("Number of hits in a reconstructed track;N_{hits}");
+  fph_reco_fsta->SetTitle("First station of reconstructed track;ID_{f.st.}");
+  fph_reco_purity->SetTitle("Purity of reconstructed track;purity [%]");
+  fph_reco_chi2_ndf->SetTitle("Fit #chi^{2}/NDF of reconstructed track;(#chi^{2}/NDF)_{reco}");
+  fph_reco_chi2_ndf->SetTitle("Fit #chi^{2}/NDF of the rest track;(#chi^{2}/NDF)_{reco}");
+  fph_reco_prob->SetTitle("Fit probability of reconstructed track;Prob_{reco}");
+  fph_rest_prob->SetTitle("Fit probability of the rest tracks;Prob_{reco}");
+
+
+  // Distributions of reconstructed tracks by different quantities
+  fph_ghost_p        = MakeHisto<TH1F>("ca_ghost_p", "", kNbinsRecoP, 0., kAxMaxRecoP);
+  fph_ghost_pt       = MakeHisto<TH1F>("ca_ghost_pt", "", kNbinsRecoPt, 0., kAxMaxRecoPt);
+  fph_ghost_phi      = MakeHisto<TH1F>("ca_ghost_phi", "", kNbinsRecoPhi, -kAxMaxRecoPhi, kAxMaxRecoPhi);
+  fph_ghost_nhits    = MakeHisto<TH1F>("ca_ghost_nhits", "", kNbinsNofHits, -0.5, kNbinsNofHits - 0.5);
+  fph_ghost_fsta     = MakeHisto<TH1F>("ca_ghost_fsta", "", kNbinsNofSta, -0.5, kNbinsNofSta - 0.5);
+  fph_ghost_purity   = MakeHisto<TH1F>("ca_ghost_purity", "", kNbinsPurity, -0.5, 100.5);
+  fph_ghost_chi2_ndf = MakeHisto<TH1F>("ca_ghost_chi2_ndf", "", kNbinsChi2NDF, 0., kAxMaxChi2NDF);
+  fph_ghost_prob     = MakeHisto<TH1F>("ca_ghost_prob", "", kNbinsProb, 0., 1.01);
+  fph_ghost_tx       = MakeHisto<TH1F>("ca_ghost_tx", "", kNbinsTx, -2., 2.);
+  fph_ghost_ty       = MakeHisto<TH1F>("ca_ghost_ty", "", kNbinsTy, -2., 2.);
+  fph_ghost_fhitR    = MakeHisto<TH1F>("ca_ghost_fhitR", "", kNbinsHitDist, 0., 150.);
+  fph_ghost_nhits_vs_p =
+    MakeHisto<TH2F>("ca_ghost_nhits_vs_p", "", kNbinsRecoP, 0., kAxMaxRecoP, kNbinsNofHits, -0.5, kNbinsNofHits - 0.5);
+  fph_ghost_fsta_vs_p =
+    MakeHisto<TH2F>("ca_ghost_fsta_vs_p", "", kNbinsRecoP, 0., kAxMaxRecoP, kNbinsNofSta, -0.5, kNbinsNofSta - 0.5);
+  fph_ghost_lsta_vs_fsta = MakeHisto<TH2F>("ca_ghost_lsta_vs_fsta", "", kNbinsNofSta, -0.5, kNbinsNofSta - 0.5,
+                                           kNbinsNofSta, -0.5, kNbinsNofSta - 0.5);
+
+  fph_ghost_p->SetTitle("Momentum of ghost track;(p/q)_{reco} [GeV/ec]");
+  fph_ghost_pt->SetTitle("Transverse momentum of ghost track;(p_{T}/q)_{reco} [GeV/ec]");
+  fph_ghost_phi->SetTitle("Azimuthal angle of ghost track;#phi_{reco} [rad]");
+  fph_ghost_nhits->SetTitle("Number of hits in a ghost track;N_{hits}");
+  fph_ghost_fsta->SetTitle("First station of ghost track;ID_{f.st.}");
+  fph_ghost_purity->SetTitle("Purity of ghost track;purity [%]");
+  fph_ghost_chi2_ndf->SetTitle("Fit #chi^{2}/NDF of ghost track;(#chi^{2}/NDF)_{reco}");
+  fph_ghost_prob->SetTitle("Fit probability of ghost track;Prob_{reco}");
+  fph_ghost_tx->SetTitle("Slope along x-axis of ghost tracks;t_{x}");
+  fph_ghost_ty->SetTitle("Slope along y-axis of ghost tracks;t_{y}");
+  fph_ghost_fhitR->SetTitle("Distance of the first hit from z-axis;R [cm]");
+  fph_ghost_nhits_vs_p->SetTitle("Number of hits vs. momentum for ghost track;(p/q)_{reco} [GeV/ec];N_{hits}");
+  fph_ghost_fsta_vs_p->SetTitle("First station vs. momentum for ghost track;(p/q)_{reco} [GeV/ec];ID_{f.st.}");
+  fph_ghost_lsta_vs_fsta->SetTitle("Last station vs. first station for ghost track;ID_{first st.};ID_{last st.}");
+
+  // ----- Reset event counter
+  fNofEvents = 0;
+
+  LOG(info) << "CA QA: Initialized histograms: (directory \033[1;31m" << gDirectory->GetName() << "\033[0m)";
+  for (auto& entry : fmpHistoList) {
+    LOG(info) << "\t- " << entry.first;
+  }
+
+  gDirectory = currentDir;
+}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void Qa::FillHistograms() {}
+void Qa::FillHistograms()
+{
+  assert(fpParameters);
+  assert(fpMCData);
+  assert(fpvRecoTracks);
+  assert(fpvHits);
+
+  // Update event counter
+  fNofEvents++;
+
+
+  // ************************************************************************
+  // ** Fill distributions for reconstructed tracks (including ghost ones) **
+  // ************************************************************************
+
+  for (const auto& recoTrk : *fpvRecoTracks) {
+    // Reject tracks, which do not contain hits
+    if (recoTrk.GetNofHits() < 1) { continue; }
+
+    const auto& hitFst = (*fpvHits)[recoTrk.GetHitIndexes()[0]];                         // first hit of track
+    const auto& hitSnd = (*fpvHits)[recoTrk.GetHitIndexes()[1]];                         // second hit of track
+    const auto& hitLst = (*fpvHits)[recoTrk.GetHitIndexes()[recoTrk.GetNofHits() - 1]];  // last hit of track
+
+    fph_reco_p->Fill(recoTrk.GetP());
+    fph_reco_pt->Fill(recoTrk.GetPt());
+    fph_reco_phi->Fill(recoTrk.GetPhi());
+    fph_reco_nhits->Fill(recoTrk.GetNofHits());
+    fph_reco_fsta->Fill(hitFst.GetStationId());
+    fph_reco_purity->Fill(100 * recoTrk.GetMaxPurity());
+
+    if (recoTrk.GetNDF() > 0) {
+      if (recoTrk.IsGhost()) {
+        fph_ghost_chi2_ndf->Fill(recoTrk.GetChiSq() / recoTrk.GetNDF());
+        fph_ghost_prob->Fill(recoTrk.GetProb());
+      }
+      else {
+        int iTmc          = recoTrk.GetMCTrackIndexes()[0];  // Index of first MC-track
+        const auto& mcTrk = fpMCData->GetTrack(iTmc);
+        // NOTE: reconstructed tracks usually have reference to only one MC-track
+        if (mcTrk.IsReconstructable()) {
+          fph_reco_chi2_ndf->Fill(recoTrk.GetChiSq() / recoTrk.GetNDF());
+          fph_reco_prob->Fill(recoTrk.GetProb());
+        }
+        else {
+          fph_rest_chi2_ndf->Fill(recoTrk.GetChiSq() / recoTrk.GetNDF());
+          fph_rest_prob->Fill(recoTrk.GetProb());
+        }
+      }
+    }  // recoTrk.GetNDF() > 0: end
+
+    if (recoTrk.IsGhost()) {
+      fph_ghost_purity->Fill(100 * recoTrk.GetMaxPurity());
+      fph_ghost_p->Fill(recoTrk.GetP());
+      fph_ghost_pt->Fill(recoTrk.GetPt());
+      fph_ghost_nhits->Fill(recoTrk.GetNofHits());
+      fph_ghost_fsta->Fill(hitFst.GetStationId());
+
+      // z-positions of the first and second hit stations
+      double z0 = fpParameters->GetStation(hitFst.GetStationId()).fZ[0];
+      double z1 = fpParameters->GetStation(hitSnd.GetStationId()).fZ[0];
+
+      if (fabs(z1 - z0) > 1.e-4) {  // test for z1 != z2
+        fph_ghost_tx->Fill((hitSnd.GetX() - hitFst.GetX()) / (z1 - z0));
+        fph_ghost_ty->Fill((hitSnd.GetY() - hitFst.GetY()) / (z1 - z0));
+      }
+
+      fph_ghost_nhits_vs_p->Fill(recoTrk.GetP(), recoTrk.GetNofHits());
+      fph_ghost_fsta_vs_p->Fill(recoTrk.GetP(), hitFst.GetStationId());
+      if (hitFst.GetStationId() <= hitLst.GetStationId()) {
+        fph_ghost_lsta_vs_fsta->Fill(hitFst.GetStationId(), hitLst.GetStationId());
+      }
+    }  // recoTrk.IsGhost(): end
+  }    // loop over recoTrk: end
+
+
+  // *************************************
+  // ** Fill distributions of MC-tracks **
+  // *************************************
+
+  //for (const auto& mcTrk: fpMCData->GetTrackContainer()) {
+  //
+  //}
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Qa::SaveHistograms()
+{
+  fpOutFile->cd();
+  RecursiveWrite(fpHistoDir);
+  LOG(info) << "CA QA: Histograms have been written to \033[1;31m" << fpOutFile->GetName() << "\033[0m";
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Qa::RecursiveWrite(TObject* pObj)
+{
+  if (!pObj->IsFolder()) { pObj->Write(); }
+  else {
+    TDirectory* pObjDir = dynamic_cast<TDirectory*>(pObj);
+    LOG_IF(fatal, !pObjDir) << "CA QA: Incorrect dynamic cast from the input pointer to RecursiveWrite function";
+
+    TDirectory* pCurDir = gDirectory;
+    TDirectory* pSubDir = pCurDir->mkdir(pObjDir->GetName());
+    pSubDir->cd();
+    TList* pObjList = pObjDir->GetList();
+    TIter it(pObjList);
+    while (TObject* pNext = it()) {
+      RecursiveWrite(pNext);
+    }
+    pCurDir->cd();
+  }
+}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void Qa::SaveHistograms() {}
+void Qa::SetOutputFilename(const char* filename)
+{
+  fpOutFile = std::make_unique<TFile>(filename, "RECREATE");
+  if (!fpOutFile.get() || fpOutFile->IsZombie()) {
+    LOG(fatal) << "CA QA: ROOT histogram output file \"" << filename << "\" cannot be created";
+  }
+  fpHistoDir = gROOT->mkdir("histograms");
+}
diff --git a/reco/L1/catools/CaToolsQa.h b/reco/L1/catools/CaToolsQa.h
index 87a58b31b9..e6c05fed33 100644
--- a/reco/L1/catools/CaToolsQa.h
+++ b/reco/L1/catools/CaToolsQa.h
@@ -1,6 +1,6 @@
-/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2022-2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Sergey Gorbunov, Sergei Zharko [committer] */
+   Authors: Sergei Zharko [committer] */
 
 /// \file   CaToolsQa.h
 /// \brief  Tracking performance class (header)
@@ -10,14 +10,22 @@
 #ifndef CaToolsQa_h
 #define CaToolsQa_h 1
 
+#include "Logger.h"
+
+#include "TFile.h"
+#include "TH1F.h"
+#include "TH2F.h"
+#include "TProfile.h"
+#include "TROOT.h"
+
+#include <string_view>
+
 #include "L1Vector.h"
 
 class L1Parameters;
 class TDirectory;
-class TH1;
-class TH1F;
-class TH2F;
-class TProfile;
+class CbmL1HitDebugInfo;
+class CbmL1Track;
 
 namespace ca::tools
 {
@@ -54,7 +62,7 @@ namespace ca::tools
     void InitHistograms();
 
     /// Fills histograms
-    /// \note should be called in the end of the run
+    /// \note should be called in the end of the event
     void FillHistograms();
 
     /// Saves histograms to the file
@@ -65,28 +73,176 @@ namespace ca::tools
     // **     Accessors     **
     // ***********************
 
+    /// Sets pointer to hit container from reference
+    void SetHitContainer(const L1Vector<CbmL1HitDebugInfo>& vHits) { fpvHits = &vHits; }
+
+    /// Sets pointer to hit container from pointer
+    void SetHitContainer(const L1Vector<CbmL1HitDebugInfo>* pvHits)
+    {
+      assert(pvHits);
+      fpvHits = pvHits;
+    }
+
+
     /// Sets pointer to MC data object
     void SetMCData(const MCData* pMCData) { fpMCData = pMCData; }
 
+    /// Sets output filename
+    void SetOutputFilename(const char* filename);
+
     /// Sets pointer to tracking parameters object
     void SetParameters(const L1Parameters* pParameters) { fpParameters = pParameters; }
 
+    /// Sets pointer to the vector of reconstructed tracks from reference
+    void SetRecoTrackContainer(const L1Vector<CbmL1Track>& vRecoTracks) { fpvRecoTracks = &vRecoTracks; }
+
+    /// Sets pointer to the vector of reconstructed tracks from pointer
+    void SetRecoTrackContainer(const L1Vector<CbmL1Track>* pvRecoTracks)
+    {
+      assert(pvRecoTracks);
+      fpvRecoTracks = pvRecoTracks;
+    }
+
 
   private:
-    const L1Parameters* fpParameters = nullptr;  ///< Instance of the tracking core class parameters
-    const MCData* fpMCData           = nullptr;  ///< Container for MC information on the event
+    /// Function, which creates a histogram and initializes it
+    /// \tparam  T     Type of the histogram (\note T should not be a pointer)
+    /// \param   name  Name of histogram
+    /// \param   args  The rest of the arguments, which will be passed to the histogram constructor
+    template<typename T, typename... Args>
+    T* MakeHisto(const char* name, Args... args);
+
+    /// Writes recursively all registered objects inside a current directory
+    /// \param  pObj  Pointer to object to write
+    void RecursiveWrite(TObject* pObj);
+
+    const L1Parameters* fpParameters           = nullptr;  ///< Instance of the tracking core class parameters
+    const MCData* fpMCData                     = nullptr;  ///< Container for MC information on the event
+    const L1Vector<CbmL1Track>* fpvRecoTracks  = nullptr;  ///< Pointer to external reconstructed track container
+    const L1Vector<CbmL1HitDebugInfo>* fpvHits = nullptr;  ///< Pointer to external hit container
 
     // ***********************
     // **    Histograms     **
     // ***********************
 
-    TDirectory* fpHistoDirectory = nullptr;  ///< directory object to store performance histograms
-
-    TH1F* fphRegMcMom = nullptr;
-    TH1F* fphAccMCMom = nullptr;
-
-    L1Vector<TH1*> fvpHistoList = {"ca::tools::Qa::fvpHistoList"};  ///< List of histograms for iteration over them
+    TDirectory* fpHistoDir = nullptr;  ///< directory to store QA histograms
+
+    std::unique_ptr<TFile> fpOutFile = nullptr;
+
+    // **  Number of bins for different quantities
+
+    static constexpr int kNbinsRecoP   = 50;   ///< momentum of reconstructed tracks
+    static constexpr int kNbinsRecoPt  = 50;   ///< transverse momentum of reconstructed tracks
+    static constexpr int kNbinsRecoPhi = 68;   ///< azimuthal angle of reconstructed tracks
+    static constexpr int kNbinsNofHits = 10;   ///< number of hits of reconstructed tracks
+    static constexpr int kNbinsPurity  = 100;  ///< track purity
+    static constexpr int kNbinsChi2NDF = 50;   ///< fit chi-square over NDF
+    static constexpr int kNbinsProb    = 505;  ///< fit probability
+    static constexpr int kNbinsNofSta  = 15;   ///< number of stations
+    static constexpr int kNbinsTx      = 50;   ///< slope along x-axis
+    static constexpr int kNbinsTy      = 50;   ///< slope along y-axis
+    static constexpr int kNbinsHitDist = 50;   ///< distance from transverse hit position to z-axis
+
+    // ** Upper bound for quantity range for histograms
+    static constexpr double kAxMaxRecoP   = 5.;    ///< momentum of reconstructed tracks [GeV/c]
+    static constexpr double kAxMaxRecoPt  = 5.;    ///< transverse momentum of reconstructed tracks [GeV/c]
+    static constexpr double kAxMaxRecoPhi = 3.2;   ///< azimuthal angle of reconstructed tracks [rad]
+    static constexpr double kAxMaxChi2NDF = 100.;  ///< fit chi-square over NDF
+
+
+    // **  Pointers to histogram objects **
+
+    // Reconstructed tracks
+    TH1F* fph_reco_p        = nullptr;  ///< Total momentum over charge of reconstructed tracks
+    TH1F* fph_reco_pt       = nullptr;  ///< Transverse momentum over charge of reconstructed tracks
+    TH1F* fph_reco_phi      = nullptr;  ///< Azimuthal angle of reconstructed tracks
+    TH1F* fph_reco_nhits    = nullptr;  ///< Hit number of reconstructed tracks
+    TH1F* fph_reco_fsta     = nullptr;  ///< First station index of reconstructed tracks
+    TH1F* fph_reco_purity   = nullptr;  ///< Purity of reconstructed tracks (\note purity requires MC information)
+    TH1F* fph_reco_chi2_ndf = nullptr;  ///< Fit chi2 over NDF of reconstructed tracks
+    TH1F* fph_reco_prob     = nullptr;  ///< Fit probability of reconstructed tracks
+    TH1F* fph_rest_chi2_ndf = nullptr;  ///< Fit chi2 over NDF of non-reconstructable tracks
+    TH1F* fph_rest_prob     = nullptr;  ///< Fit probability of non-reconstructable tracks
+
+    // Ghost tracks
+    TH1F* fph_ghost_p            = nullptr;  ///< Total momentum over charge of ghost tracks
+    TH1F* fph_ghost_pt           = nullptr;  ///< Transverse momentum over charge of ghost tracks
+    TH1F* fph_ghost_phi          = nullptr;  ///< Azimuthal angle of ghost tracks
+    TH1F* fph_ghost_nhits        = nullptr;  ///< Hit number of ghost tracks
+    TH1F* fph_ghost_fsta         = nullptr;  ///< First station index of ghost tracks
+    TH1F* fph_ghost_purity       = nullptr;  ///< Purity of ghost tracks
+    TH1F* fph_ghost_chi2_ndf     = nullptr;  ///< Fit chi2 over NDF of ghost tracks
+    TH1F* fph_ghost_prob         = nullptr;  ///< Fit probability of ghost tracks
+    TH1F* fph_ghost_tx           = nullptr;  ///< Slope along x-axis of ghost tracks
+    TH1F* fph_ghost_ty           = nullptr;  ///< Slope along y-axis of ghost tracks
+    TH1F* fph_ghost_fhitR        = nullptr;  ///< Distance of the first hit from z-axis for ghost tracks
+    TH2F* fph_ghost_nhits_vs_p   = nullptr;  ///< Hit number vs. total momentum over charge of ghost tracks
+    TH2F* fph_ghost_fsta_vs_p    = nullptr;  ///< First station index vs. total momentum over charge for ghost tracks
+    TH2F* fph_ghost_lsta_vs_fsta = nullptr;  ///< Last station index vs. first station index of ghost tracks
+
+    // TODO: Add other histograms
+
+    // Registered primary tracks
+    // Registered secondary tracks
+    // Reconstructable primary tracks
+    // Reconstructable secondary tracks
+    // Reconstructed primary tracks
+    // Reconstructed secondary tracks
+    // Reconstructed tracks
+    // Ghost tracks
+
+    // ** Histograms appearance attributes
+    static constexpr double kHtextSize = 0.04;
+
+
+    /// A map of the histogram pointers. The key is a name of the histogram
+    std::unordered_map<std::string_view, TH1*> fmpHistoList;  ///< List of pointers to histograms
+
+    // *******************************
+    // **     Utility variables     **
+    // *******************************
+
+    int fNofEvents = 0;  ///< Event counter
   };
 }  // namespace ca::tools
 
+// **********************************************************
+// **     Inline and template function implementations     **
+// **********************************************************
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<typename T, typename... Args>
+T* ca::tools::Qa::MakeHisto(const char* name, Args... args)
+{
+  // Check, if the histogram with a given name was already created. If so, delete it
+  if (gROOT->FindObject(name)) {
+    LOG(warn) << "CA QA: A histogram with name \"" << name << "\" was previously created and will be deleted now "
+              << "to avoid memory leaks";
+    T* pHisto = (T*) gROOT->FindObject(name);
+    delete pHisto;
+  }
+
+  // Create a new histogram or profile
+  T* pHisto = new T(name, args...);
+
+  // Register histogram in the current TDirectory
+  pHisto->SetDirectory(fpHistoDir);
+
+  // Register histogram in the list
+  fmpHistoList[pHisto->GetName()] = (TH1*) pHisto;
+
+  pHisto->Sumw2();
+
+  // Appearance settings
+  std::array<TAxis*, 2> vpAxes = {pHisto->GetXaxis(), pHisto->GetYaxis()};
+  for (auto* pAxis : vpAxes) {
+    pAxis->SetTitleSize(kHtextSize);
+    pAxis->SetLabelSize(kHtextSize);
+  }
+
+  return pHisto;
+}
+
+
 #endif  // CaToolsQa_h
-- 
GitLab