Fun with TH2F data members

I’ve got a class that a) inherits from TNamed and b) contains a TH2F as one of its data members. These objects are written to a file and I use a shared library to read them back when I load the file.

For the most part this works quite well, except for the annoying fact that I get this when I close the file:

root [3] VASkyMap *tmp2; f.GetObject(“RingBackgroundModelAnalysis/fExcessMap”, tmp2);
root [4] f.Close();
*** glibc detected *** free(): invalid pointer: 0x08a46c5c ***

It’s got to have something to do with ROOT memory management with respect to histograms, and both ROOT and the class deleting the data member. But all constructors for the class call SetDirectory(0) for the histogram data member, so I don’t see why.

I also seem to be able to access the histogram data member directly from the file using GetObject, and then the same problem does not occur:

root [3] TH2F *tmp2a; f.GetObject("RingBackgroundModelAnalysis/fExcessMap_hist, tmp2a);
root [4] f.Close();

Any advice on what I should do to get around this? I’m using ROOT 5.18.

                           Amanda

You must disable the ROOT ownership for your TH2 object by calling
hist.SetDirectory(0) in your VASkipMap constructor.

Rene

I’m already doing that, though, so that can’t be the whole explanation. The relevant constructors are excerpted here:

VASkyMap::VASkyMap() : pi(acos(-1.)),fBinSizeX(0.0), fBinSizeY(0.0), fOrigin_first(0.0), fOrigin_second(0.0), fFoVRadius(1.5), fMode(RADec_Coord)
{
fHist.SetDirectory(0);
fHist_smooth.SetDirectory(0);
}

VASkyMap::VASkyMap(const char* name, const char* title,
double originRA, double originDec, double fov,
double binsize, double fovRadius, CoordinateMode mode) :
pi(acos(-1.)),fBinSizeX(binsize), fBinSizeY(binsize), fOrigin_first(originRA),
fOrigin_second(originDec), fFoVRadius(fovRadius), fMode(mode)
{
// Stretch the bins in RA by 1/cos(dec), for dec<~84 deg. For sources above
// a declination of 84 deg, this will need to be revisited. Also stretch the
// FoV in RA - eg, at a dec of 60 deg, the array’s 3.5 deg FoV corresponds
// to 7.0 deg in RA. Assume that over the 3.5 deg range in Dec that the
// change in RA is symmetric, so that the FoV becomes an ellipse.

fOneOverCosDec = 1; // Don’t scale if we’re not in RA/DEC mode

if (fMode == RADec_Coord) {
double oneOverCosDec = 0.;
double fOrigin_RA = fOrigin_first;
double fOrigin_Dec = fOrigin_second;

if ( fOrigin_Dec < 1.47 && fOrigin_Dec > -1.47 ) {
  oneOverCosDec = 1./cos(fOrigin_Dec);
} else {
  oneOverCosDec = 10.;
}

fOneOverCosDec = oneOverCosDec;

}

fFoVX = fov*fOneOverCosDec; // Stretch X FoV
fFoVY = fov;

fbinsX=static_cast(floor(fFoVX/fBinSizeX+0.5));
fbinsY=static_cast(floor(fFoVY/fBinSizeY+0.5));
fXRange.fLo=fOrigin_first*(180./pi)-fFoVX/2;
fXRange.fHi=fXRange.fLo+fFoVX;
fYRange.fLo=fOrigin_second*(180./pi)-fFoVY/2;
fYRange.fHi=fYRange.fLo+fFoVY;

SetName(name);

fHist.SetDirectory(0);
fHist_smooth.SetDirectory(0);

TString histName(name);
histName += TString("_hist");
fHist.SetName(histName);
fHist.SetTitle(title);
fHist.SetBins(fbinsX, fXRange.fLo,fXRange.fHi,
fbinsY,fYRange.fLo,fYRange.fHi);

TString tmpName = histName + TString("_smooth");
fHist_smooth.SetName(tmpName);
fHist_smooth.SetTitle(title);
fHist_smooth.SetBins(fbinsX, fXRange.fLo,fXRange.fHi,
fbinsY,fYRange.fLo,fYRange.fHi);

cout << GetName() << " Coord Mode: " << fMode << "Scale: " << fOneOverCosDec << endl;
}

The header file is here:

#ifndef VASKYMAP_H_
#define VASKYMAP_H_

[includes snipped]

class VASkyMap : public TNamed

{

public :

enum CoordinateMode { Camera_Coord, RADec_Coord, Galactic_Coord };

VASkyMap();

VASkyMap(const char* name, const char* title,
double originRA, double originDec, double fov, double binsize,
double fovRadius = 1.5, CoordinateMode mode = RADec_Coord);

virtual ~VASkyMap();

// add a setter so the user can “fix” this offline if it was mis-set
void SetFoVRadius(double fovRadius) {fFoVRadius = fovRadius;}
void FillCoordinates(std::pair<double,double> pos,double w);
void FindBinPosition(std::pair<double,double> pos,int& x,int& y);
void Smooth(double radius);
void Draw(Option_t *option =""); // will be able to pass “smooth” as an option
void Gauss2DPlot();
void SetBinContent(std::pair<double,double> pos,double w);
virtual void SetBinContent( int xbin, int ybin, double w );
void SetBinError( int xbin, int ybin, double e );
double GetBinContent(std::pair<double,double> pos);
double GetBinContent( int xbin, int ybin );
double GetBinArea(int xbin, int ybin);
void SetTitle(const char *title) {fHist.SetTitle(title);}

TAxis *GetXaxis() {return fHist.GetXaxis();}
TAxis *GetYaxis() {return fHist.GetYaxis();}

int GetNbinsX() {return fHist.GetNbinsX();}
int GetNbinsY() {return fHist.GetNbinsY();}

TH2F *GetHistogram() {return &fHist;}
TH2F *GetSmoothedHistogram() {return &fHist_smooth;}

TF2 * Fit2D();
std::pair<double,double> GetBinCoordinates(int x,int y);

private :

double pi;
double fBinSizeX;
double fBinSizeY;
double fFoVX;
double fFoVY;
double fOrigin_first;
double fOrigin_second;

CoordinateMode fMode;

uint16_t fbinsX;
uint16_t fbinsY;
VARange fXRange;
VARange fYRange;
double fOneOverCosDec;
double fFoVRadius;

TH2F fHist;
TH2F fHist_smooth;

ClassDef(VASkyMap,1);

}; // class VASkyMap

#endif

Could you send the shortest possible RUNNING system reproducing the problem?

Rene

I can try, yes—It will take some time to cut it down to a test example.

In the meantime, however, I’ve figured out something of what happens. Let’s take a very simple file with a couple of these objects in it:

root [10] TFile f(“quiggle.root”);

This what happens when I list the file contents:

root [11] f.ls();
TFile** quiggle.root
TFile* quiggle.root
KEY: VASkyMap skymap_default;1
KEY: VASkyMap minnie;1

Now let’s get the objects in question:

root [13] VASkyMap *tmp2; f.GetObject(“skymap_default”, tmp2);
skymap_default
root [14] cout << tmp2->GetName() << endl;
skymap_default
root [15] tmp2->Draw();
TCanvas::MakeDefCanvas: created default TCanvas with name c1
root [16] VASkyMap *tmp3; f.GetObject(“minnie”, tmp3);
skymap_default
root [17] tmp3->Draw();
root [18] cout << tmp3->GetName() << endl;
minnie

And then do another ls:

root [19] f.ls();
TFile** quiggle.root
TFile* quiggle.root
OBJ: TH2F skymap_hist_default : 0 at: 0x87a5924
OBJ: TH2F skymap_hist_default_smooth : 0 at: 0x87a5b90
OBJ: TH2F minnie_hist mouse : 0 at: 0x898c36c
OBJ: TH2F minnie_hist_smooth mouse : 0 at: 0x898c5d8
KEY: VASkyMap skymap_default;1
KEY: VASkyMap minnie;1

If, now I do:

root [24] skymap_hist_default->SetDirectory(0);
root [25] skymap_hist_default_smooth->SetDirectory(0);
root [26] minnie_hist_smooth->SetDirectory(0);
root [27] minnie_hist->SetDirectory(0);
root [28] f.Close();

Everything works fine, without the free error.

Now here’s my question: how does GetObject actually work? It clearly calls through to the default constructor (I was mistaken about that when I originally made this post)—but after that takes place, somehow the real information associated with that object is read in, and that’s when the histograms get reassociated with the directory again. Is that done using a copy constructor of some kind?

            Amanda

That means that in your default class constructor you are not calling hist.SetDirectory(0).
Another way to avoid the problem is to call the static function
TH1::AddDirectory(kFALSE);
This call will prevent hsitograms to be added by default to the current directory.

Rene

Here is the text, verbatim, of the VASkyMap default constructor implementation. I’ve put a couple of extra print statements into it that dump the directory pointer and histogram name before and after the relevant SetDirectory command:

VASkyMap::VASkyMap() : pi(acos(-1.)),fBinSizeX(0.0), fBinSizeY(0.0), fOrigin_first(0.0), fOrigin_second(0.0), fFoVRadius(1.5), fMode(RADec_Coord)
{
SetName(“skymap_default”);
fHist.SetName(“skymap_hist_default”);
fHist_smooth.SetName(“skymap_hist_default_smooth”);
cout << "Directory for : " << fHist.GetName() << " is " << fHist.GetDirectory() << endl;
fHist.SetDirectory(0);
fHist_smooth.SetDirectory(0);
cout << "Directory for : " << fHist.GetName() << " is " << fHist.GetDirectory() << endl;
}

Here’s a dump of the relevant interactive session:

root [5] TFile f(“quiggle.root”);
root [6] VASkyMap *tmp3; f.GetObject(“minnie”, tmp3);
Directory for : skymap_hist_default is 0
Directory for : skymap_hist_default is 0
root [7] cout << tmp3->GetHistogram()->GetDirectory() << endl;
0x85931b8
root [8] cout << tmp3->GetHistogram()->GetName() << endl;
minnie_hist

When the default constructor is called (and completes) the histogram name is set to the default name value and the histogram is not associated with a directory.

At some point after that, the actual information associated with object “minnie” is read in from the file, into the memory space created by the default constructor. Once that happens, the histogram now has the name “minnie_hist”, and the directory pointer is no longer zero. I do not see how this can possibly have anything whatsoever to do with the default constructor. Something else has to have been called, after the default constructor, that is reading in the relevant information and re-associating the histogram with the directory.

That association is not implied in the original object information, either, because the non-default constructor is calling SetDirectory(0) as it should.
Here’s the text from when the VASkyMap object “minnie” was actually instantiated:

root [1] TFile f(“quiggle.root”, “RECREATE”);
root [2] VASkyMap test(“minnie”, “mouse”, 0.0, 0.0, 1.5, 0.025);
root [3] cout << test.GetHistogram()->GetName() << endl;
minnie_hist
root [4] cout << test.GetHistogram()->GetDirectory() << endl;
0
root [5] test.Write();
root [6] f.Close();

As an aside, you are right that TH1::AddDirectory(kFALSE) will do the trick, but this will have side effects since it applies to all histograms in the session.

Hi,

This problem is fixed in the SVN trunk and hence in the upcoming release (5.19/04). To work-around the problem in your current release you can either call TH1::AddDirectory before loading the object (and reset it after loading the object) or write a custom streamer for your class (where you call SetDirectory(0) on the histogram).

Namely the issue is that any histogram being read was attached to the current directory. In 5.19/04, only histogram that are saved individually (i.e. not as part of an object) are attached to the current directly when being read.

Cheers,
Philippe.