Project
Loading...
Searching...
No Matches
TPCDigitRootWriterSpec.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
16
24#include "TPCBase/Sector.h"
30#include <TFile.h>
31#include <TTree.h>
32#include <TBranch.h>
33#include <memory> // for make_shared, make_unique, unique_ptr
34#include <stdexcept>
35#include <string>
36#include <vector>
37#include <utility>
38
39using namespace o2::framework;
40using namespace o2::header;
43
44namespace o2
45{
46template <typename T>
48
49namespace tpc
50{
51
55DataProcessorSpec getTPCDigitRootWriterSpec(std::vector<int> const& laneConfiguration, bool mctruth)
56{
57 // the callback to be set as hook for custom action when the writer is closed
58 auto finishWriting = [](TFile* outputfile, TTree* outputtree) {
59 // check/verify number of entries (it should be same in all branches)
60
61 // will return a TObjArray
62 const auto brlist = outputtree->GetListOfBranches();
63 int entries = -1; // init to -1 (as unitialized)
64 for (TObject* entry : *brlist) {
65 auto br = static_cast<TBranch*>(entry);
66 int brentries = br->GetEntries();
67 entries = std::max(entries, brentries);
68 if (brentries != entries && !TString(br->GetName()).Contains("CommonMode")) {
69 LOG(warning) << "INCONSISTENT NUMBER OF ENTRIES IN BRANCH " << br->GetName() << ": " << entries << " vs " << brentries;
70 }
71 }
72 if (entries <= 0) {
73 // A timeframe holds no collision at all whenever the interaction rate is low enough, and
74 // then no branch is filled. Write one empty entry in every branch instead of nothing, so
75 // that the file is an ordinary timeframe that happens to contain no digit and every reader
76 // downstream stays on its normal path. Each branch is bound to a default constructed object
77 // of its own type by RootTreeWriter, so Fill() writes exactly that.
78 LOG(info) << "No branch was filled, writing one empty entry per branch";
79 for (TObject* entry : *brlist) {
80 static_cast<TBranch*>(entry)->Fill();
81 }
82 entries = 1;
83 }
84 LOG(info) << "Setting entries to " << entries;
85 outputtree->SetEntries(entries);
86 // write the tree explicitly, the way RootTreeWriter's own close does. Closing the file alone
87 // leaves an empty tree without a key, so the file comes out with no tree in it at all.
88 // kOverwrite matters: without it a second cycle of the tree is written next to the first.
89 outputfile->Write("", TObject::kOverwrite);
90 outputfile->Close();
91 };
92
93 // branch definitions for RootTreeWriter spec
94 using DigitsOutputType = std::vector<o2::tpc::Digit>;
95 using CommonModeOutputType = std::vector<o2::tpc::CommonMode>;
96
97 // extracts the sector from header of an input
98 auto extractSector = [](auto const& ref) {
99 auto sectorHeader = DataRefUtils::getHeader<o2::tpc::TPCSectorHeader*>(ref);
100 if (!sectorHeader) {
101 throw std::runtime_error("Missing sector header in TPC data");
102 }
103 // the TPCSectorHeader now allows to transport information for more than one sector,
104 // e.g. for transporting clusters in one single data block. The digitization is however
105 // only on sector level
106 if (sectorHeader->sector() >= TPCSectorHeader::NSectors) {
107 throw std::runtime_error("Digitizer can only work on single sectors");
108 }
109 return sectorHeader->sector();
110 };
111
112 // The generic writer needs a way to associate incoming data with the individual branches for
113 // the TPC sectors. The sector number is transmitted as part of the sector header, the callback
114 // finds the corresponding index in the vector of configured sectors
115 auto getIndex = [laneConfiguration, extractSector](o2::framework::DataRef const& ref) -> size_t {
116 auto sector = extractSector(ref);
117 if (sector < 0) {
118 // special data sets, don't write
119 return ~(size_t)0;
120 }
121 size_t index = 0;
122 for (auto const& s : laneConfiguration) {
123 if (sector == s) {
124 return index;
125 }
126 ++index;
127 }
128 throw std::runtime_error("sector " + std::to_string(sector) + " not configured for writing");
129 };
130
131 // callback to create branch name
132 auto getName = [laneConfiguration](std::string base, size_t index) -> std::string {
133 return base + "_" + std::to_string(laneConfiguration.at(index));
134 };
135
136 // container for cached grouping of digits
137 auto trigP2Sect = std::make_shared<std::array<std::vector<DigiGroupRef>, 36>>();
138
139 // preprocessor callback
140 // read the trigger data first and store in the trigP2Sect shared pointer
141 auto preprocessor = [extractSector, trigP2Sect](ProcessingContext& pc) {
142 for (auto& cont : *trigP2Sect) {
143 cont.clear();
144 }
145 std::vector<InputSpec> filter = {
146 {"check", ConcreteDataTypeMatcher{"TPC", "DIGTRIGGERS"}, Lifetime::Timeframe},
147 };
148 for (auto const& ref : InputRecordWalker(pc.inputs(), filter)) {
149 auto sector = extractSector(ref);
150 auto const* dh = DataRefUtils::getHeader<DataHeader*>(ref);
151 LOG(info) << "HAVE TRIGGER DATA FOR SECTOR " << sector << " ON CHANNEL " << dh->subSpecification;
152 if (sector >= 0) {
153 // extract the trigger information and make it available for the other handlers
154 auto triggers = pc.inputs().get<std::vector<DigiGroupRef>>(ref);
155 (*trigP2Sect)[sector].assign(triggers.begin(), triggers.end());
156 const auto& trigS = (*trigP2Sect)[sector];
157 LOG(info) << "GOT Triggers of sector " << sector << " | SIZE " << trigS.size();
158 }
159 }
160 };
161
162 // handler to fill the digit branch, this handles filling based on the trigger information, each trigger
163 // will be a new entry
164 auto fillDigits = [extractSector, trigP2Sect](TBranch& branch, DigitsOutputType const& digiData, DataRef const& ref) {
165 auto sector = extractSector(ref);
166 auto const* dh = DataRefUtils::getHeader<DataHeader*>(ref);
167 LOG(info) << "HAVE DIGIT DATA FOR SECTOR " << sector << " ON CHANNEL " << dh->subSpecification;
168 if (sector >= 0) {
169 LOG(info) << "DIGIT SIZE " << digiData.size();
170 const auto& trigS = (*trigP2Sect.get())[sector];
171 int entries = 0;
172 if (trigS.size() == 0) {
173 LOG(warn) << "Digits for sector " + std::to_string(sector) + " are received w/o trigger info. Will assume continuous mode";
174 } else { // check consistency of Ndigits with that of expected from the trigger
175 int nExp = trigS.back().getFirstEntry() + trigS.back().getEntries() - trigS.front().getFirstEntry();
176 if (nExp != digiData.size()) {
177 LOG(error) << "Number of digits " << digiData.size() << " is inconsistent with expectation " << nExp
178 << " from digits grouping for sector " << sector;
179 }
180 }
181
182 {
183 if (trigS.size() <= 1) { // just 1 entry (continous mode?), use digits directly
184 auto ptr = &digiData;
185 branch.SetAddress(&ptr);
186 branch.Fill();
187 entries++;
188 branch.ResetAddress();
189 branch.DropBaskets("all");
190 } else { // triggered mode (>1 entries will be written)
191 std::vector<o2::tpc::Digit> digGroup; // group of digits related to single trigger
192 auto ptr = &digGroup;
193 branch.SetAddress(&ptr);
194 for (auto const& group : trigS) {
195 digGroup.clear();
196 for (int i = 0; i < group.getEntries(); i++) {
197 digGroup.emplace_back(digiData[group.getFirstEntry() + i]); // fetch digits of given trigger
198 }
199 branch.Fill();
200 entries++;
201 }
202 branch.ResetAddress();
203 branch.DropBaskets("all");
204 }
205 }
206 auto tree = branch.GetTree();
207 tree->SetEntries(entries);
208 tree->Write("", TObject::kOverwrite);
209 }
210 };
211
212 // handler for labels
213 // TODO: this is almost a copy of the above, reduce to a single methods with amends
214 auto fillLabels = [extractSector, trigP2Sect](TBranch& branch, std::vector<char> const& labelbuffer, DataRef const& ref) {
217 // first of all redefine the output format (special to labels)
218 auto tree = branch.GetTree();
219 auto sector = extractSector(ref);
220 auto ptr = &outputcontainer;
222
223 auto const* dh = DataRefUtils::getHeader<DataHeader*>(ref);
224 LOG(info) << "HAVE LABEL DATA FOR SECTOR " << sector << " ON CHANNEL " << dh->subSpecification;
225 int entries = 0;
226 if (sector >= 0) {
227 LOG(info) << "MCTRUTH ELEMENTS " << labeldata.getIndexedSize()
228 << " WITH " << labeldata.getNElements() << " LABELS";
229 const auto& trigS = (*trigP2Sect.get())[sector];
230 if (trigS.size() == 0) {
231 LOG(warn) << "MCTruth for sector " + std::to_string(sector) + " received w/o trigger info. Will assume continuous mode";
232 } else {
233 int nExp = trigS.back().getFirstEntry() + trigS.back().getEntries() - trigS.front().getFirstEntry();
234 if (nExp != labeldata.getIndexedSize()) {
235 LOG(error) << "Number of indexed (label) slots " << labeldata.getIndexedSize()
236 << " is inconsistent with expectation " << nExp
237 << " from digits grouping for sector " << sector;
238 }
239 }
240 {
241 if (trigS.size() <= 1) { // just 0 or 1 entry (continous mode?), use labels directly
242 outputcontainer.adopt(labelbuffer);
243 br->Fill();
244 br->ResetAddress();
245 br->DropBaskets("all");
246 entries = 1;
247 } else {
248 o2::dataformats::MCTruthContainer<o2::MCCompLabel> lblGroup; // labels for group of digits related to single trigger
249 for (auto const& group : trigS) {
250 lblGroup.clear();
251 for (int i = 0; i < group.getEntries(); i++) {
252 auto lbls = labeldata.getLabels(group.getFirstEntry() + i);
253 lblGroup.addElements(i, lbls);
254 }
255 // init the output container
256 std::vector<char> flatbuffer;
257 lblGroup.flatten_to(flatbuffer);
258 outputcontainer.adopt(flatbuffer);
259 br->Fill();
260 br->DropBaskets("all");
261 entries++;
262 }
263 br->ResetAddress();
264 }
265 }
266 tree->SetEntries(entries);
267 tree->Write("", TObject::kOverwrite);
268 }
269 };
270
271 // A spectator to print logging for the common mode data
272 auto commonModeSpectator = [extractSector](CommonModeOutputType const& commonModeData, DataRef const& ref) {
273 auto sector = extractSector(ref);
274 auto const* dh = DataRefUtils::getHeader<DataHeader*>(ref);
275 LOG(info) << "HAVE COMMON MODE DATA FOR SECTOR " << sector << " ON CHANNEL " << dh->subSpecification;
276 LOG(info) << "COMMON MODE SIZE " << commonModeData.size();
277 };
278
279 auto digitsdef = BranchDefinition<DigitsOutputType>{InputSpec{"digits", ConcreteDataTypeMatcher{"TPC", "DIGITS"}},
280 "TPCDigit", "digits-branch-name",
281 laneConfiguration.size(),
282 fillDigits,
283 getIndex,
284 getName};
285
286 auto labelsdef = BranchDefinition<std::vector<char>>{InputSpec{"labelinput", ConcreteDataTypeMatcher{"TPC", "DIGITSMCTR"}},
287 "TPCDigitMCTruth", "labels-branch-name",
288 // this branch definition is disabled if MC labels are not processed
289 (mctruth ? laneConfiguration.size() : 0),
290 fillLabels,
291 getIndex,
292 getName};
293
294 auto commddef = BranchDefinition<CommonModeOutputType>{InputSpec{"commonmode", ConcreteDataTypeMatcher{"TPC", "COMMONMODE"}},
295 "TPCCommonMode", "common-mode-branch-name",
296 laneConfiguration.size(),
297 commonModeSpectator,
298 getIndex,
299 getName};
300
301 return MakeRootTreeWriterSpec("TPCDigitWriter", "tpcdigits.root", "o2sim",
302 // the preprocessor reads the trigger info object and makes it available
303 // to the Fill handlers
305 // defining the input for the trigger object, as an auxiliary input it is
306 // not written to any branch
307 MakeRootTreeWriterSpec::AuxInputRoute{{"triggerinput", ConcreteDataTypeMatcher{"TPC", "DIGTRIGGERS"}}},
308 // setting a custom callback for closing the writer
310 // passing the branch configuration as argument pack
311 std::move(digitsdef), std::move(labelsdef), std::move(commddef))();
312}
313} // end namespace tpc
314} // end namespace o2
Definition of the common mode container class.
std::string getName(const TDataMember *dm, int index, int size)
A const (ready only) version of MCTruthContainer.
Definition of the TPC Digit.
o2::framework::DataAllocator::SubSpecificationType SubSpecificationType
int32_t i
A special IO container - splitting a given vector to enable ROOT IO.
A helper class to iteratate over all parts of all input routes.
Configurable generator for RootTreeWriter processor spec.
Class to refer to the 1st entry and N elements of some group in the continuous container.
TBranch * ptr
gsl::span< const TruthElement > getLabels(uint32_t dataindex) const
void adopt(gsl::span< const char > const input)
"adopt" (without taking ownership) from an existing buffer
A container to hold and manage MC truth information/labels.
void addElements(uint32_t dataindex, gsl::span< CompatibleLabel > elements)
size_t flatten_to(ContainerType &container) const
o2::header::DataHeader::SubSpecificationType SubSpecificationType
A helper class to iteratate over all parts of all input routes.
Generate a processor spec for the RootTreeWriter utility.
static TBranch * remapBranch(TBranch &branchRef, T **newdata)
GLuint entry
Definition glcorearb.h:5735
GLuint index
Definition glcorearb.h:781
GLboolean GLuint group
Definition glcorearb.h:3991
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
Definition glcorearb.h:1308
GLint ref
Definition glcorearb.h:291
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
O2 data header classes and API, v0.1.
Definition DetID.h:49
o2::framework::DataProcessorSpec getTPCDigitRootWriterSpec(std::vector< int > const &laneConfiguration, bool mctruth)
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
static constexpr int NSectors
target1_1 Fill(5)
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))