From c18cd63bd9372fc0dee5c40e23fa1dc1e61ca1ab Mon Sep 17 00:00:00 2001 From: aferrero2707 Date: Fri, 31 May 2024 17:11:39 +0200 Subject: [PATCH] Add a generic reference data comparator The code introduces some tools that allow to compare the plots produced by given task(s) with that of a reference run: - `ReferenceComparatorTask` draws histograms with their reference superimposed, as well as the ratio between histograms and reference - `ReferenceComparatorCheck` compares the histograms with their corresponding reference, and assesses their compatibility. The comparison is performed by a dynamically loadable module that provides the actual comparison algorithm - `ReferenceComparatorPlot` is an utility class to draw the current and reference histograms, and their ratio - `ObjectComparator*` are dynamically loadable modules than implement different histogram comparison methods Two examples are provided: - `ObjectComparatorDeviation` evaluates the average relative difference between the histogram bins - `ObjectComparatorChi2` compares the histograms using a chi2-based test --- Modules/Common/CMakeLists.txt | 11 + Modules/Common/include/Common/LinkDef.h | 4 + .../include/Common/ObjectComparatorChi2.h | 42 ++ .../Common/ObjectComparatorDeviation.h | 42 ++ .../Common/ObjectComparatorInterface.h | 62 +++ .../include/Common/ReferenceComparatorCheck.h | 59 +++ .../include/Common/ReferenceComparatorPlot.h | 45 ++ .../include/Common/ReferenceComparatorTask.h | 84 ++++ .../Common/ReferenceComparatorTaskConfig.h | 58 +++ Modules/Common/src/ObjectComparatorChi2.cxx | 85 ++++ .../Common/src/ObjectComparatorDeviation.cxx | 91 ++++ .../Common/src/ReferenceComparatorCheck.cxx | 295 +++++++++++ .../Common/src/ReferenceComparatorPlot.cxx | 473 ++++++++++++++++++ .../Common/src/ReferenceComparatorTask.cxx | 243 +++++++++ .../src/ReferenceComparatorTaskConfig.cxx | 47 ++ 15 files changed, 1641 insertions(+) create mode 100644 Modules/Common/include/Common/ObjectComparatorChi2.h create mode 100644 Modules/Common/include/Common/ObjectComparatorDeviation.h create mode 100644 Modules/Common/include/Common/ObjectComparatorInterface.h create mode 100644 Modules/Common/include/Common/ReferenceComparatorCheck.h create mode 100644 Modules/Common/include/Common/ReferenceComparatorPlot.h create mode 100644 Modules/Common/include/Common/ReferenceComparatorTask.h create mode 100644 Modules/Common/include/Common/ReferenceComparatorTaskConfig.h create mode 100644 Modules/Common/src/ObjectComparatorChi2.cxx create mode 100644 Modules/Common/src/ObjectComparatorDeviation.cxx create mode 100644 Modules/Common/src/ReferenceComparatorCheck.cxx create mode 100644 Modules/Common/src/ReferenceComparatorPlot.cxx create mode 100644 Modules/Common/src/ReferenceComparatorTask.cxx create mode 100644 Modules/Common/src/ReferenceComparatorTaskConfig.cxx diff --git a/Modules/Common/CMakeLists.txt b/Modules/Common/CMakeLists.txt index 0a5770c938..e330e90690 100644 --- a/Modules/Common/CMakeLists.txt +++ b/Modules/Common/CMakeLists.txt @@ -22,6 +22,12 @@ target_sources(O2QcCommon src/CcdbInspectorTask.cxx src/CcdbInspectorTaskConfig.cxx src/CcdbInspectorCheck.cxx + src/ObjectComparatorDeviation.cxx + src/ObjectComparatorChi2.cxx + src/ReferenceComparatorPlot.cxx + src/ReferenceComparatorTask.cxx + src/ReferenceComparatorTaskConfig.cxx + src/ReferenceComparatorCheck.cxx src/NonEmpty.cxx src/MeanIsAbove.cxx src/TH1Reductor.cxx @@ -54,6 +60,11 @@ add_root_dictionary(O2QcCommon include/Common/BigScreen.h include/Common/CcdbInspectorTask.h include/Common/CcdbInspectorCheck.h + include/Common/ObjectComparatorInterface.h + include/Common/ObjectComparatorDeviation.h + include/Common/ObjectComparatorChi2.h + include/Common/ReferenceComparatorTask.h + include/Common/ReferenceComparatorCheck.h include/Common/MeanIsAbove.h include/Common/TH1Ratio.h include/Common/TH2Ratio.h diff --git a/Modules/Common/include/Common/LinkDef.h b/Modules/Common/include/Common/LinkDef.h index e954150209..32cbe21121 100644 --- a/Modules/Common/include/Common/LinkDef.h +++ b/Modules/Common/include/Common/LinkDef.h @@ -19,6 +19,10 @@ #pragma link C++ class o2::quality_control_modules::common::BigScreen + ; #pragma link C++ class o2::quality_control_modules::common::CcdbInspectorTask + ; #pragma link C++ class o2::quality_control_modules::common::CcdbInspectorCheck + ; +#pragma link C++ class o2::quality_control_modules::common::ObjectComparatorDeviation + ; +#pragma link C++ class o2::quality_control_modules::common::ObjectComparatorChi2 + ; +#pragma link C++ class o2::quality_control_modules::common::ReferenceComparatorTask + ; +#pragma link C++ class o2::quality_control_modules::common::ReferenceComparatorCheck + ; #pragma link C++ class o2::quality_control_modules::common::WorstOfAllAggregator + ; #pragma link C++ class o2::quality_control_modules::common::IncreasingEntries + ; #pragma link C++ class o2::quality_control_modules::common::TH1SliceReductor + ; diff --git a/Modules/Common/include/Common/ObjectComparatorChi2.h b/Modules/Common/include/Common/ObjectComparatorChi2.h new file mode 100644 index 0000000000..e54a784fc0 --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorChi2.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorChi2.h +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on a chi2 test +/// + +#ifndef QUALITYCONTROL_ObjectComparatorChi2_H +#define QUALITYCONTROL_ObjectComparatorChi2_H + +#include "Common/ObjectComparatorInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A class for comparing two histogram objects based on a chi2 test +class ObjectComparatorChi2 : public ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorChi2() = default; + /// \brief Destructor + virtual ~ObjectComparatorChi2() = default; + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + o2::quality_control::core::Quality compare(TObject* obj, TObject* objRef, std::string& message) override; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorChi2_H diff --git a/Modules/Common/include/Common/ObjectComparatorDeviation.h b/Modules/Common/include/Common/ObjectComparatorDeviation.h new file mode 100644 index 0000000000..282ddd5a76 --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorDeviation.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorDeviation.h +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +/// + +#ifndef QUALITYCONTROL_ObjectComparatorDeviation_H +#define QUALITYCONTROL_ObjectComparatorDeviation_H + +#include "Common/ObjectComparatorInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +class ObjectComparatorDeviation : public ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorDeviation() = default; + /// \brief Destructor + virtual ~ObjectComparatorDeviation() = default; + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + o2::quality_control::core::Quality compare(TObject* obj, TObject* objRef, std::string& message) override; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorDeviation_H diff --git a/Modules/Common/include/Common/ObjectComparatorInterface.h b/Modules/Common/include/Common/ObjectComparatorInterface.h new file mode 100644 index 0000000000..dd86110e3e --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorInterface.h @@ -0,0 +1,62 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorInterface.h +/// \author Andrea Ferrero +/// \brief An interface for comparing two TObject +/// + +#ifndef QUALITYCONTROL_ObjectComparatorInterface_H +#define QUALITYCONTROL_ObjectComparatorInterface_H + +#include "QualityControl/Quality.h" +#include "QualityControl/CustomParameters.h" +#include "QualityControl/Activity.h" +class TObject; + +namespace o2::quality_control_modules::common +{ + +/// \brief An interface for comparing two TObject +class ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorInterface() = default; + /// \brief Destructor + virtual ~ObjectComparatorInterface() = default; + + /// \brief comparator configuration via CustomParameters + // virtual void configure(const o2::quality_control::core::CustomParameters& customParameters, const o2::quality_control::core::Activity activity = {}){}; + + /// setter/getter methods for the threshold to define the goodness of the comparison + void setThreshold(double threshold) { mThreshold = threshold; } + double getThreshold() { return mThreshold; } + + /// setter/getter methods for the flag indicating wether the reference histogram shoould be normalized before performing the comparison + void setNormalizeReference(bool normalize) { mNormalizeReference = normalize; } + bool doNormalizeReference() { return mNormalizeReference; } + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + virtual o2::quality_control::core::Quality compare(TObject* obj, TObject* objRef, std::string& message) = 0; + + private: + /// the threshold to define the goodness of the comparison + double mThreshold{ 0 }; + /// wether to normalize the reference histogram to the current one before performing the comparison + bool mNormalizeReference{ false }; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorInterface_H diff --git a/Modules/Common/include/Common/ReferenceComparatorCheck.h b/Modules/Common/include/Common/ReferenceComparatorCheck.h new file mode 100644 index 0000000000..1f7f070b25 --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorCheck.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorCheck.h +/// \author Andrea Ferrero +/// \brief A generic QC check that compares a given set of histograms with their corresponding references +/// + +#ifndef QUALITYCONTROL_ReferenceComparatorCheck_H +#define QUALITYCONTROL_ReferenceComparatorCheck_H + +#include "QualityControl/CheckInterface.h" +#include "Common/ObjectComparatorInterface.h" + +#include + +class TPaveText; + +namespace o2::quality_control_modules::common +{ + +/// \brief A generic QC check that compares a given set of histograms with their corresponding references +/// \author Andrea Ferrero +class ReferenceComparatorCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ReferenceComparatorCheck() = default; + /// Destructor + ~ReferenceComparatorCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void reset() override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + std::string getAcceptedType() override; + + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + private: + std::unique_ptr mComparator; + std::map mQualityFlags; + std::map> mQualityLabels; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_SKELETON_ReferenceComparatorCheck_H diff --git a/Modules/Common/include/Common/ReferenceComparatorPlot.h b/Modules/Common/include/Common/ReferenceComparatorPlot.h new file mode 100644 index 0000000000..45f1032971 --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorPlot.h @@ -0,0 +1,45 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorPlot.h +/// \author Andrea Ferrero +/// \brief Utility class for the combined drawing of the current and reference plots, and their ratio +/// + +#ifndef QUALITYCONTROL_REFERENCECOMPARATORPLOT_H +#define QUALITYCONTROL_REFERENCECOMPARATORPLOT_H + +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +class ReferenceComparatorPlotImpl; + +class ReferenceComparatorPlot +{ + public: + ReferenceComparatorPlot(TH1* refHist, std::string outputPath, bool scaleRef, bool drawRatioOnly, std::string drawOption1D, std::string drawOption2D); + virtual ~ReferenceComparatorPlot() = default; + + TObject* getObject(); + void update(TH1* hist, TH1* histRef); + + private: + std::shared_ptr mImplementation; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_REFERENCECOMPARATORTASK_H diff --git a/Modules/Common/include/Common/ReferenceComparatorTask.h b/Modules/Common/include/Common/ReferenceComparatorTask.h new file mode 100644 index 0000000000..dd18c983f9 --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorTask.h @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTask.h +/// \author Andrea Ferrero +/// \brief Post-processing task that compares a given set of plots with reference ones +/// + +#ifndef QUALITYCONTROL_REFERENCECOMPARATORTASK_H +#define QUALITYCONTROL_REFERENCECOMPARATORTASK_H + +#include "Common/ReferenceComparatorTaskConfig.h" +#include "Common/BigScreenCanvas.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/Quality.h" +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +class ReferenceComparatorPlot; + +/// \brief Post-processing task that compares a given set of plots with reference ones +/// +/// For each input plot, the task publishes the ratio between the plot and the corresponding reference. +/// Moreover, for 1-D histograms it also publishes the plot itself with the reference superimposed, for visual comparison. +/// +/// \author Andrea Ferrero +class ReferenceComparatorTask : public quality_control::postprocessing::PostProcessingInterface +{ + public: + ReferenceComparatorTask() = default; + ~ReferenceComparatorTask() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + struct HistoWithRef { + std::shared_ptr mPlot; + std::shared_ptr mRefPlot; + std::shared_ptr mRatioPlot; + std::shared_ptr mPadHist; + std::shared_ptr mPadHistRatio; + std::shared_ptr mCanvas; + }; + + private: + std::shared_ptr getRefPlot(o2::quality_control::repository::DatabaseInterface& qcdb, std::string fullPath, o2::quality_control::core::Activity activity); + void updatePlot(std::string plotName, TObject* object); + + int mRefRun{ 0 }; + int mNotOlderThan{ 120 }; + + /// \brief configuration parameters + ReferenceComparatorTaskConfig mConfig; + /// \brief list of plot names, separately for each group + std::map> mPlotNames; + /// \brief reference MOs + std::map> mRefPlots; + /// \brief histograms with comparison to reference + std::map> mHistograms; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_REFERENCECOMPARATORTASK_H diff --git a/Modules/Common/include/Common/ReferenceComparatorTaskConfig.h b/Modules/Common/include/Common/ReferenceComparatorTaskConfig.h new file mode 100644 index 0000000000..f75680d5b5 --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorTaskConfig.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTaskConfig.h +/// \author Andrea Ferrero +/// \brief ReferenceComparatorTask configuration structure +/// + +#ifndef QUALITYCONTROL_REFERENCECOMPARATORTASKCONFIG_H +#define QUALITYCONTROL_REFERENCECOMPARATORTASKCONFIG_H + +#include +#include +#include +#include "QualityControl/PostProcessingConfig.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief ReferenceComparatorTask configuration structure +struct ReferenceComparatorTaskConfig : quality_control::postprocessing::PostProcessingConfig { + ReferenceComparatorTaskConfig() = default; + ReferenceComparatorTaskConfig(std::string name, const boost::property_tree::ptree& config); + ~ReferenceComparatorTaskConfig() = default; + + struct DataGroup { + std::string name; + // QCDB path where the source objects are located + std::string inputPath; + // QCDB path where the output plots are uploaded + std::string outputPath; + // wether to normalize the reference plot with respect to the current one + bool normalizeReference{ false }; + // wether to only draw the current/reference ratio, or the inidividual histograms as well + bool drawRatioOnly{ false }; + // ROOT option to be used for drawing 1-D plots ("HIST" by default) + std::string drawOption1D{ "HIST" }; + // ROOT option to be used for drawing 2-D plots ("COLZ" by default) + std::string drawOption2D{ "COLZ" }; + // list of QCDB objects in this group + std::vector objects; + }; + + std::vector dataGroups; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_REFERENCECOMPARATORTASKCONFIG_H diff --git a/Modules/Common/src/ObjectComparatorChi2.cxx b/Modules/Common/src/ObjectComparatorChi2.cxx new file mode 100644 index 0000000000..523f82aa1d --- /dev/null +++ b/Modules/Common/src/ObjectComparatorChi2.cxx @@ -0,0 +1,85 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorChi2.cxx +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on a chi2 test +/// + +#include "Common/ObjectComparatorChi2.h" +// ROOT +#include + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +Quality ObjectComparatorChi2::compare(TObject* obj, TObject* objRef, std::string& message) +{ + if (!obj || !objRef) { + message = "missing objects"; + return Quality::Null; + } + + // only consider objects that inherit from TH1 + auto* hist = dynamic_cast(obj); + auto* histRef = dynamic_cast(objRef); + + if (!hist || !histRef) { + message = "objects are not TH1"; + return Quality::Null; + } + + // the object and the reference must correspond to the same ROOT class + if (hist->IsA() != histRef->IsA()) { + message = "incompatible objects"; + return Quality::Null; + } + + if (histRef->GetEntries() < 1) { + message = "empty reference plot"; + return Quality::Null; + } + + if (hist->GetNcells() < 3 || hist->GetNcells() != histRef->GetNcells()) { + message = "incompatible number of bins"; + return Quality::Null; + } + + std::string chi2TestOption = "UU P"; + if (doNormalizeReference()) { + // the reference histogram is scaled to match the integral of the current histogram + double integral = hist->Integral(); + double refIntegral = histRef->Integral(); + if (integral != 0 && refIntegral != 0) { + histRef->Scale(integral / refIntegral); + chi2TestOption = "UW P"; + } + } + + // perform a chi2 compatibility test between the two histograms + double chi2Prob = hist->Chi2Test(histRef, chi2TestOption.c_str()); + + // compare the chi2 probability with the minimum allowed value + if (chi2Prob < getThreshold()) { + message = fmt::format("chi2 test failed: {:.2f} < {:.2f}", chi2Prob, getThreshold()); + return Quality::Bad; + } + + return Quality::Good; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ObjectComparatorDeviation.cxx b/Modules/Common/src/ObjectComparatorDeviation.cxx new file mode 100644 index 0000000000..9a0dd23134 --- /dev/null +++ b/Modules/Common/src/ObjectComparatorDeviation.cxx @@ -0,0 +1,91 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorDeviation.cxx +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +/// + +#include "Common/ObjectComparatorDeviation.h" +// ROOT +#include + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +Quality ObjectComparatorDeviation::compare(TObject* obj, TObject* objRef, std::string& message) +{ + if (!obj || !objRef) { + message = "missing objects"; + return Quality::Null; + } + + // only consider objects that inherit from TH1 + auto* hist = dynamic_cast(obj); + auto* histRef = dynamic_cast(objRef); + + if (!hist || !histRef) { + message = "objects are not TH1"; + return Quality::Null; + } + + // the object and the reference must correspond to the same ROOT class + if (hist->IsA() != histRef->IsA()) { + message = "incompatible objects"; + return Quality::Null; + } + + if (histRef->GetEntries() < 1) { + message = "empty reference plot"; + return Quality::Null; + } + + if (hist->GetNcells() < 3 || hist->GetNcells() != histRef->GetNcells()) { + message = "incompatible number of bins"; + return Quality::Null; + } + + double scale = 1; + if (doNormalizeReference()) { + // the reference histogram is scaled to match the integral of the current histogram + double integral = hist->Integral(); + double refIntegral = histRef->Integral(); + if (integral != 0 && refIntegral != 0) { + scale = integral / refIntegral; + } + } + + // compute the average relative deviation between the bins + double averageDeviation = 0; + int numberOfBins = hist->GetNcells() - 2; + for (int bin = 1; bin <= numberOfBins; bin++) { + double val = hist->GetBinContent(bin); + double refVal = scale * histRef->GetBinContent(bin); + averageDeviation += (refVal == 0) ? 0 : std::abs((val - refVal) / refVal); + } + averageDeviation /= numberOfBins; + + // compare the average deviation with the maximum allowed value + if (averageDeviation > getThreshold()) { + message = fmt::format("large deviation: {:.2f} > {:.2f}", averageDeviation, getThreshold()); + return Quality::Bad; + } + + return Quality::Good; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ReferenceComparatorCheck.cxx b/Modules/Common/src/ReferenceComparatorCheck.cxx new file mode 100644 index 0000000000..4448508a61 --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorCheck.cxx @@ -0,0 +1,295 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorCheck.cxx +/// \author Andrea Ferrero +/// + +#include "Common/ReferenceComparatorCheck.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/RootClassFactory.h" + +#include +#include + +// ROOT +#include +#include +#include +#include + +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +void ReferenceComparatorCheck::configure() +{ +} + +void ReferenceComparatorCheck::startOfActivity(const Activity& activity) +{ + auto moduleName = mCustomParameters.atOptional("moduleName").value_or(""); + auto comparatorName = mCustomParameters.atOptional("comparatorName").value_or(""); + double threshold = std::stof(mCustomParameters.atOptional("threshold").value_or("0")); + bool normalizeReference = std::stoi(mCustomParameters.atOptional("normalizeReference").value_or("0")) == 1; + + mComparator.reset(); + if (!moduleName.empty() && !comparatorName.empty()) { + mComparator.reset(root_class_factory::create(moduleName, comparatorName)); + } + + if (mComparator) { + mComparator->setThreshold(threshold); + mComparator->setNormalizeReference(normalizeReference); + } +} + +void ReferenceComparatorCheck::endOfActivity(const Activity& activity) +{ +} + +//_________________________________________________________________________________________ +// +// Get the current and reference histograms from the canvas. +// The two histograms are returned as a std::pair +static std::pair getPlotsFromCanvas(TCanvas* canvas, std::string& message) +{ + // Get the pad containing the current histogram, as well as the reference one in the case of 1-D plots + TPad* padHist = (TPad*)canvas->GetPrimitive(TString::Format("%s_PadHist", canvas->GetName())); + if (!padHist) { + message = "missing PadHist"; + return { nullptr, nullptr }; + } + // Get the pad containing the reference histogram. + // This pad is only present ofr 2-D histograms. + // 1-D histograms are drawn superimposed in the same pad + TPad* padHistRef = (TPad*)canvas->GetPrimitive(TString::Format("%s_PadHistRef", canvas->GetName())); + + // Get the current histogram + TH1* hist = dynamic_cast(padHist->GetPrimitive(TString::Format("%s_hist", canvas->GetName()))); + if (!hist) { + message = "missing histogram"; + return { nullptr, nullptr }; + } + + // Get the reference histogram, trying both pads + TH1* histRef = nullptr; + if (padHistRef) { + histRef = dynamic_cast(padHistRef->GetPrimitive(TString::Format("%s_hist_ref", canvas->GetName()))); + } else { + histRef = dynamic_cast(padHist->GetPrimitive(TString::Format("%s_hist_ref", canvas->GetName()))); + } + + if (!histRef) { + message = "missing reference histogram"; + return { nullptr, nullptr }; + } + + // return a pair with the two histograms + return { hist, histRef }; +} + +// Get the current and reference histograms from the canvas, and compare them using the comparator object passed as parameter +static Quality compare(TCanvas* canvas, ObjectComparatorInterface* comparator, std::string& message) +{ + if (!canvas) { + message = "missing canvas"; + return Quality::Null; + } + if (!comparator) { + message = "missing comparator"; + return Quality::Null; + } + + // extract the histograms from the canvas + auto plots = getPlotsFromCanvas(canvas, message); + if (!plots.first || !plots.second) { + return Quality::Null; + } + + // Return the result of the comparison. Details are stored in the "message" string. + return comparator->compare(plots.first, plots.second, message); +} + +//_________________________________________________________________________________________ +// +// Get the THxyRatio histogram from the MO, and compare the current histogram (numerator) with +// the reference histogram (denominator), using the comparator object passed as parameter + +/*static Quality compare(std::shared_ptr mo, ObjectComparatorInterface* comparator, std::string& message) +{ + if (!mo) { + message = "missing mo"; + return Quality::Null; + } + if (!comparator) { + message = "missing comparator"; + return Quality::Null; + } + + auto object = mo->getObject(); + if (!object) { + message = "missing object"; + return Quality::Null; + } + + // check if the MO contains an histogram derived from THxRatio + if (auto histRatio = dynamic_cast(object)) { + return comparator->compare(histRatio->getNum(), histRatio->getDen(), message); + } else if (auto histRatio = dynamic_cast(object)) { + return comparator->compare(histRatio->getNum(), histRatio->getDen(), message); + } else if (auto histRatio = dynamic_cast(object)) { + return comparator->compare(histRatio->getNum(), histRatio->getDen(), message); + } else if (auto histRatio = dynamic_cast(object)) { + return comparator->compare(histRatio->getNum(), histRatio->getDen(), message); + } + + message = "object is not a TH*Ratio"; + return Quality::Null; +} + +static bool isRatio(const std::string& name) +{ + static const std::string suffix = "Ratio"; + bool result = false; + auto pos = name.rfind(suffix); + if (pos == (name.size() - suffix.size())) { + result = true; + } + std::cout << "TOTO3 name '" << name << "' size " << name.size() << " pos " << pos << " result " << result << std::endl; + return result; +}*/ + +//_________________________________________________________________________________________ +// +// Loop over all the input MOs and compare each of them with the corresponding MO from the reference run +// The final quality is the combination of the individual values stored in the mQualityFlags map + +Quality ReferenceComparatorCheck::check(std::map>* moMap) +{ + Quality result = Quality::Good; + for (auto& [key, mo] : *moMap) { + auto moName = mo->getName(); + + auto* canvas = dynamic_cast(mo->getObject()); + std::cout << "TOTO3 moName '" << moName << " canvas " << canvas << std::endl; + if (!canvas) { + continue; + } + + Quality quality; + std::string message; + quality = compare(canvas, mComparator.get(), message); + std::cout << "TOTO3 " << message << std::endl; + + // update the overall quality + if (quality.isWorseThan(result)) { + result.set(quality); + } + + // add the message if not empty + if (!message.empty()) { + quality.addFlag(FlagTypeFactory::Unknown(), fmt::format("{} {}", moName, message)); + result.addFlag(FlagTypeFactory::Unknown(), fmt::format("{} {}", moName, message)); + } + + // store the quality associated to this MO + // will be used to beautify the corresponding plots + mQualityFlags[moName] = quality; + } + + return result; +} + +void ReferenceComparatorCheck::reset() +{ + mQualityFlags.clear(); +} + +std::string ReferenceComparatorCheck::getAcceptedType() { return "TH1"; } + +// return the ROOT color index associated to a give quality level +static int getQualityColor(const Quality& q) +{ + if (q == Quality::Null) + return kViolet - 6; + if (q == Quality::Bad) + return kRed; + if (q == Quality::Medium) + return kOrange - 3; + if (q == Quality::Good) + return kGreen + 2; + + return 0; +} + +// Write the quality level and flags in the existing PaveText inside the canvas +static void setQualityLabel(TCanvas* canvas, const Quality& quality) +{ + if (!canvas) { + return; + } + + canvas->cd(); + + // search for the label to show the quality status + TIter next(canvas->GetListOfPrimitives()); + TObject* obj; + while ((obj = next())) { + auto* label = dynamic_cast(obj); + if (!label) { + continue; + } + + label->SetTextColor(getQualityColor(quality)); + label->AddText(quality.getName().c_str()); + + auto flags = quality.getFlags(); + for (auto& flag : flags) { + auto message = flag.second; + auto pos = message.find(" "); + if (pos != std::string::npos) { + message.erase(0, pos + 1); + } + auto* text = label->AddText(message.c_str()); + text->SetTextColor(kGray + 1); + } + break; + } +} + +void ReferenceComparatorCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto* canvas = dynamic_cast(mo->getObject()); + if (!canvas) { + return; + } + + auto moName = mo->getName(); + auto quality = mQualityFlags[moName]; + + std::string dummyMessage; + auto plots = getPlotsFromCanvas(canvas, dummyMessage); + + if (plots.second) { + plots.second->SetLineColor(getQualityColor(quality)); + } + + setQualityLabel(canvas, quality); +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ReferenceComparatorPlot.cxx b/Modules/Common/src/ReferenceComparatorPlot.cxx new file mode 100644 index 0000000000..db1080aee7 --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorPlot.cxx @@ -0,0 +1,473 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorPlot.xx +/// \author Andrea Ferrero +/// \brief Utility class for the combined drawing of the current and reference plots, and their ratio +/// + +#include "Common/ReferenceComparatorPlot.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +static bool checkAxis(TH1* h1, TH1* h2) +{ + // check consistency of X-axis binning + if (h1->GetXaxis()->GetNbins() != h2->GetXaxis()->GetNbins()) { + return false; + } + if (h1->GetXaxis()->GetXmin() != h2->GetXaxis()->GetXmin()) { + return false; + } + if (h1->GetXaxis()->GetXmax() != h2->GetXaxis()->GetXmax()) { + return false; + } + + // check consistency of Y-axis binning + if (h1->GetYaxis()->GetNbins() != h2->GetYaxis()->GetNbins()) { + return false; + } + if (h1->GetYaxis()->GetXmin() != h2->GetYaxis()->GetXmin()) { + return false; + } + if (h1->GetYaxis()->GetXmax() != h2->GetYaxis()->GetXmax()) { + return false; + } + + // check consistency of Z-axis binning + if (h1->GetZaxis()->GetNbins() != h2->GetZaxis()->GetNbins()) { + return false; + } + if (h1->GetZaxis()->GetXmin() != h2->GetZaxis()->GetXmin()) { + return false; + } + if (h1->GetZaxis()->GetXmax() != h2->GetZaxis()->GetXmax()) { + return false; + } + + return true; +} + +//_________________________________________________________________________________________ + +static void copyAndScaleHistograms(TH1* histo, TH1* refHisto, TH1* outputHisto, TH1* outputRefHisto, bool scaleReference) +{ + if (!histo || !refHisto || !outputHisto || !outputRefHisto) { + ILOG(Warning, Devel) << "histogram is nullptr" << ENDM; + return; + } + + if (!checkAxis(histo, refHisto)) { + ILOG(Warning, Devel) << "mismatch in axis dimensions for '" << histo->GetName() << "'" << ENDM; + return; + } + + outputHisto->Reset(); + outputHisto->Add(histo); + + outputRefHisto->Reset(); + outputRefHisto->Add(refHisto); + + if (scaleReference) { + // the reference histogram is scaled to match the integral of the current histogram + double integral = histo->Integral(); + double integralRef = refHisto->Integral(); + if (integral != 0 && integralRef != 0) { + outputRefHisto->Scale(integral / integralRef); + } + } +} + +//_________________________________________________________________________________________ + +class ReferenceComparatorPlotImpl +{ + public: + ReferenceComparatorPlotImpl(bool scaleRef) + : mScaleRef(scaleRef) + { + } + + virtual ~ReferenceComparatorPlotImpl() = default; + + virtual TObject* init(TH1* refHist, std::string outputPath, bool scaleRef, bool drawRatioOnly, std::string drawOption) + { + return nullptr; + } + + virtual TObject* getObject() + { + return nullptr; + } + + void setScaleRef(bool scaleRef) + { + mScaleRef = scaleRef; + } + + bool getScaleRef() { return mScaleRef; } + + virtual void update(TH1* hist, TH1* histRef) = 0; + + private: + bool mScaleRef{ true }; +}; + +template +class ReferenceComparatorPlotImpl1D : public ReferenceComparatorPlotImpl +{ + public: + ReferenceComparatorPlotImpl1D(TH1* refHist, std::string outputPath, bool scaleRef, bool drawRatioOnly, std::string drawOption) + : ReferenceComparatorPlotImpl(scaleRef) + { + if (!refHist) { + return; + } + + // full name of the main canvas + std::string canvasName = outputPath + "_RefComp"; + mCanvas = std::make_shared(canvasName.c_str(), canvasName.c_str(), 800, 600); + + // Pad where the histograms are drawn. If drawRatioOnly is true the pad is placed hidden + // behind the second pad where the ratio histogram is drawn + mCanvas->cd(); + if (drawRatioOnly) { + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0.2, 0.2, 0.8, 0.8); + } else { + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0, 0, 1, 1); + mPadHist->SetBottomMargin(1.0 / 3); + mPadHist->SetFillStyle(4000); + } + mPadHist->Draw(); + + // Pad where the histogram ratio is drawn. If drawRatioOnly is true the pad is placed on top + // without any transparency, and fully covers the other pad + mCanvas->cd(); + mPadHistRatio = std::make_shared((canvasName + "_PadHistRatio").c_str(), "PadHistRatio", 0, 0, 1, 1); + if (!drawRatioOnly) { + mPadHistRatio->SetTopMargin(2.0 / 3); + mPadHistRatio->SetFillStyle(4000); + } + mPadHistRatio->Draw(); + + // histogram from the current run + mPadHist->cd(); + mPlot = std::make_shared((canvasName + "_hist").c_str(), + refHist->GetTitle(), + refHist->GetXaxis()->GetNbins(), + refHist->GetXaxis()->GetXmin(), + refHist->GetXaxis()->GetXmax()); + // hide the X-axis + mPlot->GetXaxis()->SetTitle(""); + mPlot->GetXaxis()->SetLabelSize(0); + mPlot->GetYaxis()->SetTitle(refHist->GetYaxis()->GetTitle()); + mPlot->SetLineColor(kBlack); + mPlot->SetStats(0); + mPlot->SetOption(drawOption.c_str()); + mPlot->Draw(drawOption.c_str()); + + // histogram from the reference run + mRefPlot = std::make_shared((canvasName + "_hist_ref").c_str(), + refHist->GetTitle(), + refHist->GetXaxis()->GetNbins(), + refHist->GetXaxis()->GetXmin(), + refHist->GetXaxis()->GetXmax()); + mRefPlot->SetLineColor(kBlue); + // mRefPlot->SetLineStyle(kDashed); + // mRefPlot->SetLineWidth(2); + mRefPlot->SetOption((drawOption + "SAME").c_str()); + mRefPlot->Draw((drawOption + "SAME").c_str()); + + // histogram with current/reference ratio + mPadHistRatio->cd(); + mRatioPlot = std::make_shared((canvasName + "_hist_ratio").c_str(), + refHist->GetTitle(), + refHist->GetXaxis()->GetNbins(), + refHist->GetXaxis()->GetXmin(), + refHist->GetXaxis()->GetXmax()); + mRatioPlot->GetXaxis()->SetTitle(refHist->GetXaxis()->GetTitle()); + if (drawRatioOnly) { + mRatioPlot->GetYaxis()->SetTitle("current / reference"); + } else { + mRatioPlot->GetYaxis()->SetTitle("ratio"); + mRatioPlot->GetYaxis()->CenterTitle(kTRUE); + mRatioPlot->GetYaxis()->SetNdivisions(5); + mRatioPlot->SetTitle(""); + } + mRatioPlot->SetLineColor(kBlack); + mRatioPlot->SetStats(0); + mRatioPlot->SetOption("E"); + mRatioPlot->Draw("E"); + mRatioPlot->SetMinimum(0); + if (drawRatioOnly) { + mRatioPlot->SetMaximum(2); + } else { + // set the maximum sligtly below 2.0, such that the corresponding bin label is not shown + // and does not overlap with the zero of the histogram above + mRatioPlot->SetMaximum(1.999); + } + + // Apparently with transparent pads the histogram border is also not draw, + // so we need to add it by hand when drawing multiple pads + mCanvas->cd(); + if (!drawRatioOnly) { + mBorderTop = std::make_shared(0.1, 0.9, 0.9, 0.9); + mBorderTop->SetLineColor(kBlack); + mBorderTop->Draw(); + + mBorderRight = std::make_shared(0.9, 0.1, 0.9, 0.9); + mBorderRight->SetLineColor(kBlack); + mBorderRight->Draw(); + } + + // We place an empty TPaveText in the good place, it will be used by the checker + // to draw the quality labels and flags + mQualityLabel = std::make_shared(0.00, 0.9, 0.9, 0.98, "brNDC"); + mQualityLabel->SetBorderSize(0); + mQualityLabel->SetFillStyle(0); + mQualityLabel->SetTextAlign(12); + mQualityLabel->SetTextFont(42); + mQualityLabel->Draw(); + } + + TObject* getObject() + { + return mCanvas.get(); + } + + void update(TH1* hist, TH1* histRef) + { + if (!hist || !histRef) { + return; + } + + copyAndScaleHistograms(hist, histRef, mPlot.get(), mRefPlot.get(), getScaleRef()); + + mRatioPlot->Reset(); + mRatioPlot->Add(mPlot.get()); + mRatioPlot->Divide(mRefPlot.get()); + mRatioPlot->SetMinimum(0); + mRatioPlot->SetMaximum(1.999); + } + + private: + std::shared_ptr mCanvas; + std::shared_ptr mPadHist; + std::shared_ptr mPadHistRatio; + std::shared_ptr mPlot; + std::shared_ptr mRefPlot; + std::shared_ptr mRatioPlot; + std::shared_ptr mBorderTop; + std::shared_ptr mBorderRight; + std::shared_ptr mQualityLabel; +}; + +template +class ReferenceComparatorPlotImpl2D : public ReferenceComparatorPlotImpl +{ + public: + ReferenceComparatorPlotImpl2D(TH1* refHist, std::string outputPath, bool scaleRef, bool drawRatioOnly, std::string drawOption) + : ReferenceComparatorPlotImpl(scaleRef) + { + if (!refHist) { + return; + } + + setScaleRef(scaleRef); + + // full name of the main canvas + std::string canvasName = outputPath + "_RefComp"; + mCanvas = std::make_shared(canvasName.c_str(), canvasName.c_str(), 800, 600); + + // Size of the pad for the ratio histogram, relative to the canvas size + // Only used when drawRatioOnly is false, and both the ratio and the individual histograms are drawn + const float padSizeRatio = 2.0 / 3; + + // Pad where the current histogram is drawn. If drawRatioOnly is true the pad is draw hidden + // behind the main pad where the ratio histogram is drawn + mCanvas->cd(); + if (!drawRatioOnly) { + // the pad occupies a bottom-left portion of the canvas with a size equal to (1-padSizeRatio) + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0, 0, 0.5, 1.0 - padSizeRatio); + } else { + // hide the pad below the one with the ratio plot + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0.1, 2.0 / 3, 0.5, 0.9); + } + mPadHist->Draw(); + + // Pad where the reference histogram is drawn. If drawRatioOnly is true the pad is draw hidden + // behind the main pad where the ratio histogram is drawn + mCanvas->cd(); + if (!drawRatioOnly) { + // the pad occupies a bottom-right portion of the canvas with a size equal to (1-padSizeRatio) + mPadHistRef = std::make_shared((canvasName + "_PadHistRef").c_str(), "PadHistRef", 0.5, 0, 1, 1.0 - padSizeRatio); + } else { + // hide the pad below the one with thenratio plot + mPadHistRef = std::make_shared((canvasName + "_PadHistRef").c_str(), "PadHistRef", 0.5, 2.0 / 3, 0.9, 0.9); + } + mPadHistRef->Draw(); + + // Pad where the histogram ratio is drawn. If drawRatioOnly is true the pad occupies the full canvas + mCanvas->cd(); + if (!drawRatioOnly) { + // the pad occupies a top portion of the canvas with a size equal to padSizeRatio + mPadHistRatio = std::make_shared((canvasName + "_PadHistRatio").c_str(), "PadHistRatio", 0, 1.0 - padSizeRatio, 1, 1); + // The top margin of the pad is increased in a way inversely proportional to the pad size. + // The resulting margin corresponds to 0.1 in the outer canvas coordinates, such that the histogram + // title is drawn with the usual text size + mPadHistRatio->SetTopMargin(0.1 / padSizeRatio); + } else { + // the pad occupies the full canvas + mPadHistRatio = std::make_shared((canvasName + "_PadHistRatio").c_str(), "PadHistRatio", 0, 0, 1, 1); + } + mPadHistRatio->Draw(); + + // histogram from the current run + mPadHist->cd(); + mPlot = std::make_shared((canvasName + "_hist").c_str(), + refHist->GetTitle(), + refHist->GetXaxis()->GetNbins(), + refHist->GetXaxis()->GetXmin(), + refHist->GetXaxis()->GetXmax(), + refHist->GetYaxis()->GetNbins(), + refHist->GetYaxis()->GetXmin(), + refHist->GetYaxis()->GetXmax()); + mPlot->GetXaxis()->SetTitle(refHist->GetXaxis()->GetTitle()); + mPlot->GetYaxis()->SetTitle(refHist->GetYaxis()->GetTitle()); + mPlot->SetStats(0); + mPlot->SetOption(drawOption.c_str()); + mPlot->Draw(drawOption.c_str()); + + // histogram from the reference run + mPadHistRef->cd(); + mRefPlot = std::make_shared((canvasName + "_hist_ref").c_str(), + TString::Format("%s (reference)", refHist->GetTitle()), + refHist->GetXaxis()->GetNbins(), + refHist->GetXaxis()->GetXmin(), + refHist->GetXaxis()->GetXmax(), + refHist->GetYaxis()->GetNbins(), + refHist->GetYaxis()->GetXmin(), + refHist->GetYaxis()->GetXmax()); + mRefPlot->GetXaxis()->SetTitle(refHist->GetXaxis()->GetTitle()); + mRefPlot->GetYaxis()->SetTitle(refHist->GetYaxis()->GetTitle()); + mRefPlot->SetStats(0); + mRefPlot->SetOption(drawOption.c_str()); + mRefPlot->Draw(drawOption.c_str()); + + // histogram with current/reference ratio + mPadHistRatio->cd(); + mRatioPlot = std::make_shared((canvasName + "_hist_ratio").c_str(), + TString::Format("%s (ratio)", refHist->GetTitle()), + refHist->GetXaxis()->GetNbins(), + refHist->GetXaxis()->GetXmin(), + refHist->GetXaxis()->GetXmax(), + refHist->GetYaxis()->GetNbins(), + refHist->GetYaxis()->GetXmin(), + refHist->GetYaxis()->GetXmax()); + mRatioPlot->GetXaxis()->SetTitle(refHist->GetXaxis()->GetTitle()); + mRatioPlot->GetYaxis()->SetTitle(refHist->GetYaxis()->GetTitle()); + if (!drawRatioOnly) { + mRatioPlot->GetZaxis()->SetTitle("ratio"); + } else { + mRatioPlot->GetZaxis()->SetTitle("current / reference"); + } + mRatioPlot->SetStats(0); + mRatioPlot->SetOption("COLZ"); + mRatioPlot->Draw("COLZ"); + mRatioPlot->SetMinimum(0); + mRatioPlot->SetMaximum(2); + + // We place an empty TPaveText in the good place, it will be used by the checker + // to draw the quality labels and flags + mCanvas->cd(); + mQualityLabel = std::make_shared(0.0, 0.9, 0.9, 0.98, "brNDC"); + mQualityLabel->SetBorderSize(0); + mQualityLabel->SetFillStyle(0); + mQualityLabel->SetTextAlign(12); + mQualityLabel->SetTextFont(42); + mQualityLabel->Draw(); + } + + TObject* getObject() + { + return mCanvas.get(); + } + + void update(TH1* hist, TH1* histRef) + { + if (!hist || !histRef) { + return; + } + + copyAndScaleHistograms(hist, histRef, mPlot.get(), mRefPlot.get(), getScaleRef()); + + mRatioPlot->Reset(); + mRatioPlot->Add(mPlot.get()); + mRatioPlot->Divide(mRefPlot.get()); + mRatioPlot->SetMinimum(0); + mRatioPlot->SetMaximum(2); + } + + private: + std::shared_ptr mCanvas; + std::shared_ptr mPadHist; + std::shared_ptr mPadHistRef; + std::shared_ptr mPadHistRatio; + std::shared_ptr mPlot; + std::shared_ptr mRefPlot; + std::shared_ptr mRatioPlot; + std::shared_ptr mQualityLabel; +}; + +ReferenceComparatorPlot::ReferenceComparatorPlot(TH1* refHist, std::string outputPath, bool scaleRef, bool drawRatioOnly, std::string drawOption1D, std::string drawOption2D) +{ + if (refHist->IsA() == TClass::GetClass() || refHist->InheritsFrom("TH1F")) { + mImplementation = std::make_shared>(refHist, outputPath, scaleRef, drawRatioOnly, drawOption1D); + } + + if (refHist->IsA() == TClass::GetClass() || refHist->InheritsFrom("TH1D")) { + mImplementation = std::make_shared>(refHist, outputPath, scaleRef, drawRatioOnly, drawOption1D); + } + + if (refHist->IsA() == TClass::GetClass() || refHist->InheritsFrom("TH2F")) { + mImplementation = std::make_shared>(refHist, outputPath, scaleRef, drawRatioOnly, drawOption2D); + } + + if (refHist->IsA() == TClass::GetClass() || refHist->InheritsFrom("TH2D")) { + mImplementation = std::make_shared>(refHist, outputPath, scaleRef, drawRatioOnly, drawOption2D); + } +} + +TObject* ReferenceComparatorPlot::getObject() +{ + return (mImplementation.get() ? mImplementation->getObject() : nullptr); +} + +void ReferenceComparatorPlot::update(TH1* hist, TH1* histRef) +{ + if (mImplementation) { + mImplementation->update(hist, histRef); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ReferenceComparatorTask.cxx b/Modules/Common/src/ReferenceComparatorTask.cxx new file mode 100644 index 0000000000..fb1aaf422b --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorTask.cxx @@ -0,0 +1,243 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTask.xx +/// \author Andrea Ferrero +/// \brief Post-processing task that compares a given set of plots with reference ones +/// + +#include "Common/ReferenceComparatorTask.h" +#include "Common/ReferenceComparatorPlot.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/ActivityHelpers.h" +// ROOT +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +static bool splitObjectPath(std::string fullPath, std::string& path, std::string& name) +{ + std::string delimiter = "/"; + std::string det; + size_t pos = fullPath.rfind(delimiter); + if (pos == std::string::npos) { + return false; + } + path = fullPath.substr(0, pos); + name = fullPath.substr(pos + 1); + return true; +} + +//_________________________________________________________________________________________ +// +// Helper function for retrieving the last MonitorObject for a give run number + +static std::shared_ptr getMOFromRun(repository::DatabaseInterface* qcdb, std::string fullPath, uint32_t run, Activity activity) +{ + uint64_t timeStamp = 0; + activity.mId = run; + const auto filterMetadata = activity_helpers::asDatabaseMetadata(activity, false); + const auto objectValidity = qcdb->getLatestObjectValidity(activity.mProvenance + "/" + fullPath, filterMetadata); + if (objectValidity.isValid()) { + timeStamp = objectValidity.getMax() - 1; + } else { + ILOG(Warning, Devel) << "Could not find the object '" << fullPath << "' for run " << activity.mId << ENDM; + return nullptr; + } + + std::string path; + std::string name; + if (!splitObjectPath(fullPath, path, name)) { + return nullptr; + } + // retrieve MO from CCDB + auto mo = qcdb->retrieveMO(path, name, timeStamp, activity); + + return mo; +} + +//_________________________________________________________________________________________ +// Helper function for retrieving a MonitorObject from the QCDB, in the form of a std::pair, bool> +// A non-null MO is returned in the first element of the pair if the MO is found in the QCDB +// The second element of the pair is set to true if the MO has a time stamp more recent than a user-supplied threshold + +static std::pair, bool> getMO(repository::DatabaseInterface& qcdb, std::string fullPath, Trigger t, long notOlderThan) +{ + // find the time-stamp of the most recent object matching the current activity + // if ignoreActivity is true the activity matching criteria are not applied + auto timestamp = t.timestamp; + const auto filterMetadata = activity_helpers::asDatabaseMetadata(t.activity, false); + const auto objectValidity = qcdb.getLatestObjectValidity(t.activity.mProvenance + "/" + fullPath, filterMetadata); + if (objectValidity.isValid()) { + timestamp = objectValidity.getMax() - 1; + } else { + ILOG(Warning, Devel) << "Could not find the object '" << fullPath << "' for activity " << t.activity << ENDM; + return { nullptr, false }; + } + + std::string path; + std::string name; + if (!splitObjectPath(fullPath, path, name)) { + return { nullptr, false }; + } + // retrieve QO from CCDB - do not associate to trigger activity if ignoreActivity is true + auto qo = qcdb.retrieveMO(path, name, timestamp, t.activity); + if (!qo) { + return { nullptr, false }; + } + + long elapsed = static_cast(t.timestamp) - timestamp; + // check if the object is not older than a given number of milliseconds + if (elapsed > notOlderThan) { + ILOG(Warning, Devel) << "Object '" << fullPath << "' for activity " << t.activity << " is too old: " << elapsed << " > " << notOlderThan << ENDM; + return { qo, false }; + } + + return { qo, true }; +} + +//_________________________________________________________________________________________ +// +// Get the reference plot for a given MonitorObject path + +std::shared_ptr ReferenceComparatorTask::getRefPlot(repository::DatabaseInterface& qcdb, std::string fullPath, Activity activity) +{ + return getMOFromRun(&qcdb, fullPath, mRefRun, activity); +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::configure(const boost::property_tree::ptree& config) +{ + mConfig = ReferenceComparatorTaskConfig(getID(), config); +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::initialize(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef services) +{ + // reset all existing objects + mPlotNames.clear(); + mRefPlots.clear(); + mHistograms.clear(); + + auto& qcdb = services.get(); + mNotOlderThan = std::stoi(mCustomParameters.atOptional("notOlderThan").value_or("120")); + mRefRun = std::stoi(mCustomParameters.atOptional("referenceRun").value_or("0")); + + // load and initialize the input groups + for (auto group : mConfig.dataGroups) { + auto groupName = group.name; + auto& plotVec = mPlotNames[groupName]; + for (auto path : group.objects) { + auto fullPath = group.inputPath + "/" + path; + auto fullOutPath = group.outputPath + "/" + path; + + // retrieve the reference MO + auto refPlot = getRefPlot(qcdb, fullPath, t.activity); + if (!refPlot) { + continue; + } + + // extract the reference histogram + TH1* refHist = dynamic_cast(refPlot->getObject()); + if (!refHist) { + continue; + } + + // store the reference MO + mRefPlots[fullPath] = refPlot; + + // fill an array with the full paths of the plots associated to this group + plotVec.push_back(fullPath); + + // create and store the plotter object + mHistograms[fullPath] = std::make_shared(refHist, fullOutPath, + group.normalizeReference, + group.drawRatioOnly, + group.drawOption1D, + group.drawOption2D); + auto* outObject = mHistograms[fullPath]->getObject(); + // publish the object created by the plotter + if (outObject) { + getObjectsManager()->startPublishing(outObject); + } + } + } +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::updatePlot(std::string plotName, TObject* object) +{ + // make sure that the objects inherits from TH1 + TH1* hist = dynamic_cast(object); + if (!hist) { + return; + } + + // check if a corresponding output plot was initialized + auto iter = mHistograms.find(plotName); + if (iter == mHistograms.end()) { + return; + } + + // update the plot ratios and the histigrams with superimposed reference + auto moRef = mRefPlots[plotName]; + TH1* histRef = dynamic_cast(moRef->getObject()); + + iter->second->update(hist, histRef); +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::update(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + for (auto& [groupName, plotVec] : mPlotNames) { + for (auto& plotName : plotVec) { + // get object for current timestamp - age limit is converted to milliseconds + auto object = getMO(qcdb, plotName, t, mNotOlderThan * 1000); + + // skip objects that are not found or too old + if (!object.second) { + continue; + } + + // if the object is valid, draw it together with the reference + if (object.first && object.first->getObject() && object.first->getObject()->InheritsFrom("TH1")) { + updatePlot(plotName, object.first->getObject()); + } + } + } +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::finalize(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ReferenceComparatorTaskConfig.cxx b/Modules/Common/src/ReferenceComparatorTaskConfig.cxx new file mode 100644 index 0000000000..f7f848e3c3 --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorTaskConfig.cxx @@ -0,0 +1,47 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTaskConfig.cxx +/// \author Andrea Ferrero +/// + +#include "Common/ReferenceComparatorTaskConfig.h" +#include +#include + +namespace o2::quality_control_modules::common +{ + +ReferenceComparatorTaskConfig::ReferenceComparatorTaskConfig(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + for (const auto& dataGroupConfig : config.get_child("qc.postprocessing." + name + ".dataGroups")) { + DataGroup dataGroup{ + dataGroupConfig.second.get("name"), + dataGroupConfig.second.get("inputPath"), + dataGroupConfig.second.get("outputPath"), + dataGroupConfig.second.get("normalizeReference", false), + dataGroupConfig.second.get("drawRatioOnly", false), + dataGroupConfig.second.get("drawOption1D", "HIST"), + dataGroupConfig.second.get("drawOption2D", "COLZ") + }; + if (const auto& inputObjects = dataGroupConfig.second.get_child_optional("inputObjects"); inputObjects.has_value()) { + for (const auto& inputObject : inputObjects.value()) { + dataGroup.objects.emplace_back(inputObject.second.data()); + } + } + + dataGroups.emplace_back(std::move(dataGroup)); + } +} + +} // namespace o2::quality_control_modules::common