Reading boolean data from TTree using PyROOT


ROOT Version: master
Platform: Linux 5.4 (Manjaro)
Compiler: GCC 9.3


I can create a TTree with a branch holding boolean values like this -

#include "TFile.h"
#include "TTree.h"
#include "TBranch.h"

void tleafo() {
    TFile *f;
    f = new TFile("tleafo.root", "RECREATE");
    f->SetCompressionLevel(0);

    TTree *t = new TTree("sample", "");
    Bool_t b;
    t->Branch("b", &b, "b/O");
    for (int i = 0; i<5; i++) {
        b = 1;
        t->Fill();
    }
    t->Write();
    f->Close();
}

I want to read the data out using PyROOT.
This is how I am trying to do that -

import ROOT
import numpy

ROOT.gInterpreter.Declare("""
void readnewbasket(bool* arr, char* filename) {
    Bool_t x;
    TFile f(filename);
    auto tree = f.Get<TTree>("t");
    auto branch = tree->GetBranch("booleanBranch");
    branch->SetAddress(&x);
    for (int i=0; i<tree->GetEntries(); i++) {
        tree->GetEvent(i);
        arr[i] = x;
    }
}""")

initializer = numpy.array([0, 0, 0, 0, 0], dtype=">?")
ROOT.readnewbasket(initializer, "tleafo.root")
for i in range(5):
    print(initializer[i])

But when I try to execute the above Python code, I get an error (on both pyroot_experimental ON and OFF) -

TypeError: void ::readnewbasket(bool* arr, char* filename) =>
    TypeError: could not convert argument 1 (could not convert argument to buffer or nullptr)

I would really appreciate any help with this. Thanks!

Not directly related but might be useful information - I think this used to work in the past when I had originally written the above Python script (maybe for ROOT 6.16… I am not sure). But this also works on the conda-forge build of ROOT.

Hi!

Indeed, I think the automatic conversion from a boolean buffer to a bool* is broken. We will investigate! However, to fix your application, you can do the following:

import ROOT
import numpy

ROOT.gInterpreter.Declare("""
void tleafo() {
    TFile *f;
    f = new TFile("tleafo.root", "RECREATE");
    f->SetCompressionLevel(0);

    TTree *t = new TTree("sample", "");
    Bool_t b;
    t->Branch("b", &b, "b/O");
    for (int i = 0; i<5; i++) {
        b = 1;
        t->Fill();
    }
    t->Write();
    f->Close();
}

void readnewbasket(void* arr, char* filename) {
    Bool_t x;
    auto arr2 = (bool*) arr;
    TFile f(filename);
    auto tree = f.Get<TTree>("sample");
    auto branch = tree->GetBranch("b");
    branch->SetAddress(&x);
    for (int i=0; i<tree->GetEntries(); i++) {
        tree->GetEvent(i);
        arr2[i] = x;
    }
    f.Close();
}""")

ROOT.tleafo()
initializer = numpy.array([0, 0, 0, 0, 0], dtype=numpy.bool)
ROOT.readnewbasket(initializer, "tleafo.root")
for i in range(5):
    print(initializer[i])

So in short: Take a void* in the signature in C++ and then simply cast it to a bool* inside. This fixes your issue with the PyROOT layer as long we investigate further.

Side note, you can achieve your conversion to numpy also in a single line like this

b = ROOT.RDataFrame('sample', 'tleafo.root').AsNumpy(['b'])['b']

Though, if the buffer is small, your implementation is more efficient.

Best
Stefan

1 Like

Hi again,

I’ve created a Jira ticket to keep track of the issue:
https://sft.its.cern.ch/jira/browse/ROOT-10731

Best
Stefan

Thanks! :slight_smile:

Small update, I think I found the issue. We don’t recognize correctly the type identifier "?", just use "b", and you should be good to go.

But “b” means numpy.int8 or ROOT’s TLeafB and not the boolean data type right?

Actually fully correct! If you don’t care on the python side about that detail (normally numpy abstracts this detail for you very nicely), your code should do the same thing. On the C++ side, both of them have 1 byte and we are fine. Also, the Python type code b is actually what we recognize as a bool in PyROOT for conversion.

1 Like