From 302977ed9bc2ca73c4642c9b7272fd7766977dd4 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 23 Jan 2023 17:37:19 +0100
Subject: [PATCH] CA: QA task for tracking input from STS (v.2)

---
 core/qa/CMakeLists.txt               |    6 +
 core/qa/CbmQaBaseLinkDef.h           |    2 +
 core/qa/CbmQaCanvas.h                |   11 +
 core/qa/CbmQaHist.h                  |   19 +-
 core/qa/CbmQaTask.cxx                |  170 ++++
 core/qa/CbmQaTask.h                  |  229 +++++
 macro/run/run_reco_qa.C              |  292 +++++++
 reco/L1/CMakeLists.txt               |    3 +
 reco/L1/CbmCaMCModule.h              |  507 +++++++++++
 reco/L1/L1LinkDef.h                  |    2 +
 reco/L1/qa/CbmCaInputQaSts.cxx       | 1193 ++++++++++++++++++++++++++
 reco/L1/qa/CbmCaInputQaSts.h         |  201 +++++
 reco/L1/qa/CbmCaQaCuts.h             |   17 +
 reco/L1/qa/CbmTrackerInputQaTrd.cxx  |    2 +
 reco/L1/qa/CbmTrackingInputQaSts.cxx |   20 +-
 15 files changed, 2643 insertions(+), 31 deletions(-)
 create mode 100644 core/qa/CbmQaTask.cxx
 create mode 100644 core/qa/CbmQaTask.h
 create mode 100644 macro/run/run_reco_qa.C
 create mode 100644 reco/L1/CbmCaMCModule.h
 create mode 100644 reco/L1/qa/CbmCaInputQaSts.cxx
 create mode 100644 reco/L1/qa/CbmCaInputQaSts.h
 create mode 100644 reco/L1/qa/CbmCaQaCuts.h

diff --git a/core/qa/CMakeLists.txt b/core/qa/CMakeLists.txt
index 5a87997b4e..3589694a85 100644
--- a/core/qa/CMakeLists.txt
+++ b/core/qa/CMakeLists.txt
@@ -7,12 +7,14 @@ set(SRCS
   CbmQaPie.cxx
   CbmQaHist.cxx
   CbmQaTable.cxx
+  CbmQaTask.cxx
   )
 
 
 set(LIBRARY_NAME CbmQaBase)
 set(LINKDEF ${LIBRARY_NAME}LinkDef.h)
 set(PUBLIC_DEPENDENCIES
+  FairRoot::Base
   FairLogger::FairLogger
   ROOT::Core
   ROOT::Gpad
@@ -24,3 +26,7 @@ set(INTERFACE_DEPENDENCIES
   )
 
 generate_cbm_library()
+
+Install(FILES CbmQaTask.h CbmQaCanvas.h CbmQaTable.h CbmQaHist.h
+        DESTINATION include
+       )
diff --git a/core/qa/CbmQaBaseLinkDef.h b/core/qa/CbmQaBaseLinkDef.h
index 9da99d54d5..5d2349aa39 100644
--- a/core/qa/CbmQaBaseLinkDef.h
+++ b/core/qa/CbmQaBaseLinkDef.h
@@ -16,6 +16,7 @@
 #pragma link C++ class CbmQaPie - ;
 
 // create streamers automatically
+#pragma link C++ class CbmQaEff + ;
 #pragma link C++ class CbmQaPieSlice + ;
 #pragma link C++ class CbmQaHist < TH1F> + ;
 #pragma link C++ class CbmQaHist < TH1D> + ;
@@ -23,5 +24,6 @@
 #pragma link C++ class CbmQaHist < TProfile> + ;
 #pragma link C++ class CbmQaHist < TProfile2D> + ;
 #pragma link C++ class CbmQaTable + ;
+#pragma link C++ class CbmQaTask + ;
 
 #endif
diff --git a/core/qa/CbmQaCanvas.h b/core/qa/CbmQaCanvas.h
index ffcf663812..bce7112517 100644
--- a/core/qa/CbmQaCanvas.h
+++ b/core/qa/CbmQaCanvas.h
@@ -46,6 +46,17 @@ public:
   /// Divide canvas into nPads in 2D in a nice way
   void Divide2D(int nPads);
 
+
+  /// a static canvas for temporary drawing
+  static CbmQaCanvas& GetDummyCanvas()
+  {
+    /// the static variable will be initialised at the first call;
+    /// deleted at the application end (c++11)
+    static CbmQaCanvas dummy("CbmQaTempCanvas", "CbmQaTempCanvas", 1, 1);
+    return dummy;
+  }
+
+
 private:
   /// Use a specific type name in order to avoid ambiguities when unrolling
   /// templates
diff --git a/core/qa/CbmQaHist.h b/core/qa/CbmQaHist.h
index 546e573b25..194710611c 100644
--- a/core/qa/CbmQaHist.h
+++ b/core/qa/CbmQaHist.h
@@ -70,10 +70,10 @@ public:
   TFitResultPtr Fit(Types... args)
   {
     TVirtualPad* padsav = gPad;
-    GetDummyCanvas().cd();
+    CbmQaCanvas::GetDummyCanvas().cd();
     this->Sumw2();
     auto ret = HistTypeT::Fit(args...);
-    GetDummyCanvas().Clear();
+    CbmQaCanvas::GetDummyCanvas().Clear();
 
     // make the output look nice
 
@@ -118,15 +118,15 @@ public:
     int saveStat         = gStyle->GetOptStat();
     int saveFit          = gStyle->GetOptFit();
 
-    GetDummyCanvas().cd();
+    CbmQaCanvas::GetDummyCanvas().cd();
     gStyle->SetOptStat(fOptStat);
     gStyle->SetOptFit(fOptFit);
 
     this->SetStats(0);  // remove the old stat window
     this->SetStats(1);  // set the flag to create the stat window during Draw()
     this->Draw();
-    GetDummyCanvas().Update();
-    GetDummyCanvas().Clear();
+    CbmQaCanvas::GetDummyCanvas().Update();
+    CbmQaCanvas::GetDummyCanvas().Clear();
 
     // restore the environment
     gStyle->SetOptStat(saveStat);
@@ -135,15 +135,6 @@ public:
   }
 
 private:
-  /// a static canvas for temporary drawing
-  static CbmQaCanvas& GetDummyCanvas()
-  {
-    /// the static variable will be initialised at the first call;
-    /// deleted at the application end (c++11)
-    static CbmQaCanvas dummy("CbmQaTempCanvas", "CbmQaTempCanvas", 1, 1);
-    return dummy;
-  }
-
   int fOptStat = 1;
   int fOptFit  = 0;
 
diff --git a/core/qa/CbmQaTask.cxx b/core/qa/CbmQaTask.cxx
new file mode 100644
index 0000000000..d74cd4582c
--- /dev/null
+++ b/core/qa/CbmQaTask.cxx
@@ -0,0 +1,170 @@
+/* opyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaTask.cxx
+/// \brief  A base class for CBM QA task logic (implementation)
+/// \author S.Zharko <s.zharko@lx-pool.gsi.de>
+/// \data   12.01.2023
+
+
+#include "CbmQaTask.h"
+
+#include "CbmQaCanvas.h"
+
+#include "FairRootManager.h"
+#include "FairRunAna.h"
+#include "FairSink.h"
+
+#include "TAxis.h"
+
+#include <array>
+
+ClassImp(CbmQaTask);
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CbmQaTask::CbmQaTask(const char* name, int verbose, bool isMCUsed) : FairTask(name, verbose), fbUseMC(isMCUsed) {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaTask::Exec(Option_t* /*option*/)
+{
+  fNofEvents.SetVal(fNofEvents.GetVal() + 1);
+  LOG_IF(info, fVerbose > 1) << fName << ": event " << fNofEvents.GetVal();
+  FillHistograms();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaTask::Finish()
+{
+  // Processes histograms in the end of the run
+  AnalyzeHistograms();
+
+  // Check QA
+  Check();
+
+  // Processes canvases in the end of the run
+  LOG_IF(info, fVerbose > 1) << fName << ": initializing canvases";
+  InitCanvases();
+
+  // Write the root folder to sinker
+  FairSink* pSink = FairRootManager::Instance()->GetSink();
+  LOG_IF(fatal, !pSink) << fName << ": sink file was not found";
+
+  pSink->WriteObject(fpFolderRoot.get(), nullptr);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmQaTask::Init()
+{
+  LOG_IF(info, fVerbose > 0) << fName << ": initializing task ...";
+
+  InitStatus res = kSUCCESS;
+
+  // ----- Clear map of the histograms (note)
+  DeInitBase();
+
+  // ----- Initialize data branches
+  LOG_IF(info, fVerbose > 1) << fName << ": initializing input data branches";
+  res = std::max(res, InitDataBranches());
+
+  // ----- Initialize I/O
+  fpFolderRoot = std::make_unique<TFolder>(fName, fName);  // The name of the folder follows the name of the task
+  fpFolderRoot->SetOwner(true);                            // When true, TFolder owns all added objects
+  fpFolderRoot->Add(&fNofEvents);
+
+  // ----- Initialize histograms and canvases
+  LOG_IF(info, fVerbose > 1) << fName << ": initializing histograms";
+  res = std::max(res, InitHistograms());
+
+  fNofEvents.SetVal(0);
+
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmQaTask::ReInit()
+{
+  LOG_IF(info, fVerbose > 2) << fName << "::DeInitBase() is called";
+  return Init();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaTask::DeInitBase()
+{
+  LOG_IF(info, fVerbose > 2) << fName << "::DeInitBase() is called";
+  // De-initialize basic data members
+  // ...
+
+  delete fpFolderRoot.get();
+  fpFolderRoot = nullptr;
+
+  // De-initialize particular QA-task implementation
+  DeInit();
+}
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+// NOTE: Example
+void CbmQaTask::SetHistoProperties(TH1* pHisto)
+{
+  constexpr double kHdefTextSize = 0.04;
+
+  pHisto->SetStats(kTRUE);
+  pHisto->Sumw2();
+
+  // Axis property settings
+  std::array<TAxis*, 3> vpAxes = {pHisto->GetXaxis(), pHisto->GetYaxis(), pHisto->GetZaxis()};
+  for (auto* pAxis : vpAxes) {
+    pAxis->SetTitleSize(kHdefTextSize);
+    pAxis->SetLabelSize(kHdefTextSize);
+  }
+  vpAxes[0]->SetNdivisions(504);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmQaTask::InitDataBranches()
+{
+  LOG_IF(info, fVerbose > 1) << fName << ": data branches initialization function is not defined";
+  return kSUCCESS;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmQaTask::InitHistograms()
+{
+  LOG_IF(info, fVerbose > 1) << fName << ": histogram initialization function is not defined";
+  return kSUCCESS;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaTask::FillHistograms()
+{
+  LOG_IF(info, fVerbose > 1) << fName << ": histogram filling function is not defined";
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaTask::AnalyzeHistograms()
+{
+  LOG_IF(info, fVerbose > 1) << fName << ": no action on histograms in the end of the run is provided";
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmQaTask::InitCanvases()
+{
+  LOG_IF(info, fVerbose > 1) << fName << ": no initialization of canvases is provided";
+  return kSUCCESS;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmQaTask::DeInit() { LOG_IF(info, fVerbose > 1) << fName << ": no extra de-initialization is provided"; }
diff --git a/core/qa/CbmQaTask.h b/core/qa/CbmQaTask.h
new file mode 100644
index 0000000000..c18e596485
--- /dev/null
+++ b/core/qa/CbmQaTask.h
@@ -0,0 +1,229 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmQaTask.h
+/// \brief  A base class for CBM QA task logic
+/// \author S.Zharko <s.zharko@gsi.de>
+/// \data   12.01.2022
+
+#ifndef CbmQaTask_h
+#define CbmQaTask_h 1
+
+#include "FairTask.h"
+#include "Logger.h"
+
+#include "TCanvas.h"
+#include "TEfficiency.h"
+#include "TFolder.h"
+#include "TH1.h"
+#include "TParameter.h"
+#include "TROOT.h"
+
+#include <string_view>
+
+/// Class CbmQaTask is to be inherited with a particular QA-task. It provides mechanisms for storage and management
+/// of QA canvases and histograms management
+class CbmQaTask : public FairTask {
+public:
+  /// Constructor from parameters
+  /// \param  name     Name of the task
+  /// \param  verbose  Verbose level
+  /// \param  isMCUsed Flag: true - MC information is used, false - only reconstructed data QA is processed
+  CbmQaTask(const char* name, int verbose, bool isMCUsed);
+
+  /// Default constructor
+  CbmQaTask() = delete;  // TODO: Let's see, what can happen, if one deletes default constructor
+
+  /// Destructor
+  virtual ~CbmQaTask() = default;
+
+  // Copy and move semantics
+  CbmQaTask(const CbmQaTask&) = delete;
+  CbmQaTask(CbmQaTask&&)      = delete;
+  CbmQaTask& operator=(const CbmQaTask&) = delete;
+  CbmQaTask& operator=(CbmQaTask&&) = delete;
+
+  /// Returns flag, whether MC information is used or not in the task
+  bool IsMCUsed() const { return fbUseMC; }
+
+  /// FairTask: Task initialization in the beginning of the run
+  InitStatus Init();
+
+  /// FairTask: Task reinitialization
+  InitStatus ReInit();
+
+  /// FairTask: Defines action of the task in the event/TS
+  void Exec(Option_t* /*option*/);
+
+  /// FairTask: Defines action of the task in the end of run
+  void Finish();
+
+  ClassDef(CbmQaTask, 0);
+
+protected:
+  // *****************************************************
+  // ** Functions accessible inside the derived classes **
+  // *****************************************************
+
+  /// Method to check, if the QA results are acceptable
+  /// NOTE: Here one can add a geometry check ...
+  virtual bool Check() const { return true; }
+
+  /// De-initialize the task
+  virtual void DeInit();
+
+  /// Initializes data branches
+  virtual InitStatus InitDataBranches();
+
+  /// Initializes histograms
+  virtual InitStatus InitHistograms();
+
+  /// Initializes canvases
+  virtual InitStatus InitCanvases();
+
+  /// Method to fill histograms per event or time-slice
+  virtual void FillHistograms();
+
+  /// Method to process histograms in the end of the run (optional)
+  virtual void AnalyzeHistograms();
+
+  /// Creates, initializes and registers the histogram
+  /// \tparam  T     Type of the canvas: TCanvas or CbmQaCanvas (\note T should not be a pointer)
+  /// \param   name  Name of canvas
+  /// \param   args  The rest of the arguments, which will be passed to the canvas constructor
+  template<typename T, typename... Args>
+  T* MakeCanvas(const char* name, Args... args);
+
+  /// Creates, initializes and registers the efficiency
+  /// \tparam  T     Type of the canvas: either TProfile, TProfile2D or TEfficiency (\note T should not be a pointer)
+  /// \param   name  Name of canvas
+  /// \param   args  The rest of the arguments, which will be passed to the efficiency constructor
+  template<typename T, typename... Args>
+  T* MakeEfficiency(const char* name, Args... args);
+
+  /// Creates, initializes and registers the histogram
+  /// \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);
+
+  /// Method to setup histogram properties (text sizes etc.)
+  /// \param  pHist  Pointer to a histogram to tune
+  virtual void SetHistoProperties(TH1* pHist);
+
+  /// Gets a reference to histograms map
+  ///const auto& GetHistoMap() const { return fmpHistList; }
+
+  /// Gets a reference to canvases map
+  ///const auto& GetCanvasMap() const { return fmpCanvList; }
+
+  /// Get current event number
+  int GetEventNumber() const { return fNofEvents.GetVal(); }
+
+
+private:
+  // **********************************************************
+  // ** Functions accessible inside the CbmQaTask class only **
+  // **********************************************************
+
+  /// De-initialize this task
+  void DeInitBase();
+
+  // ***********************
+  // ** Private variables **
+  // ***********************
+
+  // Flags
+  bool fbUseMC = false;  ///< Flag, if MC is used
+
+  // I/O
+  std::unique_ptr<TFolder> fpFolderRoot = nullptr;  ///< Root folder to store histograms and canvases
+  TFolder* fpFolderHist                 = nullptr;  ///< Folder for raw histograms
+  TFolder* fpFolderCanv                 = nullptr;  ///< Folder for canvases
+  TFolder* fpFolderEff                  = nullptr;  ///< Folder for efficiencies
+
+  TParameter<int> fNofEvents {"nEvents", 0};  ///< Number of processed events
+};
+
+
+// *************************************************
+// ** Inline and template function implementation **
+// *************************************************
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<typename T, typename... Args>
+T* CbmQaTask::MakeCanvas(const char* name, Args... args)
+{
+  if (gROOT->FindObject(name)) {
+    LOG(warn) << fName << ": A canvas with name \"" << name << "\" was previously created and will be deleted now "
+              << "to avoid memory leaks";
+    T* pCanv = (T*) gROOT->FindObject(name);
+    delete pCanv;
+  }
+
+  // Create a new canvas
+  T* pCanv = new T(name, args...);
+  pCanv->SetLeftMargin(0.2);
+  pCanv->SetBottomMargin(0.2);
+
+
+  // Register canvas in the folder
+  if (!fpFolderCanv) { fpFolderCanv = fpFolderRoot->AddFolder("canvases", "Canvases"); }
+  fpFolderCanv->Add(pCanv);
+
+  return pCanv;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<typename T, typename... Args>
+T* CbmQaTask::MakeEfficiency(const char* name, Args... args)
+{
+  if (gROOT->FindObject(name)) {
+    LOG(warn) << fName << ": An efficiency with name \"" << name << "\" was previously created and will be deleted now "
+              << "to avoid memory leaks";
+    auto* pEff = (T*) gROOT->FindObject(name);
+    delete pEff;
+  }
+
+  // Create a new efficiency
+  auto* pEff = new T(name, args...);
+
+  // Register efficiency in the folder
+  if (!fpFolderEff) { fpFolderEff = fpFolderRoot->AddFolder("efficiencies", "Efficiencies"); }
+  fpFolderEff->Add(pEff);
+
+  return pEff;
+}
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<typename T, typename... Args>
+T* CbmQaTask::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) << fName << ": A histogram with name \"" << name << "\" was previously created and will be deleted now "
+              << "to avoid memory leaks";
+    T* pHist = (T*) gROOT->FindObject(name);
+    delete pHist;
+  }
+
+  // Create a new histogram or profile
+  T* pHist = new T(name, args...);
+  pHist->Sumw2();
+
+  // Register histogram in the folder
+  if (!fpFolderHist) { fpFolderHist = fpFolderRoot->AddFolder("histograms", "Histograms"); }
+  fpFolderHist->Add(pHist);
+
+  SetHistoProperties(pHist);
+
+  return pHist;
+}
+
+#endif  // CbmQaTask_h
diff --git a/macro/run/run_reco_qa.C b/macro/run/run_reco_qa.C
new file mode 100644
index 0000000000..f286a031ea
--- /dev/null
+++ b/macro/run/run_reco_qa.C
@@ -0,0 +1,292 @@
+/* Copyright (C) 2006-2020 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergey Gorbunov, Denis Bertini [committer], Florian Uhlig */
+
+// --------------------------------------------------------------------------
+//
+// Macro for simulation & reconstruction QA
+//
+// The following naming conventions are assumed:
+// Raw data file:  [dataset].event.raw.root
+// Transport file: [dataset].tra.root
+// Parameter file:  [dataset].par.root
+// Reconstruction file: [dataset].rec.root
+//
+// S. Gorbunov 28/09/2020
+//
+// --------------------------------------------------------------------------
+
+// Includes needed for IDE
+#if !defined(__CLING__)
+#include "CbmDefs.h"
+#include "CbmMCDataManager.h"
+#include "CbmMuchDigitizerQa.h"
+#include "CbmMuchHitFinderQa.h"
+#include "CbmMuchTransportQa.h"
+#include "CbmSetup.h"
+
+#include <FairFileSource.h>
+#include <FairMonitor.h>
+#include <FairParAsciiFileIo.h>
+#include <FairParRootFileIo.h>
+#include <FairRootFileSink.h>
+#include <FairRunAna.h>
+#include <FairRuntimeDb.h>
+#include <FairSystemInfo.h>
+
+#include <TStopwatch.h>
+#endif
+
+void run_reco_qa(TString dataTra = "data/sis100_muon_jpsi_test", TString dataRaw = "data/sis100_muon_jpsi_test",
+                 TString dataReco = "data/sis100_muon_jpsi_test", TString dataPar = "data/sis100_muon_jpsi_test",
+                 TString dataSink = "data/sis100_muon_jpsi_test", TString setup = "sis100_muon_jpsi",
+                 Int_t nEvents = -1, TString dataTra2 = "", TString dataTra3 = "")
+{
+
+  gROOT->SetBatch(kTRUE);
+
+  // ========================================================================
+  //          Adjust this part according to your requirements
+
+  // -----   Logger settings   ----------------------------------------------
+  FairLogger::GetLogger()->SetLogScreenLevel("INFO");
+  fair::Logger::DefineVerbosity(
+    "user1", fair::VerbositySpec::Make(fair::VerbositySpec::Info::severity, fair::VerbositySpec::Info::file_line));
+  FairLogger::GetLogger()->SetLogVerbosityLevel("user1");
+  FairLogger::GetLogger()->SetColoredLog(true);
+  // ------------------------------------------------------------------------
+
+  // -----   Environment   --------------------------------------------------
+  int verbose    = 100;
+  bool bUseMC    = true;
+  TString myName = "run_qa";                       // this macro's name for screen output
+  TString srcDir = gSystem->Getenv("VMCWORKDIR");  // top source directory
+  // ------------------------------------------------------------------------
+
+  // -----   In- and output file names   ------------------------------------
+  TString traFile  = dataTra + ".tra.root";
+  TString tra2File = dataTra2 + ".tra.root";
+  TString tra3File = dataTra3 + ".tra.root";
+  TString rawFile  = dataRaw + ".raw.root";
+  TString parFile  = dataPar + ".par.root";
+  TString recFile  = dataReco + ".reco.root";
+  TString sinkFile = dataSink + ".qa.root";
+  // ------------------------------------------------------------------------
+
+  // -----   Load the geometry setup   -------------------------------------
+  std::cout << std::endl;
+  std::cout << "-I- " << myName << ": Loading setup " << setup << std::endl;
+  CbmSetup::Instance()->LoadSetup(setup);
+  // You can modify the pre-defined setup by using
+  // CbmSetup::Instance()->RemoveModule(ESystemId) or
+  // CbmSetup::Instance()->SetModule(ESystemId, const char*, Bool_t) or
+  // CbmSetup::Instance()->SetActive(ESystemId, Bool_t)
+  // See the class documentation of CbmSetup.
+  // ------------------------------------------------------------------------
+
+  // -----   Parameter files as input to the runtime database   -------------
+  std::cout << std::endl;
+  std::cout << "-I- " << myName << ": Defining parameter files " << std::endl;
+  TList* parFileList = new TList();
+  TString geoTag;
+
+  // - MUCH digitisation parameters
+  TString muchParFile {};
+  if (CbmSetup::Instance()->GetGeoTag(ECbmModuleId::kMuch, geoTag)) {
+    bool mcbmFlag = geoTag.Contains("mcbm", TString::kIgnoreCase);
+    muchParFile   = srcDir + "/parameters/much/much_";
+    muchParFile += (mcbmFlag) ? geoTag : geoTag(0, 4);
+    muchParFile += "_digi_sector.root";
+    {  // init geometry from the file
+      TFile* f            = new TFile(muchParFile, "R");
+      TObjArray* stations = (TObjArray*) f->Get("stations");
+      CbmMuchGeoScheme::Instance()->Init(stations, mcbmFlag);
+    }
+  }
+
+  // - TRD digitisation parameters
+  if (CbmSetup::Instance()->GetGeoTag(ECbmModuleId::kTrd, geoTag)) {
+    const Char_t* npar[4] = {"asic", "digi", "gas", "gain"};
+    TObjString* trdParFile(NULL);
+    for (Int_t i(0); i < 4; i++) {
+      trdParFile = new TObjString(srcDir + "/parameters/trd/trd_" + geoTag + "." + npar[i] + ".par");
+      parFileList->Add(trdParFile);
+      std::cout << "-I- " << myName << ": Using parameter file " << trdParFile->GetString() << std::endl;
+    }
+  }
+
+  // - TOF digitisation parameters
+  if (CbmSetup::Instance()->GetGeoTag(ECbmModuleId::kTof, geoTag)) {
+    TObjString* tofBdfFile = new TObjString(srcDir + "/parameters/tof/tof_" + geoTag + ".digibdf.par");
+    parFileList->Add(tofBdfFile);
+    std::cout << "-I- " << myName << ": Using parameter file " << tofBdfFile->GetString() << std::endl;
+  }
+  // ------------------------------------------------------------------------
+
+  // In general, the following parts need not be touched
+  // ========================================================================
+
+  // -----   Timer   --------------------------------------------------------
+  TStopwatch timer;
+  timer.Start();
+  // ------------------------------------------------------------------------
+
+  // ----    Debug option   -------------------------------------------------
+  gDebug = 0;
+  // ------------------------------------------------------------------------
+
+  // -----   FairRunAna   ---------------------------------------------------
+  FairFileSource* inputSource = new FairFileSource(rawFile);
+  inputSource->AddFriend(traFile);
+  inputSource->AddFriend(recFile);
+  if (!dataTra2.IsNull()) inputSource->AddFriend(tra2File);
+  if (!dataTra3.IsNull()) inputSource->AddFriend(tra3File);
+
+  FairRunAna* run = new FairRunAna();
+  run->SetSource(inputSource);
+  run->SetGenerateRunInfo(kFALSE);
+
+  FairRootFileSink* sink = new FairRootFileSink(sinkFile);
+  run->SetSink(sink);
+
+  TString monitorFile {sinkFile};
+  monitorFile.ReplaceAll("qa", "qa.monitor");
+  FairMonitor::GetMonitor()->EnableMonitor(kTRUE, monitorFile);
+  // ------------------------------------------------------------------------
+
+  // -----   MCDataManager  -----------------------------------
+  if (bUseMC) {
+    CbmMCDataManager* mcManager = new CbmMCDataManager("MCDataManager", 0);
+    mcManager->AddFile(traFile);
+    if (!dataTra2.IsNull()) mcManager->AddFile(tra2File);
+    if (!dataTra3.IsNull()) mcManager->AddFile(tra3File);
+
+    run->AddTask(mcManager);
+  }
+  // ------------------------------------------------------------------------
+
+
+  // ----- Match reco to MC ------
+  if (bUseMC) {
+    CbmMatchRecoToMC* matchTask = new CbmMatchRecoToMC();
+    run->AddTask(matchTask);
+  }
+
+  // ----- Geometry interface initializer for tracking
+  run->AddTask(new CbmTrackingDetectorInterfaceInit());
+
+
+  // *******************************
+  // **     Detector QA tasks     **
+  // *******************************
+
+  // ----- MUCH QA  ---------------------------------
+  if (CbmSetup::Instance()->IsActive(ECbmModuleId::kMuch)) {
+    run->AddTask(new CbmMuchTransportQa());
+    run->AddTask(new CbmMuchDigitizerQa());
+    CbmMuchHitFinderQa* muchHitFinderQa = new CbmMuchHitFinderQa();
+    muchHitFinderQa->SetGeoFileName(muchParFile);
+    run->AddTask(muchHitFinderQa);
+  }
+
+  // ----- TRD QA  ---------------------------------
+  if (CbmSetup::Instance()->IsActive(ECbmModuleId::kTrd)) {
+    run->AddTask(new CbmTrdMCQa());
+    //run->AddTask(new CbmTrdHitRateQa());  //opens lots of windows
+    //run->AddTask(new CbmTrdDigitizerPRFQa()); //works put currently doesn't do anything
+    //run->AddTask(new CbmTrdHitRateFastQa());  //opens lots of windows
+    run->AddTask(new CbmTrdHitProducerQa());
+    run->AddTask(new CbmTrdCalibTracker());
+    run->AddTask(new CbmTrackerInputQaTrd());  // Tracker requirements to TRD
+  }
+  // ------------------------------------------------------------------------
+
+  // ----- TRD QA  ---------------------------------
+  if (CbmSetup::Instance()->IsActive(ECbmModuleId::kTof)) {
+    // TODO: SZh 19.10.2022:
+    // After the proper TOF digi parameters initialization it is appeared,
+    // that CbmTrackerInputQaTof causes segmentation violation in
+    // CbmTrackerInputQaTof::ResolutionQa() for the event-based running
+    // (before the branch with the related code inside the task was never
+    // executed). Temporarily commented that task, till the bug would not be
+    // resolved.
+
+    //run->AddTask(new CbmTrackerInputQaTof());  // Tracker requirements to TRD
+  }
+  // ------------------------------------------------------------------------
+
+  // ----- STS QA  ---------------------------------
+  if (CbmSetup::Instance()->IsActive(ECbmModuleId::kSts)) {
+    //run->AddTask(new CbmStsDigitizeQa()); //opens lots of windows
+    run->AddTask(new CbmStsFindTracksQa());
+    run->AddTask(new CbmTrackingInputQaSts());
+    run->AddTask(new CbmCaInputQaSts(verbose, bUseMC));
+  }
+  // ------------------------------------------------------------------------
+
+  // ----- Event builder QA  ---------------------------------
+  CbmBuildEventsQa* evBuildQA = new CbmBuildEventsQa();
+  run->AddTask(evBuildQA);
+  // ------------------------------------------------------------------------
+
+  // -----  Parameter database   --------------------------------------------
+  std::cout << std::endl << std::endl;
+  std::cout << "-I- " << myName << ": Set runtime DB" << std::endl;
+  FairRuntimeDb* rtdb       = run->GetRuntimeDb();
+  FairParRootFileIo* parIo1 = new FairParRootFileIo();
+  parIo1->open(parFile.Data(), "in");
+  rtdb->setFirstInput(parIo1);
+  if (!parFileList->IsEmpty()) {
+    FairParAsciiFileIo* parIo2 = new FairParAsciiFileIo();
+    parIo2->open(parFileList, "in");
+    rtdb->setSecondInput(parIo2);
+  }
+  rtdb->print();
+  // ------------------------------------------------------------------------
+
+  // -----   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;
+  run->Run(0, nEvents);
+  // ------------------------------------------------------------------------
+
+  // -----   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 << "Output file is " << sinkFile << std::endl;
+  std::cout << "Parameter file is " << parFile << std::endl;
+  std::cout << "Real time " << rtime << " s, CPU time " << ctime << " s" << std::endl;
+  std::cout << std::endl;
+  std::cout << " Test passed" << std::endl;
+  std::cout << " All ok " << std::endl;
+  // ------------------------------------------------------------------------
+
+  // -----   Resource monitoring   ------------------------------------------
+  // Extract the maximal used memory an add is as Dart measurement
+  // This line is filtered by CTest and the value send to CDash
+  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;
+  // ------------------------------------------------------------------------
+
+  // -----   Function needed for CTest runtime dependency   -----------------
+  RemoveGeoManager();
+  // ------------------------------------------------------------------------
+}
diff --git a/reco/L1/CMakeLists.txt b/reco/L1/CMakeLists.txt
index df1bb9c9ab..e9c9c00351 100644
--- a/reco/L1/CMakeLists.txt
+++ b/reco/L1/CMakeLists.txt
@@ -77,6 +77,8 @@ set(SRCS
   qa/CbmTrackerInputQaTrd.cxx
   qa/CbmTrackerInputQaTof.cxx
   qa/CbmTrackingInputQaSts.cxx
+  qa/CbmCaInputQaMuch.cxx
+  qa/CbmCaInputQaSts.cxx
 )
 
 set(NO_DICT_SRCS
@@ -95,6 +97,7 @@ set(HEADERS
   L1Algo/L1Def.h
   L1Algo/L1Vector.h
   catools/CaToolsWindowFinder.h
+  qa/CbmCaQaCuts.h
 )
 
 
diff --git a/reco/L1/CbmCaMCModule.h b/reco/L1/CbmCaMCModule.h
new file mode 100644
index 0000000000..1c7f4ea633
--- /dev/null
+++ b/reco/L1/CbmCaMCModule.h
@@ -0,0 +1,507 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   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>
+
+#ifndef CbmCaMCModule_h
+#define CbmCaMCModule_h 1
+
+#include "CbmL1DetectorID.h"
+#include "CbmL1Track.h"
+#include "CbmLink.h"
+#include "CbmMCDataArray.h"
+#include "CbmMCEventList.h"
+#include "CbmMCTrack.h"
+#include "CbmMatch.h"
+#include "CbmMuchPoint.h"
+#include "CbmMvdPoint.h"
+#include "CbmStsPoint.h"
+#include "CbmTimeSlice.h"
+#include "CbmTofPoint.h"
+#include "CbmTrdPoint.h"
+
+#include "TClonesArray.h"
+#include "TDatabasePDG.h"
+
+#include <numeric>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+#include "CaToolsMCData.h"
+#include "CaToolsMCPoint.h"
+#include "CaToolsQa.h"
+#include "L1Constants.h"
+#include "L1Parameters.h"
+#include "L1Undef.h"
+
+class CbmEvent;
+class CbmMCDataObject;
+class CbmL1Hit;
+class CbmL1Track;
+
+enum class L1DetectorID;
+
+/// Class CbmCaPerformcance is an interface to communicate between
+class CbmCaMCModule {
+public:
+  // *****************************************
+  // **     Constructors and destructor     **
+  // *****************************************
+
+  /// Constructor
+  /// \param verbosity Verbosity level
+  CbmCaMCModule(int verb = 0, int perfMode = 3) : fVerbose(verb), fPerformanceMode(perfMode) {}
+
+  /// Destructor
+  ~CbmCaMCModule() = default;
+
+  /// Copy constructor
+  CbmCaMCModule(const CbmCaMCModule&) = delete;
+
+  /// Move constructor
+  CbmCaMCModule(CbmCaMCModule&&) = delete;
+
+  /// Copy assignment operator
+  CbmCaMCModule& operator=(const CbmCaMCModule&) = delete;
+
+  /// 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
+  void Finish();
+
+  /// 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
+  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
+  /// 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 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);
+
+
+  // ***********************
+  // **     Accessors     **
+  // ***********************
+
+  /// 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; }
+
+  /// 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
+  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     **
+  // *******************************
+
+  /// 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
+  void CheckInit() const;
+
+  /// Reads MC tracks from external trees and saves them to MCDataObject
+  void ReadMCTracks();
+
+  /// Reads MC points from external trees and saves them to MCDataObject
+  void ReadMCPoints();
+
+  /// 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
+  template<L1DetectorID DetID>
+  bool FillMCPoint(int iExtId, int iEvent, int iFile, ca::tools::MCPoint& point);
+
+
+  // *******************************
+  // **     Utility variables     **
+  // *******************************
+
+  const L1Parameters* fpParameters = nullptr;  ///< Pointer to tracking parameters object
+
+  // **  Flags **
+
+  bool fbUseMvd  = false;  ///< is MVD used
+  bool fbUseSts  = false;  ///< is STS used
+  bool fbUseMuch = false;  ///< is MuCh used
+  bool fbUseTrd  = false;  ///< is TRD used
+  bool fbUseTof  = false;  ///< is TOF used
+
+  bool fbLegacyEventMode = false;  ///< if tracking uses events instead of time-slices (back compatibility)
+  int fVerbose           = 0;      ///< Verbosity level
+  int fPerformanceMode   = -1;     ///< Mode of performance
+
+  // *********************************
+  // **     Input data branches     **
+  // *********************************
+
+  const CbmTimeSlice* fpTimeSlice = nullptr;  ///< Current time slice
+
+  // ------ Mc-event
+  CbmMCEventList* fpMCEventList    = nullptr;  ///< MC event list
+  CbmMCDataObject* fpMCEventHeader = nullptr;  ///< MC event header
+  CbmMCDataArray* fpMCTracks       = nullptr;  ///< MC tracks input
+
+  // ------ Mc-points
+  CbmMCDataArray* fpMvdPoints  = nullptr;  ///< MVD MC-points input container
+  CbmMCDataArray* fpStsPoints  = nullptr;  ///< STS MC-points input container
+  CbmMCDataArray* fpMuchPoints = nullptr;  ///< MuCh MC-points input container
+  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
+
+  // ------ Matches
+  TClonesArray* fpMvdHitMatches  = nullptr;  ///< Array of MVD hit matches
+  TClonesArray* fpStsHitMatches  = nullptr;  ///< Array of STS hit matches
+  TClonesArray* fpMuchHitMatches = nullptr;  ///< Array of MuCh hit matches
+  TClonesArray* fpTrdHitMatches  = nullptr;  ///< Array of TRD hit matches
+  TClonesArray* fpTofHitMatches  = nullptr;  ///< Array of TOF hit matches
+
+  TClonesArray* fpStsHits           = nullptr;  ///< Array of STS hits (currently needed for matching)
+  TClonesArray* fpStsClusterMatches = nullptr;  ///< Array of STS cluster matches
+
+  // ------ Matching information
+  std::set<std::pair<int, int>> fFileEventIDs;  ///< Set of file and event indexes: first - iFile, second - iEvent
+
+  // ************************************
+  // **     Current MC data object     **
+  // ************************************
+
+  ca::tools::MCData fMCData {};  ///< MC-information in CA tracking internal format (tracks and points)
+
+  std::unique_ptr<ca::tools::Qa> fpQaModule = nullptr;  ///< Pointer to CA QA module
+
+  // ********************************************
+  // **     Pointers to reconstructed data     **
+  // ********************************************
+
+  const L1Vector<CbmL1Track>* fpvRecoTracks = nullptr;  ///< Pointer to reconstructed track container
+  const L1Vector<CbmL1Hit>* fpvHits         = nullptr;  ///< Pointer to hits container
+};
+
+// **********************************************
+// **     Template function implementation     **
+// **********************************************
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<L1DetectorID DetID>
+bool CbmCaMCModule::FillMCPoint(int iExtId, int iEvent, int iFile, ca::tools::MCPoint& point)
+{
+  // ----- Get positions, momenta, time and track ID
+  TVector3 posIn;             // Position at entrance to station [cm]
+  TVector3 posOut;            // Position at exist of station [cm]
+  TVector3 momIn;             // 3-momentum at entrance to station [GeV/c]
+  TVector3 momOut;            // 3-momentum at exit of station [GeV/c]
+  double time = undef::kD;    // Time of MC point (after beginning of the event) [ns]
+  int iTmcExt = undef::kI32;  // Track ID in the external container
+  // MVD
+  if constexpr (L1DetectorID::kMvd == DetID) {
+    auto* pExtPoint = L1_DYNAMIC_CAST<CbmMvdPoint*>(fpMvdPoints->Get(iFile, iEvent, iExtId));
+    if (!pExtPoint) {
+      LOG(warn) << "CbmCaMCModule: MVD MC point with iExtId = " << iExtId << ", iEvent = " << iEvent
+                << ", iFile = " << iFile << " does not exist";
+      return false;
+    }
+    pExtPoint->Position(posIn);
+    pExtPoint->PositionOut(posOut);
+    pExtPoint->Momentum(momIn);
+    pExtPoint->MomentumOut(momOut);
+    time    = pExtPoint->GetTime();
+    iTmcExt = pExtPoint->GetTrackID();
+  }
+  // STS
+  else if constexpr (L1DetectorID::kSts == DetID) {
+    auto* pExtPoint = L1_DYNAMIC_CAST<CbmStsPoint*>(fpStsPoints->Get(iFile, iEvent, iExtId));
+    if (!pExtPoint) {
+      LOG(warn) << "CbmCaMCModule: STS MC point with iExtId = " << iExtId << ", iEvent = " << iEvent
+                << ", iFile = " << iFile << " does not exist";
+      return false;
+    }
+    pExtPoint->Position(posIn);
+    pExtPoint->PositionOut(posOut);
+    pExtPoint->Momentum(momIn);
+    pExtPoint->MomentumOut(momOut);
+    time    = pExtPoint->GetTime();
+    iTmcExt = pExtPoint->GetTrackID();
+  }
+  // MuCh
+  else if constexpr (L1DetectorID::kMuch == DetID) {
+    auto* pExtPoint = L1_DYNAMIC_CAST<CbmMuchPoint*>(fpMuchPoints->Get(iFile, iEvent, iExtId));
+    if (!pExtPoint) {
+      LOG(warn) << "CbmCaMCModule: MuCh MC point with iExtId = " << iExtId << ", iEvent = " << iEvent
+                << ", iFile = " << iFile << " does not exist";
+      return false;
+    }
+    pExtPoint->Position(posIn);
+    pExtPoint->PositionOut(posOut);
+    pExtPoint->Momentum(momIn);
+    pExtPoint->Momentum(momOut);
+    time    = pExtPoint->GetTime();
+    iTmcExt = pExtPoint->GetTrackID();
+  }
+  // TRD
+  else if constexpr (L1DetectorID::kTrd == DetID) {
+    auto* pExtPoint = L1_DYNAMIC_CAST<CbmTrdPoint*>(fpTrdPoints->Get(iFile, iEvent, iExtId));
+    if (!pExtPoint) {
+      LOG(warn) << "CbmCaMCModule: TRD MC point with iExtId = " << iExtId << ", iEvent = " << iEvent
+                << ", iFile = " << iFile << " does not exist";
+      return false;
+    }
+    pExtPoint->Position(posIn);
+    pExtPoint->PositionOut(posOut);
+    pExtPoint->Momentum(momIn);
+    pExtPoint->MomentumOut(momOut);
+    time    = pExtPoint->GetTime();
+    iTmcExt = pExtPoint->GetTrackID();
+  }
+  // TOF
+  else if constexpr (L1DetectorID::kTof == DetID) {
+    auto* pExtPoint = L1_DYNAMIC_CAST<CbmTofPoint*>(fpTofPoints->Get(iFile, iEvent, iExtId));
+    if (!pExtPoint) {
+      LOG(warn) << "CbmCaMCModule: TOF MC point with iExtId = " << iExtId << ", iEvent = " << iEvent
+                << ", iFile = " << iFile << " does not exist";
+      return false;
+    }
+    pExtPoint->Position(posIn);
+    pExtPoint->Position(posOut);
+    pExtPoint->Momentum(momIn);
+    pExtPoint->Momentum(momOut);
+    time    = pExtPoint->GetTime();
+    iTmcExt = pExtPoint->GetTrackID();
+  }
+  if (iTmcExt < 0) {
+    LOG(warn) << "CbmCaMCModule: For MC point with iExtId = " << iExtId << ", iEvent = " << iEvent
+              << ", iFile = " << iFile << " MC track is undefined (has ID = " << iTmcExt << ')';
+    return false;
+  }
+  TVector3 posMid = 0.5 * (posIn + posOut);
+  TVector3 momMid = 0.5 * (momIn + momOut);
+
+  // // ----- Get station index
+  int stationID    = undef::kI32;  // Index of station in the array of active tracking stations
+  int nStationsGeo = fpParameters->GetNstationsGeometry(DetID);
+  // MVD, STS TODO: Try to extend for MuCh and TRD
+  if constexpr (L1DetectorID::kMvd == DetID || L1DetectorID::kSts == DetID) {
+    // Determine active station global index
+    double bestDist = 1.e+20;
+    for (int iStLocGeo = 0; iStLocGeo < nStationsGeo; ++iStLocGeo) {
+      int iStActive = fpParameters->GetStationIndexActive(iStLocGeo, DetID);
+      if (iStActive < 0) { continue; }
+      const L1Station& station = fpParameters->GetStation(iStActive);
+      double dist              = posIn.Z() - station.fZ[0];
+      if (fabs(dist) < fabs(bestDist)) {
+        bestDist  = dist;
+        stationID = iStActive;
+      }
+    }
+  }
+  // MuCh, TRD
+  else if constexpr (L1DetectorID::kMuch == DetID || L1DetectorID::kTrd == DetID) {
+    // Offset for MuCh - 2.5 cm, for TRD - 20. cm. TODO: Where did these values came from?
+    double offset = (L1DetectorID::kMuch == DetID) ? 2.5 : 20.;
+    for (int iStLocGeo = 0; iStLocGeo < nStationsGeo; ++iStLocGeo) {
+      int iStActive = fpParameters->GetStationIndexActive(iStLocGeo, DetID);
+      if (iStActive < 0) { continue; }
+      const L1Station& station = fpParameters->GetStation(iStActive);
+      if (posMid.Z() > station.fZ[0] - offset) { stationID = iStActive; }
+    }
+  }
+
+  // ----- Reject MC points falling out of the time slice
+  // STS, MuCh, TRD, TOF
+  if constexpr (DetID != L1DetectorID::kMvd) {
+    if (!fbLegacyEventMode) {  // fpTimeSlice != nullptr
+      double startT  = fpTimeSlice->GetStartTime();
+      double endT    = fpTimeSlice->GetEndTime();
+      double mcGlobT = time + fpMCEventList->GetEventTime(iEvent, iFile);
+
+      if ((startT > 0. && mcGlobT < startT) || (endT > 0. && mcGlobT > endT)) {
+        LOG(warn) << "CbmCaMCModule: MC point with iExtId = " << iExtId << ", iEvent = " << iEvent
+                  << ", iFile = " << iFile << " and det id " << int(DetID) << " fell out of the TS duration [" << startT
+                  << ", " << endT << "] with measured time = " << mcGlobT << " [ns]";
+        return false;
+      }
+    }
+  }
+
+  // ----- Fill MC point
+  point.SetEventId(iEvent);
+  point.SetFileId(iFile);
+  point.SetTime(time);
+
+  point.SetX(posMid.X());
+  point.SetY(posMid.Y());
+  point.SetZ(posMid.Z());
+
+  point.SetXIn(posIn.X());
+  point.SetYIn(posIn.Y());
+  point.SetZIn(posIn.Z());
+
+  point.SetXOut(posOut.X());
+  point.SetYOut(posOut.Y());
+  point.SetZOut(posOut.Z());
+
+  point.SetPx(momMid.X());
+  point.SetPy(momMid.Y());
+  point.SetPz(momMid.Z());
+
+  point.SetPxIn(momIn.X());
+  point.SetPyIn(momIn.Y());
+  point.SetPzIn(momIn.Z());
+
+  point.SetPxOut(momOut.X());
+  point.SetPyOut(momOut.Y());
+  point.SetPzOut(momOut.Z());
+
+  int iTmcInt = fMCData.FindInternalTrackIndex(iTmcExt, iEvent, iFile);
+
+  point.SetId(fMCData.GetNofPoints());  // select current number of points as a local id of points
+  point.SetTrackId(iTmcInt);
+  point.SetStationId(stationID);
+  point.SetDetectorId(DetID);
+
+  auto* pExtTrk = L1_DYNAMIC_CAST<CbmMCTrack*>(fpMCTracks->Get(iFile, iEvent, iTmcExt));
+  if (!pExtTrk) {
+    LOG(warn) << "CbmCaMCModule: MC track with iTmcExt = " << iTmcExt << ", iEvent = " << iEvent
+              << ", iFile = " << iFile << " MC track is undefined (nullptr)";
+    return false;
+  }
+  point.SetPdgCode(pExtTrk->GetPdgCode());
+  point.SetMotherId(pExtTrk->GetMotherId());
+
+  auto* pPdgDB = TDatabasePDG::Instance()->GetParticle(point.GetPdgCode());
+  point.SetMass(pPdgDB ? pPdgDB->Mass() : 0.);  /// TODO: Set from track
+  point.SetCharge(pPdgDB ? pPdgDB->Charge() / 3. : 0.);
+
+  return true;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// NOTE: template is used, because another template function FillMCPoint is used inside
+template<L1DetectorID DetID>
+void CbmCaMCModule::ReadMCPointsForDetector(CbmMCDataArray* pPoints)
+{
+  if constexpr (L1DetectorID::kTof != DetID) {
+    fvNofPointsUsed[int(DetID)] = 0;
+    for (const auto& key : fFileEventIDs) {
+      int iFile        = key.first;
+      int iEvent       = key.second;
+      int nPointsEvent = pPoints->Size(iFile, iEvent);
+      for (int iP = 0; iP < nPointsEvent; ++iP) {
+        ca::tools::MCPoint aPoint;
+        if (FillMCPoint<DetID>(iP, iEvent, iFile, aPoint)) {
+          aPoint.SetExternalId(CalcGlobMCPointIndex(iP, DetID));
+          int iPInt = fMCData.GetNofPoints();  // assign an index of point in internal container
+          if (aPoint.GetTrackId() > -1) { fMCData.GetTrack(aPoint.GetTrackId()).AddPointIndex(iPInt); }
+          fMCData.AddPoint(aPoint);
+          ++fvNofPointsUsed[int(DetID)];
+        }
+      }  // iP: end
+    }    // key: end
+  }
+}
+
+
+#endif  // CbmCaMCModule_h
diff --git a/reco/L1/L1LinkDef.h b/reco/L1/L1LinkDef.h
index 7a1cccc9b4..ad266732d7 100644
--- a/reco/L1/L1LinkDef.h
+++ b/reco/L1/L1LinkDef.h
@@ -30,6 +30,8 @@
 #pragma link C++ class CbmTrackingInputQaSts + ;
 #pragma link C++ class CbmTrackerInputQaTrd + ;
 #pragma link C++ class CbmTrackerInputQaTof + ;
+#pragma link C++ class CbmCaInputQaMuch + ;
+#pragma link C++ class CbmCaInputQaSts + ;
 #pragma link C++ class ca::tools::WindowFinder + ;
 
 #endif
diff --git a/reco/L1/qa/CbmCaInputQaSts.cxx b/reco/L1/qa/CbmCaInputQaSts.cxx
new file mode 100644
index 0000000000..7193fd7f17
--- /dev/null
+++ b/reco/L1/qa/CbmCaInputQaSts.cxx
@@ -0,0 +1,1193 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmCaInputQaSts.cxx
+/// \date   13.01.2023
+/// \brief  QA-task for CA tracking input from MuCh detector (implementation)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#include "CbmCaInputQaSts.h"
+
+#include "CbmAddress.h"
+#include "CbmCaQaCuts.h"
+#include "CbmMCDataArray.h"
+#include "CbmMCEventList.h"
+#include "CbmMCTrack.h"
+#include "CbmMatch.h"
+#include "CbmQaCanvas.h"
+#include "CbmQaEff.h"
+#include "CbmStsCluster.h"
+#include "CbmStsHit.h"
+#include "CbmStsPoint.h"
+#include "CbmStsTrackingInterface.h"
+#include "CbmTimeSlice.h"
+
+#include "FairRootManager.h"
+#include "Logger.h"
+
+#include "TBox.h"
+#include "TClonesArray.h"
+#include "TEllipse.h"
+#include "TF1.h"
+#include "TFormula.h"
+#include "TGraphAsymmErrors.h"
+#include "TH1F.h"
+#include "TH2F.h"
+#include "TMath.h"
+#include "TStyle.h"
+
+#include <algorithm>
+#include <fstream>
+#include <iomanip>
+#include <numeric>
+
+#include "L1Constants.h"
+
+ClassImp(CbmCaInputQaSts);
+
+namespace cuts = cbm::ca::qa::cuts;  // from CbmCaQaCuts.h
+namespace phys = L1Constants::phys;  // from L1Constants.h
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CbmCaInputQaSts::CbmCaInputQaSts(int verbose, bool isMCUsed) : CbmQaTask("CbmCaInputQaSts", verbose, isMCUsed) {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmCaInputQaSts::AnalyzeHistograms()
+{
+  int nSt = fpDetInterface->GetNtrackingStations();
+
+  // ----- Fit efficiency curves
+  TF1* pFitFn = new TF1("fitfn", "pol0", kEffRangeMin, kEffRangeMax);
+
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    pFitFn->SetParameter(0, 0.5);
+    fvpe_reco_eff_vs_r[iSt]->Fit(pFitFn);
+  }
+
+  // ----- Fit pull distributions
+  if (!gROOT->FindObject("Expk")) {
+    new TFormula("Expk", "TMath::Power(TMath::Sqrt(1 + x[1] * x[1] * x[0] * x[0]) + x[0] * x[1], 1./x[1])");
+  }
+
+  TF1* pPullFit = new TF1("pullFitGausn", "gausn", -10., 10.);
+  //TF1* pPullFit = new TF1("pullFitGausn", "[0] * Expk(-[2] * (x - [1]) * (x - [1]), [3])", -10., 10.);
+
+  auto FitPulls = [&](TH1* pH, TF1* pFit) {
+    pFit->SetParameter(0, 100);
+    pFit->SetParameter(1, 0.001);
+    pFit->SetParameter(2, 1.000);
+    //pFit->SetParameter(3, 0.5);
+    pH->Fit(pFit);
+  };
+
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    FitPulls(fvph_pull_x[iSt], pPullFit);
+    FitPulls(fvph_pull_y[iSt], pPullFit);
+    FitPulls(fvph_pull_u[iSt], pPullFit);
+    FitPulls(fvph_pull_v[iSt], pPullFit);
+    FitPulls(fvph_pull_t[iSt], pPullFit);
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool CbmCaInputQaSts::Check() const
+{
+  LOG_IF(info, fVerbose > 0) << fName << ": Checking QA ...";
+
+  bool res = true;
+
+  int nSt = fpDetInterface->GetNtrackingStations();
+
+
+  // **************************************************************
+  // ** Basic checks, available both for real and simulated data **
+  // **************************************************************
+
+  // ----- Checks for mismatches in the order of stations
+  //
+  std::vector<double> vStationPos(nSt, 0.);
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    vStationPos[iSt] = fpDetInterface->GetZ(iSt);
+  }
+
+  if (!std::is_sorted(vStationPos.cbegin(), vStationPos.cend(), [](int l, int r) { return l <= r; })) {
+    if (fVerbose > 0) {
+      LOG(error) << fName << ": stations are ordered improperly along the beam axis:";
+      for (auto zPos : vStationPos) {
+        LOG(error) << "\t- " << zPos;
+      }
+    }
+    res = false;
+  }
+
+  // ----- Checks for mismatch between station and hit z positions
+  //   The purpose of this block is to be ensured, that hits belong to the correct tracking station. For each tracking
+  // station a unified position z-coordinate is defined, which generally differs from the corresponding positions of
+  // reconstructed hits. This is due to non-regular position along the beam axis for each detector sensor. Thus, the
+  // positions of the hits along z-axis are distributed relatively to the position of the station in some range.
+  // If hits goes out this range, it can signal about a mismatch between hits and geometry. For limits of the range
+  // one should select large enough values to cover the difference described above and in the same time small enough
+  // to avoid overlaps with the neighboring stations.
+  //   For each station, a distribution of z_{hit} - z_{st} is integrated over the defined range and scaled by the
+  // total number of entries to the distribution. If this value is smaller then unity, then some of the hits belong to
+  // another station.
+  //
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    int nHits = fvph_hit_station_delta_z[iSt]->GetEntries();
+    if (!nHits) {
+      LOG_IF(error, fVerbose > 0) << fName << ": station " << iSt << " does not have hits";
+      res = false;
+      continue;
+    }
+    int iBinMin = fvph_hit_station_delta_z[iSt]->FindBin(-cuts::kMaxDzStHitSts);
+    int iBinMax = fvph_hit_station_delta_z[iSt]->FindBin(+cuts::kMaxDzStHitSts);
+
+    if (fvph_hit_station_delta_z[iSt]->Integral(iBinMin, iBinMax) < nHits) {
+      LOG_IF(error, fVerbose > 0) << fName << ": station " << iSt << " has mismatches in hit z-positions";
+      res = false;
+    }
+  }
+
+
+  // *******************************************************
+  // ** Additional checks, if MC information is available **
+  // *******************************************************
+
+  if (IsMCUsed()) {
+
+    // ----- Check efficiencies
+    // Error is raised, if any station has integrated efficiency lower then a selected threshold.
+    // Integrated efficiency is estimated with fitting the efficiency curve for hit distances (r) with a uniform
+    // distribution in the range from kEffRangeMin to kEffRangeMax, where the efficiency is assigned to be constant
+    //
+    LOG(info) << "-- Hit efficiency integrated over hit distance from station center";
+    LOG(info) << "\tintergration range: [" << kEffRangeMin << ", " << kEffRangeMax << "] cm";
+    LOG(info) << std::setw(10) << std::setfill(' ') << "St. ID" << ' ' << std::setw(12) << std::setfill(' ') << "eps.";
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      auto* pFitfn = (TF1*) fvpe_reco_eff_vs_r[iSt]->GetListOfFunctions()->FindObject("fitfn");
+      double eff   = pFitfn->GetParameter(0);
+      LOG(info) << std::setw(10) << std::setfill(' ') << iSt << ' ' << std::setw(12) << std::setfill(' ') << eff;
+      if (eff < cuts::kMinEff) {
+        LOG(error) << fName << ": station " << iSt << " has efficiency lower threshold (" << eff << " < "
+                   << cuts::kMinEff << ')';
+        res = false;
+      }
+    }
+
+    // ----- Checks for pulls
+    // For the hit pull is defined as a ration of the difference between hit and MC-point position or time component
+    // values and an error of the hit position (or time) component, calculated in the same z-positions. In ideal case,
+    // when the resolutions are well determined for detecting stations, the pull distributions should have a RMS equal
+    // to unity. Here we allow a RMS of the pull distributions to be varied in a predefined range. If the RMS runs out
+    // this range, QA task fails.
+    // TODO: Add checks for center
+
+    // Function returns width of histogram
+    auto GetPullWidth = [&](TH1* pHist) {
+      if (pHist->FindObject("pullFitGausn")) {
+        TF1* pFitFn = (TF1*) pHist->FindObject("pullFitGausn");
+        return pFitFn->GetParameter(2);
+      }
+      LOG(error) << fName << ": no fit function provided for pulls fitting (" << pHist->GetName() << "), RMS returned";
+      return pHist->GetRMS();
+    };
+
+    LOG(info) << "-- Hit pull RMS:";
+    LOG(info) << std::setw(10) << std::setfill(' ') << "St. ID" << ' ' << std::setw(12) << std::setfill(' ') << "RMS(x)"
+              << ' ' << std::setw(12) << std::setfill(' ') << "RMS(y)" << ' ' << std::setw(12) << std::setfill(' ')
+              << "RMS(u)" << ' ' << std::setw(12) << std::setfill(' ') << "RMS(v)" << ' ' << std::setw(12)
+              << std::setfill(' ') << "RMS(t)";
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      double rmsPullX = GetPullWidth(fvph_pull_x[iSt]);
+      double rmsPullY = GetPullWidth(fvph_pull_y[iSt]);
+      double rmsPullU = GetPullWidth(fvph_pull_u[iSt]);
+      double rmsPullV = GetPullWidth(fvph_pull_v[iSt]);
+      double rmsPullT = GetPullWidth(fvph_pull_t[iSt]);
+      LOG(info) << std::setw(10) << std::setfill(' ') << iSt << ' ' << std::setw(12) << std::setfill(' ') << rmsPullX
+                << ' ' << std::setw(12) << std::setfill(' ') << rmsPullY << ' ' << std::setw(12) << std::setfill(' ')
+                << rmsPullU << ' ' << std::setw(12) << std::setfill(' ') << rmsPullV << ' ' << std::setw(12)
+                << std::setfill(' ') << rmsPullT;
+    }
+  }
+
+  LOG_IF(error, !res) << fName << ": QA check failed";
+
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmCaInputQaSts::DeInit()
+{
+  // Vectors with pointers to histograms
+  fvph_hit_ypos_vs_xpos.clear();
+  fvph_hit_xpos_vs_zpos.clear();
+  fvph_hit_ypos_vs_zpos.clear();
+
+  fvph_hit_station_delta_z.clear();
+
+  fvph_hit_dx.clear();
+  fvph_hit_dy.clear();
+  fvph_hit_du.clear();
+  fvph_hit_dv.clear();
+  fvph_hit_dt.clear();
+
+  fvph_n_points_per_hit.clear();
+
+  fvph_point_ypos_vs_xpos.clear();
+  fvph_point_xpos_vs_zpos.clear();
+  fvph_point_ypos_vs_zpos.clear();
+
+  fvph_point_hit_delta_z.clear();
+
+  fvph_res_x.clear();
+  fvph_res_y.clear();
+  fvph_res_u.clear();
+  fvph_res_v.clear();
+  fvph_res_t.clear();
+
+  fvph_pull_x.clear();
+  fvph_pull_y.clear();
+  fvph_pull_u.clear();
+  fvph_pull_v.clear();
+  fvph_pull_t.clear();
+
+  fvph_res_x_vs_x.clear();
+  fvph_res_y_vs_y.clear();
+  fvph_res_u_vs_u.clear();
+  fvph_res_v_vs_v.clear();
+  fvph_res_t_vs_t.clear();
+
+  fvph_pull_x_vs_x.clear();
+  fvph_pull_y_vs_y.clear();
+  fvph_pull_u_vs_u.clear();
+  fvph_pull_v_vs_v.clear();
+  fvph_pull_t_vs_t.clear();
+
+  fvpe_reco_eff_vs_xy.clear();
+  fvpe_reco_eff_vs_r.clear();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmCaInputQaSts::FillHistograms()
+{
+  int nSt       = fpDetInterface->GetNtrackingStations();
+  int nHits     = fpHits->GetEntriesFast();
+  int nMCevents = (IsMCUsed()) ? fpMCEventList->GetNofEvents() : -1;
+
+  std::vector<std::vector<int>> vNofHitsPerPoint;  // Number of hits per MC point in different MC events
+
+  if (IsMCUsed()) {
+    vNofHitsPerPoint.resize(nMCevents);
+    for (int iE = 0; iE < nMCevents; ++iE) {
+      int iFile   = fpMCEventList->GetFileIdByIndex(iE);
+      int iEvent  = fpMCEventList->GetEventIdByIndex(iE);
+      int nPoints = fpMCPoints->Size(iFile, iEvent);
+      vNofHitsPerPoint[iE].resize(nPoints, 0);
+    }
+  }
+
+  for (int iH = 0; iH < nHits; ++iH) {
+    const auto* pHit = dynamic_cast<const CbmStsHit*>(fpHits->At(iH));
+    LOG_IF(fatal, !pHit) << fName << ": hit with iH = " << iH << " is not an CbmStsHit (dynamic cast failed)";
+
+    // *************************
+    // ** Reconstructed hit QA
+
+    // Hit station geometry info
+    int iSt = fpDetInterface->GetTrackingStationIndex(pHit);
+    LOG_IF(fatal, iSt < 0 || iSt >= nSt) << fName << ": index of station (" << iSt << ") is out of range "
+                                         << "for hit with id = " << iH;
+    double stPhiF = fpDetInterface->GetStripsStereoAngleFront(iSt);  // STS front strips stereo angle
+    double stPhiB = fpDetInterface->GetStripsStereoAngleBack(iSt);   // STS back strips stereo angle
+
+    // Hit position
+    double xHit = pHit->GetX();
+    double yHit = pHit->GetY();
+    double zHit = pHit->GetZ();
+    double uHit = xHit * cos(stPhiF) + yHit * sin(stPhiB);
+    double vHit = xHit * cos(stPhiB) + yHit * sin(stPhiB);
+
+    double duHit = pHit->GetDu();
+    double dvHit = pHit->GetDv();
+    double dxHit = pHit->GetDx();
+    double dyHit = pHit->GetDy();
+    //double dxyHit = pHit->GetDxy();
+
+    // Hit time
+    double tHit  = pHit->GetTime();
+    double dtHit = pHit->GetTimeError();
+
+    fvph_hit_ypos_vs_xpos[iSt]->Fill(xHit, yHit);
+    fvph_hit_xpos_vs_zpos[iSt]->Fill(zHit, xHit);
+    fvph_hit_ypos_vs_zpos[iSt]->Fill(zHit, yHit);
+
+    fvph_hit_station_delta_z[iSt]->Fill(zHit - fpDetInterface->GetZ(iSt));
+
+    fvph_hit_dx[iSt]->Fill(dxHit);
+    fvph_hit_dy[iSt]->Fill(dyHit);
+    fvph_hit_du[iSt]->Fill(duHit);
+    fvph_hit_dv[iSt]->Fill(dvHit);
+    fvph_hit_dt[iSt]->Fill(dtHit);
+
+    fvph_hit_ypos_vs_xpos[nSt]->Fill(xHit, yHit);
+    fvph_hit_xpos_vs_zpos[nSt]->Fill(zHit, xHit);
+    fvph_hit_ypos_vs_zpos[nSt]->Fill(zHit, yHit);
+    fvph_hit_dx[nSt]->Fill(dxHit);
+    fvph_hit_dy[nSt]->Fill(dyHit);
+    fvph_hit_du[nSt]->Fill(duHit);
+    fvph_hit_dv[nSt]->Fill(dvHit);
+    fvph_hit_dt[nSt]->Fill(dtHit);
+
+
+    // **********************
+    // ** MC information QA
+
+    if (IsMCUsed()) {
+      CbmMatch hitMatch = GetHitMatch(iH);
+
+      // Evaluate number of hits per one MC point
+      int nMCpoints = 0;  // Number of MC points for this hit
+      for (int iLink = 0; iLink < hitMatch.GetNofLinks(); ++iLink) {
+        const auto& link = hitMatch.GetLink(iLink);
+
+        int iP = link.GetIndex();  // Index of MC point
+
+        // Skip noisy links
+        if (iP < 0) { continue; }
+
+        ++nMCpoints;
+
+        int iE = fpMCEventList->GetEventIndex(link);
+
+        LOG_IF(fatal, iE < 0 || iE >= nMCevents) << fName << ": id of MC event is out of range (hit id = " << iH
+                                                 << ", link id = " << iLink << ", event id = " << iE << ')';
+
+        LOG_IF(fatal, iP >= (int) vNofHitsPerPoint[iE].size())
+          << fName << ": index of MC point is out of range (hit id = " << iH << ", link id = " << iLink
+          << ", event id = " << iE << ", point id = " << iP << ')';
+        vNofHitsPerPoint[iE][iP]++;
+      }
+
+      fvph_n_points_per_hit[iSt]->Fill(nMCpoints);
+      fvph_n_points_per_hit[nSt]->Fill(nMCpoints);
+
+      if (nMCpoints != 1) { continue; }  // ??
+
+      // The best link to in the match (probably, the cut on nMCpoints is meaningless)
+      const auto& bestPointLink = hitMatch.GetMatchedLink();
+
+      // Skip noisy links
+      if (bestPointLink.GetIndex() < 0) { continue; }
+
+      // Point matched by the best link
+      const auto* pMCPoint = dynamic_cast<const CbmStsPoint*>(fpMCPoints->Get(bestPointLink));
+      LOG_IF(fatal, !pMCPoint) << fName << ": MC point object does not exist for hit " << iH;
+
+      // MC track for this point
+      CbmLink bestTrackLink = bestPointLink;
+      bestTrackLink.SetIndex(pMCPoint->GetTrackID());
+      const auto* pMCTrack = dynamic_cast<const CbmMCTrack*>(fpMCTracks->Get(bestTrackLink));
+      LOG_IF(fatal, !pMCTrack) << fName << ": MC track object does not exist for hit " << iH << " and link: ";
+
+      double t0MC = fpMCEventList->GetEventTime(bestPointLink);
+      LOG_IF(fatal, t0MC < 0) << fName << ": MC time zero is lower then 0 ns: " << t0MC;
+
+
+      // ----- MC point properties
+      //
+      double mass = pMCTrack->GetMass();
+      //double charge = pMCTrack->GetCharge();
+      //double pdg    = pMCTrack->GetPdgCode();
+
+      // Entrance position and time
+      double xMC = pMCPoint->GetXIn();
+      double yMC = pMCPoint->GetYIn();
+      //double uMC = xMC * cos(stPhiF) + yMC * sin(stPhiF);
+      //double vMC = xMC * cos(stPhiB) + yMC * sin(stPhiB);
+      double zMC = pMCPoint->GetZIn();
+      double tMC = pMCPoint->GetTime() + t0MC;
+
+      // MC point entrance momenta
+      double pxMC = pMCPoint->GetPx();
+      double pyMC = pMCPoint->GetPy();
+      double pzMC = pMCPoint->GetPz();
+      double pMC  = sqrt(pxMC * pxMC + pyMC * pyMC + pzMC * pzMC);
+
+      // MC point exit momenta
+      double pxMCo = pMCPoint->GetPxOut();
+      double pyMCo = pMCPoint->GetPyOut();
+      double pzMCo = pMCPoint->GetPzOut();
+      double pMCo  = sqrt(pxMCo * pxMCo + pyMCo * pyMCo + pzMCo * pzMCo);
+
+      // Position and time shifted to z-coordinate of the hit
+      double shiftZ = zHit - zMC;  // Difference between hit and point z positions
+      double xMCs   = xMC + shiftZ * pxMC / pzMC;
+      double yMCs   = yMC + shiftZ * pyMC / pzMC;
+      double uMCs   = xMCs * cos(stPhiF) + yMCs * sin(stPhiF);
+      double vMCs   = xMCs * cos(stPhiB) + yMCs * sin(stPhiB);
+      double tMCs   = tMC + shiftZ / (pzMC * phys::kSpeedOfLight) * sqrt(mass * mass + pMC * pMC);
+
+      // Residuals
+      double xRes = xHit - xMCs;
+      double yRes = yHit - yMCs;
+      double uRes = uHit - uMCs;
+      double vRes = vHit - vMCs;
+      double tRes = tHit - tMCs;
+
+      // ------ Cuts
+
+      if (std::fabs(pMCPoint->GetPzOut()) < 0.01) { continue; }  // CUT ON MINIMUM MOMENTUM
+      //if (pMCo < cuts::kMinP) { continue; }  // Momentum threshold
+
+
+      fvph_point_ypos_vs_xpos[iSt]->Fill(xMC, yMC);
+      fvph_point_xpos_vs_zpos[iSt]->Fill(zMC, xMC);
+      fvph_point_ypos_vs_zpos[iSt]->Fill(zMC, yMC);
+
+      fvph_point_hit_delta_z[iSt]->Fill(shiftZ);
+
+      fvph_res_x[iSt]->Fill(xRes);
+      fvph_res_y[iSt]->Fill(yRes);
+      fvph_res_u[iSt]->Fill(uRes);
+      fvph_res_v[iSt]->Fill(vRes);
+      fvph_res_t[iSt]->Fill(tRes);
+
+      fvph_pull_x[iSt]->Fill(xRes / dxHit);
+      fvph_pull_y[iSt]->Fill(yRes / dyHit);
+      fvph_pull_u[iSt]->Fill(uRes / duHit);
+      fvph_pull_v[iSt]->Fill(vRes / dvHit);
+      fvph_pull_t[iSt]->Fill(tRes / dtHit);
+
+      fvph_res_x_vs_x[iSt]->Fill(xHit, xRes);
+      fvph_res_y_vs_y[iSt]->Fill(yHit, yRes);
+      fvph_res_u_vs_u[iSt]->Fill(uHit, uRes);
+      fvph_res_v_vs_v[iSt]->Fill(vHit, vRes);
+      fvph_res_t_vs_t[iSt]->Fill(tHit, tRes);
+
+      fvph_pull_x_vs_x[iSt]->Fill(xHit, xRes / dxHit);
+      fvph_pull_y_vs_y[iSt]->Fill(yHit, yRes / dyHit);
+      fvph_pull_u_vs_u[iSt]->Fill(uHit, uRes / duHit);
+      fvph_pull_v_vs_v[iSt]->Fill(vHit, vRes / dvHit);
+      fvph_pull_t_vs_t[iSt]->Fill(tHit, tRes / dtHit);
+
+      fvph_point_ypos_vs_xpos[nSt]->Fill(xMC, yMC);
+      fvph_point_xpos_vs_zpos[nSt]->Fill(zMC, xMC);
+      fvph_point_ypos_vs_zpos[nSt]->Fill(zMC, yMC);
+
+      fvph_point_hit_delta_z[nSt]->Fill(shiftZ);
+
+      fvph_res_x[nSt]->Fill(xRes);
+      fvph_res_y[nSt]->Fill(yRes);
+      fvph_res_u[nSt]->Fill(uRes);
+      fvph_res_v[nSt]->Fill(vRes);
+      fvph_res_t[nSt]->Fill(tRes);
+
+      fvph_pull_x[nSt]->Fill(xRes / dxHit);
+      fvph_pull_y[nSt]->Fill(yRes / dyHit);
+      fvph_pull_u[nSt]->Fill(uRes / duHit);
+      fvph_pull_v[nSt]->Fill(vRes / dvHit);
+      fvph_pull_t[nSt]->Fill(tRes / dtHit);
+
+      fvph_res_x_vs_x[nSt]->Fill(xHit, xRes);
+      fvph_res_y_vs_y[nSt]->Fill(yHit, yRes);
+      fvph_res_u_vs_u[nSt]->Fill(uHit, uRes);
+      fvph_res_v_vs_v[nSt]->Fill(vHit, vRes);
+      fvph_res_t_vs_t[nSt]->Fill(tHit, tRes);
+
+      fvph_pull_x_vs_x[nSt]->Fill(xHit, xRes / dxHit);
+      fvph_pull_y_vs_y[nSt]->Fill(yHit, yRes / dyHit);
+      fvph_pull_u_vs_u[nSt]->Fill(uHit, uRes / duHit);
+      fvph_pull_v_vs_v[nSt]->Fill(vHit, vRes / dvHit);
+      fvph_pull_t_vs_t[nSt]->Fill(tHit, tRes / dtHit);
+    }
+  }  // loop over hits: end
+
+  // Fill hit reconstruction efficiencies
+  if (IsMCUsed()) {
+    for (int iE = 0; iE < nMCevents; ++iE) {
+      int iFile   = fpMCEventList->GetFileIdByIndex(iE);
+      int iEvent  = fpMCEventList->GetEventIdByIndex(iE);
+      int nPoints = fpMCPoints->Size(iFile, iEvent);
+
+      for (int iP = 0; iP < nPoints; ++iP) {
+        const auto* pMCPoint = dynamic_cast<const CbmStsPoint*>(fpMCPoints->Get(iFile, iEvent, iP));
+        LOG_IF(fatal, !pMCPoint) << fName << ": MC point does not exist for iFile = " << iFile
+                                 << ", iEvent = " << iEvent << ", iP = " << iP;
+        int address = pMCPoint->GetDetectorID();
+        int iSt     = fpDetInterface->GetTrackingStationIndex(address);
+        LOG_IF(fatal, iSt < 0 || iSt >= nSt)
+          << fName << ": MC point for FEI = " << iFile << ", " << iEvent << ", " << iP << " and address " << address
+          << " has wrong station index: iSt = " << iSt;
+
+        double xMC = pMCPoint->GetXIn();
+        double yMC = pMCPoint->GetYIn();
+        double rMC = sqrt(xMC * xMC + yMC * yMC);
+
+        // Conditions under which point is accounted as reconstructed: point
+        bool isOneHitForOnePoint  = (vNofHitsPerPoint[iE][iP] == 1);
+        bool isSevHitsForOnePoint = (vNofHitsPerPoint[iE][iP] > 1);
+
+        fvpe_reco_eff_vs_xy[iSt]->Fill(isOneHitForOnePoint, xMC, yMC);
+        fvpe_reco_eff_vs_xy[nSt]->Fill(isOneHitForOnePoint, xMC, yMC);
+
+        fvpe_reco_eff_vs_r[iSt]->Fill(isOneHitForOnePoint, rMC);
+        fvpe_reco_eff_vs_r[nSt]->Fill(isOneHitForOnePoint, rMC);
+
+      }  // loop over MC-points: end
+    }    // loop over MC-events: end
+  }      // MC is used: end
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmCaInputQaSts::InitDataBranches()
+{
+  // STS tracking detector interface
+  fpDetInterface = CbmStsTrackingInterface::Instance();
+
+  LOG_IF(fatal, !fpDetInterface) << "\033[1;31m" << fName << ": tracking detector interface is undefined\033[0m";
+
+  // FairRootManager
+  auto* pFairRootManager = FairRootManager::Instance();
+  LOG_IF(fatal, !pFairRootManager) << "\033[1;31m" << fName << ": FairRootManager instance is a null pointer\033[0m";
+
+  // Time-slice
+  fpTimeSlice = dynamic_cast<CbmTimeSlice*>(pFairRootManager->GetObject("TimeSlice."));
+  LOG_IF(fatal, !fpTimeSlice) << "\033[1;31m" << fName << ": time-slice branch is not found\033[0m";
+
+  // ----- Reconstructed data-branches initialization
+
+  // Hits container
+  fpHits = dynamic_cast<TClonesArray*>(pFairRootManager->GetObject("StsHit"));
+  LOG_IF(fatal, !fpHits) << "\033[1;31m" << fName << ": container of reconstructed hits in STS is not found\033[0m";
+
+  // Clusters container
+  fpClusters = dynamic_cast<TClonesArray*>(pFairRootManager->GetObject("StsCluster"));
+  LOG_IF(fatal, !fpClusters) << "\033[1;31m" << fName << ": container of hit clusters in STS is not found\033[0m";
+
+
+  // ----- MC-information branches initialization
+  if (IsMCUsed()) {
+    // MC manager
+    fpMCDataManager = dynamic_cast<CbmMCDataManager*>(pFairRootManager->GetObject("MCDataManager"));
+    LOG_IF(fatal, !fpMCDataManager) << "\033[1;31m" << fName << ": MC data manager branch is not found\033[0m";
+
+    // MC event list
+    fpMCEventList = dynamic_cast<CbmMCEventList*>(pFairRootManager->GetObject("MCEventList."));
+    LOG_IF(fatal, !fpMCEventList) << "\033[1;31m" << fName << ": MC event list branch is not found\033[0m";
+
+    // MC tracks
+    fpMCTracks = fpMCDataManager->InitBranch("MCTrack");
+    LOG_IF(fatal, !fpMCTracks) << "\033[1;31m" << fName << ": MC track branch is not found\033[0m";
+
+    // MC points
+    fpMCPoints = fpMCDataManager->InitBranch("StsPoint");
+    LOG_IF(fatal, !fpMCTracks) << "\033[1;31m" << fName << ": MC point branch is not found for STS\033[0m";
+
+    // Cluster matches
+    fpClusterMatches = dynamic_cast<TClonesArray*>(pFairRootManager->GetObject("StsClusterMatch"));
+    LOG_IF(fatal, !fpClusterMatches) << "\033[1;31m]" << fName << ": cluster match branch is not found for STS\033[0m";
+  }
+
+  return kSUCCESS;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmCaInputQaSts::InitCanvases()
+{
+  gStyle->SetOptFit(1);
+  int nSt = fpDetInterface->GetNtrackingStations();
+
+  // ********************
+  // ** Drawing options
+
+  // Contours
+  constexpr auto contColor = kOrange + 7;
+  constexpr auto contWidth = 2;  // Line width
+  constexpr auto contStyle = 2;  // Line style
+  constexpr auto contFill  = 0;  // Fill style
+
+  // *********************************
+  // ** Hit occupancies
+
+  // ** Occupancy in XY plane
+  auto* pc_hit_ypos_vs_xpos = MakeCanvas<CbmQaCanvas>("hit_ypos_vs_xpos", "", 1600, 800);
+  pc_hit_ypos_vs_xpos->Divide2D(nSt);
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    pc_hit_ypos_vs_xpos->cd(iSt + 1);
+    fvph_hit_ypos_vs_xpos[iSt]->DrawCopy("colz", "");
+
+
+    // Reference size of the station saved to the detector interface
+    auto* pInnerCircle = new TEllipse(0., 0., fpDetInterface->GetRmin(iSt));
+    pInnerCircle->SetLineWidth(contWidth);
+    pInnerCircle->SetLineStyle(contStyle);
+    pInnerCircle->SetLineColor(contColor);
+    pInnerCircle->SetFillStyle(contFill);
+    pInnerCircle->Draw("SAME");
+
+    auto* pOuterCircle = new TEllipse(0., 0., fpDetInterface->GetRmax(iSt));
+    pOuterCircle->SetLineWidth(contWidth);
+    pOuterCircle->SetLineStyle(contStyle);
+    pOuterCircle->SetLineColor(contColor);
+    pOuterCircle->SetFillStyle(contFill);
+    pOuterCircle->Draw("SAME");
+
+    // Square boarders of station, which are used for the field approximation
+    double stXmin = +fpDetInterface->GetXmax(iSt);
+    double stXmax = -fpDetInterface->GetXmax(iSt);
+    double stYmin = -fpDetInterface->GetYmax(iSt);
+    double stYmax = +fpDetInterface->GetYmax(iSt);
+
+    auto* pBox = new TBox(stXmin, stYmin, stXmax, stYmax);
+    pBox->SetLineWidth(contWidth);
+    pBox->SetLineStyle(contStyle);
+    pBox->SetLineColor(contColor);
+    pBox->SetFillStyle(contFill);
+    pBox->Draw("SAME");
+  }
+
+  // ----- Occupancy projections
+  auto* pc_hit_xpos = MakeCanvas<CbmQaCanvas>("hit_xpos", "", 1400, 800);
+  pc_hit_xpos->Divide2D(nSt);
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    pc_hit_xpos->cd(iSt + 1);
+    auto* phProj = fvph_hit_ypos_vs_xpos[iSt]->ProjectionY();
+    phProj->DrawCopy();
+  }
+
+  auto* pc_hit_ypos = MakeCanvas<CbmQaCanvas>("hit_ypos", "", 1400, 800);
+  pc_hit_ypos->Divide2D(nSt);
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    pc_hit_ypos->cd(iSt + 1);
+    auto* phProj = fvph_hit_ypos_vs_xpos[iSt]->ProjectionX();
+    phProj->DrawCopy();
+  }
+
+
+  // ** Occupancy in XZ plane
+  MakeCanvas<CbmQaCanvas>("hit_xpos_vs_zpos", "", 600, 400);
+  fvph_hit_xpos_vs_zpos[nSt]->DrawCopy("colz", "");
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    // Station positions in detector IFS
+    double stZmin = fpDetInterface->GetZ(iSt) - 0.5 * fpDetInterface->GetThickness(iSt);
+    double stZmax = fpDetInterface->GetZ(iSt) + 0.5 * fpDetInterface->GetThickness(iSt);
+    double stHmin = -fpDetInterface->GetRmax(iSt);
+    double stHmax = +fpDetInterface->GetRmax(iSt);
+
+    auto* pBox = new TBox(stZmin, stHmin, stZmax, stHmax);
+    pBox->SetLineWidth(contWidth);
+    pBox->SetLineStyle(contStyle);
+    pBox->SetLineColor(contColor);
+    pBox->SetFillStyle(contFill);
+    pBox->Draw("SAME");
+  }
+
+  // ** Occupancy in YZ plane
+  MakeCanvas<CbmQaCanvas>("hit_ypos_vs_zpos", "", 600, 400);
+  fvph_hit_ypos_vs_zpos[nSt]->DrawCopy("colz", "");
+  for (int iSt = 0; iSt < nSt; ++iSt) {
+    // Station positions in detector IFS
+    double stZmin = fpDetInterface->GetZ(iSt) - 0.5 * fpDetInterface->GetThickness(iSt);
+    double stZmax = fpDetInterface->GetZ(iSt) + 0.5 * fpDetInterface->GetThickness(iSt);
+    double stHmin = -fpDetInterface->GetRmax(iSt);
+    double stHmax = +fpDetInterface->GetRmax(iSt);
+
+    auto* pBox = new TBox(stZmin, stHmin, stZmax, stHmax);
+    pBox->SetLineWidth(contWidth);
+    pBox->SetLineStyle(contStyle);
+    pBox->SetLineColor(contColor);
+    pBox->SetFillStyle(contFill);
+    pBox->Draw("SAME");
+  }
+
+  // ************
+  // ************
+
+  if (IsMCUsed()) {
+
+    // **********************
+    // ** Point occupancies
+
+    // ** Occupancy in XY plane
+    auto* pc_point_ypos_vs_xpos = MakeCanvas<CbmQaCanvas>("point_ypos_vs_xpos", "", 1600, 800);
+    pc_point_ypos_vs_xpos->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_point_ypos_vs_xpos->cd(iSt + 1);
+      fvph_point_ypos_vs_xpos[iSt]->DrawCopy("colz", "");
+
+      // Reference size of the station saved to the detector interface
+      auto* pInnerCircle = new TEllipse(0., 0., fpDetInterface->GetRmin(iSt));
+      pInnerCircle->SetLineWidth(contWidth);
+      pInnerCircle->SetLineStyle(contStyle);
+      pInnerCircle->SetLineColor(contColor);
+      pInnerCircle->SetFillStyle(contFill);
+      pInnerCircle->Draw("SAME");
+
+      auto* pOuterCircle = new TEllipse(0., 0., fpDetInterface->GetRmax(iSt));
+      pOuterCircle->SetLineWidth(contWidth);
+      pOuterCircle->SetLineStyle(contStyle);
+      pOuterCircle->SetLineColor(contColor);
+      pOuterCircle->SetFillStyle(contFill);
+      pOuterCircle->Draw("SAME");
+
+      // Square boarders of station, which are used for the field approximation
+      double stXmin = +fpDetInterface->GetXmax(iSt);
+      double stXmax = -fpDetInterface->GetXmax(iSt);
+      double stYmin = -fpDetInterface->GetYmax(iSt);
+      double stYmax = +fpDetInterface->GetYmax(iSt);
+
+      auto* pBox = new TBox(stXmin, stYmin, stXmax, stYmax);
+      pBox->SetLineWidth(contWidth);
+      pBox->SetLineStyle(contStyle);
+      pBox->SetLineColor(contColor);
+      pBox->SetFillStyle(contFill);
+      pBox->Draw("SAME");
+    }
+
+    // ** Occupancy in XZ plane
+    MakeCanvas<CbmQaCanvas>("point_xpos_vs_zpos", "", 600, 400);
+    fvph_point_xpos_vs_zpos[nSt]->SetStats(false);
+    fvph_point_xpos_vs_zpos[nSt]->DrawCopy("colz", "");
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      // Station positions in detector IFS
+      double stZmin = fpDetInterface->GetZ(iSt) - 0.5 * fpDetInterface->GetThickness(iSt);
+      double stZmax = fpDetInterface->GetZ(iSt) + 0.5 * fpDetInterface->GetThickness(iSt);
+      double stHmin = -fpDetInterface->GetRmax(iSt);
+      double stHmax = +fpDetInterface->GetRmax(iSt);
+
+      auto* pBox = new TBox(stZmin, stHmin, stZmax, stHmax);
+      pBox->SetLineWidth(contWidth);
+      pBox->SetLineStyle(contStyle);
+      pBox->SetLineColor(contColor);
+      pBox->SetFillStyle(contFill);
+      pBox->Draw("SAME");
+    }
+
+    // ** Occupancy in YZ plane
+    MakeCanvas<CbmQaCanvas>("point_ypos_vs_zpos", "", 600, 400);
+    fvph_point_ypos_vs_zpos[nSt]->SetStats(false);
+    fvph_point_ypos_vs_zpos[nSt]->DrawCopy("colz", "");
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      // Station positions in detector IFS
+      double stZmin = fpDetInterface->GetZ(iSt) - 0.5 * fpDetInterface->GetThickness(iSt);
+      double stZmax = fpDetInterface->GetZ(iSt) + 0.5 * fpDetInterface->GetThickness(iSt);
+      double stHmin = -fpDetInterface->GetRmax(iSt);
+      double stHmax = +fpDetInterface->GetRmax(iSt);
+
+      auto* pBox = new TBox(stZmin, stHmin, stZmax, stHmax);
+      pBox->SetLineWidth(contWidth);
+      pBox->SetLineStyle(contStyle);
+      pBox->SetLineColor(contColor);
+      pBox->SetFillStyle(contFill);
+      pBox->Draw("SAME");
+    }
+
+    // **************
+    // ** Residuals
+
+    // x-coordinate
+    auto* pc_res_x = MakeCanvas<CbmQaCanvas>("res_x", "Residuals for x coordinate", 1600, 800);
+    pc_res_x->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_res_x->cd(iSt + 1);
+      fvph_res_x[iSt]->DrawCopy("", "");
+    }
+
+    // y-coordinate
+    auto* pc_res_y = MakeCanvas<CbmQaCanvas>("res_y", "Residuals for y coordinate", 1600, 800);
+    pc_res_y->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_res_y->cd(iSt + 1);
+      fvph_res_y[iSt]->DrawCopy("", "");
+    }
+
+    // x-coordinate
+    auto* pc_res_u = MakeCanvas<CbmQaCanvas>("res_u", "Residuals for u coordinate", 1600, 800);
+    pc_res_u->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_res_u->cd(iSt + 1);
+      fvph_res_u[iSt]->DrawCopy("", "");
+    }
+
+    // x-coordinate
+    auto* pc_res_v = MakeCanvas<CbmQaCanvas>("res_v", "Residuals for v coordinate", 1600, 800);
+    pc_res_v->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_res_v->cd(iSt + 1);
+      fvph_res_v[iSt]->DrawCopy("", "");
+    }
+
+    // x-coordinate
+    auto* pc_res_t = MakeCanvas<CbmQaCanvas>("res_t", "Residuals for time", 1600, 800);
+    pc_res_t->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_res_t->cd(iSt + 1);
+      fvph_res_t[iSt]->DrawCopy("", "");
+    }
+
+
+    // **********
+    // ** Pulls
+
+    // x-coordinate
+    auto* pc_pull_x = MakeCanvas<CbmQaCanvas>("pull_x", "Pulls for x coordinate", 1600, 800);
+    pc_pull_x->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_pull_x->cd(iSt + 1);
+      fvph_pull_x[iSt]->DrawCopy("", "");
+    }
+
+    // y-coordinate
+    auto* pc_pull_y = MakeCanvas<CbmQaCanvas>("pull_y", "Pulls for y coordinate", 1600, 800);
+    pc_pull_y->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_pull_y->cd(iSt + 1);
+      fvph_pull_y[iSt]->DrawCopy("", "");
+    }
+
+    // x-coordinate
+    auto* pc_pull_u = MakeCanvas<CbmQaCanvas>("pull_u", "Pulls for u coordinate", 1600, 800);
+    pc_pull_u->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_pull_u->cd(iSt + 1);
+      fvph_pull_u[iSt]->DrawCopy("", "");
+    }
+
+    // x-coordinate
+    auto* pc_pull_v = MakeCanvas<CbmQaCanvas>("pull_v", "Pulls for v coordinate", 1600, 800);
+    pc_pull_v->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_pull_v->cd(iSt + 1);
+      fvph_pull_v[iSt]->DrawCopy("", "");
+    }
+
+    // x-coordinate
+    auto* pc_pull_t = MakeCanvas<CbmQaCanvas>("pull_t", "Pulls for time", 1600, 800);
+    pc_pull_t->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_pull_t->cd(iSt + 1);
+      fvph_pull_t[iSt]->DrawCopy("", "");
+    }
+
+
+    // ************************************
+    // ** Hit reconstruction efficiencies
+
+    auto* pc_reco_eff_vs_r =
+      MakeCanvas<CbmQaCanvas>("reco_eff_vs_r", "Hit efficiencies wrt distance from center", 1600, 800);
+    pc_reco_eff_vs_r->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_reco_eff_vs_r->cd(iSt + 1);
+      fvpe_reco_eff_vs_r[iSt]->Paint("AP");
+      auto* pGr = dynamic_cast<TGraphAsymmErrors*>(fvpe_reco_eff_vs_r[iSt]->GetPaintedGraph());
+      if (!pGr) {
+        LOG(error) << fName << ": unable to get painted graph from efficiency " << fvpe_reco_eff_vs_xy[iSt]->GetName();
+        continue;
+      }
+      pGr->DrawClone("AP");
+    }
+
+    auto* pc_reco_eff_vs_xy = MakeCanvas<CbmQaCanvas>("reco_eff_vs_xy", "Hit efficiencies wrt x and y", 1600, 800);
+    pc_reco_eff_vs_xy->Divide2D(nSt);
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      pc_reco_eff_vs_xy->cd(iSt + 1);
+      fvpe_reco_eff_vs_xy[iSt]->Paint("colz");
+      auto* pH2 = dynamic_cast<TH2F*>(fvpe_reco_eff_vs_xy[iSt]->GetPaintedHistogram());
+      if (!pH2) {
+        LOG(error) << fName << ": unable to get painted histogram from efficiency "
+                   << fvpe_reco_eff_vs_xy[iSt]->GetName();
+        continue;
+      }
+      pH2->DrawCopy("colz", "");
+    }
+  }
+
+  return kSUCCESS;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+InitStatus CbmCaInputQaSts::InitHistograms()
+{
+  int nSt = fpDetInterface->GetNtrackingStations();
+
+  // ----- Histograms initialization
+  fvph_hit_ypos_vs_xpos.resize(nSt + 1, nullptr);
+  fvph_hit_ypos_vs_zpos.resize(nSt + 1, nullptr);
+  fvph_hit_xpos_vs_zpos.resize(nSt + 1, nullptr);
+
+  fvph_hit_station_delta_z.resize(nSt);
+
+  fvph_hit_dx.resize(nSt + 1, nullptr);
+  fvph_hit_dy.resize(nSt + 1, nullptr);
+  fvph_hit_dt.resize(nSt + 1, nullptr);
+  fvph_hit_dv.resize(nSt + 1, nullptr);
+  fvph_hit_du.resize(nSt + 1, nullptr);
+
+  for (int iSt = 0; iSt <= nSt; ++iSt) {
+    TString nsuff = (iSt == nSt) ? "" : Form("_st%d", iSt);               // Histogram name suffix
+    TString tsuff = (iSt == nSt) ? "" : Form(" in STS station %d", iSt);  // Histogram title suffix
+    TString sN    = "";
+    TString sT    = "";
+
+    // Hit occupancy
+    sN                         = (TString) "hit_ypos_vs_xpos" + nsuff;
+    sT                         = (TString) "Hit occupancy in xy-Plane" + tsuff + ";x_{hit} [cm];y_{hit} [cm]";
+    fvph_hit_ypos_vs_xpos[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, kRHitX[0], kRHitX[1], kNbins, kRHitY[0], kRHitY[1]);
+
+    sN                         = (TString) "hit_xpos_vs_zpos" + nsuff;
+    sT                         = (TString) "Hit occupancy in xz-Plane" + tsuff + ";z_{hit} [cm];x_{hit} [cm]";
+    fvph_hit_xpos_vs_zpos[iSt] = MakeHisto<TH2F>(sN, sT, kNbinsZ, kRHitZ[0], kRHitZ[1], kNbins, kRHitX[0], kRHitX[1]);
+
+    sN                         = (TString) "hit_ypos_vs_zpos" + nsuff;
+    sT                         = (TString) "Hit occupancy in yz-plane" + tsuff + ";z_{hit} [cm];y_{hit} [cm]";
+    fvph_hit_ypos_vs_zpos[iSt] = MakeHisto<TH2F>(sN, sT, kNbinsZ, kRHitZ[0], kRHitZ[1], kNbins, kRHitY[0], kRHitY[1]);
+
+    // Hit errors
+    sN               = (TString) "hit_dx" + nsuff;
+    sT               = (TString) "Hit position error along x-axis" + tsuff + ";dx_{hit} [cm]";
+    fvph_hit_dx[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRHitDx[0], kRHitDx[1]);
+
+    sN               = (TString) "hit_dy" + nsuff;
+    sT               = (TString) "Hit position error along y-axis" + tsuff + ";dy_{hit} [cm]";
+    fvph_hit_dy[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRHitDy[0], kRHitDy[1]);
+
+    sN               = (TString) "hit_du" + nsuff;
+    sT               = (TString) "Hit position error across front strips" + tsuff + ";du_{hit} [cm]";
+    fvph_hit_du[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRHitDu[0], kRHitDu[1]);
+
+    sN               = (TString) "hit_dv" + nsuff;
+    sT               = (TString) "Hit position error across back strips" + tsuff + ";dv_{hit} [cm]";
+    fvph_hit_dv[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRHitDv[0], kRHitDv[1]);
+
+    sN               = (TString) "hit_dt" + nsuff;
+    sT               = (TString) "Hit time error" + tsuff + ";dt_{hit} [ns]";
+    fvph_hit_dt[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRHitDt[0], kRHitDt[1]);
+
+    if (iSt == nSt) { continue; }  // Following histograms are defined only for separate station
+
+    sN = (TString) "hit_station_delta_z" + nsuff;
+    sT = (TString) "Different between hit and station z-positions" + tsuff + ";z_{hit} - z_{st} [cm]";
+    fvph_hit_station_delta_z[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, -5., 5);
+
+  }  // loop over station index: end
+
+  // ----- Initialize histograms, which are use MC-information
+  if (IsMCUsed()) {
+    // Resize histogram vectors
+    fvph_n_points_per_hit.resize(nSt + 1, nullptr);
+    fvph_point_ypos_vs_xpos.resize(nSt + 1, nullptr);
+    fvph_point_xpos_vs_zpos.resize(nSt + 1, nullptr);
+    fvph_point_ypos_vs_zpos.resize(nSt + 1, nullptr);
+    fvph_point_hit_delta_z.resize(nSt + 1, nullptr);
+    fvph_res_x.resize(nSt + 1, nullptr);
+    fvph_res_y.resize(nSt + 1, nullptr);
+    fvph_res_u.resize(nSt + 1, nullptr);
+    fvph_res_v.resize(nSt + 1, nullptr);
+    fvph_res_t.resize(nSt + 1, nullptr);
+    fvph_pull_x.resize(nSt + 1, nullptr);
+    fvph_pull_y.resize(nSt + 1, nullptr);
+    fvph_pull_u.resize(nSt + 1, nullptr);
+    fvph_pull_v.resize(nSt + 1, nullptr);
+    fvph_pull_t.resize(nSt + 1, nullptr);
+    fvph_res_x_vs_x.resize(nSt + 1, nullptr);
+    fvph_res_y_vs_y.resize(nSt + 1, nullptr);
+    fvph_res_u_vs_u.resize(nSt + 1, nullptr);
+    fvph_res_v_vs_v.resize(nSt + 1, nullptr);
+    fvph_res_t_vs_t.resize(nSt + 1, nullptr);
+    fvph_pull_x_vs_x.resize(nSt + 1, nullptr);
+    fvph_pull_y_vs_y.resize(nSt + 1, nullptr);
+    fvph_pull_u_vs_u.resize(nSt + 1, nullptr);
+    fvph_pull_v_vs_v.resize(nSt + 1, nullptr);
+    fvph_pull_t_vs_t.resize(nSt + 1, nullptr);
+    fvpe_reco_eff_vs_xy.resize(nSt + 1, nullptr);
+    fvpe_reco_eff_vs_r.resize(nSt + 1, nullptr);
+
+    for (int iSt = 0; iSt <= nSt; ++iSt) {
+      TString nsuff = (iSt == nSt) ? "" : Form("_st%d", iSt);               // Histogram name suffix
+      TString tsuff = (iSt == nSt) ? "" : Form(" in STS station %d", iSt);  // Histogram title suffix
+      TString sN    = "";
+      TString sT    = "";
+
+      sN                         = (TString) "n_points_per_hit" + nsuff;
+      sT                         = (TString) "Number of points per hit" + tsuff + ";N_{point}/hit";
+      fvph_n_points_per_hit[iSt] = MakeHisto<TH1F>(sN, sT, 10, -0.5, 9.5);
+
+      // Point occupancy
+      sN = (TString) "point_ypos_vs_xpos" + nsuff;
+      sT = (TString) "Point occupancy in XY plane" + tsuff + ";x_{MC} [cm];y_{MC} [cm]";
+      fvph_point_ypos_vs_xpos[iSt] =
+        MakeHisto<TH2F>(sN, sT, kNbins, kRHitX[0], kRHitX[1], kNbins, kRHitY[0], kRHitY[1]);
+
+      sN = (TString) "point_xpos_vs_zpos" + nsuff;
+      sT = (TString) "Point Occupancy in XZ plane" + tsuff + ";z_{MC} [cm];x_{MC} [cm]";
+      fvph_point_xpos_vs_zpos[iSt] =
+        MakeHisto<TH2F>(sN, sT, kNbinsZ, kRHitZ[0], kRHitZ[1], kNbins, kRHitX[0], kRHitX[1]);
+
+      sN = (TString) "point_ypos_vs_zpos" + nsuff;
+      sT = (TString) "Point Occupancy in YZ Plane" + tsuff + ";z_{MC} [cm];y_{MC} [cm]";
+      fvph_point_ypos_vs_zpos[iSt] =
+        MakeHisto<TH2F>(sN, sT, kNbinsZ, kRHitZ[0], kRHitZ[1], kNbins, kRHitY[0], kRHitY[1]);
+
+      // Difference between MC input z and hit z coordinates
+      sN = (TString) "point_hit_delta_z" + nsuff;
+      sT = (TString) "Distance between STS point and hit along z axis" + tsuff + ";z_{reco} - z_{MC} [cm]";
+      fvph_point_hit_delta_z[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, -0.04, 0.04);
+
+      sN              = (TString) "res_x" + nsuff;
+      sT              = (TString) "Residuals for x-coordinate" + tsuff + ";x_{reco} - x_{MC} [cm]";
+      fvph_res_x[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRResX[0], kRResX[1]);
+
+      sN              = (TString) "res_y" + nsuff;
+      sT              = (TString) "Residuals for y-coordinate" + tsuff + ";y_{reco} - y_{MC} [cm]";
+      fvph_res_y[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRResY[0], kRResY[1]);
+
+      sN              = (TString) "res_u" + nsuff;
+      sT              = (TString) "Residuals for front strips coordinate" + tsuff + ";u_{reco} - u_{MC} [cm]";
+      fvph_res_u[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRResU[0], kRResU[1]);
+
+      sN              = (TString) "res_v" + nsuff;
+      sT              = (TString) "Residuals for back strips coordinate" + tsuff + ";v_{reco} - v_{MC} [cm]";
+      fvph_res_v[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRResV[1], kRResV[1]);
+
+      sN              = (TString) "res_t" + nsuff;
+      sT              = (TString) "Residuals for time" + tsuff + ";t_{reco} - t_{MC} [ns]";
+      fvph_res_t[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRResT[0], kRResT[1]);
+
+      sN               = (TString) "pull_x" + nsuff;
+      sT               = (TString) "Pulls for x-coordinate" + tsuff + ";(x_{reco} - x_{MC}) / #sigma_{x}^{reco}";
+      fvph_pull_x[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRPullX[0], kRPullX[1]);
+
+      sN               = (TString) "pull_y" + nsuff;
+      sT               = (TString) "Pulls for y-coordinate" + tsuff + ";(y_{reco} - y_{MC}) / #sigma_{y}^{reco}";
+      fvph_pull_y[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRPullY[0], kRPullY[1]);
+
+      sN = (TString) "pull_u" + nsuff;
+      sT = (TString) "Pulls for front strips coordinate" + tsuff + ";(u_{reco} - u_{MC}) / #sigma_{u}^{reco}";
+      fvph_pull_u[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRPullU[0], kRPullU[1]);
+
+      sN = (TString) "pull_v" + nsuff;
+      sT = (TString) "Pulls for back strips coordinate" + tsuff + ";(v_{reco} - v_{MC}) / #sigma_{v}^{reco}";
+      fvph_pull_v[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRPullV[0], kRPullV[1]);
+
+      sN               = (TString) "pull_t" + nsuff;
+      sT               = (TString) "Pulls for time" + tsuff + ";(t_{reco} - t_{MC}) / #sigma_{t}^{reco}";
+      fvph_pull_t[iSt] = MakeHisto<TH1F>(sN, sT, kNbins, kRPullT[0], kRPullT[1]);
+
+      sN                   = (TString) "res_x_vs_x" + nsuff;
+      sT                   = (TString) "Residuals for x-coordinate" + tsuff + ";x_{reco} [cm];x_{reco} - x_{MC} [cm]";
+      fvph_res_x_vs_x[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRResX[0], kRResX[1]);
+
+      sN                   = (TString) "res_y_vs_y" + nsuff;
+      sT                   = (TString) "Residuals for y-coordinate" + tsuff + ";y_{reco} [cm];y_{reco} - y_{MC} [cm]";
+      fvph_res_y_vs_y[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRResY[0], kRResY[1]);
+
+      sN = (TString) "res_u_vs_u" + nsuff;
+      sT = (TString) "Residuals for front strips coordinate" + tsuff + ";u_{reco} [cm];u_{reco} - u_{MC} [cm]";
+      fvph_res_u_vs_u[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRResU[0], kRResU[1]);
+
+      sN = (TString) "res_v_vs_u" + nsuff;
+      sT = (TString) "Residuals for back strips coordinate" + tsuff + ";v_{reco} [cm];v_{reco} - v_{MC} [cm]";
+      fvph_res_v_vs_v[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRResV[0], kRResV[1]);
+
+      sN                   = (TString) "res_t_vs_t" + nsuff;
+      sT                   = (TString) "Residuals for time" + tsuff + "t_{reco} [ns];t_{reco} - t_{MC} [ns]";
+      fvph_res_t_vs_t[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRResT[0], kRResT[1]);
+
+      sN = (TString) "pull_x_vs_x" + nsuff;
+      sT = (TString) "Pulls for x-coordinate" + tsuff + "x_{reco} [cm];(x_{reco} - x_{MC}) / #sigma_{x}^{reco}";
+      fvph_pull_x_vs_x[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRPullX[0], kRPullX[1]);
+
+      sN = (TString) "pull_y_vs_y" + nsuff;
+      sT = (TString) "Pulls for y-coordinate" + tsuff + ";y_{reco} [cm];(y_{reco} - y_{MC}) / #sigma_{y}^{reco}";
+      fvph_pull_y_vs_y[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRPullY[0], kRPullY[1]);
+
+      sN = (TString) "pull_u_vs_u" + nsuff;
+      sT = (TString) "Pulls for front strips coordinate" + tsuff
+           + ";u_{reco} [cm];(u_{reco} - u_{MC}) / #sigma_{u}^{reco}";
+      fvph_pull_u_vs_u[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRPullU[0], kRPullU[1]);
+
+      sN = (TString) "pull_v_vs_v" + nsuff;
+      sT =
+        (TString) "Pulls for back strips coordinate" + tsuff + ";v_{reco} [cm];(v_{reco} - v_{MC}) / #sigma_{v}^{reco}";
+      fvph_pull_v_vs_v[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRPullV[0], kRPullV[1]);
+
+      sN = (TString) "pull_t_vs_t" + nsuff;
+      sT = (TString) "Pulls for time" + tsuff + ";t_{reco} [ns];(t_{reco} - t_{MC}) / #sigma_{t}^{reco}";
+      fvph_pull_t_vs_t[iSt] = MakeHisto<TH2F>(sN, sT, kNbins, 0, 0, kNbins, kRPullT[0], kRPullT[1]);
+
+
+      sN                       = (TString) "reco_eff_vs_xy" + nsuff;
+      sT                       = (TString) "Hit rec. efficiency" + tsuff + ";x_{MC} [cm];y_{MC} [cm];#epsilon";
+      fvpe_reco_eff_vs_xy[iSt] = MakeEfficiency<TEfficiency>(sN, sT, 100, -50, 50, 100, -50, 50);
+
+      sN                      = (TString) "reco_eff_vs_r" + nsuff;
+      sT                      = (TString) "Hit rec. efficiency" + tsuff + ";r_{MC} [cm];#epsilon";
+      fvpe_reco_eff_vs_r[iSt] = MakeEfficiency<TEfficiency>(sN, sT, 100, -70, 70);
+    }
+  }
+  return kSUCCESS;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CbmMatch CbmCaInputQaSts::GetHitMatch(int iHit) const
+{
+  CbmMatch aMatch;
+
+  const auto* pHit = static_cast<const CbmStsHit*>(fpHits->At(iHit));  // NOTE: dynamic cast check is done in main loop
+
+  // Get front and back clusters
+  int iCf = pHit->GetFrontClusterId();
+  int iCb = pHit->GetBackClusterId();
+
+  // TODO: errors instead of fatals?
+  LOG_IF(fatal, iCf < 0) << fName << ": hit with id = " << iHit << " has wrong front cluster (iCf = " << iCf << ')';
+  LOG_IF(fatal, iCb < 0) << fName << ": hit with id = " << iHit << " has wrong back cluster (iCb = " << iCb << ')';
+
+  const auto* pClusterF = dynamic_cast<const CbmStsCluster*>(fpClusters->At(iCf));
+  const auto* pClusterB = dynamic_cast<const CbmStsCluster*>(fpClusters->At(iCb));
+
+  LOG_IF(fatal, !pClusterF) << fName << ": front cluster does not exist for hit with id = " << iHit
+                            << " and cluster id = " << iCf;
+  LOG_IF(fatal, !pClusterB) << fName << ": back cluster does not exist for hit with id = " << iHit
+                            << " and cluster id = " << iCb;
+
+  const auto* pClusterMatchF = dynamic_cast<const CbmMatch*>(fpClusterMatches->At(iCf));
+  const auto* pClusterMatchB = dynamic_cast<const CbmMatch*>(fpClusterMatches->At(iCb));
+
+  LOG_IF(fatal, !pClusterMatchF) << fName << ": front cluster match was not found for cluster with id = " << iCf;
+  LOG_IF(fatal, !pClusterMatchB) << fName << ": back cluster match was not found for cluster with id = " << iCf;
+
+  // Check addresses of hit and cluster
+  int addrHit      = pHit->GetAddress();
+  int addrClusterF = pClusterF->GetAddress();
+  int addrClusterB = pClusterB->GetAddress();
+  bool ifAddrCons  = addrHit == addrClusterF && addrClusterF == addrClusterB;
+  LOG_IF(fatal, !ifAddrCons) << fName << ": hit, front and (or) back cluster has inconsistent addresses: hit - "
+                             << addrHit << ", front - " << addrClusterF << ", back - " << addrClusterB << '\n';
+
+  // CUSTOM MATCHING IN STS: The link is selected if it is presented BOTH in front and back cluster, thus only true
+  //                         hits are matched. The standard matching in STS assumes only one (front or back) cluster
+  //                         to be matched, so it matches both true and fake hits.
+
+  for (int iLinkF = 0; iLinkF < pClusterMatchF->GetNofLinks(); ++iLinkF) {
+    const auto& linkF = pClusterMatchF->GetLink(iLinkF);
+    for (int iLinkB = 0; iLinkB < pClusterMatchB->GetNofLinks(); ++iLinkB) {
+      const auto& linkB = pClusterMatchB->GetLink(iLinkB);
+      if (linkF == linkB) {
+        aMatch.AddLink(linkF);
+        aMatch.AddLink(linkB);
+      }
+    }
+  }
+  return aMatch;
+}
diff --git a/reco/L1/qa/CbmCaInputQaSts.h b/reco/L1/qa/CbmCaInputQaSts.h
new file mode 100644
index 0000000000..3e205c7803
--- /dev/null
+++ b/reco/L1/qa/CbmCaInputQaSts.h
@@ -0,0 +1,201 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CbmCaInputQaSts.h
+/// \date   13.01.2023
+/// \brief  QA-task for CA tracking input from MuCh detector (header)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+
+#ifndef CbmCaInputQaSts_h
+#define CbmCaInputQaSts_h 1
+
+#include "CbmMCDataManager.h"
+#include "CbmQaTask.h"
+
+#include "TMath.h"
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+class CbmMCEventList;
+class CbmMCDataArray;
+class CbmMCDataManager;
+class CbmTimeSlice;
+class CbmMatch;
+class CbmStsHit;
+class CbmStsTrackingInterface;
+class TClonesArray;
+class TH1F;
+class TH2F;
+class TEfficiency;
+
+/// A QA-task class, which provides assurance of MuCh hits and geometry
+class CbmCaInputQaSts : public CbmQaTask {
+public:
+  /// Constructor from parameters
+  /// \param  verbose   Verbose level
+  /// \param  isMCUsed  Flag, whether MC information is available for this task
+  CbmCaInputQaSts(int verbose, bool isMCUsed);
+
+  ClassDef(CbmCaInputQaSts, 0);
+
+protected:
+  // ********************************************
+  // ** Virtual method override from CbmQaTask **
+  // ********************************************
+
+  /// Checks results of the QA and returns some flag
+  bool Check() const;
+
+  /// Initializes data branches
+  InitStatus InitDataBranches();
+
+  /// Initializes canvases
+  InitStatus InitCanvases();
+
+  /// Initializes histograms
+  InitStatus InitHistograms();
+
+  /// Fills histograms per event or time-slice
+  void FillHistograms();
+
+  /// Process filled histograms in the end of the run
+  void AnalyzeHistograms();
+
+  /// De-initializes histograms
+  void DeInit();
+
+private:
+  // *********************
+  // ** Cut variables
+
+
+  // *********************
+  // ** Private methods **
+  // *********************
+
+  /// Gets match object for the hit
+  /// \param   iHit  Index of hit
+  /// \return        Match object
+  CbmMatch GetHitMatch(int iHit) const;
+
+
+  // *********************
+  // ** Class variables **
+  // *********************
+
+
+  // ** Data branches **
+  CbmStsTrackingInterface* fpDetInterface = nullptr;  ///< Instance of detector interface
+
+  CbmTimeSlice* fpTimeSlice = nullptr;  ///< Pointer to current time-slice
+
+  TClonesArray* fpHits     = nullptr;  ///< Array of hits
+  TClonesArray* fpClusters = nullptr;  ///< Array of hit clusters
+
+  CbmMCDataManager* fpMCDataManager = nullptr;  ///< MC data manager
+  CbmMCEventList* fpMCEventList     = nullptr;  ///< MC event list
+  CbmMCDataArray* fpMCTracks        = nullptr;  ///< Array of MC tracks
+  CbmMCDataArray* fpMCPoints        = nullptr;  ///< Array of MC points
+
+  TClonesArray* fpClusterMatches = nullptr;  ///< Array of hit matches
+
+  // ** Histograms **
+
+  static constexpr double kEffRangeMin = 10.;  ///< Lower limit of hit distance for efficiency integration [cm]
+  static constexpr double kEffRangeMax = 30.;  ///< Upper limit of hit distance for efficiency integration [cm]
+
+  static constexpr double kRHitX[2] = {-50., 50};  ///< Range for hit x coordinate [cm]
+  static constexpr double kRHitY[2] = {-50., 50};  ///< Range for hit y coordinate [cm]
+  static constexpr double kRHitZ[2] = {-20., 60};  ///< Range for hit z coordinate [cm]
+
+  static constexpr int kNbins  = 200;  ///< General number of bins
+  static constexpr int kNbinsZ = 800;  ///< Number of bins for z coordinate axis
+
+  static constexpr double kRHitDx[2] = {-.005, .005};  ///< Range for hit x coordinate [cm]
+  static constexpr double kRHitDy[2] = {-.005, .005};  ///< Range for hit y coordinate [cm]
+  static constexpr double kRHitDu[2] = {-.005, .005};  ///< Range for hit u coordinate [cm]
+  static constexpr double kRHitDv[2] = {-.005, .005};  ///< Range for hit v coordinate [cm]
+  static constexpr double kRHitDt[2] = {-10., 10.};    ///< Range for hit time [ns]
+
+  static constexpr double kRResX[2] = {-.02, .02};  ///< Range for residual of x coordinate [cm]
+  static constexpr double kRResY[2] = {-.1, .1};    ///< Range for residual of y coordinate [cm]
+  static constexpr double kRResU[2] = {-5., 5.};    ///< Range for residual of v coordinate [cm]
+  static constexpr double kRResV[2] = {-.4, .4};    ///< Range for residual of y coordinate [cm]
+  static constexpr double kRResT[2] = {-15., 15.};  ///< Range for residual of time [ns]
+
+  static constexpr double kRPullX[2] = {-10., 10.};    ///< Range for pull of x coordinate
+  static constexpr double kRPullY[2] = {-10., 10.};    ///< Range for pull of y coordinate
+  static constexpr double kRPullU[2] = {-400., 400.};  ///< Range for pull of v coordinate
+  static constexpr double kRPullV[2] = {-10., 10.};    ///< Range for pull of y coordinate
+  static constexpr double kRPullT[2] = {-5., 5.};      ///< Range for pull of time
+
+
+  // NOTE: the last element of each vector stands for integral distribution over all stations
+
+  // hit occupancy
+  std::vector<TH2F*> fvph_hit_ypos_vs_xpos;  ///< hit ypos vs xpos in different stations
+  std::vector<TH2F*> fvph_hit_xpos_vs_zpos;  ///< hit xpos vs zpos in different stations
+  std::vector<TH2F*> fvph_hit_ypos_vs_zpos;  ///< hit ypos vs zpos in different stations
+
+  // difference between hit and station z positions
+  std::vector<TH1F*> fvph_hit_station_delta_z;  ///< Difference between station and hit z positions [cm]
+
+  // hit errors
+  std::vector<TH1F*> fvph_hit_dx;
+  std::vector<TH1F*> fvph_hit_dy;
+  std::vector<TH1F*> fvph_hit_du;
+  std::vector<TH1F*> fvph_hit_dv;
+  std::vector<TH1F*> fvph_hit_dt;
+
+  // MC points occupancy
+  std::vector<TH1F*> fvph_n_points_per_hit;  ///< number of points per one hit in different stations
+
+
+  std::vector<TH2F*> fvph_point_ypos_vs_xpos;  ///< point ypos vs xpos in different stations
+  std::vector<TH2F*> fvph_point_xpos_vs_zpos;  ///< point xpos vs zpos in different stations
+  std::vector<TH2F*> fvph_point_ypos_vs_zpos;  ///< point ypos vs zpos in different stations
+
+  std::vector<TH1F*> fvph_point_hit_delta_z;  ///< difference between z of hit and MC point in different stations
+
+  // Residuals
+  std::vector<TH1F*> fvph_res_x;  ///< residual for x coordinate in different stations
+  std::vector<TH1F*> fvph_res_y;  ///< residual for y coordinate in different stations
+  std::vector<TH1F*> fvph_res_u;  ///< residual for u coordinate in different stations
+  std::vector<TH1F*> fvph_res_v;  ///< residual for v coordinate in different stations
+  std::vector<TH1F*> fvph_res_t;  ///< residual for t coordinate in different stations
+
+  std::vector<TH2F*> fvph_res_x_vs_x;  ///< residual for x coordinate in different stations
+  std::vector<TH2F*> fvph_res_y_vs_y;  ///< residual for y coordinate in different stations
+  std::vector<TH2F*> fvph_res_u_vs_u;  ///< residual for u coordinate in different stations
+  std::vector<TH2F*> fvph_res_v_vs_v;  ///< residual for v coordinate in different stations
+  std::vector<TH2F*> fvph_res_t_vs_t;  ///< residual for t coordinate in different stations
+
+  // Pulls
+  std::vector<TH1F*> fvph_pull_x;  ///< pull for x coordinate in different stations
+  std::vector<TH1F*> fvph_pull_y;  ///< pull for y coordinate in different stations
+  std::vector<TH1F*> fvph_pull_u;  ///< pull for u coordinate in different stations
+  std::vector<TH1F*> fvph_pull_v;  ///< pull for v coordinate in different stations
+  std::vector<TH1F*> fvph_pull_t;  ///< pull for t coordinate in different stations
+
+  std::vector<TH2F*> fvph_pull_x_vs_x;  ///< pull for x coordinate in different stations
+  std::vector<TH2F*> fvph_pull_y_vs_y;  ///< pull for y coordinate in different stations
+  std::vector<TH2F*> fvph_pull_u_vs_u;  ///< pull for u coordinate in different stations
+  std::vector<TH2F*> fvph_pull_v_vs_v;  ///< pull for v coordinate in different stations
+  std::vector<TH2F*> fvph_pull_t_vs_t;  ///< pull for t coordinate in different stations
+
+  // Hit efficiencies
+  std::vector<TEfficiency*> fvpe_reco_eff_vs_xy;  ///< Efficiency of hit reconstruction vs. x and y coordinates of MC
+  std::vector<TEfficiency*> fvpe_reco_eff_vs_r;   ///< Efficiency of hit reconstruction vs. distance from center
+
+  /// Kaniadakis exponent
+  static Double_t Expk(Double_t x, Double_t k)
+  {
+    return TMath::Power((TMath::Sqrt(1 + k * k * x * x) + k * x), 1. / k);
+  }
+};
+
+#endif  // CbmCaInputQaSts_h
diff --git a/reco/L1/qa/CbmCaQaCuts.h b/reco/L1/qa/CbmCaQaCuts.h
new file mode 100644
index 0000000000..08810ac00a
--- /dev/null
+++ b/reco/L1/qa/CbmCaQaCuts.h
@@ -0,0 +1,17 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+#ifndef CbmCaQaCuts_h
+#define CbmCaQaCuts_h 1
+
+namespace cbm::ca::qa::cuts
+{
+  constexpr double kMinP   = 0.05;  ///< minimal momentum [Gev/c]
+  constexpr double kMinEff = 0.50;  ///< minimal efficiency of hit finders (NOTE: value is tmp)
+
+  // Max difference between hit and station z
+  constexpr double kMaxDzStHitSts = 1.;  ///< Max distance for STS
+};                                       // namespace cbm::ca::qa::cuts
+
+#endif  // CbmCaQaCuts_h
diff --git a/reco/L1/qa/CbmTrackerInputQaTrd.cxx b/reco/L1/qa/CbmTrackerInputQaTrd.cxx
index 5ebe1fae51..04f61b5112 100644
--- a/reco/L1/qa/CbmTrackerInputQaTrd.cxx
+++ b/reco/L1/qa/CbmTrackerInputQaTrd.cxx
@@ -123,6 +123,8 @@ void CbmTrackerInputQaTrd::DeInit()
 
 void CbmTrackerInputQaTrd::SetParContainers()
 {
+  // FIXME: SZh 13.01.2023: With tracking detector interfaces these data bases are no longer needed
+  //                        to be initialized in this class
   fTrdDigiPar = nullptr;
   fTrdGeoPar  = nullptr;
 
diff --git a/reco/L1/qa/CbmTrackingInputQaSts.cxx b/reco/L1/qa/CbmTrackingInputQaSts.cxx
index b1d32e068c..b05d2dcbcf 100644
--- a/reco/L1/qa/CbmTrackingInputQaSts.cxx
+++ b/reco/L1/qa/CbmTrackingInputQaSts.cxx
@@ -542,26 +542,11 @@ CbmMatch CbmTrackingInputQaSts::MatchHits(const CbmStsHit* pHit, int iHit)
 //
 double CbmTrackingInputQaSts::ParticleMass(int pdg)
 {
+  // FIXME: SZh: Use masses defined by CbmMCTrack class instead
   if (fabs(pdg) < 9999999 && ((TParticlePDG*) TDatabasePDG::Instance()->GetParticle(pdg))) {
     return TDatabasePDG::Instance()->GetParticle(pdg)->Mass();
   }
 
-  constexpr double kAmuToGev {0.93149410242};  // 1 amu in GeV/c2
-
-  // Define masses of several light nuclei by hands
-  if (fabs(pdg) == 1000010020) {
-    return kAmuToGev * 2.01410177812;  // deutron
-  }
-  else if (fabs(pdg) == 1000010030) {
-    return kAmuToGev * 3.01604927790;  // triton
-  }
-  else if (fabs(pdg) == 1000020030) {
-    return kAmuToGev * 3.01602932007;  // He-3
-  }
-  else if (fabs(pdg) == 1000020040) {
-    return kAmuToGev * 4.00260325413;  // He-4
-  }
-
   LOG(warn) << "\033[1;31m Found mass for pdg = " << pdg << " is undefined. "
             << "Please, provide data for this particle\033[0m\n";
 
@@ -753,8 +738,9 @@ void CbmTrackingInputQaSts::ResolutionQa()
     double mass = ParticleMass(pdgCode);
     constexpr double speedOfLight {29.9792458};  // cm/ns
     TVector3 mom;
+    double mom2 = mcPx * mcPx + mcPy * mcPy + mcPz * mcPz;
 
-    mcT += dz / (mcPz * speedOfLight) * sqrt(mass * mass + mom.Mag2());
+    mcT += dz / (mcPz * speedOfLight) * sqrt(mass * mass + mom2);
 
     double dx   = pHit->GetX() - mcX;
     double dy   = pHit->GetY() - mcY;
-- 
GitLab