Saving histograms into a pdf / png format directly from ROOT

If you have a TCanvas saved in a ROOT file you can do (C++ code):

   auto f = new TFile("your_root_file.root");
   TCanvas *c = (TCanvas *) f->get("name_of_the_canvas_stored_in_the_root_file");
   c->Print("image.pdf");

I do have a TCanvas saved in each ROOT file. But, I must confess that I am not well-versed in C++. In Python, there are no pointers as in your example; how would I format into something more like PyROOT? (i.e. if you could type it to me like a “coding for dummies” book, that would be much appreciated) :slight_smile:

@couet 's code translated to python is:

import ROOT

f = ROOT.TFile("your_root_file.root")
c = f.Get("name_of_the_canvas_stored_in_the_root_file")
c.Print("image.pdf")
1 Like

a small sanity check, produce a canvas from this macro:

void Canvas(){
  TFile* f = TFile::Open("test.root","RECREATE");
  TH1F* h = new TH1F("h","h",100,-5,5);
  h->FillRandom("gaus");
  h->SetLineColor(kRed);
  h->SetFillColor(kGreen+3);
  TCanvas* c = new TCanvas("c","c",800,600);
  h->Draw("hist");
  c->Write();
}

then:

import ROOT

f = ROOT.TFile("test.root")
c = f.Get("c")
c.Print("img_from_py.pdf")

is the same as:

root [0]    auto f = new TFile("test.root");
root [1]    TCanvas *c = (TCanvas *) f->Get("c");
root [2]    c->Print("image.pdf");
Info in <TCanvas::Print>: pdf file image.pdf has been created
1 Like

Thanks so much @ikabadzhov for the translation; super helpful! I attempted the void Canvas(){} total block inside my root application, then wrote the import ROOT inside my python file. When I ran my python file, I get that test.root does not exist. What am I doing incorrectly?
Whole code here:

root [10] void Canvas(){
root (cont'ed, cancel with .@) [11]  TFile* f = TFile::Open("test.root","RECREATE"); 
root (cont'ed, cancel with .@) [12]  TH1F* h = new TH1F("h","h",100,-5,5);
root (cont'ed, cancel with .@) [13]  h->FillRandom("gaus");
root (cont'ed, cancel with .@) [14]  h->SetLineColor(kRed);
root (cont'ed, cancel with .@) [15]  h->SetFillColor(kGreen+3);
root (cont'ed, cancel with .@) [16]  TCanvas* c = new TCanvas("c","c",800,600);
root (cont'ed, cancel with .@) [17]  h->Draw("hist");
root (cont'ed, cancel with .@) [18]  c->Write();
root (cont'ed, cancel with .@) [19]}
root [20] .q
[tcarnaha@lxplus751 spark_tnp]$  ./tnp_fitter.py fit muon generalTracks Z Run2018_UL configs/muon_pog_official_run2_Z.json --baseDir ./UL_2018 --registry data/registry_muon_Z_generalTracks_oldNtuples.json --validate

RooFit v3.60 -- Developed by Wouter Verkerke and David Kirkby 
                Copyright (C) 2000-2013 NIKHEF, University of California & Stanford University
                All rights reserved, please read http://roofit.sourceforge.net/license.txt

Error in <TFile::TFile>: file test.root does not exist
Traceback (most recent call last):
  File "./tnp_fitter.py", line 445, in <module>
    status = main()
  File "./tnp_fitter.py", line 423, in main
    validateFits(job[0])
  File "/eos/home-t/tcarnaha/spark_tnp/scripts/validateFits.py", line 80, in validateFits
    c.Print("img_from_py.pdf")
ReferenceError: attempt to access a null-pointer

You could also save directly the canvases as PDF at the same time that you write the ROOT file, rather than doing that in a separate script later on.

To save all open canvases in a ROOT session, just type the following in a terminal:

        TIter next(gROOT->GetListOfCanvases());
        TCanvas* c = nullptr;
        while((c = (TCanvas*)next()))
        {
            const TString canvName = c->GetName();
            c->SaveAs(canvName+".pdf");
        }

You could do something similar if the canvases are not open, but rather inside a TFile.
Just iterate over all keys in the TFile and only call SaveAs if the key->ClassName()=="TCanvas".

Hey @ferhue, thank you for the advice. The root files are already generated, and I’m building a validation tool in order to open them and inspect them for future applications, so that unfortunately wouldn’t be the best way for me to continue. Thank you for the idea though!

Then just use sth like this:

f = ROOT.TFile("mydata.root")
for k, o in getall(f):
    if o.ClassName() == "TCanvas":
        o.SaveAs("abcd.pdf")

Hey @ferhue! Tried something like the above, but I think I’m getting hung up on the f = ROOT.TFile("mydata.root") portion.

All of my root files come from a base directory, UL_2018, which has numerous folders that are opened and sifted through before getting to a single root file. I don’t know how to call “mydata.root” in this instance because one file that will be caught in the if loop will be:
./UL_2018/fits_data/muon/generalTracks/Z/Run2018_UL/AltBkg/NUM_LooseID_DEN_genTracks/NUM_LooseID_DEN_genTracks_abseta_1_pt_2.root

and another will be:

./UL_2018/fits_mc/muon/generalTracks/Z/Run2018_UL/AltSigOld/NUM_LooseID_DEN_genTracks/NUM_LooseID_DEN_genTracks_abseta_1_pt_3.root

and so on.

I can get these files from calling: Print(inFile) inside my script…but ROOT.TFile(inFile) does not work.

How would you go about defining “mydata.root” in your example for my data?

It must be an issue with where your script is located, compared to where the data are.
It seems that your script is in:

/eos/home-t/tcarnaha/spark_tnp/scripts/validateFits.py

whereas your data might be in a different folder.

For example, if your data are under spark_tnp, then inFile should probably be ../UL_2018 instead of ./UL_2018. Alternatively, try to specify the full path to avoid this kind of nuisances. So to say: inFile = "/eos/home-t/tcarnaha/UL_2018/...."

Ah maybe! Yes, the data is /eos/…/spark_tnp/UL_2018.
inFile is already tagged with what is necessary to pull through all of those folders, but if I redefine a pathway to insert, say:
dir = "eos/home/tcarnaha/UL_2018/

And, ROOT.TFile(dir), it complains. Also, what header do you use to have getall be defined?

Aren’t you missing the leading slash when you define dir?

Try this:

dir = "/eos/home/tcarnaha/UL_2018/ ROOT.TFile(dir+"fits_data/muon/generalTracks/Z/Run2018_UL/AltBkg/NUM_LooseID_DEN_genTracks/NUM_LooseID_DEN_genTracks_abseta_1_pt_2.root")

Does that work?

For getall to work, take a look at Loop over all objects in a ROOT file

Hey @ferhue , thank you for the loop reference. And, I did originally have the forward slash in my code; sorry for the typo in rewriting it in this forum. Still doesn’t work to output an actual png file. Here’s what I’ve got so far:

        c = ROOT.TCanvas('','',200,10,700,500)                                                                 \

        dir1 = '/eos/user/t/tcarnaha/spark_tnp/'                                                               \

        file_name = (dir1 +inFile.strip(".")[1:])
        #print('Loading file: ', file_name)                                                                    \
                                                                                                                
        myfile = ROOT.TFile.Open(file_name, "READ")                                                            \

        root_dir = myfile.GetDirectory(file_name+"_Canv") #may cause problems                                   

	#List of Keys()                                                                                        \
                                                                                                                
        root_keys = myfile.GetListOfKeys()                                                                     \

        c.cd()                                                                                                 \

        for key in root_keys:                                                                                  \

            if key.ClassName() == "TCanvas":
                key.SaveAs('folder/' + mytitle() + '.png')
                print("Saving: " + mytitle() + ".png")

where “mytitle()” is just a name-tagging function. I’m just wondering why my SaveAs is not saving a png now. It seems like the last if block is not returning anything at all (no print statements either).

hmm what is the output of:

for key in root_keys:
    print(key,key.ClassName())

?

One example output of:

for key in root_keys:
    print("key: ", key, "ClassName: ", key.ClassName())
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_Canv Title: c ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_resP Title: Result of fit of p.d.f. pdfPass to dataset hPass ClassName:  TKey
key:  Name: ProcessID0 Title: 8555bfbe-cc6e-11ec-a3de-513b8e80beef ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_resF Title: Result of fit of p.d.f. pdfFail to dataset hFail ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_statTests Title: statTests ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_GenPass Title:  ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_GenFail Title:  ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_Pass Title:  ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_1_pt_2_Fail Title:  ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_2_pt_1_Canv Title: c ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_2_pt_1_resP Title: Result of fit of p.d.f. pdfPass to dataset hPass ClassName:  TKey
key:  Name: ProcessID0 Title: c5340b0e-cc6e-11ec-bcef-8ca0b8bcbeef ClassName:  TKey
key:  Name: NUM_LooseID_DEN_genTracks_abseta_2_pt_1_resF Title: Result of fit of p.d.f. pdfFail to dataset hFail ClassName:  TKey

Try instead with:

for key in root_keys:
    if key.InheritsFrom('TCanvas'):
        print(key)
        c = key.ReadObj()
        c.SaveAs("test.png")

Here I get the error:

SysError in <TFile::ReadBuffer>: error reading from file ./UL_2018/fits_data/muon/generalTracks/Z/Run2018_UL/massRangeDown/NUM_TightID_DEN_genTracks/NUM_TightID_DEN_genTracks_abseta_3_pt_7.root (Input/output error)
Error in <TFile::Init>: ./UL_2018/fits_data/muon/generalTracks/Z/Run2018_UL/massRangeDown/NUM_TightID_DEN_genTracks/NUM_TightID_DEN_genTracks_abseta_3_pt_7.root failed to read the file type data.

Does it happen with all files, or only with one?

If only with one, it seems that this particular ROOT file is corrupt. See: Corrupted ROOT files without reason ?! - #15 by linev

Also, please check if you have read permission on this file/folder.

Ah, just with this one! When I go to the actual file and look around, I get:

SysError in <TFile::ReadBuffer>: error reading from file ./UL_2018/fits_data/muon/generalTracks/Z/Run2018_UL/massRangeDown/NUM_TightID_DEN_genTracks/NUM_TightID_DEN_genTracks_abseta_3_pt_7.root (Remote I/O error)
Error in <TFile::Init>: ./UL_2018/fits_data/muon/generalTracks/Z/Run2018_UL/massRangeDown/NUM_TightID_DEN_genTracks/NUM_TightID_DEN_genTracks_abseta_3_pt_7.root failed to read the file type data.
(TFile *) nullptr

@ferhue Thank you so much for that! Is there a way I can go around those null pointers (not include them when trying to get TCanvases from ~200 other files that are good)?