Skip to content

Commit e4426e4

Browse files
TPC: fix pair swap when reading CalArray<PadFlags> written by the custom streamer
The custom member streamer for CalArray<PadFlags>::mData writes the count N/2 followed by N single shorts, but reads N/2 ints and reinterprets each as a pair of flags (the layout written by ROOT < 6.33). Every object written by the streamer itself (ROOT >= 6.33) therefore comes back with each pair of adjacent pads swapped. This affects all IDC_PadStatusMap objects written since the streamer was introduced (e.g. 2026 data, ROOT 6.36.04): the dead channel map used in reconstruction marks the neighbour of an isolated bad pad instead of the pad itself. Objects written with ROOT < 6.33 (e.g. 2025, ROOT 6.32.06) are read correctly. The class version did not change, so the reader now selects the layout by the ROOT version that wrote the buffer (TBuffer::GetVersionOwner(); the current ROOT if the buffer has no file): < 6.33 as before, >= 6.33 N shorts. The writer is unchanged, so existing objects stay readable and no re-upload is needed. Add a ROOT I/O round-trip unit test for CalDet<PadFlags>, which fails with the old reader (349440 of 524160 flags differ) and passes with the new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 2ddcfc8 commit e4426e4

2 files changed

Lines changed: 69 additions & 5 deletions

File tree

‎Detectors/TPC/baserecsim/src/TPCFlagsMemberCustomStreamer.cxx‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "TPCBase/CalArray.h"
1313
#include <TMemberStreamer.h>
1414
#include <TBuffer.h>
15+
#include <TROOT.h>
1516
#include <DataFormatsTPC/Defs.h>
1617
#include <iostream>
1718

@@ -26,6 +27,14 @@
2627
// This member was written incorrectly to the TFile in ROOT versions < 6.36, causing
2728
// segfaults when reading on ARM64 (occassionally).
2829
// We continue to write it in the incorrect format and fix the reading back.
30+
//
31+
// Two layouts exist on disk, both with the count N/2 (N = number of flags):
32+
// - written by ROOT < 6.33 (no custom streamer): N/2 ints, each holding a pair of
33+
// flags as they sit in memory;
34+
// - written by this streamer (ROOT >= 6.33): N single shorts.
35+
// Reading the second layout as ints swaps every pair of adjacent flags, so the
36+
// layout is chosen by the ROOT version that wrote the buffer. The class version
37+
// did not change, so this is the only marker.
2938

3039
// See also:
3140
// - https://github.com/root-project/root/pull/17009
@@ -40,23 +49,40 @@ void MemberVectorPadFlagsStreamer(TBuffer& R__b, void* objp, int n)
4049
std::vector<o2::tpc::PadFlags>* obj = static_cast<std::vector<o2::tpc::PadFlags>*>(objp);
4150
if (R__b.IsReading()) {
4251
obj->clear();
43-
std::vector<int> R__stl;
44-
R__stl.clear();
4552
int R__n;
4653
R__b >> R__n;
54+
// ROOT version of the file the buffer belongs to (e.g. 63206); 0 without a file (e.g. TMessage):
55+
// then writer and reader run the same ROOT
56+
int writerVersion = R__b.GetVersionOwner();
57+
if (writerVersion <= 0) {
58+
writerVersion = gROOT->GetVersionInt();
59+
}
60+
constexpr size_t bloatfactor = sizeof(int) / sizeof(o2::tpc::PadFlags);
61+
if (writerVersion >= 63300) {
62+
// written by this streamer: N single shorts
63+
obj->reserve(bloatfactor * R__n);
64+
for (size_t i = 0; i < bloatfactor * R__n; ++i) {
65+
Short_t readtemp;
66+
R__b >> readtemp;
67+
obj->push_back(static_cast<o2::tpc::PadFlags>(static_cast<unsigned short>(readtemp)));
68+
}
69+
return;
70+
}
71+
// written by ROOT < 6.33: N/2 ints, each a pair of flags in memory order
72+
std::vector<int> R__stl;
4773
R__stl.reserve(R__n);
4874
for (int R__i = 0; R__i < R__n; R__i++) {
4975
Int_t readtemp;
5076
R__b >> readtemp;
5177
R__stl.push_back(readtemp);
5278
}
5379
auto data = reinterpret_cast<unsigned short*>(R__stl.data());
54-
constexpr size_t bloatfactor = sizeof(int) / sizeof(o2::tpc::PadFlags);
55-
for (int i = 0; i < bloatfactor * R__n; ++i) {
80+
for (size_t i = 0; i < bloatfactor * R__n; ++i) {
5681
obj->push_back(static_cast<o2::tpc::PadFlags>(data[i]));
5782
}
5883
} else {
59-
// We always save things with the old format.
84+
// count N/2, then N single shorts: NOT the old (ROOT < 6.33) layout of N/2 ints; the reader above
85+
// tells the two apart by the ROOT version. Unchanged, so that existing objects stay readable.
6086
R__b << (int)obj->size() / 2;
6187
for (size_t i = 0; i < obj->size(); i++) {
6288
R__b << (short)obj->at(i);

‎Detectors/TPC/baserecsim/test/testTPCCalDet.cxx‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,44 @@ BOOST_AUTO_TEST_CASE(CalDetTypeTest)
346346
BOOST_CHECK(testDict == true);
347347
}
348348

349+
BOOST_AUTO_TEST_CASE(CalDetPadFlags_ROOTIO)
350+
{
351+
// CalArray<PadFlags>::mData has a custom member streamer (TPCFlagsMemberCustomStreamer.cxx);
352+
// a write + read round trip must give back every flag at its own pad (a pattern in which
353+
// adjacent pads always differ, so that a swap of neighbouring pads shows up)
354+
CalDet<PadFlags> flags("flags", PadSubset::Region);
355+
const PadFlags bad = PadFlags::flagSkip | PadFlags::flagLowPad;
356+
size_t n = 0;
357+
for (auto& arr : flags.getData()) {
358+
for (size_t i = 0; i < arr.getData().size(); ++i, ++n) {
359+
arr.setValue(i, (n % 3 == 0) ? bad : PadFlags::flagGoodPad);
360+
}
361+
}
362+
363+
auto f = TFile::Open("CalDetPadFlags_ROOTIO.root", "recreate");
364+
f->WriteObject(&flags, "flags");
365+
f->Close();
366+
delete f;
367+
368+
f = TFile::Open("CalDetPadFlags_ROOTIO.root");
369+
auto* read = f->Get<CalDet<PadFlags>>("flags");
370+
BOOST_REQUIRE(read != nullptr);
371+
BOOST_REQUIRE_EQUAL(read->getData().size(), flags.getData().size());
372+
size_t nDiffer = 0;
373+
for (size_t iarr = 0; iarr < flags.getData().size(); ++iarr) {
374+
const auto& written = flags.getCalArray(iarr).getData();
375+
const auto& back = read->getCalArray(iarr).getData();
376+
BOOST_REQUIRE_EQUAL(back.size(), written.size());
377+
for (size_t i = 0; i < written.size(); ++i) {
378+
nDiffer += back[i] != written[i];
379+
}
380+
}
381+
BOOST_CHECK_EQUAL(nDiffer, 0);
382+
delete read;
383+
f->Close();
384+
delete f;
385+
}
386+
349387
BOOST_AUTO_TEST_CASE(CalDetStreamerTest)
350388
{
351389
// simple code executing the TPC IDCPadFlags loading in a standalone env --> easy to valgrind

0 commit comments

Comments
 (0)