Skip to content

Commit

Permalink
added merging of TCanvas to mergers
Browse files Browse the repository at this point in the history
  • Loading branch information
Michal Tichák committed Jan 23, 2025
1 parent c4f4a41 commit e76663c
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Utilities/Mergers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
79 changes: 77 additions & 2 deletions Utilities/Mergers/src/MergerAlgorithm.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <TEfficiency.h>
#include <TGraph.h>
Expand All @@ -28,7 +28,12 @@
#include <THn.h>
#include <THnSparse.h>
#include <TObjArray.h>
#include <TObject.h>
#include <TTree.h>
#include <TPad.h>
#include <TCanvas.h>
#include <algorithm>
#include <stdexcept>

namespace o2::mergers::algorithm
{
Expand All @@ -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<TObject*>
{
auto collectFromTPad = [](TPad* pad, std::vector<TObject*>& 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<TPad*>(primitive)) {
collectFromTPad(primitivePad, objects, collectFromTPad);
} else {
objects.push_back(primitive);
}
}
};

std::vector<TObject*> 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<TObject*>& targetObjects, const std::vector<TObject*> otherObjects) -> std::vector<MatchedCollectedObjects>
{
std::vector<MatchedCollectedObjects> 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) {
Expand Down Expand Up @@ -82,6 +134,29 @@ void merge(TObject* const target, TObject* const other)
}
}
delete otherIterator;
} else if (auto targetCanvas = dynamic_cast<TCanvas*>(target)) {

auto otherCanvas = dynamic_cast<TCanvas*>(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;
Expand Down Expand Up @@ -169,4 +244,4 @@ void deleteTCollections(TObject* obj)
}
}

} // namespace o2::mergers::algorithm
} // namespace o2::mergers::algorithm
114 changes: 113 additions & 1 deletion Utilities/Mergers/test/test_Algorithm.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
///
/// \author Piotr Konopka, [email protected]

#include <boost/test/tools/interface.hpp>
#include <gsl/span>
#include <memory>
#include <stdexcept>
#define BOOST_TEST_MODULE Test Utilities MergerAlgorithm
#define BOOST_TEST_MAIN
#define BOOST_TEST_DYN_LINK
Expand All @@ -39,6 +39,7 @@
#include <TF1.h>
#include <TGraph.h>
#include <TProfile.h>
#include <TCanvas.h>

// using namespace o2::framework;
using namespace o2::mergers;
Expand Down Expand Up @@ -305,6 +306,117 @@ BOOST_AUTO_TEST_CASE(MergerCollection)
delete target;
}

TCanvas* createCanvas(std::string name, std::string title, std::vector<std::shared_ptr<TH1I>>& 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<TObject*>
{
auto collectFromTPad = [](TPad* pad, std::vector<TObject*>& 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<TPad*>(primitive)) {
collectFromTPad(primitivePad, objects, collectFromTPad);
} else {
objects.push_back(primitive);
}
}
};

std::vector<TObject*> collectedObjects;
collectFromTPad(canvas, collectedObjects, collectFromTPad);

return collectedObjects;
}

BOOST_AUTO_TEST_CASE(MergerTCanvas)
{
// working example
{
std::vector<std::shared_ptr<TH1I>> histsC1{
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
std::make_shared<TH1I>("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<std::shared_ptr<TH1I>> histsC2{
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
std::make_shared<TH1I>("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<TH1*>(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<std::shared_ptr<TH1I>> histsC1{
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
std::make_shared<TH1I>("th2", "obj2", bins, min, max),
};

std::vector<std::shared_ptr<TH1I>> histsC2{
std::make_shared<TH1I>("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<std::shared_ptr<TH1I>> histsC1{
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
};

std::vector<std::shared_ptr<TH1I>> histsC2{
std::make_shared<TH1I>("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();
Expand Down

0 comments on commit e76663c

Please sign in to comment.