diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/CutFlow.h b/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/CutFlow.h
new file mode 100644
index 0000000000000000000000000000000000000000..120e01013bc43d2c18e3ec3a62a9544e2120c1a2
--- /dev/null
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/CutFlow.h
@@ -0,0 +1,255 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+/**
+ * @file CutList.h
+ * @author shaun roe
+ **/
+
+#ifndef _IDPVM_CutFlow_h_
+#define _IDPVM_CutFlow_h_
+#include <functional>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+
+/** Templated class containing a cut, name of cut and description of cut(optional)
+ * Typically, the class will be templated on <xAOD::TruthParticle> or <xAOD::TrackParticle>.
+ * The cut is passed in as any predicate function, the type is declared as function<bool(A)>
+ * although typically these will be lambda functions.
+ * The cut is an 'accept' cut, i.e. the value passes if it is true.
+ * These predicate functions are called each time the 'pass' method is called.
+ * The class keeps an internal count of how many values passed, and may be used
+ * as a functional due to the overloaded () operator.
+ */
+template<class A>
+class Accept {
+public:
+  ///Default constructor with a simple predicate returning false
+  Accept() : m_predicate([](A) {
+    return false;
+  }), m_name {}, m_desc {} {
+    // nop
+  }
+  /** @brief Normal constructor
+   * @param predicate any function taking the templated value and returning a bool
+   * @param name optional name for this cut
+   * @param description optional description for this cut
+   */
+  Accept(const std::function<bool(const A&)>& predicate, const std::string& name = "",
+         const std::string& description = "") : m_predicate(predicate), m_name(name), m_desc(description)
+  {
+    // nop
+  }
+
+  ///Overloading the () operator allows the class to be used as a functional
+  bool
+  operator () (const A& i) const {
+    return pass(i);
+  }
+
+  ///Apply the predicate function and return the value, also updating an internal counter
+  bool
+  pass(const A& i) const {
+    const bool passed = m_predicate(i);
+    return passed;
+  }
+
+
+  ///Return cut name
+  std::string
+  name() const {
+    return m_name;
+  }
+
+  ///Return cut description
+  std::string
+  description() const {
+    return m_desc;
+  }
+
+  ///Utility typedefs to help callers: the value type
+  typedef A value_type;
+  ///Utility typedefs to help callers: the function type
+  typedef const std::function<bool (const A&)> func_type;
+private:
+  // std::function<bool(A)> m_predicate;
+  func_type m_predicate;
+  std::string m_name;
+  std::string m_desc;
+};
+
+/** Templated CutList class to contain a group of cuts.
+ * The CutList is typically instantiated with a vector of Accept objects, although
+ * additional cuts may be added with the 'add' method.
+ * Cuts may be applied in one of two ways, determined by the 'mode' parameter:
+ * ALL: Always apply every cut
+ * UNTIL_FAIL: Apply cuts until the first one fails
+ * The 'accept' method applies the cuts and keeps internal count of how many times it is called.
+ */
+template<class A>
+class CutList {
+public:
+
+  ///Normal constructor takes a vector<Accept>. Note default mode is 'ALL'.
+  CutList(const std::vector<Accept<A> >& cuts) : m_cuts(cuts) {
+    // nop
+  }
+
+  ///Default constructor with no cuts implemented
+  CutList() : m_cuts{} {
+    // nop
+  }
+
+
+  ///Add one cut
+  void
+  add(const Accept<A>& newCut) {
+    m_cuts.push_back(newCut);
+  }
+
+  ///Apply cuts and return the boolean result; keep count of number of calls and passes
+  unsigned int
+  accept(const A& value) const {
+    unsigned int missing_cuts = m_cuts.size();
+
+    for (auto& thisCut:m_cuts) {
+      if (not thisCut.pass(value)) {
+        break;
+      }
+      --missing_cuts;
+    }
+    return missing_cuts;
+  }
+
+  unsigned int
+  testAllCuts(const A& value, std::vector<unsigned int> &counter) const {
+    unsigned int idx          = 0;
+    ++(counter[idx++]);
+    if (counter.size() != m_cuts.size()+2 /* CutFlow::kNReserved */) {
+      throw std::logic_error("Number of cuts and counters do not match." );
+    }
+    unsigned int missing_cuts = 0;
+    ++(counter[idx++]);
+    for (auto& thisCut:m_cuts) {
+      if (!thisCut.pass(value)) { ++missing_cuts;   }
+      else                      { ++(counter[idx]); }
+      ++idx;
+    }
+    return missing_cuts;
+  }
+
+  ///Return the number of cuts
+  unsigned int
+  size() const {
+    return m_cuts.size();
+  }
+
+  ///Return a vector of the cut names
+  std::vector<std::string>
+  names() const {
+    std::vector<std::string> result(m_cuts.size());
+    unsigned int idx(0);
+    for (const auto& i:m_cuts) {
+      result[idx++] = i.name();
+    }
+    return result; // return-value-optimisation is invoked
+  }
+
+private:
+  std::vector<Accept<A> > m_cuts;
+};
+
+class CutFlow
+{
+public:
+
+  enum CutMode {
+    ALL, UNTIL_FAIL
+  };
+
+  enum EReservedCuts {
+    kAll,
+    kIsValidParticle,
+    kNReserved
+  };
+
+  CutFlow() {}
+
+  CutFlow(unsigned int n_cuts, CutMode cut_mode = UNTIL_FAIL)
+    : m_counter(n_cuts+kNReserved,0) ,
+      m_integrated( cut_mode == ALL ),
+      m_accumulateIntegrated( cut_mode == ALL )
+  {}
+
+  std::vector<unsigned int>       &counter()       {return m_counter; }
+  const std::vector<unsigned int> &counter() const {return m_counter; }
+
+ private:
+  // disallow implicit conversion of CutResult
+  void update(bool) {
+  }
+
+ public:
+  //@TODO event weights ?
+  void update(unsigned int missing_cuts) {
+    assert( m_integrated == false);
+    ++(m_counter.at(m_counter.size()-missing_cuts-1) );
+  }
+
+  void merge(CutFlow &&a_cutflow) {
+    if (m_counter.empty()) {
+      m_counter              = std::move(a_cutflow.m_counter);
+      m_integrated           = a_cutflow.m_integrated;
+      m_accumulateIntegrated = a_cutflow.m_accumulateIntegrated;
+    }
+    else {
+      if (m_counter.size() != a_cutflow.m_counter.size() || m_integrated != a_cutflow.m_integrated) {
+         throw std::logic_error("Tried to merge non matching cut_flows.");
+      }
+      std::vector<unsigned int>::iterator iter=m_counter.begin();
+      for(unsigned int count : a_cutflow.m_counter)  {
+        *(iter++)  += count;
+      }
+    }
+  }
+
+  void clear() {
+    for(unsigned int &count : m_counter)  {count=0; }
+  }
+
+  ///Produce a formatted string report of the results
+  std::string
+  report(const std::vector<std::string> &names) {
+    if (not m_integrated) {
+      unsigned int sum=0;
+      for(std::vector<unsigned int>::reverse_iterator iter = m_counter.rbegin();
+          iter != m_counter.rend();
+          ++iter)  {
+        *iter += sum;
+        sum    = *iter;
+      };
+      m_integrated=true;
+    }
+
+    std::string op = "\nCutList Report; Total processed: " + std::to_string(m_counter[kIsValidParticle]);
+    op += "\nTotal passed: " + std::to_string(m_counter[m_counter.size()-1]);
+    std::string modeString = (m_accumulateIntegrated) ? "\nAll cuts were applied\n" : "\nCuts were applied until one fails\n";
+    op += modeString;
+    for (unsigned int idx=0; idx<names.size(); ++idx) {
+      op += names[idx] + ": " + std::to_string( idx+kNReserved<m_counter.size() ? m_counter[idx+kNReserved] : -1) + " passed\n";
+    }
+    if (names.size()+kNReserved != m_counter.size()) {
+      throw std::logic_error(std::string( "Number of cuts and counters do not match. Resulting report:\n") + op );
+    }
+    return op;
+  }
+
+private:
+  std::vector<unsigned int> m_counter;
+  bool                      m_integrated           = false;
+  bool                      m_accumulateIntegrated = false;
+};
+#endif
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/IAthSelectionTool.h b/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/IAthSelectionTool.h
index 882aa2afe1745174e6c6cdf8904a1eddab717edc..9b02bef4f875c7f91c13803e34c6bfd798b36588 100644
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/IAthSelectionTool.h
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/IAthSelectionTool.h
@@ -22,26 +22,52 @@
 
 static const InterfaceID IID_IAthSelectionTool("IAthSelectionTool",1,0);
 
+
 /// IAthSelectionTool is a virtual baseclass for selection methods
 class IAthSelectionTool:virtual public IAlgTool{
 public:
+
+  class CutResult {
+  public:
+    CutResult(unsigned int missing_cuts) : m_missingCuts(missing_cuts) {}
+
+    unsigned int missingCuts() const {
+      return m_missingCuts;
+    }
+
+    operator bool() const {
+      return m_missingCuts==0;
+    }
+
+  private:
+    unsigned int m_missingCuts = 0;
+  };
+
   ///interfaceID reimplemented from base
   static const InterfaceID & interfaceID();
   ///virtual destructor, does nothing
   virtual ~IAthSelectionTool(){ }
+
+  /** @brief The most important method to determine whether the particle is accepted
+   * @param p Pointer to particle baseclass, will be cast to truth or track
+   * @return true if particle passes cuts
+   */
+  virtual IAthSelectionTool::CutResult
+  testAllCuts(const xAOD::IParticle * p, std::vector<unsigned int> &counter) const = 0;
+
   /** @brief The most important method to determine whether the particle is accepted
     * @param p Pointer to particle baseclass, will be cast to truth or track
-    * @return true if particle passes cuts
-    */ 
-  virtual bool accept(const xAOD::IParticle * p) = 0;
-  ///Clear internal counters for each cut
-  virtual void clearCounters()=0;
-  ///Gives a vector of unsigned int counters; relies on return-value-optimisation to be efficient
-  virtual std::vector<unsigned int> counters() const =0;
+    * @return the number of cuts which are not passed or tested
+    */
+  virtual IAthSelectionTool::CutResult accept(const xAOD::IParticle * p) const = 0;
+
+  /** @brief return the number of cuts.
+   * @return the number of cuts
+   */
+  virtual unsigned int nCuts() const = 0;
+
   ///return the names of the cuts as a vector<string>
   virtual std::vector<std::string> names() const = 0;
-  ///Returns a formatted text string reporting the cuts' results
-  virtual std::string str() const =0;
 };
 
 inline const InterfaceID & IAthSelectionTool::interfaceID(){
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/InDetPhysValMonitoringTool.h b/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/InDetPhysValMonitoringTool.h
index 699f2497d1a08bd373bad956032c838ddd2a6fae..a29dbe65f5dcf1453542527a2e1d9f6288d96b92 100644
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/InDetPhysValMonitoringTool.h
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/InDetPhysValMonitoring/InDetPhysValMonitoringTool.h
@@ -16,6 +16,7 @@
 
 //local include
 #include "InDetPhysValMonitoring/IAthSelectionTool.h"
+#include "InDetPhysValMonitoring/CutFlow.h"
 
 //#include "PATCore/IAsgSelectionTool.h"
 #include "AthenaMonitoring/ManagedMonitorToolBase.h"
@@ -125,6 +126,8 @@ private:
 	bool m_TrkSelectPV;   // make track selection relative to PV
 	ToolHandle<InDet::IInDetTrackSelectionTool> m_trackSelectionTool;
 	ToolHandle<IAthSelectionTool> m_truthSelectionTool;
+        mutable std::mutex  m_mutex;
+        mutable CutFlow     m_truthCutFlow;
 	std::vector<int> m_prospectsMatched;
 	int m_twoMatchedEProb;
 	int m_threeMatchedEProb;
@@ -133,7 +136,6 @@ private:
 
 	std::vector<std::string> m_trackCutflowNames;
 	std::vector<int> m_trackCutflow;
-	std::vector<unsigned int> m_truthCutCounters;
 	std::string m_pileupSwitch; // All, PileUp, or HardScatter
 	///Jet Things
         SG::ReadHandleKey<xAOD::JetContainer> m_jetContainerName
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.cxx b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.cxx
index b7802f6a93e962ef2dc154121ef87ace0fae6d88..3033c3525729a19c2b7386d5525a72fc36c7a279 100644
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.cxx
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.cxx
@@ -47,11 +47,6 @@ AthTruthSelectionTool::AthTruthSelectionTool(const std::string& type, const std:
   declareProperty("zDisc", m_zDisc=-1.0, "Select truth particle based on extrapolated position on disks placed at +/- z positions. Enabled if greater than 0.");
   declareProperty("minRadiusDisc", m_minRadiusDisc=0.0, "Minimum radius on disk for accepting extrapolated truth particle to surface.");
   declareProperty("maxRadiusDisc", m_maxRadiusDisc=0.0, "Maximum radius on disk for accepting extrapolated truth particle to surface.");
-
-  //reset cache
-  m_cylinder = 0;
-  m_disc1 = 0;
-  m_disc2 = 0;
 }
 
 StatusCode
@@ -73,150 +68,148 @@ AthTruthSelectionTool::initialize() {
     }, std::string("min_pt"))
   };
   //
-  m_cutFlow = CutFlow<P_t>(filters);
+  m_cutList = CutList<P_t>(filters);
   if (m_maxProdVertRadius>0) {
-    m_cutFlow.add(Accept_t([this](const P_t& p) -> bool {
+    m_cutList.add(Accept_t([this](const P_t& p) -> bool {
                        return((not (p.hasProdVtx()))or(p.prodVtx()->perp() < m_maxProdVertRadius));
                   },
                   "decay_before_" + std::to_string(m_maxProdVertRadius)));
   }
   if (m_maxPt > 0) {
-    m_cutFlow.add(Accept_t([this](const P_t& p) {
+    m_cutList.add(Accept_t([this](const P_t& p) {
       return(p.pt() < m_maxPt);
     }, "max_pt"));
   }
   if (m_maxBarcode > -1) {
-    m_cutFlow.add(Accept_t([this](const P_t& p) {
+    m_cutList.add(Accept_t([this](const P_t& p) {
       return(p.barcode() < m_maxBarcode);
     }, "barcode < " + std::to_string(m_maxBarcode)));
   }
   if (m_requireCharged) {
-    m_cutFlow.add(Accept_t([](const P_t& p) {
+    m_cutList.add(Accept_t([](const P_t& p) {
       return(not (p.isNeutral()));
     }, "charged"));
   }
   if (m_requireStatus1) {
-    m_cutFlow.add(Accept_t([](const P_t& p) {
+    m_cutList.add(Accept_t([](const P_t& p) {
       return(p.status() == 1);
     }, "status1"));
   }
   if (m_pdgId > 0) {
-    m_cutFlow.add(Accept_t([this](const P_t& p) {
+    m_cutList.add(Accept_t([this](const P_t& p) {
       return(std::abs(p.pdgId()) == m_pdgId);
     }, "pdgId"));
   }
   if (m_grandparent) {
-    m_cutFlow.add(Accept_t([](const P_t& p) {
+    m_cutList.add(Accept_t([](const P_t& p) {
       return((p.nParents() == 0) || ((p.nParents() == 1)and((p.parent(0))->nParents() == 0)));
     }, "hasNoGrandparent"));
   }
   if (m_poselectronfromgamma) {
-    m_cutFlow.add(Accept_t([](const P_t& p) {
+    m_cutList.add(Accept_t([](const P_t& p) {
       return((p.absPdgId() == electronId)and(p.nParents() >= 1) and(p.parent(0)) and(p.parent(0)->pdgId() == gammaId));
     }, "poselectronfromgamma"));
   }
   if (m_radiusCylinder > 0) {
-    //    m_cutFlow.add(Accept_t(acceptExtrapolatedTPToSurface, "SelectCylinder"));
-    m_cutFlow.add(Accept_t([this](const P_t& p) -> bool {
-	  ATH_MSG_VERBOSE("Checking particle for intersection with cylinder of radius " << m_radiusCylinder);
-	  //create surface we extrapolate to and cache it
-	  if (m_cylinder == 0) {
-	    ATH_MSG_VERBOSE("Creating and caching cylinder surface");
-	    Amg::Transform3D *trnsf = new Amg::Transform3D();
-	    trnsf->setIdentity();
-	    m_cylinder = new Trk::CylinderSurface( trnsf, m_radiusCylinder, 20000.);
-	  }
-	  const xAOD::TruthVertex* ptruthVertex = p.prodVtx();
-	  if (ptruthVertex == 0) {
-	    //cannot derive production vertex, reject track
-	    ATH_MSG_VERBOSE("Rejecting particle without production vertex.");
-	    return false;
-	  }
-	  const auto xPos = ptruthVertex->x();
-	  const auto yPos = ptruthVertex->y();
-	  const auto z_truth = ptruthVertex->z();
-	  const Amg::Vector3D position(xPos, yPos, z_truth);    
-	  const Amg::Vector3D momentum(p.px(), p.py(), p.pz());
-	  const Trk::CurvilinearParameters cParameters(position, momentum, p.charge());
-	  const Trk::TrackParameters *exParameters = m_extrapolator->extrapolate(cParameters, *m_cylinder, Trk::anyDirection, false, Trk::pion);
-	  if (!exParameters) {
-	    ATH_MSG_VERBOSE("Failed extrapolation. Rejecting track.");
-	    return false;
-	  }
-	  ATH_MSG_VERBOSE("Extrapolated parameters to cylinder: " << *exParameters);
-	  const float ex_abs_z = fabs(exParameters->position().z());
-	  if ( (ex_abs_z > m_minZCylinder) and (ex_abs_z < m_maxZCylinder) ) {
-	    ATH_MSG_VERBOSE("Particle accepted.");
-	    return true;
-	  }
-	  //else..
-	  ATH_MSG_VERBOSE("Particle rejected");
-	  return false;	  
-	}, "SelectCylinder"));
+    //    m_cutList.add(Accept_t(acceptExtrapolatedTPToSurface, "SelectCylinder"));
+    if (not m_cylinder) {
+      ATH_MSG_VERBOSE("Creating and caching cylinder surface");
+      Amg::Transform3D *trnsf = new Amg::Transform3D;
+      trnsf->setIdentity();
+      m_cylinder = std::make_unique<Trk::CylinderSurface>( trnsf, m_radiusCylinder, 20000.);
+    }
+    m_cutList.add(Accept_t([this](const P_t& p) -> bool {
+          ATH_MSG_VERBOSE("Checking particle for intersection with cylinder of radius " << m_radiusCylinder);
+          //create surface we extrapolate to and cache it
+          const xAOD::TruthVertex* ptruthVertex = p.prodVtx();
+          if (ptruthVertex == 0) {
+            //cannot derive production vertex, reject track
+            ATH_MSG_VERBOSE("Rejecting particle without production vertex.");
+            return false;
+          }
+          const auto xPos = ptruthVertex->x();
+          const auto yPos = ptruthVertex->y();
+          const auto z_truth = ptruthVertex->z();
+          const Amg::Vector3D position(xPos, yPos, z_truth);
+          const Amg::Vector3D momentum(p.px(), p.py(), p.pz());
+          const Trk::CurvilinearParameters cParameters(position, momentum, p.charge());
+          const Trk::TrackParameters *exParameters = m_extrapolator->extrapolate(cParameters, *m_cylinder, Trk::anyDirection, false, Trk::pion);
+          if (!exParameters) {
+            ATH_MSG_VERBOSE("Failed extrapolation. Rejecting track.");
+            return false;
+          }
+          ATH_MSG_VERBOSE("Extrapolated parameters to cylinder: " << *exParameters);
+          const float ex_abs_z = fabs(exParameters->position().z());
+          if ( (ex_abs_z > m_minZCylinder) and (ex_abs_z < m_maxZCylinder) ) {
+            ATH_MSG_VERBOSE("Particle accepted.");
+            return true;
+          }
+          //else..
+          ATH_MSG_VERBOSE("Particle rejected");
+          return false;
+        }, "SelectCylinder"));
   } else if (m_zDisc > 0) {
-    //m_cutFlow.add(Accept_t(acceptExtrapolatedTPToSurface, "SelectDisc"));
-    m_cutFlow.add(Accept_t([this](const P_t& p) -> bool {	  
-	  ATH_MSG_VERBOSE("Checking particle for intersection with discs of |z| " << m_zDisc);
-	  //create surface we extrapolate to and cache it
-	  if (m_disc1 == 0) { //m_disc2 == 0 implied
-	    ATH_MSG_VERBOSE("Creating and caching disc surface");
-	    Amg::Transform3D *trnsf_shiftZ = new Amg::Transform3D();
-	    (*trnsf_shiftZ) = Amg::Translation3D(0.,0.,m_zDisc);
-	    m_disc1 = new Trk::DiscSurface( trnsf_shiftZ, m_minRadiusDisc, m_maxRadiusDisc);
-	    (*trnsf_shiftZ) = Amg::Translation3D(0.,0.,-m_zDisc);
-	    m_disc2 = new Trk::DiscSurface( trnsf_shiftZ, m_minRadiusDisc, m_maxRadiusDisc);
-	  }
-	  const xAOD::TruthVertex* ptruthVertex = p.prodVtx();
-	  if (ptruthVertex == 0) {
-	    //cannot derive production vertex, reject track
-	    ATH_MSG_VERBOSE("Rejecting particle without production vertex.");
-	    return false;
-	  }
-	  const auto xPos = ptruthVertex->x();
-	  const auto yPos = ptruthVertex->y();
-	  const auto z_truth = ptruthVertex->z();
-	  const Amg::Vector3D position(xPos, yPos, z_truth);    
-	  const Amg::Vector3D momentum(p.px(), p.py(), p.pz());
-	  const Trk::CurvilinearParameters cParameters(position, momentum, p.charge());
-	  const Trk::TrackParameters *exParameters = m_extrapolator->extrapolate(cParameters, *m_disc1, Trk::anyDirection, true, Trk::pion);
-	  if (exParameters) {
-	    //since boundary check is true, should be enough to say we've hit the disk..
-	    ATH_MSG_VERBOSE("Successfully extrapolated track to disk at +" << m_zDisc << ": " << *exParameters);
-	    float ex_radius = sqrt(pow(exParameters->position().x(),2)+pow(exParameters->position().y(),2));
-	    ATH_MSG_VERBOSE("radial position at surface: " << ex_radius);
-	    if ((ex_radius > m_minRadiusDisc) and (ex_radius < m_maxRadiusDisc)) {
-	      ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
-	      return true;
-	    }
-	    //else...
-	    ATH_MSG_VERBOSE("Strange, extrapolation succeeded but extrapolated position not within disc radius! Test next disc");
-	  }
-	  exParameters = m_extrapolator->extrapolate(cParameters, *m_disc2, Trk::anyDirection, true, Trk::pion);
-	  if (exParameters) {
-	    //since boundary check is true, should be enough to say we've hit the disk..
-	    ATH_MSG_VERBOSE("Successfully extrapolated track to disk at -" << m_zDisc << ": " << *exParameters);
-	    float ex_radius = sqrt(pow(exParameters->position().x(),2)+pow(exParameters->position().y(),2));
-	    ATH_MSG_VERBOSE("radial position at surface: " << ex_radius);
-	    if ((ex_radius > m_minRadiusDisc) and (ex_radius < m_maxRadiusDisc)) {
-	      ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
-	      return true;
-	    }
-	    //else...
-	    ATH_MSG_VERBOSE("Strange, extrapolation succeeded but extrapolated position not within disc radius! Rejecting");
-	  }
-	  //else..
-	  ATH_MSG_VERBOSE("Particle rejected");
-	  return false;        
-	}, "SelectDisc"));
+    //m_cutList.add(Accept_t(acceptExtrapolatedTPToSurface, "SelectDisc"));
+    if (not m_disc1 || not m_disc2) { //m_disc2 == 0 implied
+      ATH_MSG_VERBOSE("Creating and caching disc surface");
+      Amg::Transform3D *trnsf_shiftZ = new Amg::Transform3D();
+      (*trnsf_shiftZ) = Amg::Translation3D(0.,0.,m_zDisc);
+      m_disc1 = std::make_unique<Trk::DiscSurface>( trnsf_shiftZ, m_minRadiusDisc, m_maxRadiusDisc);
+      (*trnsf_shiftZ) = Amg::Translation3D(0.,0.,-m_zDisc);
+      m_disc2 = std::make_unique<Trk::DiscSurface>( trnsf_shiftZ, m_minRadiusDisc, m_maxRadiusDisc);
+    }
+    m_cutList.add(Accept_t([this](const P_t& p) -> bool {
+          ATH_MSG_VERBOSE("Checking particle for intersection with discs of |z| " << m_zDisc);
+          //create surface we extrapolate to and cache it
+          const xAOD::TruthVertex* ptruthVertex = p.prodVtx();
+          if (ptruthVertex == 0) {
+            //cannot derive production vertex, reject track
+            ATH_MSG_VERBOSE("Rejecting particle without production vertex.");
+            return false;
+          }
+          const auto xPos = ptruthVertex->x();
+          const auto yPos = ptruthVertex->y();
+          const auto z_truth = ptruthVertex->z();
+          const Amg::Vector3D position(xPos, yPos, z_truth);
+          const Amg::Vector3D momentum(p.px(), p.py(), p.pz());
+          const Trk::CurvilinearParameters cParameters(position, momentum, p.charge());
+          const Trk::TrackParameters *exParameters = m_extrapolator->extrapolate(cParameters, *m_disc1, Trk::anyDirection, true, Trk::pion);
+          if (exParameters) {
+            //since boundary check is true, should be enough to say we've hit the disk..
+            ATH_MSG_VERBOSE("Successfully extrapolated track to disk at +" << m_zDisc << ": " << *exParameters);
+            float ex_radius = sqrt(pow(exParameters->position().x(),2)+pow(exParameters->position().y(),2));
+            ATH_MSG_VERBOSE("radial position at surface: " << ex_radius);
+            if ((ex_radius > m_minRadiusDisc) and (ex_radius < m_maxRadiusDisc)) {
+              ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
+              return true;
+            }
+            //else...
+            ATH_MSG_VERBOSE("Strange, extrapolation succeeded but extrapolated position not within disc radius! Test next disc");
+          }
+          exParameters = m_extrapolator->extrapolate(cParameters, *m_disc2, Trk::anyDirection, true, Trk::pion);
+          if (exParameters) {
+            //since boundary check is true, should be enough to say we've hit the disk..
+            ATH_MSG_VERBOSE("Successfully extrapolated track to disk at -" << m_zDisc << ": " << *exParameters);
+            float ex_radius = sqrt(pow(exParameters->position().x(),2)+pow(exParameters->position().y(),2));
+            ATH_MSG_VERBOSE("radial position at surface: " << ex_radius);
+            if ((ex_radius > m_minRadiusDisc) and (ex_radius < m_maxRadiusDisc)) {
+              ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
+              return true;
+            }
+            //else...
+            ATH_MSG_VERBOSE("Strange, extrapolation succeeded but extrapolated position not within disc radius! Rejecting");
+          }
+          //else..
+          ATH_MSG_VERBOSE("Particle rejected");
+          return false;
+        }, "SelectDisc"));
   } //m_zDisc > 0
 
-  m_counters = std::vector<unsigned int>(m_cutFlow.size(), 0);
-  std::string msg = std::to_string(m_cutFlow.size()) + " truth acceptance cuts are used:\n";
-  for (const auto& i:m_cutFlow.names()) {
+  std::string msg = std::to_string(m_cutList.size()) + " truth acceptance cuts are used:\n";
+  for (const auto& i:m_cutList.names()) {
     msg += i + "\n";
   }
   ATH_MSG_INFO(msg);
-  clearCounters();
 
   ATH_CHECK(m_extrapolator.retrieve());
 
@@ -229,35 +222,32 @@ AthTruthSelectionTool::finalize() {
   return StatusCode::SUCCESS;
 }
 
-void
-AthTruthSelectionTool::clearCounters() {
-  m_cutFlow.clear();
-  m_counters = m_cutFlow.counters();
-}
-
-std::vector<unsigned int>
-AthTruthSelectionTool::counters() const {
-  return m_cutFlow.counters();
-}
 
 std::vector<std::string>
 AthTruthSelectionTool::names() const {
-  return m_cutFlow.names();
+  return m_cutList.names();
 }
 
-bool
-AthTruthSelectionTool::accept(const xAOD::IParticle* particle) {
+IAthSelectionTool::CutResult
+AthTruthSelectionTool::accept(const xAOD::IParticle* particle) const {
   const xAOD::TruthParticle* pTruth = dynamic_cast<const xAOD::TruthParticle*>(particle);
 
+  //@TODO
   if (not pTruth) {
-    return false;
+    return m_cutList.size()+1; // marker for invalid particles
   }
-  return m_cutFlow.accept(*pTruth);
+  return m_cutList.accept(*pTruth);
 }
 
-std::string
-AthTruthSelectionTool::str() const {
-  return m_cutFlow.report();
+IAthSelectionTool::CutResult
+AthTruthSelectionTool::testAllCuts(const xAOD::IParticle * particle, std::vector<unsigned int> &counter) const {
+  const xAOD::TruthParticle* pTruth = dynamic_cast<const xAOD::TruthParticle*>(particle);
+
+  //@TODO
+  if (not pTruth) {
+    return m_cutList.size()+1; // marker for invalid particles
+  }
+  return m_cutList.testAllCuts(*pTruth,counter);
 }
 
 /*
@@ -280,7 +270,7 @@ bool AthTruthSelectionTool::acceptExtrapolatedTPToSurface(const xAOD::TruthParti
     const auto xPos = ptruthVertex->x();
     const auto yPos = ptruthVertex->y();
     const auto z_truth = ptruthVertex->z();
-    const Amg::Vector3D position(xPos, yPos, z_truth);    
+    const Amg::Vector3D position(xPos, yPos, z_truth);
     const Amg::Vector3D momentum(p.px(), p.py(), p.pz());
     const Trk::CurvilinearParameters cParameters(position, momentum, p.charge());
     const Trk::TrackParameters *exParameters = m_extrapolator->extrapolate(cParameters, *m_cylinder, Trk::anyDirection, false, Trk::pion);
@@ -316,7 +306,7 @@ bool AthTruthSelectionTool::acceptExtrapolatedTPToSurface(const xAOD::TruthParti
     const auto xPos = ptruthVertex->x();
     const auto yPos = ptruthVertex->y();
     const auto z_truth = ptruthVertex->z();
-    const Amg::Vector3D position(xPos, yPos, z_truth);    
+    const Amg::Vector3D position(xPos, yPos, z_truth);
     const Amg::Vector3D momentum(p.px(), p.py(), p.pz());
     const Trk::CurvilinearParameters cParameters(position, momentum, p.charge());
     const Trk::TrackParameters *exParameters = m_extrapolator->extrapolate(cParameters, *m_disc1, Trk::anyDirection, true, Trk::pion);
@@ -326,8 +316,8 @@ bool AthTruthSelectionTool::acceptExtrapolatedTPToSurface(const xAOD::TruthParti
       float ex_radius = fabs(exParameters->parameters()[Trk::d0]);
       ATH_MSG_VERBOSE("|loc1| (|d0|) parameter at surface: " << ex_radius);
       if ((ex_radius > m_minRadiusDisc) and (ex_radius < m_maxRadiusDisc)) {
-	ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
-	return true;
+        ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
+        return true;
       }
       //else...
       ATH_MSG_VERBOSE("Strange, extrapolation succeeded but extrapolated position not within disc radius! Test next disc");
@@ -339,17 +329,17 @@ bool AthTruthSelectionTool::acceptExtrapolatedTPToSurface(const xAOD::TruthParti
       float ex_radius = fabs(exParameters->parameters()[Trk::d0]);
       ATH_MSG_VERBOSE("|loc1| (|d0|) parameter at surface: " << ex_radius);
       if ((ex_radius > m_minRadiusDisc) and (ex_radius < m_maxRadiusDisc)) {
-	ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
-	return true;
+        ATH_MSG_VERBOSE("Confirmed within the disk. Accepting particle");
+        return true;
       }
       //else...
       ATH_MSG_VERBOSE("Strange, extrapolation succeeded but extrapolated position not within disc radius! Rejecting");
     }
     //else..
     ATH_MSG_VERBOSE("Particle rejected");
-    return false;        
+    return false;
   }
   //if not cut enabled, returns OK
-  return true; 
+  return true;
 }
 */
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.h b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.h
index 5148c9d74aec202835173fc72c4cbdc7be3833a4..fae47b2947a7c56b6af2adb3f07de698153f5624 100644
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.h
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/AthTruthSelectionTool.h
@@ -17,7 +17,7 @@
 #include "InDetPhysValMonitoring/IAthSelectionTool.h"
 #include "xAODTruth/TruthParticle.h" // typedef, can't fwd declare
 #include "AthenaBaseComps/AthAlgTool.h"
-#include "CutFlow.h"
+#include "InDetPhysValMonitoring/CutFlow.h"
 #include "GaudiKernel/ToolHandle.h"
 #include "TrkSurfaces/CylinderSurface.h"
 #include "TrkSurfaces/DiscSurface.h"
@@ -33,14 +33,21 @@ public:
   };
   StatusCode initialize() final;
   StatusCode finalize() final;
-  bool accept(const xAOD::IParticle* particle) final;
-  void clearCounters() final;
-  std::vector<unsigned int> counters() const final;
+
+  virtual IAthSelectionTool::CutResult
+  accept(const xAOD::IParticle* particle) const final;
+
+  virtual IAthSelectionTool::CutResult
+  testAllCuts(const xAOD::IParticle * p, std::vector<unsigned int> &counter) const final;
+
+  unsigned int nCuts() const final {
+    return m_cutList.size();
+  }
+
   std::vector<std::string> names() const final;
-  std::string str() const final;
 
 private:
-  CutFlow<xAOD::TruthParticle> m_cutFlow;
+  CutList<xAOD::TruthParticle> m_cutList;
   // Cut values;
   float m_maxEta;
   float m_maxPt;
@@ -67,9 +74,9 @@ private:
   float m_maxRadiusDisc; ///< for disk topology: maximum radius
 
   //cache surfaces
-  mutable Trk::CylinderSurface* m_cylinder;
-  mutable Trk::DiscSurface* m_disc1;
-  mutable Trk::DiscSurface* m_disc2;
+  std::unique_ptr<Trk::CylinderSurface> m_cylinder;
+  std::unique_ptr<Trk::DiscSurface>     m_disc1;
+  std::unique_ptr<Trk::DiscSurface>     m_disc2;
 
 
   /* @} */
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/CutFlow.h b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/CutFlow.h
deleted file mode 100644
index 3517e8270f9b222b6859fb493f0d8cdd07b4b472..0000000000000000000000000000000000000000
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/CutFlow.h
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
-  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
-*/
-
-/**
- * @file CutFlow.h
- * @author shaun roe
- **/
-
-
-#include <functional>
-#include <algorithm>
-#include <string>
-#include <vector>
-
-
-/** Templated class containing a cut, name of cut and description of cut(optional)
- * Typically, the class will be templated on <xAOD::TruthParticle> or <xAOD::TrackParticle>.
- * The cut is passed in as any predicate function, the type is declared as function<bool(A)>
- * although typically these will be lambda functions.
- * The cut is an 'accept' cut, i.e. the value passes if it is true.
- * These predicate functions are called each time the 'pass' method is called.
- * The class keeps an internal count of how many values passed, and may be used
- * as a functional due to the overloaded () operator.
- */
-template<class A>
-class Accept {
-public:
-  ///Default constructor with a simple predicate returning false
-  Accept() : m_predicate([](A) {
-    return false;
-  }), m_name {}, m_desc {}, m_nPassed(0) {
-    // nop
-  }
-  /** @brief Normal constructor
-   * @param predicate any function taking the templated value and returning a bool
-   * @param name optional name for this cut
-   * @param description optional description for this cut
-   */
-  Accept(const std::function<bool(const A&)>& predicate, const std::string& name = "",
-         const std::string& description = "") : m_predicate(predicate), m_name(name), m_desc(description),
-    m_nPassed(0) {
-    // nop
-  }
-
-  ///Overloading the () operator allows the class to be used as a functional
-  bool
-  operator () (const A& i) {
-    return pass(i);
-  }
-
-  ///Apply the predicate function and return the value, also updating an internal counter
-  bool
-  pass(const A& i) {
-    const bool passed = m_predicate(i);
-
-    m_nPassed += (int) passed;
-    return passed;
-  }
-
-  ///Clear internal counter
-  void
-  clearCount() {
-    m_nPassed = 0;
-  }
-
-  ///Return internal counter
-  unsigned int
-  getCount() const {
-    return m_nPassed;
-  }
-
-  ///Return cut name
-  std::string
-  name() const {
-    return m_name;
-  }
-
-  ///Return cut description
-  std::string
-  description() const {
-    return m_desc;
-  }
-
-  ///Utility typedefs to help callers: the value type
-  typedef A value_type;
-  ///Utility typedefs to help callers: the function type
-  typedef const std::function<bool (const A&)> func_type;
-private:
-  // std::function<bool(A)> m_predicate;
-  func_type m_predicate;
-  std::string m_name;
-  std::string m_desc;
-  unsigned int m_nPassed;
-};
-
-/** Templated CutFlow class to contain a group of cuts.
- * The CutFlow is typically instantiated with a vector of Accept objects, although
- * additional cuts may be added with the 'add' method.
- * Cuts may be applied in one of two ways, determined by the 'mode' parameter:
- * ALL: Always apply every cut
- * UNTIL_FAIL: Apply cuts until the first one fails
- * The 'accept' method applies the cuts and keeps internal count of how many times it is called.
- */
-template<class A>
-class CutFlow {
-public:
-  ///Cut mode: Apply ALL cuts, or until the first failure
-  enum CutFlowMode {
-    ALL, UNTIL_FAIL
-  };
-  ///Normal constructor takes a vector<Accept>. Note default mode is 'ALL'.
-  CutFlow(const std::vector<Accept<A> >& cuts) : m_cuts(cuts), m_mode(ALL),
-    m_passed(0), m_processed(0) {
-    // nop
-  }
-
-  ///Default constructor with no cuts implemented
-  CutFlow() : m_cuts{}, m_mode(ALL), m_passed(0), m_processed(0) {
-    // nop
-  }
-
-  ///Add one cut
-  void
-  add(const Accept<A>& newCut) {
-    m_cuts.push_back(newCut);
-  }
-
-  ///Clear internal counters
-  void
-  clear() {
-    m_processed = 0;
-    m_passed = 0;
-    for (auto& i:m_cuts) {
-      i.clearCount();
-    }
-  }
-
-  ///set the accept mode according to the CutFlowMode
-  void
-  setMode(const CutFlowMode m) {
-    m_mode = m;
-  }
-
-  ///get the accept mode
-  CutFlowMode
-  getMode() const {
-    return m_mode;
-  }
-
-  ///Apply cuts and return the boolean result; keep count of number of calls and passes
-  bool
-  accept(const A& value) {
-    bool result(true);
-
-    if (m_mode == ALL) {
-      for (auto& thisCut:m_cuts) {
-        const bool passed = thisCut.pass(value);
-        result &= passed;
-      }
-    } else {
-      for (auto& thisCut:m_cuts) {
-        const bool passed = thisCut.pass(value);
-        if (not passed) {
-          result = false;
-          break;
-        }
-      }
-    }
-    m_processed++;
-    m_passed += (int) result;
-    return result;
-  }
-
-  ///Return the number of cuts
-  unsigned int
-  size() const {
-    return m_cuts.size();
-  }
-
-  ///Return the number of times the 'accept' method was called
-  unsigned int
-  nProcessed() const {
-    return m_processed;
-  }
-
-  ///Returnn the number of values which passed all cuts
-  unsigned int
-  nPassed() const {
-    return m_passed;
-  }
-
-  ///Return a vector of individual counters for each cut
-  std::vector<unsigned int>
-  counters() const {
-    std::vector<unsigned int> result(m_cuts.size(), 0);
-    unsigned int idx(0);
-    for (const auto& i:m_cuts) {
-      result[idx++] = i.getCount();
-    }
-    return result; // return-value-optimisation is invoked
-  }
-
-  ///Return a vector of the cut names
-  std::vector<std::string>
-  names() const {
-    std::vector<std::string> result(m_cuts.size());
-    unsigned int idx(0);
-    for (const auto& i:m_cuts) {
-      result[idx++] = i.name();
-    }
-    return result; // return-value-optimisation is invoked
-  }
-
-  ///Produce a formatted string report of the results
-  std::string
-  report() const {
-    std::string op = "\nCutFlow Report; Total processed: " + std::to_string(m_processed);
-    op += "\nTotal passed: " + std::to_string(m_passed);
-    std::string modeString = (m_mode == ALL) ? "\nAll cuts were applied\n" : "\nCuts were applied until one fails\n";
-    op += modeString;
-    for (const auto& thisCut:m_cuts) {
-      op += thisCut.name() + ": " + std::to_string(thisCut.getCount()) + " passed\n";
-    }
-    return op;
-  }
-
-private:
-  std::vector<Accept<A> > m_cuts;
-  CutFlowMode m_mode;
-  unsigned int m_passed;
-  unsigned int m_processed;
-};
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValMonitoringTool.cxx b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValMonitoringTool.cxx
index 2761964c1fb379292052f9a979705b234ed40d1e..9a001e4ac1278c0c8b0cee5b0575952c1f831c12 100644
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValMonitoringTool.cxx
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValMonitoringTool.cxx
@@ -155,7 +155,6 @@ InDetPhysValMonitoringTool::InDetPhysValMonitoringTool(const std::string& type,
   m_threeMatchedEProb(0),
   m_fourMatchedEProb(0),
   m_truthCounter(0),
-  m_truthCutCounters{},
   m_fillTIDEPlots(false),
   m_fillExtraTIDEPlots(false) {
   declareProperty("useTrackSelection", m_useTrackSelection);
@@ -184,7 +183,7 @@ InDetPhysValMonitoringTool::initialize() {
   ATH_CHECK(m_trackSelectionTool.retrieve(EnableTool {m_useTrackSelection} ));
   ATH_CHECK(m_truthSelectionTool.retrieve(EnableTool {not m_truthParticleName.key().empty()} ));
   if (m_truthSelectionTool.get() ) {
-    m_truthCutCounters = m_truthSelectionTool->counters();
+    m_truthCutFlow = CutFlow(m_truthSelectionTool->nCuts());
   }
   m_monPlots = std::move(std::unique_ptr<InDetRttPlots> (new InDetRttPlots(0, m_dirName + m_folder)));
   m_monPlots->SetFillExtraTIDEPlots(m_fillExtraTIDEPlots);
@@ -306,7 +305,7 @@ InDetPhysValMonitoringTool::fillHistograms() {
   std::vector<int> incTrkNum = {
     0, 0, 0
   };
-  if (m_truthSelectionTool.get()) { m_truthSelectionTool->clearCounters(); }
+  CutFlow tmp_truth_cutflow( m_truthSelectionTool.get() ?  m_truthSelectionTool->nCuts() : 0 );
 
   // dummy variables
   int base(0), hasTruth(0), hashighprob(0), passtruthsel(0);
@@ -378,10 +377,12 @@ InDetPhysValMonitoringTool::fillHistograms() {
         hashighprob += 1;
       }
       // if there is associated truth also a truth selection tool was retrieved.
-      if (m_truthSelectionTool->accept(associatedTruth)) {
-        passtruthsel += 1;
+      IAthSelectionTool::CutResult passed( m_truthSelectionTool ?  m_truthSelectionTool->accept(associatedTruth) : IAthSelectionTool::CutResult(0) );
+      if (m_truthSelectionTool) {
+        tmp_truth_cutflow.update( passed.missingCuts() );
+        passtruthsel += (bool) passed;
       }
-      if ((prob > minProbEffLow) and m_truthSelectionTool->accept(associatedTruth)) {
+      if ((prob > minProbEffLow) and passed) {
         m_monPlots->fill(*thisTrack, *associatedTruth); // Make all plots requiring both truth & track (meas, res, &
                                                         // pull)
       }
@@ -390,13 +391,9 @@ InDetPhysValMonitoringTool::fillHistograms() {
     m_monPlots->fillLinkedandUnlinked(*thisTrack, Prim_w, Sec_w, Unlinked_w);
   }
   if (m_truthSelectionTool.get()) {
-     ATH_MSG_DEBUG(m_truthSelectionTool->str());
-     const auto& tmp = m_truthSelectionTool->counters(); // get array of counters for the cuts
-
-     unsigned idx(0);
-     for (auto& i:m_truthCutCounters) {
-        i += tmp[idx++]; // i=sum of all the individual counters on each cut.
-     }
+    ATH_MSG_DEBUG( CutFlow(tmp_truth_cutflow).report(m_truthSelectionTool->names()) );
+    std::lock_guard<std::mutex> lock(m_mutex);
+    m_truthCutFlow.merge(std::move(tmp_truth_cutflow));
   }
   int nTruths(0), nInsideOut(0), nOutsideIn(0);
   std::vector<int> incTrkDenom = {
@@ -443,7 +440,7 @@ InDetPhysValMonitoringTool::fillHistograms() {
     }
 
     // if the vector of truth particles is not empty also a truthSelectionTool was retrieved
-    const bool accept = m_truthSelectionTool->accept(thisTruth);
+    const IAthSelectionTool::CutResult accept = m_truthSelectionTool->accept(thisTruth);
     if (accept) {
       ++m_truthCounter;     // total number of truth tracks which pass cuts
       ++num_truth_selected; // total number of truth which pass cuts per event
@@ -699,11 +696,8 @@ InDetPhysValMonitoringTool::procHistograms() {
 
   ATH_MSG_INFO("");
   ATH_MSG_INFO("Cutflow for truth tracks:");
-  unsigned int idx(0);
   if (m_truthSelectionTool.get()) {
-     for (const auto& cutName:m_truthSelectionTool->names()) {
-        ATH_MSG_INFO("number after " << cutName << ": " << m_truthCutCounters[idx++]);
-     }
+    ATH_MSG_INFO("Truth selection report: " << m_truthCutFlow.report( m_truthSelectionTool->names()) );
   }
   if (endOfRunFlag()) {
     m_monPlots->finalize();
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.cxx b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.cxx
index 296bd95fdefa748948825791b76df643fe751bfa..f977cd046f7a464b3fb51e5d490cfb2bf6480d3b 100644
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.cxx
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.cxx
@@ -40,6 +40,9 @@ InDetPhysValTruthDecoratorAlg::initialize() {
   ATH_CHECK(m_extrapolator.retrieve());
   ATH_CHECK(m_beamSpotSvc.retrieve());
   ATH_CHECK( m_truthSelectionTool.retrieve( EnableTool { not m_truthSelectionTool.name().empty() } ) );
+  if (not m_truthSelectionTool.name().empty() ) {
+    m_cutFlow = CutFlow(m_truthSelectionTool->nCuts() );
+  }
 
   ATH_CHECK( m_truthParticleName.initialize());
 
@@ -60,6 +63,10 @@ InDetPhysValTruthDecoratorAlg::initialize() {
 
 StatusCode
 InDetPhysValTruthDecoratorAlg::finalize() {
+  if (not m_truthSelectionTool.name().empty()) {
+    std::lock_guard<std::mutex> lock(m_mutex);
+    ATH_MSG_DEBUG( "Truth selection cut flow : " << m_cutFlow.report(m_truthSelectionTool->names()) );
+  }
   return StatusCode::SUCCESS;
 }
 
@@ -81,10 +88,15 @@ InDetPhysValTruthDecoratorAlg::execute_r(const EventContext &ctx) const {
     float_decor( IDPVM::createDecoratorsWithAccessor(m_decor, ctx) );
 
   if ( m_truthSelectionTool.get() ) {
+    CutFlow tmp_cut_flow(m_truthSelectionTool->nCuts());
     for (const xAOD::TruthParticle *truth_particle : *ptruth) {
-      if (not m_truthSelectionTool->accept(truth_particle)) continue;
+      auto passed = m_truthSelectionTool->accept(truth_particle);
+      tmp_cut_flow.update( passed.missingCuts() );
+      if (not passed) continue;
       decorateTruth(*truth_particle, float_decor);
     }
+    std::lock_guard<std::mutex> lock(m_mutex);
+    m_cutFlow.merge(std::move(tmp_cut_flow));
   }
   else {
     for (const xAOD::TruthParticle *truth_particle : *ptruth) {
diff --git a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.h b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.h
index 91d1f80f97666fcc81b339d8330e2344f335fbbf..6a9ad9d4475c0cfa3324fa9d7629daad8ebfc2c5 100644
--- a/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.h
+++ b/InnerDetector/InDetValidation/InDetPhysValMonitoring/src/InDetPhysValTruthDecoratorAlg.h
@@ -22,6 +22,7 @@
 #include "AthContainers/AuxElement.h"
 #include "GaudiKernel/EventContext.h"
 #include "InDetPhysValMonitoring/IAthSelectionTool.h"
+#include "InDetPhysValMonitoring/CutFlow.h"
 #include <utility>
 #include <vector>
 
@@ -52,6 +53,9 @@ private:
   PublicToolHandle<IAthSelectionTool>         m_truthSelectionTool
      {this,"TruthSelectionTool","",""};
 
+  mutable std::mutex m_mutex;
+  mutable CutFlow m_cutFlow;
+
   ///TruthParticle container's name needed to create decorators
   SG::ReadHandleKey<xAOD::TruthParticleContainer> m_truthParticleName
     {this, "TruthParticleContainerName",  "TruthParticles", ""};