Problem putting Strings in a TTree

Folks,

I am using PyROOT to extract information from many files into a TTree.  I don't know what variables are in these files (its info from FITS headers, but thats incidental) until run time, and I use the magic of PyROOT to parse the input files, construct a Class to contain all the input variables, then build and fill a TTree.

This works fine for int's and double's, but I can't get strings to work in the TTree.

I've attached the class I built at run time, and here is a snippet using it to build and fill the TTree:

from ROOT import TTree, AddressOf, gROOT

gROOT.ProcessLine(".L tempClass.C++")
from ROOT import MyClass

t = TTree("t","t")
s = MyClass()

tag = "extname"
t.Branch(tag,AddressOf(s,tag),tag+"/C")

valuel = ["a","b","c","d","e"]
for i in range(5):
    value = valuel[i]
    pyComm = "s." + tag + " = value"
    exec(pyComm)
    t.Fill()

t.Scan()

the output this produces is corrupted:

************************
*    Row   *   extname *
************************
*        0 *      ?
                   ?  *
*        1 *      ?
                   ?  *
*        2 *      ?
                   ?  *
*        3 *      ?
                   ?  *
*        4 *      ?
                   ?  *
************************

Any suggestions on how to fix this?

thanks, Aaron
tempClass.C (1.01 KB)

“s.extname” is a “std::string”
“extname/C” means a “C character array, terminated by the 0 character”

Pepe,

I tried declaring s.extname as a char* [80] (and just a plain char* ) and that also didn’t work.

Aaron

also, Wim posted a working example of writing a string into a TTree a weeks ago, and that successfully used
a std:string. However, that example used a different call to .Branch – I tried that style too and it didn’t work
either.

The difference is that since I don’t know the input data until run time I can’t put the string directly
into the argument for Branch, but instead must use AddressOf.

Aaron

Aaron,

[quote=“roodman”]I tried declaring s.extname as a char* [80] (and just a plain char* ) and that also didn’t work.[/quote]a “char*[80]” gives you 80 pointers to string, not 80 characters, that’d be “char[80]”. See staff.py under $ROOTSYS/tutorials/pyroot for an example.

Cheers,
Wim

Wim, is there no way to get it working with a “std:string” (i.e. the “s.extname”)?

Pepe,

std::strings work, see this posting: [url]Writing std.string to ttree with this caveat: [url]Leaky strings

I’m not understanding this, however:[quote=“roodman”]The difference is that since I don’t know the input data until run time I can’t put the string directly into the argument for Branch, but instead must use AddressOf.[/quote]since the Branch() call simply takes the object, so the type doesn’t need to be typed out anywhere, and AddressOf isn’t needed either. And any which way, you can always look dynamically at type(s.extname) and change behavior based on the result of that expression.

Cheers,
Wim

The problem is that when I try: t.Branch('extname', s.extname) I get (the current v5-34-00-patches branch): Traceback (most recent call last): File "tempClass.py", line 10, in <module> t.Branch('extname', s.extname) TypeError: none of the 9 overloaded methods succeeded. Full details: Int_t TTree::Branch(TCollection* list, Int_t bufsize = 32000, Int_t splitlevel = 99, const char* name = "") => could not convert argument 1 Int_t TTree::Branch(TList* list, Int_t bufsize = 32000, Int_t splitlevel = 99) => could not convert argument 1 Int_t TTree::Branch(const char* folder, Int_t bufsize = 32000, Int_t splitlevel = 99) => could not convert argument 2 (../Objects/longobject.c:237: bad argument to internal function) TBranch* TTree::Branch(const char* name, char* address, const char* leaflist, Int_t bufsize = 32000) => takes at least 3 arguments (2 given) TBranch* TTree::Branch(const char* name, Long_t address, const char* leaflist, Int_t bufsize = 32000) => takes at least 3 arguments (2 given) TBranch* TTree::Branch(const char* name, int address, const char* leaflist, Int_t bufsize = 32000) => takes at least 3 arguments (2 given) TBranch* TTree::Branch(const char* name, void* address, const char* leaflist, Int_t bufsize = 32000) => takes at least 3 arguments (2 given) TBranch* TTree::Branch(const char* name, const char* classname, void** obj, Int_t bufsize = 32000, Int_t splitlevel = 99) => takes at least 3 arguments (2 given) TBranch* TTree::Branch(const char* name, void** obj, Int_t bufsize = 32000, Int_t splitlevel = 99) => could not convert argument 2 (The “tempClass.py” is just the python code from the first post above.)

BTW. I think he says that he needs the “AddressOf” because he does not know the exact name “extname” in advance (so he cannot hardcode “s.extname” in his python code). He knows this name first at run time, from the “tag” variable, and so everything that is needed in the “Branch” call needs to be “calculated” at run time with use of this “tag” variable.

Pepe,

okay, I see that s.extname is of (python) type str, as the string gets copied over instead of having an object returned. Not sure why I did that; it probably had some boundary cases. Okay, then I’m afraid that this requires some pointer manipulation to convince all the bits and pieces as to what is supposed to be what. How about this:[code]from ROOT import *
gROOT.ProcessLine(".L tempClass.C++")

t = TTree(“t”,“t”)
s = MyClass()
stc = TClass.GetClass(“std::string”)
s_extname = stc.DynamicCast(stc, AddressOf(s, “extname”))

t.Branch(“extname”, s_extname)
for i in range(10):
s.extname = “%d” % i
// or: s_extname.replace(0, std.string.npos, “%d” % i)
t.Fill()

t.Scan()[/code] (with the caveat on replace, as per [url]Leaky strings

As for not knowing the name “extname”, that’d be solved with getattr(s, tag), but yes, that’d still be a str, as you saw. To find out about the type (perhaps needed in the TClass snippet above, if “std::string” can not be hard-wired), something like this will then do: TClass.GetClass("MyClass").GetListOfAllPublicDataMembers().FindObject(tag).GetFullTypeName()
Cheers,
Wim

I think he knows it will be a “std::string”, but he does not know if it’s “ifile” or “date” or “extname” (three strings that appear in his “MyClass”). How about this: [code]from ROOT import *
gROOT.ProcessLine(".L tempClass.C++")

from ROOT import MyClass

t = TTree(“t”, “t”)

s = MyClass()

tag = "extname"
stc = TClass.GetClass(“std::string”)

s_tag = stc.DynamicCast(stc, AddressOf(s, tag))
t.Branch(tag, s_tag)

valuel = [“a”, “b”, “c”, “d”, “e”]
for i in range(5):
s_tag.replace(0, std.string.npos, valuel[i])
t.Fill()

t.ResetBranchAddresses()

t.Scan()[/code]

Wim and Pepe,

Wim's pointer to the use of a Char_t solved this problem for me.  (Pepe: you are correct that I don't know

the variable names at run time).

I just use:

Char_t extname[80];

in the class that holds the data, and it works fine. Thanks to you both for your help,

Aaron

I have written some code that stores a std::string in a TTree. First I create a big python data-storage object with a member that is a ROOT.std.string(). Then I immediately make sure that I reserve space for the string:

class TTreeInfo(object):
    def __init__(self):
        ...
        self.particle = ROOT.std.string()
        self.particle.reserve(100)

Later once the object is created (and its init method called), I have a separate TTree object. I link the branches of the TTree to the data members of the python object:

ttreeobj = TTreeInfo()
...
outtree.Branch('particle',ttreeobj.particle)

Then, when I need to store some data in the python object member, instead of putting a new string (which would not respect the reserved space), I do:

Where particlename is a regular python string.

I don’t know if the whole business with reserving space is necessary, but it was included when I was bashing out a lot of code to try to get it to work, and it doesn’t seem to hurt. It keeps the std::string of a static size (as long as you reserve enough space for whatever strings you want to store), like a regular array, but it allows you to use all the std::string methods when filling and later when analyzing.

Hopefully that’s useful!
Jean-François

The main findings, of this discussion here, are:

  1. if one creates a “ROOT.std.string” object directly in Python, then one can simply use it directly, as well,
  2. if a “std::string” object comes from a C++ code, then one needs to use the “DynamicCast” trick in Python,
  3. if possible, instead of a “std::string” use a “char string[MaxSize];” -> it’ll save you loads of time. :mrgreen:

P.S. The rule 3. above is a practical application of the “KISS principle”, of course. :wink: