Skip to content

Commit fb101b8

Browse files
author
Michal Tichák
committed
added merging of TCanvas to mergers
1 parent 64dd90c commit fb101b8

File tree

3 files changed

+192
-3
lines changed

3 files changed

+192
-3
lines changed

Utilities/Mergers/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ o2_add_library(Mergers
1515
SOURCES src/FullHistoryMerger.cxx src/IntegratingMerger.cxx src/Mergeable.cxx
1616
src/MergerAlgorithm.cxx src/MergerBuilder.cxx src/MergerInfrastructureBuilder.cxx
1717
src/ObjectStore.cxx
18-
PUBLIC_LINK_LIBRARIES O2::Framework AliceO2::InfoLogger)
18+
PUBLIC_LINK_LIBRARIES O2::Framework AliceO2::InfoLogger ROOT::Gpad)
1919

2020
o2_target_root_dictionary(
2121
Mergers

Utilities/Mergers/src/MergerAlgorithm.cxx

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
#include "Mergers/MergerAlgorithm.h"
1818

19-
#include "Framework/Logger.h"
2019
#include "Mergers/MergeInterface.h"
2120
#include "Mergers/ObjectStore.h"
2221

@@ -29,6 +28,10 @@
2928
#include <THnSparse.h>
3029
#include <TObjArray.h>
3130
#include <TTree.h>
31+
#include <TPad.h>
32+
#include <TCanvas.h>
33+
#include <algorithm>
34+
#include <stdexcept>
3235

3336
namespace o2::mergers::algorithm
3437
{
@@ -43,6 +46,51 @@ size_t estimateTreeSize(TTree* tree)
4346
return totalSize;
4447
}
4548

49+
// Mergeable objects are kept as primitives in TCanvas object in underlying TPad.
50+
// TPad is a linked list of primitives of any type (https://root.cern.ch/doc/master/classTPad.html)
51+
// including other TPads. So in order to collect all mergeable objects from TCanvas
52+
// we need to recursively transverse whole TPad structure.
53+
auto collectUnderlyingObjects(TCanvas* canvas) -> std::vector<TObject*>
54+
{
55+
auto collectFromTPad = [](TPad* pad, std::vector<TObject*>& objects, auto&& collectFromTPad) {
56+
if (!pad) {
57+
return;
58+
}
59+
auto* primitives = pad->GetListOfPrimitives();
60+
for (int i = 0; i < primitives->GetSize(); ++i) {
61+
auto* primitive = primitives->At(i);
62+
if (auto* primitivePad = dynamic_cast<TPad*>(primitive)) {
63+
collectFromTPad(primitivePad, objects, collectFromTPad);
64+
} else {
65+
objects.push_back(primitive);
66+
}
67+
}
68+
};
69+
70+
std::vector<TObject*> collectedObjects;
71+
collectFromTPad(canvas, collectedObjects, collectFromTPad);
72+
73+
return collectedObjects;
74+
}
75+
76+
struct MatchedCollectedObjects {
77+
TObject* target;
78+
TObject* other;
79+
};
80+
81+
auto matchCollectedToPairs(const std::vector<TObject*>& targetObjects, const std::vector<TObject*> otherObjects) -> std::vector<MatchedCollectedObjects>
82+
{
83+
std::vector<MatchedCollectedObjects> matchedObjects;
84+
matchedObjects.reserve(std::max(targetObjects.size(), otherObjects.size()));
85+
for (const auto& targetObject : targetObjects) {
86+
if (const auto found_it = std::ranges::find_if(otherObjects, [&targetObject](TObject* obj) { return std::string_view(targetObject->GetName()) == std::string_view(obj->GetName()); });
87+
found_it != otherObjects.end()) {
88+
matchedObjects.emplace_back(targetObject, *found_it);
89+
}
90+
}
91+
return matchedObjects;
92+
}
93+
4694
void merge(TObject* const target, TObject* const other)
4795
{
4896
if (target == nullptr) {
@@ -82,6 +130,29 @@ void merge(TObject* const target, TObject* const other)
82130
}
83131
}
84132
delete otherIterator;
133+
} else if (auto targetCanvas = dynamic_cast<TCanvas*>(target)) {
134+
135+
auto otherCanvas = dynamic_cast<TCanvas*>(other);
136+
if (otherCanvas == nullptr) {
137+
throw std::runtime_error(std::string("The target object '") + target->GetName() +
138+
"' is a TCanvas, while the other object '" + other->GetName() + "' is not.");
139+
}
140+
141+
const auto targetObjects = collectUnderlyingObjects(targetCanvas);
142+
const auto otherObjects = collectUnderlyingObjects(otherCanvas);
143+
if (targetObjects.size() != otherObjects.size()) {
144+
throw std::runtime_error(std::string("Trying to merge canvas: ") + targetCanvas->GetName() + " and canvas " + otherObjects.size() + "but contents are not the same");
145+
}
146+
147+
const auto matched = matchCollectedToPairs(targetObjects, otherObjects);
148+
if (targetObjects.size() != matched.size()) {
149+
throw std::runtime_error(std::string("Trying to merge canvas: ") + targetCanvas->GetName() + " and canvas " + otherObjects.size() + "but contents are not the same");
150+
}
151+
152+
for (const auto& [targetObject, otherObject] : matched) {
153+
merge(targetObject, otherObject);
154+
}
155+
85156
} else {
86157
Long64_t errorCode = 0;
87158
TObjArray otherCollection;
@@ -169,4 +240,4 @@ void deleteTCollections(TObject* obj)
169240
}
170241
}
171242

172-
} // namespace o2::mergers::algorithm
243+
} // namespace o2::mergers::algorithm

Utilities/Mergers/test/test_Algorithm.cxx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
///
1515
/// \author Piotr Konopka, piotr.jan.konopka@cern.ch
1616

17+
#include <boost/mpl/pair.hpp>
1718
#include <boost/test/tools/interface.hpp>
19+
#include <boost/test/tools/old/interface.hpp>
1820
#include <gsl/span>
1921
#include <memory>
22+
#include <stdexcept>
2023
#define BOOST_TEST_MODULE Test Utilities MergerAlgorithm
2124
#define BOOST_TEST_MAIN
2225
#define BOOST_TEST_DYN_LINK
@@ -39,6 +42,7 @@
3942
#include <TF1.h>
4043
#include <TGraph.h>
4144
#include <TProfile.h>
45+
#include <TCanvas.h>
4246

4347
// using namespace o2::framework;
4448
using namespace o2::mergers;
@@ -305,6 +309,120 @@ BOOST_AUTO_TEST_CASE(MergerCollection)
305309
delete target;
306310
}
307311

312+
TCanvas* createCanvas(std::string name, std::string title, std::vector<std::shared_ptr<TH1I>>& histograms)
313+
{
314+
auto canvas = new TCanvas(name.c_str(), title.c_str(), 100, 100);
315+
canvas->Divide(histograms.size(), 1);
316+
for (size_t i = 1; const auto& hist : histograms) {
317+
canvas->cd(i);
318+
hist->Draw();
319+
++i;
320+
}
321+
return canvas;
322+
}
323+
324+
auto collectUnderlyingObjects(TCanvas* canvas) -> std::vector<TObject*>
325+
{
326+
if (!canvas) {
327+
return {};
328+
}
329+
330+
auto collectFromTPad = []<class TPadOrTCanvas>
331+
requires std::same_as<TPadOrTCanvas, TPad> || std::same_as<TPadOrTCanvas, TCanvas>
332+
(TPadOrTCanvas * pad, std::vector<TObject*> & objects, auto&& collectFromTPad) -> void {
333+
auto* primitives = pad->GetListOfPrimitives();
334+
for (int i = 0; i < primitives->GetSize(); ++i) {
335+
auto* primitive = primitives->At(i);
336+
if (auto* primitivePad = dynamic_cast<TPad*>(primitive)) {
337+
collectFromTPad(primitivePad, objects, collectFromTPad);
338+
} else {
339+
objects.push_back(primitive);
340+
}
341+
}
342+
};
343+
344+
std::vector<TObject*> collectedObjects;
345+
collectFromTPad(canvas, collectedObjects, collectFromTPad);
346+
347+
return collectedObjects;
348+
}
349+
350+
BOOST_AUTO_TEST_CASE(MergerTCanvas)
351+
{
352+
// working example
353+
{
354+
std::vector<std::shared_ptr<TH1I>> histsC1{
355+
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
356+
std::make_shared<TH1I>("th2", "obj2", bins, min, max),
357+
};
358+
histsC1[0]->Fill(5);
359+
histsC1[1]->Fill(2);
360+
BOOST_CHECK_EQUAL(histsC1[0]->GetBinContent(histsC1[0]->FindBin(5)), 1);
361+
BOOST_CHECK_EQUAL(histsC1[1]->GetBinContent(histsC1[1]->FindBin(2)), 1);
362+
363+
std::vector<std::shared_ptr<TH1I>> histsC2{
364+
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
365+
std::make_shared<TH1I>("th2", "obj2", bins, min, max),
366+
};
367+
368+
histsC2[0]->Fill(5);
369+
histsC2[1]->Fill(2);
370+
BOOST_CHECK_EQUAL(histsC2[0]->GetBinContent(histsC2[0]->FindBin(5)), 1);
371+
BOOST_CHECK_EQUAL(histsC2[1]->GetBinContent(histsC2[1]->FindBin(2)), 1);
372+
373+
auto targetCanvas = createCanvas("c1", "test title 1", histsC1);
374+
auto otherCanvas = createCanvas("c2", "test title 2", histsC2);
375+
376+
algorithm::merge(targetCanvas, otherCanvas);
377+
378+
auto targetObjects = collectUnderlyingObjects(targetCanvas);
379+
380+
BOOST_CHECK_EQUAL(targetObjects.size(), 2);
381+
for (const auto& object : targetObjects) {
382+
auto th = static_cast<TH1*>(object);
383+
if (std::string(th->GetName()) == "th1") {
384+
BOOST_CHECK_EQUAL(th->GetBinContent(th->FindBin(5)), 2);
385+
}
386+
if (std::string(th->GetName()) == "th2") {
387+
BOOST_CHECK_EQUAL(th->GetBinContent(th->FindBin(2)), 2);
388+
}
389+
}
390+
}
391+
392+
// throw because we try to merge canvases with different number of underlying items
393+
{
394+
std::vector<std::shared_ptr<TH1I>> histsC1{
395+
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
396+
std::make_shared<TH1I>("th2", "obj2", bins, min, max),
397+
};
398+
399+
std::vector<std::shared_ptr<TH1I>> histsC2{
400+
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
401+
};
402+
403+
auto targetCanvas = createCanvas("c1", "test title 1", histsC1);
404+
auto otherCanvas = createCanvas("c2", "test title 2", histsC2);
405+
406+
BOOST_CHECK_THROW(algorithm::merge(targetCanvas, otherCanvas), std::runtime_error);
407+
}
408+
409+
// throw because we try to merge canvases with different underlying items
410+
{
411+
std::vector<std::shared_ptr<TH1I>> histsC1{
412+
std::make_shared<TH1I>("th1", "obj1", bins, min, max),
413+
};
414+
415+
std::vector<std::shared_ptr<TH1I>> histsC2{
416+
std::make_shared<TH1I>("th2", "obj2", bins, min, max),
417+
};
418+
419+
auto targetCanvas = createCanvas("c1", "test title 1", histsC1);
420+
auto otherCanvas = createCanvas("c2", "test title 2", histsC2);
421+
422+
BOOST_CHECK_THROW(algorithm::merge(targetCanvas, otherCanvas), std::runtime_error);
423+
}
424+
}
425+
308426
BOOST_AUTO_TEST_CASE(Deleting)
309427
{
310428
TObjArray* main = new TObjArray();

0 commit comments

Comments
 (0)