From e76663c484ae09189f80b3057b185ff1c36568f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Fri, 17 Jan 2025 19:20:20 +0100 Subject: [PATCH] added merging of TCanvas to mergers --- Utilities/Mergers/CMakeLists.txt | 2 +- Utilities/Mergers/src/MergerAlgorithm.cxx | 79 ++++++++++++++- Utilities/Mergers/test/test_Algorithm.cxx | 114 +++++++++++++++++++++- 3 files changed, 191 insertions(+), 4 deletions(-) diff --git a/Utilities/Mergers/CMakeLists.txt b/Utilities/Mergers/CMakeLists.txt index 43a22dd395996..0e77e62cccc07 100644 --- a/Utilities/Mergers/CMakeLists.txt +++ b/Utilities/Mergers/CMakeLists.txt @@ -15,7 +15,7 @@ o2_add_library(Mergers SOURCES src/FullHistoryMerger.cxx src/IntegratingMerger.cxx src/Mergeable.cxx src/MergerAlgorithm.cxx src/MergerBuilder.cxx src/MergerInfrastructureBuilder.cxx src/ObjectStore.cxx - PUBLIC_LINK_LIBRARIES O2::Framework AliceO2::InfoLogger) + PUBLIC_LINK_LIBRARIES O2::Framework AliceO2::InfoLogger ROOT::Gpad) o2_target_root_dictionary( Mergers diff --git a/Utilities/Mergers/src/MergerAlgorithm.cxx b/Utilities/Mergers/src/MergerAlgorithm.cxx index a873f550d8450..a3be493d8e34e 100644 --- a/Utilities/Mergers/src/MergerAlgorithm.cxx +++ b/Utilities/Mergers/src/MergerAlgorithm.cxx @@ -16,9 +16,9 @@ #include "Mergers/MergerAlgorithm.h" -#include "Framework/Logger.h" #include "Mergers/MergeInterface.h" #include "Mergers/ObjectStore.h" +#include "Framework/Logger.h" #include #include @@ -28,7 +28,12 @@ #include #include #include +#include #include +#include +#include +#include +#include namespace o2::mergers::algorithm { @@ -43,6 +48,53 @@ size_t estimateTreeSize(TTree* tree) return totalSize; } +// Mergeable objects are kept as primitives in TCanvas object in underlying TPad. +// TPad is a linked list of primitives of any type (https://root.cern.ch/doc/master/classTPad.html) +// including other TPads. So in order to collect all mergeable objects from TCanvas +// we need to recursively transverse whole TPad structure. +auto collectUnderlyingObjects(TCanvas* canvas) -> std::vector +{ + auto collectFromTPad = [](TPad* pad, std::vector& objects, const auto& collectFromTPad) { + if (!pad) { + return; + } + auto* primitives = pad->GetListOfPrimitives(); + for (int i = 0; i < primitives->GetSize(); ++i) { + auto* primitive = primitives->At(i); + if (auto* primitivePad = dynamic_cast(primitive)) { + collectFromTPad(primitivePad, objects, collectFromTPad); + } else { + objects.push_back(primitive); + } + } + }; + + std::vector collectedObjects; + collectFromTPad(canvas, collectedObjects, collectFromTPad); + + return collectedObjects; +} + +struct MatchedCollectedObjects { + MatchedCollectedObjects(TObject* t, TObject* o) : target(t), other(o) {} + + TObject* target; + TObject* other; +}; + +auto matchCollectedToPairs(const std::vector& targetObjects, const std::vector otherObjects) -> std::vector +{ + std::vector matchedObjects; + matchedObjects.reserve(std::max(targetObjects.size(), otherObjects.size())); + for (const auto& targetObject : targetObjects) { + if (const auto found_it = std::ranges::find_if(otherObjects, [&targetObject](TObject* obj) { return std::string_view(targetObject->GetName()) == std::string_view(obj->GetName()); }); + found_it != otherObjects.end()) { + matchedObjects.emplace_back(targetObject, *found_it); + } + } + return matchedObjects; +} + void merge(TObject* const target, TObject* const other) { if (target == nullptr) { @@ -82,6 +134,29 @@ void merge(TObject* const target, TObject* const other) } } delete otherIterator; + } else if (auto targetCanvas = dynamic_cast(target)) { + + auto otherCanvas = dynamic_cast(other); + if (otherCanvas == nullptr) { + throw std::runtime_error(std::string("The target object '") + target->GetName() + + "' is a TCanvas, while the other object '" + other->GetName() + "' is not."); + } + + const auto targetObjects = collectUnderlyingObjects(targetCanvas); + const auto otherObjects = collectUnderlyingObjects(otherCanvas); + if (targetObjects.size() != otherObjects.size()) { + throw std::runtime_error(std::string("Trying to merge canvas: ") + targetCanvas->GetName() + " and canvas " + otherObjects.size() + "but contents are not the same"); + } + + const auto matched = matchCollectedToPairs(targetObjects, otherObjects); + if (targetObjects.size() != matched.size()) { + throw std::runtime_error(std::string("Trying to merge canvas: ") + targetCanvas->GetName() + " and canvas " + otherObjects.size() + "but contents are not the same"); + } + + for (const auto& [targetObject, otherObject] : matched) { + merge(targetObject, otherObject); + } + } else { Long64_t errorCode = 0; TObjArray otherCollection; @@ -169,4 +244,4 @@ void deleteTCollections(TObject* obj) } } -} // namespace o2::mergers::algorithm \ No newline at end of file +} // namespace o2::mergers::algorithm diff --git a/Utilities/Mergers/test/test_Algorithm.cxx b/Utilities/Mergers/test/test_Algorithm.cxx index 237d017e6b30b..f087254e00d05 100644 --- a/Utilities/Mergers/test/test_Algorithm.cxx +++ b/Utilities/Mergers/test/test_Algorithm.cxx @@ -14,9 +14,9 @@ /// /// \author Piotr Konopka, piotr.jan.konopka@cern.ch -#include #include #include +#include #define BOOST_TEST_MODULE Test Utilities MergerAlgorithm #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK @@ -39,6 +39,7 @@ #include #include #include +#include // using namespace o2::framework; using namespace o2::mergers; @@ -305,6 +306,117 @@ BOOST_AUTO_TEST_CASE(MergerCollection) delete target; } +TCanvas* createCanvas(std::string name, std::string title, std::vector>& histograms) +{ + auto canvas = new TCanvas(name.c_str(), title.c_str(), 100, 100); + canvas->Divide(histograms.size(), 1); + for (size_t i = 1; const auto& hist : histograms) { + canvas->cd(i); + hist->Draw(); + ++i; + } + return canvas; +} + +auto collectUnderlyingObjects(TCanvas* canvas) -> std::vector +{ + auto collectFromTPad = [](TPad* pad, std::vector& objects, const auto& collectFromTPad) { + if (!pad) { + return; + } + auto* primitives = pad->GetListOfPrimitives(); + for (int i = 0; i < primitives->GetSize(); ++i) { + auto* primitive = primitives->At(i); + if (auto* primitivePad = dynamic_cast(primitive)) { + collectFromTPad(primitivePad, objects, collectFromTPad); + } else { + objects.push_back(primitive); + } + } + }; + + std::vector collectedObjects; + collectFromTPad(canvas, collectedObjects, collectFromTPad); + + return collectedObjects; +} + +BOOST_AUTO_TEST_CASE(MergerTCanvas) +{ + // working example + { + std::vector> histsC1{ + std::make_shared("th1", "obj1", bins, min, max), + std::make_shared("th2", "obj2", bins, min, max), + }; + histsC1[0]->Fill(5); + histsC1[1]->Fill(2); + BOOST_CHECK_EQUAL(histsC1[0]->GetBinContent(histsC1[0]->FindBin(5)), 1); + BOOST_CHECK_EQUAL(histsC1[1]->GetBinContent(histsC1[1]->FindBin(2)), 1); + + std::vector> histsC2{ + std::make_shared("th1", "obj1", bins, min, max), + std::make_shared("th2", "obj2", bins, min, max), + }; + + histsC2[0]->Fill(5); + histsC2[1]->Fill(2); + BOOST_CHECK_EQUAL(histsC2[0]->GetBinContent(histsC2[0]->FindBin(5)), 1); + BOOST_CHECK_EQUAL(histsC2[1]->GetBinContent(histsC2[1]->FindBin(2)), 1); + + auto targetCanvas = createCanvas("c1", "test title 1", histsC1); + auto otherCanvas = createCanvas("c2", "test title 2", histsC2); + + algorithm::merge(targetCanvas, otherCanvas); + + auto targetObjects = collectUnderlyingObjects(targetCanvas); + + BOOST_CHECK_EQUAL(targetObjects.size(), 2); + for (const auto& object : targetObjects) { + auto th = static_cast(object); + if (std::string(th->GetName()) == "th1") { + BOOST_CHECK_EQUAL(th->GetBinContent(th->FindBin(5)), 2); + } + if (std::string(th->GetName()) == "th2") { + BOOST_CHECK_EQUAL(th->GetBinContent(th->FindBin(2)), 2); + } + } + } + + // throw because we try to merge canvases with different number of underlying items + { + std::vector> histsC1{ + std::make_shared("th1", "obj1", bins, min, max), + std::make_shared("th2", "obj2", bins, min, max), + }; + + std::vector> histsC2{ + std::make_shared("th1", "obj1", bins, min, max), + }; + + auto targetCanvas = createCanvas("c1", "test title 1", histsC1); + auto otherCanvas = createCanvas("c2", "test title 2", histsC2); + + BOOST_CHECK_THROW(algorithm::merge(targetCanvas, otherCanvas), std::runtime_error); + } + + // throw because we try to merge canvases with different underlying items + { + std::vector> histsC1{ + std::make_shared("th1", "obj1", bins, min, max), + }; + + std::vector> histsC2{ + std::make_shared("th2", "obj2", bins, min, max), + }; + + auto targetCanvas = createCanvas("c1", "test title 1", histsC1); + auto otherCanvas = createCanvas("c2", "test title 2", histsC2); + + BOOST_CHECK_THROW(algorithm::merge(targetCanvas, otherCanvas), std::runtime_error); + } +} + BOOST_AUTO_TEST_CASE(Deleting) { TObjArray* main = new TObjArray();