diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9f450c880316d42cc974484c257d85cecd07c3c1..ee2ec3a8e6ad07fdd7ec95666893d5e2854b50c4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -184,6 +184,11 @@ if(fmt_FOUND)
   list(APPEND packages fmt)
 endif()
 
+#Searching for ONNXRuntime
+find_package(ONNXRuntime CONFIG)
+if (ONNXRuntime_FOUND)
+  list(APPEND packages ONNXRuntime)
+endif()
 
 #Searching for FairMQ
 find_package(FairMQ CONFIG REQUIRED)
diff --git a/core/base/CbmHistManager.cxx b/core/base/CbmHistManager.cxx
index c16b33d9df40b9498097edda57db1b81c0dc1293..1cc8bc50aa1e21b3e431495d4466fb94d3c4bf37 100755
--- a/core/base/CbmHistManager.cxx
+++ b/core/base/CbmHistManager.cxx
@@ -127,6 +127,13 @@ void CbmHistManager::WriteToFile()
   }
 }
 
+void CbmHistManager::WriteCanvasToFile()
+{
+  for (auto& canvas : fCanvases) {
+    canvas->Write();
+  }
+}
+
 void CbmHistManager::ReadFromFile(TFile* file)
 {
   assert(file != nullptr);
diff --git a/core/base/CbmHistManager.h b/core/base/CbmHistManager.h
index fae5f81a52d80b7cd59a0a0e2cd12784f151584b..f5247fe88e8346efa5c01d773fa35ca3a08d5a9a 100644
--- a/core/base/CbmHistManager.h
+++ b/core/base/CbmHistManager.h
@@ -429,6 +429,11 @@ public:
     */
   void WriteToFile();
 
+  /**
+    * \brief Write all canvas to current opened file.
+    */
+  void WriteCanvasToFile();
+
   /**
     * \brief Read histograms from file.
     * \param[in] file Pointer to file with histograms.
diff --git a/core/data/rich/CbmRichHit.cxx b/core/data/rich/CbmRichHit.cxx
index 93de285ad09bf2253a8466ad7b53317a47311cc3..5a63c6827456fe0a3a3c8a018861919d53e9753c 100644
--- a/core/data/rich/CbmRichHit.cxx
+++ b/core/data/rich/CbmRichHit.cxx
@@ -22,6 +22,7 @@ CbmRichHit::CbmRichHit()
   // fNPhotons(0),
   // fAmplitude(0.),
   fToT(0.)
+  , fIsNoiseNN(false)
 {
   SetType(kRICHHIT);
   SetTime(0.);
@@ -34,6 +35,7 @@ CbmRichHit::CbmRichHit(double x, double y)
   //  fNPhotons(0),
   //  fAmplitude(0.),
   fToT(0.)
+  , fIsNoiseNN(false)
 {
   SetType(kRICHHIT);
   SetX(x);
@@ -48,6 +50,7 @@ CbmRichHit::CbmRichHit(double x, double y, double ts, double tot)
   //    fNPhotons(0),
   //    fAmplitude(0.),
   fToT(tot)
+  , fIsNoiseNN(false)
 {
   SetType(kRICHHIT);
   SetX(x);
@@ -61,8 +64,8 @@ std::string CbmRichHit::ToString() const
 {
   stringstream ss;
   ss << "CbmRichHit: address=" << GetAddress() << " pos=(" << GetX() << "," << GetY() << "," << GetZ() << ") err=("
-     << GetDx() << "," << GetDy() << "," << GetDz() << ") dxy=" << GetDxy() << " refId="
-     << GetRefId()
+     << GetDx() << "," << GetDy() << "," << GetDz() << ") dxy=" << GetDxy() << " refId=" << GetRefId() << " isNoiseNN="
+     << GetIsNoiseNN()
      //  << " pmtId=" << GetPmtId() << " nofPhotons=" << GetNPhotons()
      //  << " amplitude=" << GetAmplitude()
      << endl;
diff --git a/core/data/rich/CbmRichHit.h b/core/data/rich/CbmRichHit.h
index b31e48c37d0090e1a24b0ec93f3a72f1383adc16..9e0dc922f8dc795c0335deaa48e82b48c654987e 100644
--- a/core/data/rich/CbmRichHit.h
+++ b/core/data/rich/CbmRichHit.h
@@ -58,12 +58,14 @@ public:
   //virtual void SetNPhotons (int32_t n) { fNPhotons = n; }
   //virtual void SetAmplitude(double amp) { fAmplitude = amp; }
   void SetToT(double tot) { fToT = tot; }
+  void SetIsNoiseNN(bool isNoiseNN) { fIsNoiseNN = isNoiseNN; }
 
   /** Accessors **/
   virtual int32_t GetPmtId() const { return fPmtId; }
   //virtual int32_t GetNPhotons() const { return fNPhotons; }
   //virtual double GetAmplitude() const { return fAmplitude; }
   double GetToT() const { return fToT; }
+  bool GetIsNoiseNN() const { return fIsNoiseNN; }
 
   /** Outdated. Use CbmHit::GetTime() and SetTime() instead. **/
   // double GetTimestamp() const { return GetTime(); }
@@ -75,8 +77,11 @@ private:
   //double fAmplitude; // hit amplitude
 
   double fToT;  // hit time-over-threshold
+  // Flag for mRICH noise hit classification
+  // if true -> hit is classified as noise and excluded from ringfinding
+  bool fIsNoiseNN;
 
-  ClassDef(CbmRichHit, 3)
+  ClassDef(CbmRichHit, 4)
 };
 
 #endif  // CBMRICHHIT_H_
diff --git a/reco/detectors/rich/CMakeLists.txt b/reco/detectors/rich/CMakeLists.txt
index becc3e386c136430fc70b1efdd8ecc87c5abab77..7b3c900478f889094e6fd994bcbd63a22ea473a4 100644
--- a/reco/detectors/rich/CMakeLists.txt
+++ b/reco/detectors/rich/CMakeLists.txt
@@ -25,6 +25,8 @@ set(SRCS
   mcbm/CbmRichMCbmAerogelAna.cxx
   mcbm/CbmRichMCbmToTShifter.cxx
   mcbm/CbmRichMCbmSEDisplay.cxx
+  mcbm/CbmRichMCbmDenoiseCnn.cxx
+  mcbm/CbmRichMCbmDenoiseQa.cxx
 
   qa/CbmRichRingFitterQa.cxx
   qa/CbmRichGeoOpt.cxx
@@ -118,6 +120,14 @@ set(PRIVATE_DEPENDENCIES
   ROOT::Minuit2
   )
 
+if(ONNXRuntime_FOUND)
+  ADD_DEFINITIONS(-DHAVE_ONNXRUNTIME)
+  Message(STATUS "Rich will be compiled with ONNXRuntime support")
+  list(APPEND PRIVATE_DEPENDENCIES ONNXRuntime::ONNXRuntime)
+else()
+  Message(STATUS "Rich will NOT be compiled with ONNXRuntime support")
+endif()
+
 set(INTERFACE_DEPENDENCIES
   FairLogger::FairLogger
   external::fles_ipc
diff --git a/reco/detectors/rich/CbmRichRecoLinkDef.h b/reco/detectors/rich/CbmRichRecoLinkDef.h
index a832c0dd25658ce645712bbbd8b6586ee53ffd7c..8423c69103f74b8e3b82ab0ce906741b836e0a95 100644
--- a/reco/detectors/rich/CbmRichRecoLinkDef.h
+++ b/reco/detectors/rich/CbmRichRecoLinkDef.h
@@ -22,6 +22,10 @@
 #pragma link C++ class CbmRichMCbmAerogelAna;
 #pragma link C++ class CbmRichMCbmToTShifter;
 #pragma link C++ class CbmRichMCbmSEDisplay;
+#ifdef HAVE_ONNXRUNTIME
+#pragma link C++ class CbmRichMCbmDenoiseCnn +;
+#endif
+#pragma link C++ class CbmRichMCbmDenoiseQa +;
 
 //qa
 #pragma link C++ class CbmRichGeoTest + ;
diff --git a/reco/detectors/rich/finder/CbmRichRingFinderHough.cxx b/reco/detectors/rich/finder/CbmRichRingFinderHough.cxx
index d0474d45b00eb286b44b6baf595f162d8f88db91..c887cd71243b57ab12bb48c449e2018494604b05 100644
--- a/reco/detectors/rich/finder/CbmRichRingFinderHough.cxx
+++ b/reco/detectors/rich/finder/CbmRichRingFinderHough.cxx
@@ -82,7 +82,7 @@ Int_t CbmRichRingFinderHough::DoFind(CbmEvent* event, TClonesArray* rHitArray, T
   for (Int_t iH0 = 0; iH0 < nofRichHits; iH0++) {
     Int_t iH        = event ? event->GetIndex(ECbmDataType::kRichHit, iH0) : iH0;
     CbmRichHit* hit = static_cast<CbmRichHit*>(rHitArray->At(iH));
-    if (hit != nullptr) {
+    if (hit != nullptr && !hit->GetIsNoiseNN()) {
       CbmRichHoughHit tempPoint;
       tempPoint.fHit.fX   = hit->GetX();
       tempPoint.fHit.fY   = hit->GetY();
diff --git a/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseCnn.cxx b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseCnn.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5ecebd00b7c392764e53dde29903fef5a023c119
--- /dev/null
+++ b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseCnn.cxx
@@ -0,0 +1,137 @@
+/* Copyright (C) 2024 UGiessen, Giessen
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Martin Beyer [committer] */
+
+#if HAVE_ONNXRUNTIME
+
+#include "CbmRichMCbmDenoiseCnn.h"
+
+#include "CbmDigiManager.h"
+#include "CbmEvent.h"
+#include "CbmRichDetectorData.h"
+#include "CbmRichDigiMapManager.h"
+#include "CbmRichHit.h"
+
+#include <Logger.h>
+
+#include <TClonesArray.h>
+#include <TStopwatch.h>
+
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+#include <onnxruntime/core/session/onnxruntime_cxx_api.h>
+
+void CbmRichMCbmDenoiseCnn::Init()
+{
+  fOrtEnv = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_WARNING, GetName());
+
+  fOrtSessionOptions = std::make_unique<Ort::SessionOptions>();
+  fOrtSession        = std::make_unique<Ort::Session>(*fOrtEnv, fOnnxFilePath.c_str(), *fOrtSessionOptions);
+
+  fOrtRunOptions    = std::make_unique<Ort::RunOptions>(nullptr);
+  fOrtAllocatorInfo = std::make_unique<Ort::MemoryInfo>(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU));
+  fOrtInput         = std::make_unique<Ort::Value>(Ort::Value::CreateTensor<float>(
+    *fOrtAllocatorInfo, fInput.data(), fInput.size(), fInputShape.data(), fInputShape.size()));
+
+  // Thread numbers need to be explicitly set. With the current Ort version it can't detect
+  // the numbers on the cluster, should be fixed in a later version.
+  fOrtSessionOptions->SetIntraOpNumThreads(1);
+  fOrtSessionOptions->SetInterOpNumThreads(1);
+  fOrtSessionOptions->SetExecutionMode(ORT_SEQUENTIAL);
+  fOrtSessionOptions->SetGraphOptimizationLevel(ORT_ENABLE_ALL);
+
+  fCbmRichDigiMapManager = &CbmRichDigiMapManager::GetInstance();
+}
+
+void CbmRichMCbmDenoiseCnn::Process(CbmEvent* event, const TClonesArray* richHits)
+{
+  int nHits = event ? static_cast<int>(event->GetNofData(ECbmDataType::kRichHit)) : richHits->GetEntriesFast();
+  std::vector<int> timeWindowHitIndices;
+  for (int i = 0; i < nHits; i++) {  // Sliding time window loop
+    timeWindowHitIndices.clear();
+    int seedIdx = event ? static_cast<int>(event->GetIndex(ECbmDataType::kRichHit, static_cast<uint32_t>(i))) : i;
+    CbmRichHit* seedHit = static_cast<CbmRichHit*>(richHits->At(seedIdx));
+    if (!seedHit) continue;
+    timeWindowHitIndices.push_back(seedIdx);
+    int hitsInTimeWindow = 1;
+    for (int j = i + 1; j < nHits; j++) {  // Search for hits in time window
+      int hitIdx      = event ? static_cast<int>(event->GetIndex(ECbmDataType::kRichHit, j)) : j;
+      CbmRichHit* hit = static_cast<CbmRichHit*>(richHits->At(hitIdx));
+      if (!hit) continue;
+      double dt = hit->GetTime() - seedHit->GetTime();
+      if (dt < fTimeWindowLength) {
+        hitsInTimeWindow++;
+        timeWindowHitIndices.push_back(hitIdx);
+      }
+      else {
+        break;
+      }
+    }
+
+    if (hitsInTimeWindow >= fMinHitsInTimeWindow) {
+      ProcessTimeWindow(timeWindowHitIndices, seedHit->GetTime(), richHits);
+      i += hitsInTimeWindow - 1;  // Move to last hit inside time window
+    }
+    else {
+      seedHit->SetIsNoiseNN(true);
+    }
+  }
+}
+
+void CbmRichMCbmDenoiseCnn::ProcessTimeWindow(const std::vector<int>& timeWindowHitIndices, const double& seedHitTime,
+                                              const TClonesArray* richHits)
+{
+  fInput = {};  // Reset all input values to 0.0
+  std::vector<int> gridIndices;
+  gridIndices.reserve(50);
+  for (const auto& hitIdx : timeWindowHitIndices) {
+    CbmRichHit* hit    = static_cast<CbmRichHit*>(richHits->At(hitIdx));
+    const auto gridIdx = AddressToGridIndex(hit->GetAddress());
+    if (gridIdx < 0 || gridIdx >= static_cast<int>(fInput.size())) {
+      LOG(error) << GetName() << "::ProcessTimeWindow: Invalid grid index: " << gridIdx;
+      continue;
+    }
+    gridIndices.push_back(gridIdx);
+    // Shift time by 1ns to distinguish empty pixels from seed hit time
+    fInput[gridIdx] = static_cast<float>((hit->GetTime() - seedHitTime + 1.0) / (fTimeWindowLength + 1.0));
+  }
+
+  const auto output = Inference(gridIndices);
+
+  for (std::size_t i = 0; i < timeWindowHitIndices.size(); i++) {
+    CbmRichHit* hit = static_cast<CbmRichHit*>(richHits->At(timeWindowHitIndices[i]));
+    bool isNoise    = output[i] < fClassificationThreshold;
+    hit->SetIsNoiseNN(isNoise);
+  }
+}
+
+std::vector<float> CbmRichMCbmDenoiseCnn::Inference(const std::vector<int>& gridIndices)
+{
+  auto output = fOrtSession->Run(*fOrtRunOptions.get(), fInputNames.data(), fOrtInput.get(), 1, fOutputNames.data(), 1);
+  float* intarr = output.front().GetTensorMutableData<float>();
+
+  std::vector<float> out(gridIndices.size());
+  std::transform(gridIndices.begin(), gridIndices.end(), out.begin(),
+                 [intarr](int gridIdx) { return intarr[gridIdx]; });
+  return out;
+}
+
+int CbmRichMCbmDenoiseCnn::AddressToGridIndex(int address)
+{
+  CbmRichPixelData* pixel_data = fCbmRichDigiMapManager->GetPixelDataByAddress(address);
+
+  // Calculate local X [0,7],Y [0,7] indices of one MAPMT
+  int pmtUID  = pixel_data->fPmtId;
+  int pmtIndX = (pmtUID >> 4) & 0xF;  // index ascending from -x to x
+  int pmtIndY = pmtUID & 0xF;         // index ascending from y to -y
+
+  // Calculate global X [0,31],Y [0,72] indices
+  int globalIndX = 8 * pmtIndX + pixel_data->fPixelId % 8;
+  int globalIndY = 8 * pmtIndY + pixel_data->fPixelId / 8;
+
+  return 32 * globalIndY + globalIndX;
+}
+
+#endif  // HAVE_ONNXRUNTIME
diff --git a/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseCnn.h b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseCnn.h
new file mode 100644
index 0000000000000000000000000000000000000000..1e6a44be9b23e5d5a0d5465e042a6a2b0b9c387f
--- /dev/null
+++ b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseCnn.h
@@ -0,0 +1,121 @@
+/* Copyright (C) 2024 UGiessen, Giessen
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Martin Beyer [committer] */
+
+/**
+* \file CbmRichMCbmDenoiseCnn.h
+* \author M.Beyer
+* \date 2024
+**/
+
+#if HAVE_ONNXRUNTIME
+
+#pragma once
+
+#include <TObject.h>
+#include <TSystem.h>
+
+#include <array>
+#include <string>
+#include <vector>
+
+#include <onnxruntime/core/session/onnxruntime_cxx_api.h>
+
+class CbmEvent;
+class CbmRichDigiMapManager;
+class TClonesArray;
+
+/**
+* \class CbmRichMCbmDenoiseCnn
+*
+* \brief MCbm mRICH noise removal using a CNN
+*
+* \author M.Beyer
+* \date 2024
+**/
+class CbmRichMCbmDenoiseCnn : public TObject {
+ public:
+  /** Default constructor */
+  CbmRichMCbmDenoiseCnn() = default;
+
+  /** Destructor */
+  ~CbmRichMCbmDenoiseCnn() = default;
+
+  /** Copy constructor (disabled) */
+  CbmRichMCbmDenoiseCnn(const CbmRichMCbmDenoiseCnn&) = delete;
+
+  /** Assignment operator (disabled) */
+  CbmRichMCbmDenoiseCnn operator=(const CbmRichMCbmDenoiseCnn&) = delete;
+
+  /** Initialize ONNX Runtime structures and .onnx model */
+  void Init();
+
+  /**
+    * \brief Process time sorted RICH hits and created non overlapping sliding time windows
+    * \param event if CbmEvent* is nullptr -> process on full Ts
+    * \param richHits TClonesArray of RICH hits
+    */
+  void Process(CbmEvent* event, const TClonesArray* richHits);
+
+  /**
+    * \brief Preparing hits in a non overlapping sliding time window and doing inference. 
+    * Fills the classification result as a flag into CbmRichHit.fIsNoiseNN
+    * \param timeWindowHitIndices vector of hit indices from richHits TClonesArray
+    * \param seedHitTime time of seed hit
+    * \param richHits TClonesArray of RICH hits
+    */
+  void ProcessTimeWindow(const std::vector<int>& timeWindowHitIndices, const double& seedHitTime,
+                         const TClonesArray* richHits);
+
+  /**
+    * \brief Mapping from pixel address to row major grid index
+    * \param int Digi/Hit address 
+    * \return Grid index [0, 2303]
+    */
+  int AddressToGridIndex(const int address);
+
+  /**
+    * \brief ONNX runtime inference on input array fInput filled in ProcessTimeWindow(...)
+    * \param gridIndices vector of grid indices [0, 2303]
+    * \return vector of classification outputs for the given grid indices
+    */
+  std::vector<float> Inference(const std::vector<int>& gridIndices);
+
+  /**
+    * \brief Set the classification threshold
+    * \param threshold value between (0.0, 1.0). Larger values will remove hits more aggressively
+    */
+  void SetClassificationThreshold(float threshold) { fClassificationThreshold = threshold; }
+
+ private:
+  const double fTimeWindowLength{25.};
+  const int fMinHitsInTimeWindow{7};
+
+  float fClassificationThreshold{0.5};
+
+  const std::string fOnnxFilePath =
+    std::string(gSystem->Getenv("VMCWORKDIR")) + "/parameters/rich/mRich/rich_v21c_mcbm_UNet.onnx";
+
+  // TODO: read names/shapes from onnx file
+  const std::vector<const char*> fInputNames{"input"};
+  const std::vector<const char*> fOutputNames{"output"};
+
+  const std::array<int64_t, 4> fInputShape{1, 1, 72, 32};
+  const std::array<int64_t, 4> fOutputShape{1, 1, 72, 32};
+
+  std::array<float, 2304> fInput{};
+
+  std::unique_ptr<Ort::Env> fOrtEnv{nullptr};
+  std::unique_ptr<Ort::SessionOptions> fOrtSessionOptions{nullptr};
+  std::unique_ptr<Ort::Session> fOrtSession{nullptr};
+  std::unique_ptr<Ort::MemoryInfo> fOrtAllocatorInfo{nullptr};
+  std::unique_ptr<Ort::RunOptions> fOrtRunOptions{nullptr};
+
+  std::unique_ptr<Ort::Value> fOrtInput{nullptr};
+
+  CbmRichDigiMapManager* fCbmRichDigiMapManager{nullptr};
+
+  ClassDef(CbmRichMCbmDenoiseCnn, 1);
+};
+
+#endif
diff --git a/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseQa.cxx b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseQa.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..fe83df3ec16be42145c81dd50687f093e5f699af
--- /dev/null
+++ b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseQa.cxx
@@ -0,0 +1,198 @@
+/* Copyright (C) 2024 UGiessen, Giessen
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Martin Beyer [committer] */
+
+#include "CbmRichMCbmDenoiseQa.h"
+
+#include "CbmEvent.h"
+#include "CbmHistManager.h"
+#include "CbmRichHit.h"
+#include "CbmRichRing.h"
+
+#include <FairRootManager.h>
+#include <Logger.h>
+
+#include <TCanvas.h>
+#include <TClonesArray.h>
+#include <TDirectory.h>
+#include <TEllipse.h>
+#include <TFile.h>
+#include <TH2D.h>
+
+InitStatus CbmRichMCbmDenoiseQa::Init()
+{
+  FairRootManager* manager = FairRootManager::Instance();
+  if (!manager) LOG(fatal) << GetName() << "::Init: No FairRootManager";
+
+  fCbmEvents = static_cast<TClonesArray*>(manager->GetObject("CbmEvent"));
+  LOG(info) << GetName() << "::Init: CbmEvent array " << (fCbmEvents ? "found" : "not found");
+
+  fRichHits = static_cast<TClonesArray*>(manager->GetObject("RichHit"));
+  if (!fRichHits) LOG(fatal) << GetName() << "::Init: No RichHit array!";
+
+  fRichRings = static_cast<TClonesArray*>(manager->GetObject("RichRing"));
+  if (!fRichRings) LOG(fatal) << GetName() << "::Init: No RichRing array!";
+
+  InitHistograms();
+
+  return kSUCCESS;
+}
+
+void CbmRichMCbmDenoiseQa::InitHistograms()
+{
+  fHM    = std::make_unique<CbmHistManager>();
+  fHMSed = std::make_unique<CbmHistManager>();
+
+  // 1D Histograms
+  fHM->Create1<TH1D>("fhRingsPerEvent", "number of ring per event;# rings per event;Entries", 6, -0.5, 5.5);
+
+  fHM->Create1<TH1D>("fhRingRadius", "ring radius;ring radius [cm];Entries", 100, 0., 7.);
+
+  fHM->Create1<TH1D>("fhRingRadiusUp", "ring radius upper half;ring radius [cm]; count", 100, 0., 7.);
+
+  fHM->Create1<TH1D>("fhRingRadiusDown", "ring radius lower half;ring radius [cm]; count", 100, 0., 7.);
+
+  fHM->Create1<TH1D>("fhRingHits", "number of ring hits;# ring hits;Entries", 50, -0.5, 49.5);
+
+  fHM->Create1<TH1D>("fhRingHitsUp", "number of ring hits upper half;# ring hits;Entries", 50, -0.5, 49.5);
+
+  fHM->Create1<TH1D>("fhRingHitsDown", "number of ring hits lower half;# ring hits;Entries", 50, -0.5, 49.5);
+
+  fHM->Create1<TH1D>("fhRingHitsTimeDiff", "ringhit to ring time difference;hittime-ringtime [ns]; count", 100, -10.,
+                     10.);
+
+  // 2D Histograms
+  fHM->Create2<TH2D>("fhRichHitsXY", "position of rich hits;X [cm];Y [cm];Entries", 37, -11.2, 11.2, 82, -24.115,
+                     23.885);
+
+  fHM->Create2<TH2D>("fhRingHitsXY", "position of ring hits;X [cm];Y [cm];Entries", 37, -11.2, 11.2, 82, -24.115,
+                     23.885);
+
+  fHM->Create2<TH2D>("fhRingCenterXY", "position of ring centers;X [cm];Y [cm];Entries", 37, -11.2, 11.2, 82, -24.115,
+                     23.885);
+}
+
+void CbmRichMCbmDenoiseQa::Exec(Option_t* /*option*/)
+{
+  LOG(debug) << GetName() << " TS " << fTsNum;
+  fTsNum++;
+
+  if (fCbmEvents) {
+    Int_t nEvents = fCbmEvents->GetEntriesFast();
+    for (Int_t iEvent = 0; iEvent < nEvents; iEvent++) {
+      CbmEvent* event = static_cast<CbmEvent*>(fCbmEvents->At(iEvent));
+      if (!event) continue;
+      if (event->GetNofData(ECbmDataType::kRichRing) > 0) {
+        fHM->H1("fhRingsPerEvent")->Fill(static_cast<double>(event->GetNofData(ECbmDataType::kRichRing)));
+        if (fnSEDs < fMaxSEDs) {
+          DrawSED(event);
+          fnSEDs++;
+        }
+      }
+      Process(event);
+      fEventNum++;
+    }
+  }
+  else {
+    Process(nullptr);
+  }
+}
+
+void CbmRichMCbmDenoiseQa::Process(CbmEvent* event)
+{
+  // RICH Hits
+  Int_t nRichHits = event ? static_cast<Int_t>(event->GetNofData(ECbmDataType::kRichHit)) : fRichHits->GetEntriesFast();
+  for (int i = 0; i < nRichHits; i++) {
+    Int_t hitIndex  = event ? static_cast<Int_t>(event->GetIndex(ECbmDataType::kRichHit, static_cast<uint32_t>(i))) : i;
+    CbmRichHit* hit = static_cast<CbmRichHit*>(fRichHits->At(hitIndex));
+    if (!hit) continue;
+    fHM->H2("fhRichHitsXY")->Fill(hit->GetX(), hit->GetY());
+  }
+
+  // RICH Rings
+  Int_t nRichRings =
+    event ? static_cast<Int_t>(event->GetNofData(ECbmDataType::kRichRing)) : fRichRings->GetEntriesFast();
+  for (int i = 0; i < nRichRings; i++) {
+    Int_t ringIndex =
+      event ? static_cast<Int_t>(event->GetIndex(ECbmDataType::kRichRing, static_cast<uint32_t>(i))) : i;
+    CbmRichRing* ring = static_cast<CbmRichRing*>(fRichRings->At(ringIndex));
+    if (!ring) continue;
+    fHM->H1("fhRingRadius")->Fill(ring->GetRadius());
+    fHM->H1(std::string("fhRingRadius") + std::string((ring->GetCenterY() > 0) ? "Up" : "Down"))
+      ->Fill(ring->GetRadius());
+    fHM->H1("fhRingHits")->Fill(ring->GetNofHits());
+    fHM->H1(std::string("fhRingHits") + std::string((ring->GetCenterY() > 0) ? "Up" : "Down"))
+      ->Fill(ring->GetNofHits());
+    fHM->H2("fhRingCenterXY")->Fill(ring->GetCenterX(), ring->GetCenterY());
+    // Ring hits
+    Int_t nRingHits = ring->GetNofHits();
+    for (int j = 0; j < nRingHits; j++) {
+      CbmRichHit* hit = static_cast<CbmRichHit*>(fRichHits->At(static_cast<Int_t>(ring->GetHit(j))));
+      if (!hit) continue;
+      fHM->H1("fhRingHitsTimeDiff")->Fill(hit->GetTime() - ring->GetTime());
+      fHM->H2("fhRingHitsXY")->Fill(hit->GetX(), hit->GetY());
+    }
+  }
+}
+
+void CbmRichMCbmDenoiseQa::DrawSED(CbmEvent* event)
+{
+  std::string hName = "fhSED" + std::to_string(fEventNum);
+  auto c            = fHMSed->CreateCanvas(hName, hName, 800, 1505);
+  c->SetMargin(0.15, 0.15, 0.1, 0.1);
+  c->cd();
+  TH2D* h = new TH2D(hName.c_str(), hName.c_str(), 37, -11.2, 11.2, 82, -24.115, 23.885);
+  h->SetXTitle("X [cm]");
+  h->SetYTitle("Y [cm]");
+  h->SetZTitle("NN classification 0: noise, 1: signal");
+  h->GetZaxis()->SetTitleOffset(1.3);
+  fHMSed->Add(hName, h);
+
+  Int_t nHits = static_cast<Int_t>(event->GetNofData(ECbmDataType::kRichHit));
+  for (int i = 0; i < nHits; i++) {
+    Int_t hitIndex  = static_cast<Int_t>(event->GetIndex(ECbmDataType::kRichHit, static_cast<uint32_t>(i)));
+    CbmRichHit* hit = static_cast<CbmRichHit*>(fRichHits->At(hitIndex));
+    if (!hit) continue;
+    if (h->GetBinContent(h->FindBin(hit->GetX(), hit->GetY())) > 0) {
+      LOG(info) << GetName() << "::DrawSED channel already filled for this event. Skipping.";
+      continue;
+    }
+    if (!hit->GetIsNoiseNN()) {
+      h->Fill(hit->GetX(), hit->GetY(), 1.0);
+    }
+    else {
+      h->Fill(hit->GetX(), hit->GetY(), 0.001);
+    }
+  }
+  h->Draw("colz");
+
+  Int_t nRings = static_cast<Int_t>(event->GetNofData(ECbmDataType::kRichRing));
+  for (int i = 0; i < nRings; i++) {
+    Int_t ringIndex   = static_cast<Int_t>(event->GetIndex(ECbmDataType::kRichRing, static_cast<uint32_t>(i)));
+    CbmRichRing* ring = static_cast<CbmRichRing*>(fRichRings->At(ringIndex));
+    if (!ring) continue;
+    TEllipse* circle = new TEllipse(ring->GetCenterX(), ring->GetCenterY(), ring->GetAaxis(), ring->GetBaxis(), 0.,
+                                    360., ring->GetPhi() * 180. / TMath::Pi());
+    circle->SetFillStyle(0);
+    circle->SetLineWidth(2);
+    circle->Draw("same");
+  }
+}
+
+void CbmRichMCbmDenoiseQa::Finish()
+{
+  TDirectory* oldir = gDirectory;
+  TFile* outFile    = FairRootManager::Instance()->GetOutFile();
+  if (outFile) {
+    outFile->mkdir(GetName());
+    outFile->cd(GetName());
+    fHM->WriteToFile();
+    outFile->cd();
+    outFile->mkdir((std::string(GetName()) + std::string("SEDs")).c_str());
+    outFile->cd((std::string(GetName()) + std::string("SEDs")).c_str());
+    fHMSed->WriteCanvasToFile();
+    fHM->Clear();
+    fHMSed->Clear();
+  }
+  gDirectory->cd(oldir->GetPath());
+}
diff --git a/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseQa.h b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseQa.h
new file mode 100644
index 0000000000000000000000000000000000000000..098a2c820048e45853593c7041f1f8515ed9f2f8
--- /dev/null
+++ b/reco/detectors/rich/mcbm/CbmRichMCbmDenoiseQa.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2024 UGiessen, Giessen
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Martin Beyer [committer] */
+
+/**
+* \file CbmRichMCbmDenoiseQa.h
+* \author M.Beyer
+* \date 2024
+**/
+
+#pragma once
+
+#include "FairTask.h"
+
+class CbmDigiManager;
+class CbmRichDigi;
+class CbmHistManager;
+class CbmEvent;
+class TClonesArray;
+
+/**
+* \class CbmRichMCbmDenoiseQa
+*
+* \brief QA for MCbm mRICH noise removal
+*
+* \author M.Beyer
+* \date 2024
+**/
+class CbmRichMCbmDenoiseQa : public FairTask {
+ public:
+  /** Default constructor */
+  CbmRichMCbmDenoiseQa() : FairTask("CbmRichMCbmDenoiseQa"){};
+
+  /** Destructor */
+  ~CbmRichMCbmDenoiseQa() = default;
+
+  /** Copy constructor (disabled) */
+  CbmRichMCbmDenoiseQa(const CbmRichMCbmDenoiseQa&) = delete;
+
+  /** Assignment operator (disabled) */
+  CbmRichMCbmDenoiseQa operator=(const CbmRichMCbmDenoiseQa&) = delete;
+
+  /** Inherited from FairTask */
+  InitStatus Init();
+
+  /** Inherited from FairTask */
+  void Exec(Option_t* option);
+
+  /** Inherited from FairTask */
+  void Finish();
+
+  /** Initialize histogram manager */
+  void InitHistograms();
+
+  /** 
+   * \brief Process data and fill histograms
+   * \param event if CbmEvent* is nullptr -> process full Ts
+   */
+  void Process(CbmEvent* event);
+
+  /** Draw a Single-event-display */
+  void DrawSED(CbmEvent* event);
+
+  /** Set maximum number of SEDs drawn */
+  void SetMaxSEDs(Int_t maxSEDs) { fMaxSEDs = maxSEDs; }
+
+ private:
+  Int_t fTsNum{};
+  Int_t fEventNum{};
+  Int_t fnSEDs{};
+
+  Int_t fMaxSEDs{20};
+
+  std::unique_ptr<CbmHistManager> fHM{nullptr};
+  std::unique_ptr<CbmHistManager> fHMSed{nullptr};  // For single event displays
+
+  TClonesArray* fCbmEvents{nullptr};
+  TClonesArray* fRichHits{nullptr};
+  TClonesArray* fRichRings{nullptr};
+
+  ClassDef(CbmRichMCbmDenoiseQa, 1)
+};
diff --git a/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.cxx b/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.cxx
index 1a86ebd6222ca1a38d6b83bc5f1785bb56473e96..7f69b82b4aac27a6055da6608cb012697104e401 100644
--- a/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.cxx
+++ b/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.cxx
@@ -12,21 +12,31 @@
 #include "CbmRichGeoManager.h"
 #include "CbmRichHit.h"
 #include "CbmRichPoint.h"
+#if HAVE_ONNXRUNTIME
+#include "CbmRichMCbmDenoiseCnn.h"
+#endif
 
 #include <FairRootManager.h>
 #include <Logger.h>
 
-#include "TClonesArray.h"
+#include <TClonesArray.h>
+#include <TStopwatch.h>
 
+#include <iomanip>
 #include <iostream>
 
+using std::fixed;
+using std::right;
+using std::setprecision;
+using std::setw;
+
 using namespace std;
 
 
 CbmRichMCbmHitProducer::CbmRichMCbmHitProducer()
   : FairTask("CbmRichMCbmHitProducer")
   , fRichHits(NULL)
-  , fEventNum(0)
+  , fNofTs(0)
   , fHitError(0.6 / sqrt(12.))
   ,
   //fMappingFile("mRICH_Mapping_vert_20190318.geo")
@@ -66,6 +76,15 @@ InitStatus CbmRichMCbmHitProducer::Init()
     a = 0.;
   if (fDoICD) read_ICD(fICD_offset_read, 0);
 
+#if HAVE_ONNXRUNTIME
+  if (fUseDenoiseNN) {
+    fDenoiseCnn = std::make_unique<CbmRichMCbmDenoiseCnn>();
+    fDenoiseCnn->Init();
+    fDenoiseCnn->SetClassificationThreshold(fDenoiseCnnThreshold);
+    LOG(info) << "CbmRichMCbmHitProducer: Initialized CbmRichMCbmDenoiseCnn";
+  }
+#endif
+
   return kSUCCESS;
 }
 
@@ -109,23 +128,39 @@ void CbmRichMCbmHitProducer::InitMapping()
 
 void CbmRichMCbmHitProducer::Exec(Option_t* /*option*/)
 {
-  fEventNum++;
-  LOG(info) << "CbmRichMCbmHitProducer Event (or TimeSlice) " << fEventNum;
-
+  fNofDigis = 0;
+  TStopwatch timer;
+  timer.Start();
   fRichHits->Delete();
 
   // if CbmEvent does not exist then process standard event.
   // if CbmEvent exists then proceed all events in time slice.
-  Int_t nUnits = (fCbmEvents != nullptr) ? fCbmEvents->GetEntriesFast() : 1;
-
+  Int_t nUnits = fCbmEvents ? fCbmEvents->GetEntriesFast() : 1;
+  if (fCbmEvents) fNofEvents += nUnits;
   for (Int_t iUnit = 0; iUnit < nUnits; iUnit++) {
-    CbmEvent* event = (fCbmEvents != nullptr) ? static_cast<CbmEvent*>(fCbmEvents->At(iUnit)) : nullptr;
+    CbmEvent* event = fCbmEvents ? static_cast<CbmEvent*>(fCbmEvents->At(iUnit)) : nullptr;
     ProcessData(event);
   }
+  timer.Stop();
+  fTotalTime += timer.RealTime();
+
+  std::stringstream logOut;
+  logOut << setw(20) << left << GetName() << "[";
+  logOut << fixed << setw(8) << setprecision(1) << right << timer.RealTime() * 1000. << " ms] ";
+  logOut << "TS " << fNofTs;
+  if (fCbmEvents) logOut << ", events " << nUnits;
+  logOut << ", digis " << fNofDigis;
+  logOut << ", hits " << fRichHits->GetEntriesFast();
+  LOG(info) << logOut.str();
+  fTotalNofDigis += fNofDigis;
+  fTotalNofHits += fRichHits->GetEntriesFast();
+  fNofTs++;
 }
 
 void CbmRichMCbmHitProducer::ProcessData(CbmEvent* event)
 {
+  TStopwatch timer;
+  timer.Start();
   if (event != NULL) {
     LOG(debug) << "CbmRichMCbmHitProducer CbmEvent mode. CbmEvent # " << event->GetNumber();
     Int_t nofDigis = event->GetNofData(ECbmDataType::kRichDigi);
@@ -145,6 +180,18 @@ void CbmRichMCbmHitProducer::ProcessData(CbmEvent* event)
       ProcessDigi(event, iDigi);
     }
   }
+  timer.Stop();
+  fHitProducerTime += timer.RealTime();
+
+// Process all hits with DenoiseNN, setting value RichHit.fIsNoiseNN
+#if HAVE_ONNXRUNTIME
+  if (fUseDenoiseNN) {
+    timer.Start();
+    fDenoiseCnn->Process(event, fRichHits);
+    timer.Stop();
+    fDenoiseCnnTime += timer.RealTime();
+  }
+#endif
 }
 
 void CbmRichMCbmHitProducer::ProcessDigi(CbmEvent* event, Int_t digiIndex)
@@ -154,6 +201,7 @@ void CbmRichMCbmHitProducer::ProcessDigi(CbmEvent* event, Int_t digiIndex)
   if (digi->GetAddress() < 0) return;
   Int_t DiRICH_Add = (digi->GetAddress() >> 16) & 0xFFFF;
   if (DiRICH_Add == 0x7901 || DiRICH_Add == 0x7902) return;  // TRBaddresses 0x7901 and 0x7902 are for FSD/NCAL
+  fNofDigis++;
   if (isInToT(digi->GetToT())) {
     TVector3 posPoint;
     CbmRichPixelData* data = CbmRichDigiMapManager::GetInstance().GetPixelDataByAddress(digi->GetAddress());
@@ -212,7 +260,43 @@ void CbmRichMCbmHitProducer::AddHit(CbmEvent* event, TVector3& posHit, const Cbm
 }
 
 
-void CbmRichMCbmHitProducer::Finish() { fRichHits->Clear(); }
+void CbmRichMCbmHitProducer::Finish()
+{
+  fRichHits->Clear();
+  std::cout << std::endl;
+  LOG(info) << "=====================================";
+  LOG(info) << GetName() << ": Run summary";
+  LOG(info) << "Time slices     : " << fNofTs;
+  LOG(info) << "Digis     / TS  : " << fixed << setprecision(2) << fTotalNofDigis / Double_t(fNofTs);
+  LOG(info) << "Hits      / TS  : " << fixed << setprecision(2) << fTotalNofHits / Double_t(fNofTs);
+  LOG(info) << "Time      / TS  : " << fixed << setprecision(2) << 1000. * fTotalTime / Double_t(fNofTs) << " ms";
+  if (fCbmEvents) {
+    LOG(info) << "Events          : " << fNofEvents;
+    LOG(info) << "Events    / TS  : " << fixed << setprecision(2) << fNofEvents / Double_t(fNofTs);
+    if (fNofEvents > 0) {
+      LOG(info) << "Digis     / ev  : " << fixed << setprecision(2) << fTotalNofDigis / Double_t(fNofEvents);
+      LOG(info) << "Hits      / ev  : " << fixed << setprecision(2) << fTotalNofHits / Double_t(fNofEvents);
+      LOG(info) << "Time      / ev  : " << fixed << setprecision(2) << 1000. * fTotalTime / Double_t(fNofEvents)
+                << " ms";
+    }
+  }
+
+  TString eventOrTsStr    = fCbmEvents && fNofEvents > 0 ? "ev: " : "TS: ";
+  Double_t eventOrTsValue = Double_t(fCbmEvents && fNofEvents > 0 ? fNofEvents : fNofTs);
+  if (fTotalTime != 0.) {
+    LOG(info) << "===== Time by task (real time) ======";
+    LOG(info) << "HitProducer        / " << eventOrTsStr << fixed << setprecision(2) << setw(9) << right
+              << 1000. * fHitProducerTime / eventOrTsValue << " ms [" << setw(5) << right
+              << 100 * fHitProducerTime / fTotalTime << " %]";
+#if HAVE_ONNXRUNTIME
+    if (fUseDenoiseNN)
+      LOG(info) << "Denoise NN         / " << eventOrTsStr << fixed << setprecision(2) << setw(9) << right
+                << 1000. * fDenoiseCnnTime / eventOrTsValue << " ms [" << setw(5) << right
+                << 100 * fDenoiseCnnTime / fTotalTime << " %]";
+#endif
+  }
+  LOG(info) << "=====================================\n";
+}
 
 
 bool CbmRichMCbmHitProducer::isInToT(const double ToT)
diff --git a/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.h b/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.h
index c51a7a98ac69ef14c38152d8fe1e2c4ceed13a8f..2f843d08e2715167348da1543dcb73ef13f6d1bb 100644
--- a/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.h
+++ b/reco/detectors/rich/mcbm/CbmRichMCbmHitProducer.h
@@ -1,6 +1,6 @@
-/* Copyright (C) 2019-2020 UGiessen/JINR-LIT, Giessen/Dubna
+/* Copyright (C) 2019-2024 UGiessen/JINR-LIT, Giessen/Dubna
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Semen Lebedev [committer], Adrian Amatus Weber */
+   Authors: Semen Lebedev [committer], Adrian Amatus Weber, Martin Beyer */
 
 #ifndef CBM_RICH_MCBM_HIT_PRODUCER
 #define CBM_RICH_MCBM_HIT_PRODUCER
@@ -14,6 +14,7 @@ class TClonesArray;
 class TVector3;
 class CbmEvent;
 class CbmRichDigi;
+class CbmRichMCbmDenoiseCnn;
 
 class CbmRichMCbmMappingData {
 public:
@@ -126,8 +127,19 @@ public:
     */
   void DoRestrictToFullAcc(bool val = true) { fRestrictToFullAcc = val; }
 
+#if HAVE_ONNXRUNTIME
+  void applyDenoiseNN(bool val = true) { fUseDenoiseNN = val; }
+  void SetClassifierThreshold(float val) { fDenoiseCnnThreshold = val; }
+#endif
+
+ private:
+#if HAVE_ONNXRUNTIME
+  bool fUseDenoiseNN                                 = true;
+  float fDenoiseCnnThreshold                         = 0.5;
+  std::unique_ptr<CbmRichMCbmDenoiseCnn> fDenoiseCnn = nullptr;
+  double fDenoiseCnnTime                             = 0.;
+#endif
 
-private:
   CbmDigiManager* fDigiMan = nullptr;
   TClonesArray* fRichHits;                      // RICH hits
   TClonesArray* fCbmEvents          = nullptr;  // CbmEvent for time-based simulations
@@ -141,10 +153,18 @@ private:
 
   std::map<Int_t, CbmRichMCbmMappingData> fRichMapping;
 
-  Int_t fEventNum;  // event number
+  double fTotalTime       = 0.;
+  double fHitProducerTime = 0.;
 
+  Int_t fNofTs;
+
+  Int_t fNofDigis = 0;
   Int_t fNofHits = 0;
 
+  Int_t fNofEvents     = 0;
+  Int_t fTotalNofDigis = 0;
+  Int_t fTotalNofHits  = 0;
+
   Double_t fHitError;
 
   std::string fMappingFile;