diff --git a/core/qa/CMakeLists.txt b/core/qa/CMakeLists.txt
index f6e9c92237b240ae50f615be424d8c5146985fd4..0974dd5734efdb20c2996210f59c228d7297de8a 100644
--- a/core/qa/CMakeLists.txt
+++ b/core/qa/CMakeLists.txt
@@ -1,4 +1,5 @@
 set(INCLUDE_DIRECTORIES
+  ${CMAKE_CURRENT_SOURCE_DIR}/checker
   ${CMAKE_CURRENT_SOURCE_DIR}
   )
 
@@ -10,6 +11,13 @@ set(SRCS
   CbmQaTable.cxx
   CbmQaTask.cxx
   CbmQaIO.cxx
+  checker/CbmQaCheckerCore.cxx
+  checker/CbmQaCheckerFileHandler.cxx
+  checker/CbmQaCheckerHist1DHandler.cxx
+  checker/CbmQaCheckerHist2DHandler.cxx
+  checker/CbmQaCheckerObjectHandler.cxx
+  checker/CbmQaCheckerProfile1DHandler.cxx
+  checker/CbmQaCheckerObjectDB.cxx
   )
 
 set(HEADERS
@@ -27,12 +35,17 @@ set(PUBLIC_DEPENDENCIES
   ROOT::Hist
   )
 
+set(PRIVATE_DEPENDENCIES
+  external::yaml-cpp
+  )
+
 set(INTERFACE_DEPENDENCIES
   ROOT::Graf
   )
 
 generate_cbm_library()
 
-Install(FILES CbmQaTask.h CbmQaCanvas.h CbmQaTable.h CbmQaHist.h CbmQaEff.h CbmQaPie.h CbmQaConstants.h CbmQaCmpDrawer.h
+Install(FILES CbmQaTask.h CbmQaCanvas.h CbmQaTable.h CbmQaHist.h CbmQaEff.h CbmQaPie.h CbmQaConstants.h CbmQaCmpDrawer.h checker/CbmQaCheckerTypedefs.h
         DESTINATION include
-       )
+        )
+
diff --git a/core/qa/CbmQaBaseLinkDef.h b/core/qa/CbmQaBaseLinkDef.h
index 5d2349aa39f067724f5857aae91324c67eed5283..cee4c1d3419cd1c9eb13b4964891d7794c50fa68 100644
--- a/core/qa/CbmQaBaseLinkDef.h
+++ b/core/qa/CbmQaBaseLinkDef.h
@@ -25,5 +25,12 @@
 #pragma link C++ class CbmQaHist < TProfile2D> + ;
 #pragma link C++ class CbmQaTable + ;
 #pragma link C++ class CbmQaTask + ;
+#pragma link C++ class cbm::qa::checker::Core + ;
+#pragma link C++ class cbm::qa::checker::FileHandler + ;
+#pragma link C++ class cbm::qa::checker::Hist1DHandler + ;
+#pragma link C++ class cbm::qa::checker::Hist2DHandler + ;
+#pragma link C++ class cbm::qa::checker::Profile1DHandler + ;
+#pragma link C++ class cbm::qa::checker::ObjectHandler + ;
+#pragma link C++ class cbm::qa::checker::ObjectDB + ;
 
 #endif
diff --git a/core/qa/checker/CbmQaCheckerCore.cxx b/core/qa/checker/CbmQaCheckerCore.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..7164d12ad61c606f673e491448d2ed9c56cd53b4
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerCore.cxx
@@ -0,0 +1,108 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerCore.cxx
+/// @brief  Core class of the QA checking framework (implementation)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  06.02.2023
+
+#include "CbmQaCheckerCore.h"
+
+#include "CbmQaCheckerFileHandler.h"
+
+#include "Logger.h"
+
+#include "TClonesArray.h"
+#include "TFolder.h"
+//#include <boost/filesystem.hpp>
+#include <regex>
+
+#include <yaml-cpp/yaml.h>
+
+using cbm::qa::checker::Core;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Core::Core()
+{
+  // Define object names data base
+  fpObjDB = std::make_shared<ObjectDB>();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Core::AddVersion(const char* version, const char* pathName) { fpObjDB->AddVersion(version, pathName); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Core::AddDataset(const char* dataset) { fpObjDB->AddDataset(dataset); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Core::RegisterOutFile(const char* filename)
+{
+  LOG(info) << "Core: Registering output file: " << filename;
+  fpOutFile = std::make_unique<TFile>(filename, "RECREATE");
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Core::Process(Option_t* opt)
+{
+  // ----- Init the object database
+  fpObjDB->Init();
+
+  // ----- Register output file
+  if (fpOutFile == nullptr) { this->RegisterOutFile("QaCheckerOutput.root"); }
+
+  // ----- Process datasets and files
+  int nDatasets = fpObjDB->GetNofDatasets();
+  std::vector<TFolder*> vDSFolders(nDatasets, nullptr);
+
+  for (int iDS = 0; iDS < nDatasets; ++iDS) {
+    // Create a new folder for dataset (/dataset)
+    auto* pDSDir = fpOutFile->mkdir(fpObjDB->GetDataset(iDS).data());
+
+    // Loop over files
+    for (int iFile = 0; iFile < fpObjDB->GetNofFiles(); ++iFile) {
+      // Define output folder for file (/dataset/file)
+      auto* pFileDir = pDSDir->mkdir(fpObjDB->GetFileLabel(iFile).data());
+
+      // Create and process a file handler
+      auto pFileHandler = std::make_unique<FileHandler>(fpObjDB, pFileDir, iDS, iFile);
+      pFileHandler->Process(opt);
+    }  // iFile
+  }    // iDS
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Core::SetFromYAML(const char* configName) { fpObjDB->ReadFromYAML(configName); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool Core::Scan() const
+{
+  LOG(info) << "Core: Scanning comparison result ...";
+  bool res = true;  /// Equal
+  for (int iFile = 0; iFile < fpObjDB->GetNofFiles(); ++iFile) {
+    for (int iObj = 0; iObj < fpObjDB->GetNofObjects(iFile); ++iObj) {
+      for (int iDS = 0; iDS < fpObjDB->GetNofDatasets(); ++iDS) {
+        for (int iVer = 0; iVer < fpObjDB->GetNofVersions(); ++iVer) {
+          // For now we check, if anything has been changed with respect to default. Later one can add a flag to test
+          // compatibility for different levels (for example, two histograms could be obtained from different systems,
+          // or different seeds, but still be consistent).
+          if (fpObjDB->GetCmpResult(iDS, iFile, iObj, iVer)) {
+            LOG(info) << "\t file: " << fpObjDB->GetInputFileName(iVer, iFile, iDS)
+                      << ", object: " << fpObjDB->GetObject(iFile, iObj);
+            res = false;
+          }
+        }  // iVer
+      }    // iDS
+    }      // iObj
+  }        // iFile
+  LOG(info) << "Core: Scanning comparison result ... done";
+
+  return res;
+}
\ No newline at end of file
diff --git a/core/qa/checker/CbmQaCheckerCore.h b/core/qa/checker/CbmQaCheckerCore.h
new file mode 100644
index 0000000000000000000000000000000000000000..e096eb610475808df194e406d1932a543de723f3
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerCore.h
@@ -0,0 +1,107 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerCore.h
+/// @brief  Core class of the QA checking framework (declaration)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @date   06.02.2023
+
+#ifndef CbmQaCheckerCore_h
+#define CbmQaCheckerCore_h 1
+
+#include "CbmQaCheckerObjectDB.h"
+#include "CbmQaCheckerTypedefs.h"
+
+#include "TFile.h"
+
+#include <bitset>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace cbm::qa::checker
+{
+  /// @brief Core class for CBM QA checker framework (declaration)
+  ///
+  /// Class CbmQaCheckerCore defines a core of the QA checker framework and provides a user interface for
+  /// the comparison routine execution
+  ///
+  class Core {
+  public:
+    /// @brief Default ctor
+    Core();
+
+    /// @brief Destructor
+    ~Core() = default;
+
+    /// @brief Copy constructor
+    Core(const Core&) = delete;
+
+    /// @brief Move constructor
+    Core(Core&&) = delete;
+
+    /// @brief Copy assignment operator
+    Core& operator=(const Core&) = delete;
+
+    /// @brief Move assignment operator
+    Core& operator=(Core&&) = delete;
+
+    // ----- User interface
+    /// @brief Adds a version of QA output for a comparison
+    /// @param version    Label of the version
+    /// @param path       Path to the QA output directory for this version
+    void AddVersion(const char* version, const char* path);
+
+    /// @brief Adds a dataset name
+    /// @param datasetName  Name of dataset
+    void AddDataset(const char* datasetName);
+
+    /// @brief Runs checking routine
+    /// @param opt  Option:
+    ///             "B": suppress canvas creation
+    ///             "P": enables bin-by-bin comparison
+    ///             "S": enables statistical hypothesis test, where is possible
+    ///             "U": enables interval comparison, where is possible
+    void Process(Option_t* opt = "P");
+
+    /// @brief Registers root-file for storing output
+    /// @param filename  Name of file
+    void RegisterOutFile(const char* filename);
+
+    /// @brief Scans comparison results
+    /// @return  Comparison flag:
+    ///          - true:  All histograms are the same under conditions
+    ///          - false: Some of the histograms were changed
+    bool Scan() const;
+
+    /// @brief Sets control flag
+    /// @param flag   Flag label
+    /// @param value  Flag value
+    void SetControlBitFlag(EFlagBit flag, bool value = true) { fControlBits[static_cast<int>(flag)] = value; }
+
+    /// @brief  Sets default version label
+    /// @param  defaultLabel  Name of default label
+    ///
+    /// If the default version is not provided as well as the provided, the first version will be used as the
+    /// default one.
+    void SetDefaultVersion(const char* defaultLabel) { fpObjDB->SetDefaultLabel(defaultLabel); }
+
+    /// @brief Sets checker configuration from YAML file
+    /// @param configName  Name of YAML configuration file
+    void SetFromYAML(const char* configName);
+
+    /// @brief Sets root path to input files
+    /// @param pathName  Relative or absolute root path to input the input directories
+    void SetInputRootPath(const char* pathName) { fpObjDB->SetInputRootPath(pathName); }
+
+  private:
+    std::shared_ptr<TFile> fpOutFile  = nullptr;  ///< Output file
+    std::shared_ptr<ObjectDB> fpObjDB = nullptr;  ///< Database of names
+
+    FlagBitSet_t fControlBits;  ///< Control bit flags
+  };
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerCore_h
\ No newline at end of file
diff --git a/core/qa/checker/CbmQaCheckerFileHandler.cxx b/core/qa/checker/CbmQaCheckerFileHandler.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..a28a8c99f1b6011f6495b1303d5ba81e330077f9
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerFileHandler.cxx
@@ -0,0 +1,196 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerFileHandler.cxx
+/// @brief  A handler class to process versions from similar files (implementation)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  08.02.2023
+
+#include "CbmQaCheckerFileHandler.h"
+
+#include "CbmQaCheckerHist1DHandler.h"
+#include "CbmQaCheckerHist2DHandler.h"
+#include "CbmQaCheckerProfile1DHandler.h"
+
+#include "Logger.h"
+
+#include "TDirectoryFile.h"
+#include "TFile.h"
+#include "TFolder.h"
+#include "TH1.h"
+#include "TH2.h"
+#include "TNamed.h"
+#include "TProfile.h"
+
+#include <boost/algorithm/string.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+
+using cbm::qa::checker::FileHandler;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+FileHandler::FileHandler(std::shared_ptr<ObjectDB>& pObjDB, TDirectory* pOutDir, int iDataset, int iFile)
+  : fFileID(iFile)
+  , fDatasetID(iDataset)
+  , fpObjDB(pObjDB)
+  , fpOutDir(pOutDir)
+{
+  fpInputFiles = std::make_unique<TClonesArray>("TFile");
+
+  for (int iVer = 0; iVer < fpObjDB->GetNofVersions(); ++iVer) {
+    std::string filename = fpObjDB->GetInputFileName(iVer, fFileID, fDatasetID);
+    auto* pInFile        = new ((*fpInputFiles)[iVer]) TFile(filename.data(), "READONLY");
+    LOG(info) << "File: " << pInFile->GetName();
+    LOG_IF(fatal, !pInFile->IsOpen()) << "FileHandler: file " << filename << " cannot be opened";
+  }
+
+  // Check registered folder
+  LOG_IF(fatal, !fpObjDB.get()) << "FileHandler: attempt to register a null pointer for the object database";
+  LOG_IF(fatal, !fpOutDir) << "FileHandler: attempt to register a null pointer for the output directory";
+  LOG_IF(fatal, fDatasetID < 0) << "FileHandler: attempt to register undefined dataset index";
+  LOG_IF(fatal, fFileID < 0) << "FileHandler: attempt to register undefined file index";
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+FileHandler::~FileHandler()
+{
+  // ----- Clean pointers
+  fpInputFiles->Delete();
+  fpInputFiles = nullptr;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+TDirectory* FileHandler::CreateNestedDirectory(const std::string& path)
+{
+  fpOutDir->mkdir(path.data());
+  return fpOutDir->GetDirectory(path.data());
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void FileHandler::Process(Option_t* opt)
+{
+  int nObjects  = fpObjDB->GetNofObjects(fFileID);
+  int nVersions = fpObjDB->GetNofVersions();
+
+  // ----- Initial checks
+  if (!nObjects) {
+    LOG(warn) << "FileHandler: No objects were passed to file \"" << nObjects << ". Skipping file";
+    return;
+  }
+
+  // ----- Option parsing
+  std::string sOption = opt;
+  for (auto& ch : sOption) {
+    ch = std::tolower(ch);
+  }
+  bool bSuppressCanvases = sOption.find("b") != std::string::npos;
+  bool bCmpPointByPoint  = sOption.find("p") != std::string::npos;
+  bool bCmpStatAny       = sOption.find("s") != std::string::npos;
+
+  LOG(info) << "FileHandler: processing objects: ";
+  std::vector<TNamed*> vpObjects(nVersions, nullptr);  // vector to keep object different versions
+  for (int iObj = 0; iObj < nObjects; ++iObj) {
+    bool skipObj = false;
+    for (int iVer = 0; iVer < nVersions; ++iVer) {
+      auto* pInputFile = static_cast<TFile*>(fpInputFiles->At(iVer));
+      vpObjects[iVer]  = ReadObjectFromFile(pInputFile, fpObjDB->GetObject(fFileID, iObj));
+      if (!vpObjects[iVer]) {
+        LOG(warn) << "FileHandler: object " << fpObjDB->GetObject(fFileID, iObj) << " is undefined for version "
+                  << fpObjDB->GetVersionLabel(iVer) << ". This object will be skipped";
+        skipObj = true;
+      }
+    }  // iVer
+    if (skipObj) { continue; }
+
+    // Create an instance of an object handler
+    std::unique_ptr<ObjectHandler> pObjHandler = nullptr;
+    LOG(info) << "FileHandler: processing object \"" << vpObjects[0]->GetName() << '\"';
+    if (dynamic_cast<TH2*>(vpObjects[0])) {
+      pObjHandler = std::make_unique<Hist2DHandler>(iObj, fFileID, fDatasetID);
+      if (bCmpPointByPoint) {
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kPOINT);  // Compare point-by-point (exact equality)
+      }
+      if (bCmpStatAny) {
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kCHI2);  // Compare with chi2 test
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kKOLM);  // Compare with Kolmogorov test
+      }
+    }
+    else if (dynamic_cast<TProfile*>(vpObjects[0])) {
+      pObjHandler = std::make_unique<Profile1DHandler>(iObj, fFileID, fDatasetID);
+      if (bCmpPointByPoint) {
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kPOINT);  // Compare point-by-point (exact equality)
+      }
+      if (bCmpStatAny) {
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kCHI2);  // Compare with chi2 test
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kKOLM);  // Compare with Kolmogorov test
+      }
+    }
+    else if (dynamic_cast<TH1*>(vpObjects[0])) {
+      pObjHandler = std::make_unique<Hist1DHandler>(iObj, fFileID, fDatasetID);
+      if (bCmpPointByPoint) {
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kPOINT);  // Compare point-by-point (exact equality)
+      }
+      if (bCmpStatAny) {
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kCHI2);  // Compare with chi2 test
+        pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kKOLM);  // Compare with Kolmogorov test
+      }
+    }
+    else {
+      LOG(warn) << "FileHandler: Object " << fpObjDB->GetObject(fFileID, iObj) << " has a type \""
+                << vpObjects[0]->ClassName()
+                << "\", which is unknown to the cbm::qa::checker framework, it will be skipped";
+      continue;
+    }
+    pObjHandler->SetObjectDB(fpObjDB);
+    pObjHandler->SetOutputDirectory(CreateNestedDirectory((fpObjDB->GetObject(fFileID, iObj))));
+    pObjHandler->AddObjects(vpObjects);
+    pObjHandler->CompareWithDefault();
+
+    // Create comparison canvas
+    if (!bSuppressCanvases) {
+      bool areDifferent = false;
+      for (int iVer = 0; iVer < nVersions; ++iVer) {
+        if (fpObjDB->GetCmpResult(fDatasetID, fFileID, iObj, iVer)) { areDifferent = true; }
+      }
+      if (areDifferent) { pObjHandler->CreateCanvases(); }
+    }
+    pObjHandler->Write();
+  }  // iObj
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+TNamed* FileHandler::ReadObjectFromFile(TFile* pFile, const std::string& path) const
+{
+  TObject* pObj = pFile;
+  size_t iPos   = 0;  // Index of first symbol
+  size_t iNext  = 0;  // Index of last symbol
+  while (iPos < path.size()) {
+    // get name
+    iNext = path.find_first_of('/', iPos);
+    if (iNext > path.size()) { iNext = path.size(); }
+    std::string part = path.substr(iPos, iNext - iPos);
+    iPos             = iNext + 1;
+    if (!part.size()) { continue; }
+
+    // test TDirectoryFile
+    if (dynamic_cast<TDirectoryFile*>(pObj)) {
+      auto* pDir = static_cast<TDirectoryFile*>(pObj);
+      pObj       = pDir->FindObjectAny(part.data());
+    }
+    // test TFolder
+    else if (dynamic_cast<TFolder*>(pObj)) {
+      auto* pDir = static_cast<TFolder*>(pObj);
+      pObj       = pDir->FindObjectAny(part.data());
+    }
+  }
+  return dynamic_cast<TNamed*>(pObj);
+}
diff --git a/core/qa/checker/CbmQaCheckerFileHandler.h b/core/qa/checker/CbmQaCheckerFileHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..0557ad3206d13231a301f98b83ab0753fc2e32bb
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerFileHandler.h
@@ -0,0 +1,93 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerFileHandler.h
+/// @brief  A handler class to process versions from similar files (declaration)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  08.02.2023
+
+#ifndef CbmQaCheckerFileHandler_h
+#define CbmQaCheckerFileHandler_h 1
+
+#include "CbmQaCheckerObjectDB.h"
+#include "CbmQaCheckerTypedefs.h"
+
+#include "TClonesArray.h"
+
+#include <memory>
+#include <vector>
+
+class TNamed;
+class TFolder;
+class TFile;
+
+namespace cbm::qa::checker
+{
+  /// @brief Handler for single files, created from different QA versions
+  ///
+  class FileHandler {
+  public:
+    /// @brief Constructor
+    /// @param pObjDB    Shared pointer to object database
+    /// @param pOutDir   Directory to store the output  (/dataset/file)
+    /// @param iDataset  Index of dataset
+    /// @param iFile     Index of file
+    FileHandler(std::shared_ptr<ObjectDB>& pObjDB, TDirectory* pOutDir, int iDataset, int iFile);
+
+    /// @brief Destructor
+    ~FileHandler();
+
+    /// @brief Copy constructor
+    FileHandler(const FileHandler&) = delete;
+
+    /// @brief Move constructor
+    FileHandler(FileHandler&&) = delete;
+
+    /// @brief Copy assignment operator
+    FileHandler& operator=(const FileHandler&) = delete;
+
+    /// @brief Move assignment operator
+    FileHandler& operator=(FileHandler&&) = delete;
+
+    /// @brief  Gets index of dataset
+    /// @return Index of dataset
+    int GetDatasetID() const { return fDatasetID; }
+
+    /// @brief  Gets index of file
+    /// @return Index of file
+    int GetFileID() const { return fFileID; }
+
+    /// @brief Processes comparison
+    /// @param opt  Option:
+    ///             "B": suppress canvas creation
+    ///             "P": enables bin-by-bin comparison
+    ///             "S": enables statistical hypothesis test, where is possible
+    ///             "U": enables interval comparison
+    void Process(Option_t* opt = "");
+
+  private:
+    /// @brief Creates nested directory from a given path
+    /// @param path  Path, parts of which are separated with slash
+    /// @return  Pointer to created TDirectory object
+    TDirectory* CreateNestedDirectory(const std::string& path);
+
+    /// @brief Reads object from file by the provided full path to the object.
+    /// @param pFile  Pointer to TFile instance
+    /// @param path   Full path to the object inside the file
+    /// @return A pointer to TNamed object
+    TNamed* ReadObjectFromFile(TFile* pFile, const std::string& path) const;
+
+
+    int fFileID    = -1;  ///< Index of file
+    int fDatasetID = -1;  ///< Index of dataset
+
+    std::shared_ptr<ObjectDB> fpObjDB          = nullptr;  ///< Pointer to object database
+    TDirectory* fpOutDir                       = nullptr;  ///< Pointer to output directory
+    std::unique_ptr<TClonesArray> fpInputFiles = nullptr;  ///< Pointer to input files array
+
+    // TODO: replace fpOutputFolder with shared_ptr
+  };
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerFileHandler_h
diff --git a/core/qa/checker/CbmQaCheckerHist1DHandler.cxx b/core/qa/checker/CbmQaCheckerHist1DHandler.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..16312a18088970ee1f7ac1351a498de599b00cb6
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerHist1DHandler.cxx
@@ -0,0 +1,203 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerHist1DHandler.cxx
+/// @brief  Handler class for 1D-histograms (including TProfile objects) (implementation)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  09.02.2023
+
+#include "CbmQaCheckerHist1DHandler.h"
+
+#include "CbmQaCanvas.h"
+#include "CbmQaCheckerTypedefs.h"
+
+#include "Logger.h"
+
+#include "TCanvas.h"
+#include "TError.h"
+#include "TFolder.h"
+#include "TGraphAsymmErrors.h"
+#include "TH1.h"
+#include "TLegend.h"
+#include "TMath.h"
+#include "TMultiGraph.h"
+#include "TObject.h"
+#include "TStyle.h"
+
+#include <bitset>
+#include <limits>
+
+using cbm::qa::checker::CmpResult_t;
+using cbm::qa::checker::Hist1DHandler;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Hist1DHandler::Hist1DHandler(int iObject, int iFile, int iDataset) : ObjectHandler(iObject, iFile, iDataset, "TH1") {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CmpResult_t Hist1DHandler::Compare(const TNamed* pNum, const TNamed* pDenom) const
+{
+  std::bitset<sizeof(CmpResult_t) * 8> res;
+  auto* pNumerator   = static_cast<const TH1*>(pNum);
+  auto* pDenominator = static_cast<const TH1*>(pDenom);
+  if (fOptionBits[EFlags::kPOINT] && ComparePointToPoint(pNumerator, pDenominator)) { res[EFlags::kPOINT] = true; }
+  if (fOptionBits[EFlags::kRATIO] && CompareRatioDeviation(pNumerator, pDenominator)) { res[EFlags::kRATIO] = true; }
+  if (fOptionBits[EFlags::kCHI2] && CompareWithChi2(pNumerator, pDenominator)) { res[EFlags::kCHI2] = true; }
+  if (fOptionBits[EFlags::kKOLM] && CompareWithKolmogorov(pNumerator, pDenominator)) { res[EFlags::kKOLM] = true; }
+  return res.to_ulong();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Hist1DHandler::CreateCanvases()
+{
+  int nVersions = fpObjDB->GetNofVersions();
+  int iDef      = fpObjDB->GetDefaultID();
+
+  // ----- Canvas: comparison of original histograms, ratios and subtractions
+  std::string sCanvName  = fsBaseName + "_cmp_canvas";
+  std::string sCanvTitle = "Comparison result for \"" + fsBaseName + "\"";
+  fpCanvas               = std::make_shared<CbmQaCanvas>(sCanvName.data(), sCanvTitle.data(), 1500, 500);
+  fpCanvas->cd();
+  auto* pPadOrig = new TPad("p1", "p1", 0.0, 0.0, 0.3333, 1.0);
+  pPadOrig->SetMargin(0.20, 0.05, 0.20, 0.10);
+  fpCanvas->cd();
+  auto* pPadRatio = new TPad("p2", "p2", 0.3333, 0.0, 0.6666, 1.0);
+  pPadRatio->SetMargin(0.20, 0.05, 0.20, 0.10);
+  fpCanvas->cd();
+  auto* pPadDiff = new TPad("p3", "p3", 0.6666, 0.0, 1.0, 1.0);
+  pPadDiff->SetMargin(0.20, 0.05, 0.20, 0.10);
+
+  // Title definitions
+  const char* title      = fvpObjects[0]->GetTitle();
+  const char* titleRatio = Form("Ratio to %s", fpObjDB->GetVersionLabel(iDef).data());
+  const char* titleDiff  = Form("Difference from %s", fpObjDB->GetVersionLabel(iDef).data());
+  const char* xAxisTitle = static_cast<TH1*>(fvpObjects[0])->GetXaxis()->GetTitle();
+  const char* yAxisTitle = static_cast<TH1*>(fvpObjects[0])->GetYaxis()->GetTitle();
+
+  // Original histograms
+  pPadOrig->cd();
+  auto* pMultiGraphOrig = new TMultiGraph(fsBaseName.data(), title);
+  for (int iV = 0; iV < nVersions; ++iV) {
+    auto* pCopy = new TGraphAsymmErrors((TH1*) fvpObjects[iV]);
+    pCopy->SetMarkerStyle(20);
+    pCopy->SetTitle(fpObjDB->GetVersionLabel(iV).data());
+    pMultiGraphOrig->Add(pCopy, "P");
+  }
+  pMultiGraphOrig->GetXaxis()->SetTitle(xAxisTitle);
+  pMultiGraphOrig->GetYaxis()->SetTitle(yAxisTitle);
+  pMultiGraphOrig->Draw("A pmc plc");
+  pPadOrig->BuildLegend();
+
+  auto* pDefault = static_cast<TH1*>(fvpObjects[iDef]);
+
+  // Histogram ratios to default
+  pPadRatio->cd();
+  auto* pMultiGraphRatio = new TMultiGraph((fsBaseName + "_ratio").data(), titleRatio);
+  for (int iV = 0; iV < nVersions; ++iV) {
+    if (iV == iDef) { continue; }
+    auto* pRatio = static_cast<TH1*>(fvpObjects[iV]->Clone());
+    pRatio->SetDirectory(fpOutDir);
+    auto currErrorLevel = gErrorIgnoreLevel;
+    gErrorIgnoreLevel   = kError;
+    auto* pCopy         = new TGraphAsymmErrors(pRatio, pDefault, "pois");
+    gErrorIgnoreLevel   = currErrorLevel;
+    pCopy->SetMarkerStyle(20);
+    pMultiGraphRatio->Add(pCopy, "P");
+    if (pRatio) {
+      delete pRatio;
+      pRatio = nullptr;
+    }
+  }
+  pMultiGraphRatio->GetYaxis()->SetTitle("ratio");
+  pMultiGraphRatio->GetXaxis()->SetTitle(xAxisTitle);
+  pMultiGraphRatio->Draw("A pmc plc");
+
+  // Histogram ratios to default
+  pPadDiff->cd();
+  auto* pMultiGraphDiff = new TMultiGraph((fsBaseName + "_diff").data(), titleDiff);
+  for (int iV = 0; iV < nVersions; ++iV) {
+    if (iV == iDef) { continue; }
+    auto* pDiff = static_cast<TH1*>(fvpObjects[iV]->Clone());
+    pDiff->SetDirectory(fpOutDir);
+    pDiff->Add(pDefault, -1.);
+    auto* pCopy = new TGraphAsymmErrors(pDiff);
+    pCopy->SetMarkerStyle(20);
+    pMultiGraphDiff->Add(pCopy, "P");
+    if (pDiff) {
+      delete pDiff;
+      pDiff = nullptr;
+    }
+  }
+  pMultiGraphDiff->GetYaxis()->SetTitle("difference");
+  pMultiGraphDiff->GetXaxis()->SetTitle(xAxisTitle);
+  pMultiGraphDiff->Draw("A pmc plc");
+  fpCanvas->cd();
+  pPadOrig->Draw();
+  pPadRatio->Draw();
+  pPadDiff->Draw();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool Hist1DHandler::ComparePointToPoint(const TH1* pNumerator, const TH1* pDenominator)
+{
+  bool ifDifferent = false;
+  // Compare content and error of each bin
+  // TODO: write a comment about
+  for (int iBinX = 0; iBinX <= pNumerator->GetNbinsX(); ++iBinX) {
+    for (int iBinY = 0; iBinY <= pNumerator->GetNbinsY(); ++iBinY) {
+      for (int iBinZ = 0; iBinZ <= pNumerator->GetNbinsZ(); ++iBinZ) {
+        auto numBinContent = pNumerator->GetBinContent(iBinX, iBinY, iBinZ);
+        auto denBinContent = pDenominator->GetBinContent(iBinX, iBinY, iBinZ);
+        // Check bin content
+        if (!TMath::IsNaN(numBinContent) && !TMath::IsNaN(denBinContent)) {
+          if (numBinContent != denBinContent) { return true; }
+        }
+        else {
+          if (TMath::IsNaN(numBinContent) != TMath::IsNaN(denBinContent)) { return true; }
+        }
+        auto numBinError = pNumerator->GetBinError(iBinX, iBinY, iBinZ);
+        auto denBinError = pDenominator->GetBinError(iBinX, iBinY, iBinZ);
+        // Check bin error
+        if (!TMath::IsNaN(numBinError) && !TMath::IsNaN(denBinError)) {
+          if (numBinError != denBinError) { return true; }
+        }
+        else {
+          if (TMath::IsNaN(numBinError) != TMath::IsNaN(denBinError)) { return true; }
+        }
+      }
+    }
+  }
+  return ifDifferent;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool Hist1DHandler::CompareRatioDeviation(const TH1* /*pNumerator*/, const TH1* /*pDenominator*/, double /*allowedDev*/)
+{
+  LOG(warn) << "Hist1DHandler::CompareRatioDeviation function was not implemented";
+  return false;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool Hist1DHandler::CompareWithChi2(const TH1* /*pNumerator*/, const TH1* /*pDenominator*/)
+{
+  LOG(warn) << "Hist1DHandler::CompareWthChi2 function was not implemented";
+  return false;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool Hist1DHandler::CompareWithKolmogorov(const TH1* /*pNumerator*/, const TH1* /*pDenominator*/)
+{
+  LOG(warn) << "Hist1DHandler::CompareWithKolmogorov function was not implemented";
+  return false;
+}
diff --git a/core/qa/checker/CbmQaCheckerHist1DHandler.h b/core/qa/checker/CbmQaCheckerHist1DHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..de0c828e5db1081257fc8c26dac6d86d2a5497e5
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerHist1DHandler.h
@@ -0,0 +1,100 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerHist1DHandler.h
+/// @brief  Handler class for 1D-histograms (including TProfile objects) (declaration)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  09.02.2023
+
+#ifndef CbmQaCheckerHist1DHandler_h
+#define CbmQaCheckerHist1DHandler_h 1
+
+#include "CbmQaCheckerObjectHandler.h"
+
+#include <memory>
+#include <string>
+
+class TObject;
+class TH1;
+
+namespace cbm::qa::checker
+{
+  /// @brief Handler for 1D-histograms.
+  ///
+  /// The handler keeps one-dimensional histogram objects of the same quantity within different code versions
+  /// and provides several comparison methods including point-by-point comparison, test of ratio deviation and
+  /// statistical hypothesis tests (Chi2 test and Kolmogorov test).
+  ///
+  class Hist1DHandler : public ObjectHandler {
+  public:
+    /// Enumeration for bit-flags of different comparison methods
+    enum EFlags
+    {
+      kPOINT,  ///< Point-by-point exact comparison
+      kRATIO,  ///< Comparison within of ratio difference from unity
+      kCHI2,   ///< Statistical hypothesis test using chi2 test
+      kKOLM    ///< Statistical hypothesis test using Kolmogorov test
+    };
+
+    /// @brief Constructor
+    /// @param iObject  Index of object
+    /// @param iFile    Index of file
+    /// @param iDataset Index of dataset
+    Hist1DHandler(int iObject, int iFile, int iDataset);
+
+    /// @brief Destructor
+    ~Hist1DHandler() = default;
+
+    /// @brief Creates object comparison canvas
+    void CreateCanvases();
+
+    /// @brief Compares objects to default
+    /// @param pNum    Pointer to "numerator" object
+    /// @param pDenom  Pointer to "denominator" object
+    /// @return  Comparison result represented as a bitset in unsigned integer
+    ///
+    /// The function provides comparison of objects and stores the comparison result in the field of the ObjectDB
+    /// instance for a given version, file, object and dataset ids. The comparison result is represented with the
+    /// unsigned integer, each raised bit of which declares, that the objects are different within the corresponding
+    /// comparison method.
+    ///         Bits used:
+    ///         0: Point to point comparison
+    ///         1: Comparison based on the ratio deviation
+    ///         2: Statistical test comparison with chi2-test
+    ///         3: Statistical test comparison with Kolmogorov test
+    CmpResult_t Compare(const TNamed* pNum, const TNamed* pDenom) const;
+
+  private:
+    /// @brief Compares two histograms point-to-point
+    /// @param pNumerator    Pointer to the numerator object of comparison (usually new version)
+    /// @param pDenominator  Pointer to the denominator object for comparison (usually default)
+    /// @return true  Histograms are different
+    /// @return false Histograms are equal
+    static bool ComparePointToPoint(const TH1* pNumerator, const TH1* pDenominator);
+
+    /// @brief Compares two histograms by maximum deviation of the ratio
+    /// @param pNumerator    Pointer to the numerator object of comparison (usually new version)
+    /// @param pDenominator  Pointer to the denominator object for comparison (usually default)
+    /// @param allowedDev    Max allowed ratio deviation from unity
+    /// @return true  Histograms are different
+    /// @return false Histograms are equal
+    static bool CompareRatioDeviation(const TH1* pNumerator, const TH1* pDenominator, double allowedDev = 10);
+
+    /// @brief Compares two histograms with chi2-test
+    /// @param pNumerator    Pointer to the numerator object of comparison (usually new version)
+    /// @param pDenominator  Pointer to the denominator object for comparison (usually default)
+    /// @return true  Histograms are different
+    /// @return false Histograms are equal
+    static bool CompareWithChi2(const TH1* pNumerator, const TH1* pDenominator);
+
+    /// @brief Compares two histograms with Kolmogorov test
+    /// @param pNumerator    Pointer to the numerator object of comparison (usually new version)
+    /// @param pDenominator  Pointer to the denominator object for comparison (usually default)
+    /// @return true  Histograms are different
+    /// @return false Histograms are equal
+    static bool CompareWithKolmogorov(const TH1* pNumerator, const TH1* pDenominator);
+  };
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerHist1DHandler_h
diff --git a/core/qa/checker/CbmQaCheckerHist2DHandler.cxx b/core/qa/checker/CbmQaCheckerHist2DHandler.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..e0ee23f9c3ee9988fc34180161b1780d991f64e8
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerHist2DHandler.cxx
@@ -0,0 +1,16 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerHist2DHandler.h
+/// @brief  Handler class for 2D-histograms (implementation)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  21.02.2023
+
+#include "CbmQaCheckerHist2DHandler.h"
+
+using cbm::qa::checker::Hist2DHandler;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Hist2DHandler::Hist2DHandler(int iObject, int iFile, int iDataset) : Hist1DHandler(iObject, iFile, iDataset) {}
diff --git a/core/qa/checker/CbmQaCheckerHist2DHandler.h b/core/qa/checker/CbmQaCheckerHist2DHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..fe3ce9b7a023e2c8d1aa2ac7fe87089c6da8c2ce
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerHist2DHandler.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerHist2DHandler.h
+/// @brief  Handler class for 2D-histograms (declaration)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  21.02.2023
+
+#ifndef CbmQaCheckerHist2DHandler_h
+#define CbmQaCheckerHist2DHandler_h 1
+
+#include "CbmQaCheckerHist1DHandler.h"
+
+namespace cbm::qa::checker
+{
+  /// @brief  Specification of the handler for TProfile class
+  ///
+  class Hist2DHandler : public Hist1DHandler {
+  public:
+    /// @brief Constructor
+    /// @param iObject  Index of object
+    /// @param iFile    Index of file
+    /// @param iDataset Index of dataset
+    Hist2DHandler(int iObject, int iFile, int iDataset);
+
+    /// @brief Destructor
+    ~Hist2DHandler() = default;
+
+    /// @brief Creates object comparison canvas
+    void CreateCanvases() {};
+  };
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerProfile1DHandler_h
diff --git a/core/qa/checker/CbmQaCheckerObjectDB.cxx b/core/qa/checker/CbmQaCheckerObjectDB.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..8625f2b7915efd2e1bbfd9298307ccdf8449dc07
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerObjectDB.cxx
@@ -0,0 +1,255 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerObjectDB.h
+/// @brief  Database for processed objects in the QA checker framework (implementation)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  15.02.2023
+
+#include "CbmQaCheckerObjectDB.h"
+
+#include "Logger.h"
+
+#include <algorithm>
+#include <regex>
+#include <sstream>
+
+using cbm::qa::checker::ObjectDB;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectDB::Clear()
+{
+  fvDatasets.clear();
+  fvFiles.clear();
+  fvFileLabels.clear();
+  fvObjects.clear();
+  fvVersionLabels.clear();
+  fvVersionPaths.clear();
+  fvGlobalToFileObject.clear();
+  fvCmpResults.clear();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+// ObjectDB::GetObjects(int iFile) const
+// {
+//   auto itBegin = std::upper_bound(fvGlobalToFileObject.begin(), fvGlobalToFileObject.end(), iFile,
+//     [](int i, const std::pair<int, int>& p) { return i <= p.first; });
+//   auto itEnd   = std::lower_bound(itBegin, fvGlobalToFileObject.end(), iFile,
+//     [](const std::pair<int, int>& p, int i) { return i >= p.first; });
+//
+//   int iBegin = itBegin - fvGlobalToFileObject.begin();
+//   int iEnd   = itEnd - fvGlobalToFileObject.begin();
+//   return boost::make_iterator_range(fvObjects.begin() + iBegin, fvObjects.begin() + iEnd);
+// }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectDB::AddVersion(const char* label, const char* path)
+{
+  fvVersionLabels.push_back(label);
+  fvVersionPaths.push_back(path);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectDB::AddDataset(const char* dataset) { fvDatasets.push_back(dataset); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string ObjectDB::GetInputFileName(int iVersion, int iFile, int iDataset) const
+{
+  std::string res = fvFiles[iFile];
+  res             = std::regex_replace(res, std::regex("\\%v"), fvVersionPaths[iVersion]);
+  res             = std::regex_replace(res, std::regex("\\%d"), fvDatasets[iDataset]);
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectDB::Init()
+{
+  // ----- Check consistency of input values
+  LOG_IF(fatal, !GetNofObjects()) << "ObjectDB: No objects were passed to the checker";
+  LOG_IF(fatal, GetNofDatasets() < 1) << "ObjectDB: No datasets were founde, at least one dataset should be provided";
+  LOG_IF(fatal, GetNofVersions() < 2) << "ObjectDB: File handler should have at least two versions to compare ("
+                                      << GetNofVersions() << " were provided)";
+
+
+  // ----- Define default version index
+  if (fsDefaultLabel.size()) {
+    auto it = std::find(fvVersionLabels.cbegin(), fvVersionLabels.cend(), fsDefaultLabel);
+    if (it == fvVersionLabels.cend()) {
+      std::stringstream msg;
+      msg << "ObjectDB: registered default label \"" << fsDefaultLabel << "\" is not found among the version labels:\n";
+      for (const auto& label : fvVersionLabels) {
+        msg << "\t- " << label << '\n';
+      }
+      LOG(fatal) << msg.str();
+    }
+    fDefVersionID = it - fvVersionLabels.cbegin();
+  }
+  else {
+    fDefVersionID = 0;
+    LOG(warn) << "ObjectDB: default version was not registered. Using the first version as the default one (\""
+              << fvVersionLabels[fDefVersionID] << "\")";
+  }
+
+  // ----- Reserve space for object comparison results
+  fvCmpResults.resize(fvVersionLabels.size() * fvObjects.size() * fvDatasets.size());
+
+  // ----- Add root path of input, if it were defined
+  auto regexSlashes = std::regex("(/+)");  // regular expression for a sequence of consecutive slashes
+  for (auto& path : fvVersionPaths) {
+    if (fsInputRootPath.size()) { path = fsInputRootPath + "/" + path; }
+    path = std::regex_replace(path, regexSlashes, "/");  // replace all consecutive slashes with a single one
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectDB::ReadFromYAML(const char* configName)
+{
+  // ----- Open input file
+  YAML::Node config;
+  try {
+    config = YAML::LoadFile(configName)["checker"];
+  }
+  catch (const YAML::BadFile* exc) {
+    LOG(fatal) << "ObjectDB: configuration file " << configName << " does not exist";
+  }
+  catch (const YAML::ParserException& exc) {
+    LOG(fatal) << "ObjectDB: configuration file " << configName << " is badly formatted";
+  }
+
+  // ----- Define file-object map
+  if (config["files"]) {
+    if (fvGlobalToFileObject.size()) {
+      LOG(warn) << "ObjectDB: file-object map was defined before. Redefining it from the config file " << configName;
+      fvGlobalToFileObject.clear();
+      fvFiles.clear();
+      fvFileLabels.clear();
+      fvObjects.clear();
+    }
+    try {
+      const auto& rootNode = config["files"];
+
+      // Calculate total number of objects and files
+      size_t nFiles   = rootNode.size();
+      size_t nObjects = 0;
+      for (const auto& fileNode : rootNode) {
+        nObjects += fileNode["objects"].size();
+      }
+      fvFiles.reserve(nFiles);
+      fvFileLabels.reserve(nFiles);
+      fvObjects.reserve(nObjects);
+      fvGlobalToFileObject.reserve(nObjects);
+
+      // Fill vectors
+      for (const auto& fileNode : rootNode) {
+        size_t iObj = 0;
+        for (const auto& objectNode : fileNode["objects"]) {
+          fvGlobalToFileObject.emplace_back(fvFiles.size(), iObj++);
+          fvObjects.push_back(objectNode.as<std::string>());
+        }
+        fvFiles.push_back(fileNode["name"].as<std::string>());
+        fvFileLabels.push_back(fileNode["label"].as<std::string>());
+      }
+    }
+    catch (const YAML::InvalidNode& exc) {
+      LOG(fatal) << "ObjectDB: error while reading checker/files node from the config " << configName;
+    }
+  }
+  else {
+    LOG(warn) << "ObjectDB: node checker/inputformat is not defined in the config " << configName;
+  }
+
+  // ----- Define dataset names
+  if (config["datasets"]) {
+    if (fvDatasets.size()) {
+      LOG(warn) << "ObjectDB: dataset names were defined before. Redefining them from the config " << configName;
+      fvDatasets.clear();
+    }
+    try {
+      const auto& rootNode = config["datasets"];
+      fvDatasets.reserve(rootNode.size());
+      for (const auto& datasetNode : rootNode) {
+        fvDatasets.push_back(datasetNode.as<std::string>());
+      }
+    }
+    catch (const YAML::InvalidNode& exc) {
+      LOG(fatal) << "ObjectDB:: error while reading checker/datasets node from the config " << configName;
+    }
+  }
+  else {
+    LOG(warn) << "ObjectDB: node checker/inputformat is not defined in the config " << configName;
+  }
+
+  // ----- Define version names
+  if (config["versions"]) {
+    if (fvVersionLabels.size()) {
+      LOG(warn) << "ObjectDB: dataset names were defined before. Redefining them from the config " << configName;
+      fvVersionLabels.clear();
+      fvVersionPaths.clear();
+    }
+    try {
+      const auto& rootNode = config["versions"];
+      fvVersionLabels.reserve(rootNode.size());
+      fvVersionPaths.reserve(rootNode.size());
+      for (const auto& versionNode : rootNode) {
+        fvVersionLabels.push_back(versionNode["label"].as<std::string>());
+        fvVersionPaths.push_back(versionNode["path"].as<std::string>());
+      }
+    }
+    catch (const YAML::InvalidNode& exc) {
+      LOG(fatal) << "ObjectDB:: error while reading checker/versions node from the config " << configName;
+    }
+  }
+  else {
+    LOG(warn) << "ObjectDB: node checker/versions is not defined in the config " << configName;
+  }
+
+  // ----- Define default version
+  if (config["default_label"]) {
+    try {
+      SetDefaultLabel(config["default_label"].as<std::string>().data());
+    }
+    catch (const YAML::InvalidNode& exc) {
+      LOG(fatal) << "ObjectDB:: error while reading checker/default_label node from the config " << configName;
+    }
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string ObjectDB::ToString() const
+{
+  std::stringstream msg;
+  msg << "*** CBM QA-Checker: defied files ****\n\n";
+
+  msg << "----- Versions:\n";
+  for (size_t iV = 0; iV < fvVersionLabels.size(); ++iV) {
+    if (iV == (size_t) fDefVersionID) {
+      msg << "\033[1;32m- " << fvVersionLabels[iV] << " (path: " << fvVersionPaths[iV] << ")\n ---> DEFAULT\033[0m";
+    }
+    else {
+      msg << "- " << fvVersionLabels[iV] << " (path: " << fvVersionPaths[iV] << ")\n";
+    }
+  }
+
+  msg << "----- Datasets:\n";
+  for (const auto& dataset : fvDatasets) {
+    msg << "- \033[1m" << dataset << "\033[0m\n";
+  }
+
+  msg << "----- Objects\n";
+  for (size_t iF = 0; iF < fvFiles.size(); ++iF) {
+    msg << "- \033[1m" << fvFiles[iF] << "\033[0m\n";
+    for (const auto& object : this->GetObjects(iF)) {
+      msg << "\t- " << object << '\n';
+    }
+  }
+  return msg.str();
+}
diff --git a/core/qa/checker/CbmQaCheckerObjectDB.h b/core/qa/checker/CbmQaCheckerObjectDB.h
new file mode 100644
index 0000000000000000000000000000000000000000..49eea504b998fe4fa7d89f94325cc7ca36b8351c
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerObjectDB.h
@@ -0,0 +1,219 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerObjectDB.h
+/// @brief  Database for processed objects in the QA checker framework (header)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  15.02.2023
+
+#ifndef CbmQaCheckerObjectDB_h
+#define CbmQaCheckerObjectDB_h 1
+
+#include "CbmQaCheckerTypedefs.h"
+
+#include "Logger.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <yaml-cpp/yaml.h>
+
+// TMP
+#include <boost/core/demangle.hpp>
+
+#include <typeinfo>
+template<typename T>
+std::string type_str()
+{
+  return boost::core::demangle(typeid(T).name());
+}
+
+namespace cbm::qa::checker
+{
+  /// @brief A data base class for processed objects
+  ///
+  /// The data base contains information on datasets, file-object pairs and versions, which are compared.
+  class ObjectDB {
+  public:
+    /// @brief Default constructor
+    ObjectDB() = default;
+
+    /// @brief Destructor
+    ~ObjectDB() = default;
+
+    /// @brief Copy constructor
+    ObjectDB(const ObjectDB&) = delete;
+
+    /// @brief Move constructor
+    ObjectDB(ObjectDB&&) = delete;
+
+    /// @brief Copy assignment operator
+    ObjectDB& operator=(const ObjectDB&) = delete;
+
+    /// @brief Move assignment operator
+    ObjectDB& operator=(ObjectDB&&) = delete;
+
+    /// @brief Adds version
+    /// @param label      Label of version
+    /// @param path       Path to output files made for this version
+    void AddVersion(const char* label, const char* path);
+
+    /// @brief Adds dataset
+    /// @param dataset  Name of dataset
+    void AddDataset(const char* dataset);
+
+    /// @brief Clears content
+    void Clear();
+
+    /// @brief Gets name of dataset
+    /// @param iDataset  Index of dataset
+    /// @return  Name of dataset
+    const auto& GetDataset(int iDataset) const { return fvDatasets[iDataset]; }
+
+    /// @brief Gets reference to dataset name array
+    const auto& GetDatasets() const { return fvDatasets; }
+
+    /// @brief Gets index of default version
+    /// @return  Index of default version
+    int GetDefaultID() const { return fDefVersionID; }
+
+    /// @brief Gets label of the default version
+    const std::string& GetDefaultLabel() const
+    {
+      assert(fDefVersionID > -1);
+      return GetVersionLabel(fDefVersionID);
+    }
+
+    /// @brief Gets label of file
+    /// @param iFile  Index of file
+    /// @return  Label of file
+    const auto& GetFileLabel(int iFile) const { return fvFileLabels[iFile]; }
+
+    /// @brief Gets iterator range for object names stored in a file
+    /// @note  The iterator_range object behaves itself just like an ordinary STL container.
+    /// @param iFile  Index of file
+    /// @return  iterator_range for ROOT objects of this file
+    /// TODO: Understand, which type is deduced
+    auto GetObjects(int iFile) const
+    {
+      auto itBegin = std::upper_bound(fvGlobalToFileObject.begin(), fvGlobalToFileObject.end(), iFile,
+                                      [](int i, const std::pair<int, int>& p) { return i <= p.first; });
+      auto itEnd   = std::lower_bound(itBegin, fvGlobalToFileObject.end(), iFile,
+                                    [](const std::pair<int, int>& p, int i) { return i >= p.first; });
+
+      int iBegin = itBegin - fvGlobalToFileObject.begin();
+      int iEnd   = itEnd - fvGlobalToFileObject.begin();
+      return boost::make_iterator_range(fvObjects.begin() + iBegin, fvObjects.begin() + iEnd);
+    }
+
+    /// @brief Gets comparison result
+    /// @param iDS    Index of dataset
+    /// @param iFile  Index of file
+    /// @param iObj   Index of object within the file
+    /// @param iVer   Index of version
+    /// @return  Value of comparison result
+    CmpResult_t GetCmpResult(int iDS, int iFile, int iObj, int iVer) const
+    {
+      int iObjGlob = GetObjects(iFile).begin() - fvObjects.begin() + iObj;
+      int iRes     = iVer + fvVersionLabels.size() * (iObjGlob + iDS * fvObjects.size());
+      return fvCmpResults[iRes];
+    }
+
+    /// @brief Gets name of file from indexes of version, file and dataset
+    /// @param iVersion  Index of version
+    /// @param iFile     Index of file
+    /// @param iDataset  Index of dataset
+    /// @return  Name of input file
+    std::string GetInputFileName(int iVersion, int iFile, int iDataset) const;
+
+    /// @brief Gets object name by its local index and index of file
+    /// @param iFile    Index of file
+    /// @param iObject  Index of object for a given file
+    /// @return  Name of object
+    const std::string& GetObject(int iFile, int iObject) const { return GetObjects(iFile)[iObject]; }
+
+    /// @brief Gets number of datasets
+    int GetNofDatasets() const { return fvDatasets.size(); }
+
+    /// @brief Gets total number of objects
+    int GetNofObjects() const { return fvObjects.size(); }
+
+    /// @brief Gets number of objects in file
+    /// @param iFile  Index of file
+    int GetNofObjects(int iFile) const { return GetObjects(iFile).size(); }
+
+    /// @brief Gets number of files
+    int GetNofFiles() const { return fvFiles.size(); }
+
+    /// @brief Gets number of versions
+    int GetNofVersions() const { return fvVersionLabels.size(); }
+
+    /// @brief Gets version label
+    /// @param iVersion  Index of version
+    const std::string& GetVersionLabel(int iVersion) const { return fvVersionLabels[iVersion]; }
+
+    /// @brief Gets reference to version label array
+    const auto& GetVersionLabels() const { return fvVersionLabels; }
+
+    /// @brief Gets version path
+    /// @param iVersion  Index of version
+    const std::string& GetVersionPath(int iVersion) const { return fvVersionPaths[iVersion]; }
+
+    /// @brief Gets reference to version path array
+    const auto& GetVersionPaths() const { return fvVersionPaths; }
+
+    /// @brief Initializes the database
+    void Init();
+
+    /// @brief Reads DB from YAML node
+    /// @param config Root node of the YAML file
+    void ReadFromYAML(const char* configName);
+
+    /// @brief Saves content to string
+    /// @return A string representation of the DB contents
+    std::string ToString() const;
+
+    /// @brief Sets comparison result
+    /// @param iDS    Index of dataset
+    /// @param iFile  Index of file
+    /// @param iObj   Index of object within the file
+    /// @param iVer   Index of version
+    /// @param value  Value of comparison result
+    void SetCmpResult(int iDS, int iFile, int iObj, int iVer, CmpResult_t value)
+    {
+      int iObjGlob       = GetObjects(iFile).begin() - fvObjects.begin() + iObj;
+      int iRes           = iVer + fvVersionLabels.size() * (iObjGlob + iDS * fvObjects.size());
+      fvCmpResults[iRes] = value;
+    }
+
+    /// @brief  Sets default version label
+    /// @param  defaultLabel  Name of default label
+    ///
+    /// If the default version is not provided as well as the provided, the first version will be used as the
+    /// default one.
+    void SetDefaultLabel(const char* defaultLabel) { fsDefaultLabel = defaultLabel; }
+
+    /// @brief Sets root path to input files
+    /// @param pathName  Relative or absolute root path to input the input directories
+    void SetInputRootPath(const char* pathName) { fsInputRootPath = pathName; }
+
+  private:
+    int fDefVersionID = -1;  ///< Index of default version
+
+    std::string fsInputRootPath = "";  ///< Root path for input files
+    std::string fsDefaultLabel  = "";  ///< Name of default version label
+
+    std::vector<std::string> fvDatasets;                    ///< Container of dataset names
+    std::vector<std::string> fvFiles;                       ///< Container of file names
+    std::vector<std::string> fvFileLabels;                  ///< Container of file labels (used in output)
+    std::vector<std::string> fvObjects;                     ///< Container of object names (joint for all the files)
+    std::vector<std::string> fvVersionLabels;               ///< Container of version labels
+    std::vector<std::string> fvVersionPaths;                ///< Container of version paths
+    std::vector<std::pair<int, int>> fvGlobalToFileObject;  ///< Map of global obj. index -> local file-object
+    std::vector<CmpResult_t> fvCmpResults;                  ///< Comparison results vs. dataset, object and version
+  };
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerObjectDB_h
diff --git a/core/qa/checker/CbmQaCheckerObjectHandler.cxx b/core/qa/checker/CbmQaCheckerObjectHandler.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..7ed93e87b9fe342c4f88bf5e2504aa17200b2f3b
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerObjectHandler.cxx
@@ -0,0 +1,88 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerObjectHandler.h
+/// @brief  Base handler class (implementation)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  09.02.2023
+
+#include "CbmQaCheckerObjectHandler.h"
+
+#include "CbmQaCanvas.h"
+
+#include "Logger.h"
+
+#include "TDirectory.h"
+#include "TNamed.h"
+
+using cbm::qa::checker::ObjectHandler;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+ObjectHandler::ObjectHandler(int iObject, int iFile, int iDataset, const char* objType)
+  : fObjectID(iObject)
+  , fFileID(iFile)
+  , fDatasetID(iDataset)
+  , fsObjType(objType)
+{
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+ObjectHandler::~ObjectHandler() {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectHandler::AddObjects(const std::vector<TNamed*>& vpObj)
+{
+  // ----- Check input
+  LOG_IF(fatal, !fpObjDB) << "ObjectHandler: object database was not defined";
+  LOG_IF(fatal, !fpOutDir) << "ObjectHandler: output directory was not defined";
+  LOG_IF(fatal, (int) vpObj.size() != fpObjDB->GetNofVersions())
+    << "ObjectHandler: Attempt to add vector with object pointers of different to the one of version labels";
+
+  TDirectory* pCurrDir = gDirectory;
+  gDirectory           = fpOutDir;
+  for (int iVer = 0; iVer < fpObjDB->GetNofVersions(); ++iVer) {
+    if (!fsBaseName.size()) { fsBaseName = vpObj[iVer]->GetName(); }
+    else {
+      LOG_IF(fatal, strcmp(fsBaseName.c_str(), vpObj[iVer]->GetName()))
+        << "Hist1DHandler: attempt to add object of different name " << fsBaseName << " vs. " << vpObj[iVer]->GetName();
+    }
+    const char* cloneName = Form("%s_orig_%s", fsBaseName.data(), fpObjDB->GetVersionLabel(iVer).data());
+    fvpObjects.push_back((TNamed*) vpObj[iVer]->Clone(cloneName));
+  }
+  gDirectory = pCurrDir;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectHandler::CompareWithDefault()
+{
+  int iDef = fpObjDB->GetDefaultID();
+  for (int iV = 0; iV < (int) fvpObjects.size(); ++iV) {
+    if (iV == iDef) { continue; }
+    auto res = this->Compare(fvpObjects[iV], fvpObjects[iDef]);
+    fpObjDB->SetCmpResult(fDatasetID, fFileID, fObjectID, iV, res);
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectHandler::SetOutputDirectory(TDirectory* pDir)
+{
+  LOG_IF(fatal, !pDir) << "ObjectHandler: attempt to pass nullptr as a folder";
+  fpOutDir = pDir;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void ObjectHandler::Write()
+{
+  fpOutDir->cd();
+  for (auto pObj : fvpObjects) {
+    pObj->Write();
+  }
+  if (fpCanvas.get()) { fpCanvas->Write(); }
+}
\ No newline at end of file
diff --git a/core/qa/checker/CbmQaCheckerObjectHandler.h b/core/qa/checker/CbmQaCheckerObjectHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..1ad5fd824caf2aa42db1deb3f40eb6a27c66eb54
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerObjectHandler.h
@@ -0,0 +1,102 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerObjectHandler.h
+/// @brief  Base handler class (declaration)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  08.02.2023
+
+#ifndef CbmQaCheckerObjectHandler_h
+#define CbmQaCheckerObjectHandler_h 1
+
+#include "CbmQaCheckerObjectDB.h"
+#include "CbmQaCheckerTypedefs.h"
+
+#include "Rtypes.h"
+
+#include <memory>
+#include <string>
+
+class TDirectory;
+class TNamed;
+class TObject;
+class TCanvas;
+class CbmQaCanvas;
+
+namespace cbm::qa::checker
+{
+  /// @brief Base abstract class for object handler.
+  ///
+  /// The class provides interface for handling objects of the same type, obtained under different versions of the code
+  /// base.
+  ///
+  class ObjectHandler {
+  public:
+    /// @brief Default constructor
+    /// @param iObject  Index of object
+    /// @param iFile    Index of file
+    /// @param iDataset Index of dataset
+    /// @param objType  Type of the handled objects
+    ObjectHandler(int iObject, int iFile, int iDataset, const char* objType = "");
+
+    /// @brief Destructor
+    virtual ~ObjectHandler();
+
+    /// @brief Adds vector of pointer to objects
+    /// @param vpObj  Vector of pointers to TNamed objects
+    void AddObjects(const std::vector<TNamed*>& vpObj);
+
+    /// @brief Creates object comparison canvas
+    virtual void CreateCanvases() {};
+
+    /// @brief Compares two objects with different methods
+    /// @param pNum    Pointer to "numerator" object
+    /// @param pDenom  Pointer to "denominator" object
+    /// @return  Comparison result represented as a bitset in unsigned integer
+    /// TODO: .....
+    virtual CmpResult_t Compare(const TNamed* pNum, const TNamed* pDenom) const = 0;
+
+    /// @brief Compares versions with default and writes result into DB
+    void CompareWithDefault();
+
+    /// @brief Sets folder to store output
+    /// @param pDir  Pointer to folder instance
+    void SetOutputDirectory(TDirectory* pDir);
+
+    /// @brief Sets objects database
+    /// @param  pObjDB  Shared pointer to object database
+    void SetObjectDB(std::shared_ptr<ObjectDB>& pObjDB) { fpObjDB = pObjDB; }
+
+    /// @brief Sets verbose level
+    /// @param verbose  Verbose level:
+    ///                 - 0: Silent mode
+    ///                 - 1:
+    void SetVerbose(int verbose) { fVerbose = verbose; }
+
+    /// @brief Sets bit flag to control handler behaviour
+    /// @param bit  Bit index
+    ///
+    /// The bit flags should be defined in an enumeration of the default class
+    void SetBitFlag(uint8_t bit) { fOptionBits.set(bit); }
+
+    /// @brief Writes objects to file
+    void Write();
+
+  protected:
+    int fObjectID                         = -1;        ///< Index of object
+    int fFileID                           = -1;        ///< Index of file
+    int fDatasetID                        = -1;        ///< Index of dataset
+    int fVerbose                          = 0;         ///< Verbosity level
+    TDirectory* fpOutDir                  = nullptr;   ///< Pointer to directory
+    std::shared_ptr<ObjectDB> fpObjDB     = nullptr;   ///< Pointer to object database
+    std::shared_ptr<CbmQaCanvas> fpCanvas = nullptr;   ///< Comparison canvas
+    std::bitset<sizeof(CmpResult_t) * 8> fOptionBits;  ///< Bitset for option
+
+    std::string fsObjType  = "";      ///< Base type of the object to be handled
+    std::string fsBaseName = "";      ///< Base names of the object
+    std::vector<TNamed*> fvpObjects;  ///< Vector of objects
+  };
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerObjectHandler_h
\ No newline at end of file
diff --git a/core/qa/checker/CbmQaCheckerProfile1DHandler.cxx b/core/qa/checker/CbmQaCheckerProfile1DHandler.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..db63fb788ef8714f9ff048cbd60dba7091e3983e
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerProfile1DHandler.cxx
@@ -0,0 +1,134 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerProfile1DHandler.h
+/// @brief  Handler class for 1D-profiles (implementation)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  19.02.2023
+
+#include "CbmQaCheckerProfile1DHandler.h"
+
+#include "CbmQaCanvas.h"
+#include "CbmQaCheckerHist1DHandler.h"
+#include "CbmQaCheckerTypedefs.h"
+
+#include "Logger.h"
+
+#include "TCanvas.h"
+#include "TError.h"
+#include "TFolder.h"
+#include "TGraphAsymmErrors.h"
+#include "TLegend.h"
+#include "TMath.h"
+#include "TMultiGraph.h"
+#include "TObject.h"
+#include "TProfile.h"
+#include "TRatioPlot.h"
+#include "TStyle.h"
+
+#include <limits>
+
+using cbm::qa::checker::Profile1DHandler;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Profile1DHandler::Profile1DHandler(int iObject, int iFile, int iDataset) : Hist1DHandler(iObject, iFile, iDataset) {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Profile1DHandler::CreateCanvases()
+{
+  int nVersions = fpObjDB->GetNofVersions();
+  int iDef      = fpObjDB->GetDefaultID();
+
+  // ----- Canvas: comparison of original histograms, ratios and subtractions
+  std::string sCanvName  = fsBaseName + "_cmp_canvas";
+  std::string sCanvTitle = "Comparison result for \"" + fsBaseName + "\"";
+  fpCanvas               = std::make_shared<CbmQaCanvas>(sCanvName.data(), sCanvTitle.data(), 1500, 500);
+  fpCanvas->cd();
+  auto* pPadOrig = new TPad("p1", "p1", 0.0, 0.0, 0.3333, 1.0);
+  pPadOrig->SetMargin(0.20, 0.05, 0.20, 0.10);
+  fpCanvas->cd();
+  auto* pPadRatio = new TPad("p2", "p2", 0.3333, 0.0, 0.6666, 1.0);
+  pPadRatio->SetMargin(0.20, 0.05, 0.20, 0.10);
+  fpCanvas->cd();
+  auto* pPadDiff = new TPad("p3", "p3", 0.6666, 0.0, 1.0, 1.0);
+  pPadDiff->SetMargin(0.20, 0.05, 0.20, 0.10);
+
+  const char* title      = fvpObjects[0]->GetTitle();
+  const char* titleRatio = Form("Ratio to %s", fpObjDB->GetVersionLabel(iDef).data());
+  const char* titleDiff  = Form("Difference from %s", fpObjDB->GetVersionLabel(iDef).data());
+  const char* xAxisTitle = static_cast<TProfile*>(fvpObjects[0])->GetXaxis()->GetTitle();
+  const char* yAxisTitle = static_cast<TProfile*>(fvpObjects[0])->GetYaxis()->GetTitle();
+
+  // Original histograms
+  pPadOrig->cd();
+  auto* pMultiGraphOrig = new TMultiGraph(fsBaseName.data(), title);
+  //auto* pLegend = new TLegend(kLegendSize[0], kLegendSize[1]);
+  for (int iV = 0; iV < nVersions; ++iV) {
+    auto* pCopy = new TGraphAsymmErrors((TH1*) fvpObjects[iV]);
+    pCopy->SetMarkerStyle(20);
+    pCopy->SetTitle(fpObjDB->GetVersionLabel(iV).data());
+    pMultiGraphOrig->Add(pCopy, "P");
+  }
+  pMultiGraphOrig->GetXaxis()->SetTitle(xAxisTitle);
+  pMultiGraphOrig->GetYaxis()->SetTitle(yAxisTitle);
+  pMultiGraphOrig->Draw("A pmc plc");
+  pPadOrig->BuildLegend();
+  //pLegend->Draw();
+
+  auto* pDefault = static_cast<TProfile*>(fvpObjects[iDef])->ProjectionX();
+
+  // Histogram ratios to default
+  pPadRatio->cd();
+  auto* pMultiGraphRatio = new TMultiGraph((fsBaseName + "_ratio").data(), titleRatio);
+  for (int iV = 0; iV < nVersions; ++iV) {
+    if (iV == iDef) { continue; }
+    auto* pRatio        = static_cast<TProfile*>(fvpObjects[iV])->ProjectionX();
+    auto currErrorLevel = gErrorIgnoreLevel;
+    gErrorIgnoreLevel   = kError;
+    auto* pCopy         = new TGraphAsymmErrors(pRatio, pDefault, "pois");
+    gErrorIgnoreLevel   = currErrorLevel;
+    pCopy->SetMarkerStyle(20);
+    pMultiGraphRatio->Add(pCopy, "P");
+
+    if (pRatio) {
+      delete pRatio;
+      pRatio = nullptr;
+    }
+  }
+  pMultiGraphRatio->GetXaxis()->SetTitle(xAxisTitle);
+  pMultiGraphRatio->GetYaxis()->SetTitle("ratio");
+  pMultiGraphRatio->Draw("A pmc plc");
+
+  // Histogram ratios to default
+  pPadDiff->cd();
+  auto* pMultiGraphDiff = new TMultiGraph((fsBaseName + "_diff").data(), titleDiff);
+  for (int iV = 0; iV < nVersions; ++iV) {
+    if (iV == iDef) { continue; }
+    auto* pDiff = static_cast<TProfile*>(fvpObjects[iV])->ProjectionX();
+    pDiff->Add(pDefault, -1.);
+    auto* pCopy = new TGraphAsymmErrors(pDiff);
+    pCopy->GetYaxis()->SetTitle("difference");
+    pCopy->SetMarkerStyle(20);
+    pMultiGraphDiff->Add(pCopy, "P");
+
+    if (pDiff) {
+      delete pDiff;
+      pDiff = nullptr;
+    }
+  }
+  pMultiGraphDiff->GetXaxis()->SetTitle(xAxisTitle);
+  pMultiGraphDiff->GetYaxis()->SetTitle("difference");
+  pMultiGraphDiff->Draw("A pmc plc");
+  fpCanvas->cd();
+  pPadOrig->Draw();
+  pPadRatio->Draw();
+  pPadDiff->Draw();
+
+  if (pDefault) {
+    delete pDefault;
+    pDefault = nullptr;
+  }
+}
diff --git a/core/qa/checker/CbmQaCheckerProfile1DHandler.h b/core/qa/checker/CbmQaCheckerProfile1DHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..c2ebbc509102003a96cb549c4acb08930b7eea64
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerProfile1DHandler.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerProfile1DHandler.h
+/// @brief  Handler class for 1D-profiles (declaration)
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  19.02.2023
+
+#ifndef CbmQaCheckerProfile1DHandler_h
+#define CbmQaCheckerProfile1DHandler_h 1
+
+#include "CbmQaCheckerHist1DHandler.h"
+
+namespace cbm::qa::checker
+{
+  /// @brief  Specification of the handler for TProfile class
+  ///
+  class Profile1DHandler : public Hist1DHandler {
+  public:
+    /// @brief Constructor
+    /// @param iObject  Index of object
+    /// @param iFile    Index of file
+    /// @param iDataset Index of dataset
+    Profile1DHandler(int iObject, int iFile, int iDataset);
+
+    /// @brief Destructor
+    ~Profile1DHandler() = default;
+
+    /// @brief Creates object comparison canvas
+    void CreateCanvases();
+  };
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerProfile1DHandler_h
diff --git a/core/qa/checker/CbmQaCheckerTypedefs.h b/core/qa/checker/CbmQaCheckerTypedefs.h
new file mode 100644
index 0000000000000000000000000000000000000000..a74da874ca66f511b1ab9019a3f9ffce58aae4a9
--- /dev/null
+++ b/core/qa/checker/CbmQaCheckerTypedefs.h
@@ -0,0 +1,44 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   CbmQaCheckerTypedefs.h
+/// @brief  Common definitions for QA-Checker framework
+/// @author S. Zharko <s.zharko@gsi.de>
+/// @since  08.02.2023
+
+#ifndef CbmQaCheckerTypedefs_h
+#define CbmQaCheckerTypedefs_h 1
+
+#include <boost/range/iterator_range.hpp>
+
+#include <bitset>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace cbm::qa::checker
+{
+  /// @brief Enumerations for QA-Checker execution control
+  ///
+  enum class EFlagBit
+  {
+    kSKIP_LOST_OBJECTS,  //< Skips objects, which are defined in config, but not presented in file
+    kEND
+  };
+
+  // ----- Aliases
+  using MapStrToStr_t     = std::unordered_map<std::string, std::string>;
+  using MapStrToStrVect_t = std::unordered_map<std::string, std::vector<std::string>>;
+  using FlagBitSet_t      = std::bitset<static_cast<int>(EFlagBit::kEND)>;
+  using CmpResult_t       = uint16_t;  ///< Bitset to keep the comparison result
+
+  template<class T>
+  using VectRange_t = boost::iterator_range<typename std::vector<T>::iterator>;
+
+  // ----- Constants
+  constexpr double kLegendSize[2] = {.3, .05};  // width and height in % of the pad size
+
+}  // namespace cbm::qa::checker
+
+#endif  // CbmQaCheckerTypedefs_h
\ No newline at end of file
diff --git a/macro/qa/objects.yaml b/macro/qa/objects.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..90aa199fe5d66395bedb8a15e88446c057047d9d
--- /dev/null
+++ b/macro/qa/objects.yaml
@@ -0,0 +1,19 @@
+# List of objects from QA output to be checked
+
+checker:
+  # Format of the input file. In the format the next indicators can be used:
+  #   - %v :  Path to directory for this version
+  #   - %f :  File type
+  #   - %d :  Dataset name
+  inputformat: "%v/%f_%d.root"
+  # Datasets (at the moment all the datasets should contain the same sets of files and objects to compare)
+  datasets:
+    - auau.10gev.mbias.mu.eb.100ev.reco
+    - auau.10gev.mbias.el.eb.100ev.reco
+  # Section defines a list of files and stored objects to be compared. If object list is empty,
+  # all of the histograms will be compared
+  files:
+    - name: L1_histo
+      objects:
+        - L1/p_eff_all_vs_mom
+        - L1/h_ghost_Rmom
diff --git a/macro/qa/qa_compare.C b/macro/qa/qa_compare.C
new file mode 100644
index 0000000000000000000000000000000000000000..434681ada84c1240025e9da028971d9cbd6d3162
--- /dev/null
+++ b/macro/qa/qa_compare.C
@@ -0,0 +1,38 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// @file   qa_compare.C
+/// @author Sergei Zharko <s.zharko@gsi.de>
+/// @since  06.02.2023
+/// @brief  ROOT macro to run QA-Checker framework
+
+/// @brief  Function to compare QA results
+/// @param configName    Name of configuration file with object list, datasets and versions
+/// @param inputRootDir  Root directory with input data
+/// @param outputName    Name of the output (default is "QaCheckerResult.root")
+/// @return  Result flag:
+///          - 0: all objects are the same within defined comparison procedure
+///          - 1: some of the objects differ within versions
+int qa_compare(const char* configName, const char* inputRootDir = ".", const char* outputName = "QaCheckerResult.root")
+{
+  // ----- Logger settings
+  FairLogger::GetLogger()->SetLogScreenLevel("INFO");
+  FairLogger::GetLogger()->SetColoredLog(true);
+
+  // ----- Style settings
+  gStyle->SetPalette(kSolar);
+
+  // ----- Configure QA-Checker
+  auto pQaChecker = std::make_unique<cbm::qa::checker::Core>();
+  pQaChecker->RegisterOutFile(outputName);  // Set name of the output file
+  pQaChecker->SetFromYAML(configName);      // Read file-object map
+  pQaChecker->SetInputRootPath(inputRootDir);
+
+  // ----- Run comparision routine
+  pQaChecker->Process("P");
+
+  // ----- Scan results
+  bool res = pQaChecker->Scan();
+  return !res;
+}