Plans for RNTuple Support for Objects like TH1D

Hello,

As we get closer to ROOT 7, I just wanted to see if there were any plans for RNTuples to natively support objects such as TH1Ds? Without the need for Streamer Objects, of course.

Thank you!
Oswaldo Cardenas

Hi Oswaldo,

As underlined by @silverweed through other channels, RNTuple already supports all TObjects out of the box, with the exception of those that have a custom streamer, such as TH1 or TF1. There are various reasons why we chose not to natively support these objects in RNTuple and the expectation is that future data models will not contain them directly. We also plan to release a new histogram class for ROOT 7 (which will already be available in ROOT 6 in the Experimental namespace) which will be natively supported by RNTuple.

In the meantime, to adapt to the real needs of our users, we built an “escape hatch” mechanism in RNTuple whereby ROOT objects with a custom streamer can still be embedded into it, using the so-called Streamer Fields. This is a way to embed any object with ROOT I/O support into an RNTuple, albeit sub-optimal (the objects won’t be fully split into columns but instead they are saved as binary blobs, negating the benefits of the columnarity of RNTuple). This is meant to be a temporary solution while experiments and users migrate to new data models that are fully compatible with RNTuple.

Below, you find an example on how to write and read TH1D with RNTuple using these Streamer Fields.

Please keep in mind that, at large scale, Streamer Fields are not a good way of using RNTuple and if you find yourself in need of consistently using them, it is a bit suspicious and we invite you to talk to us to understand your use case and potentially help you optimize your data model.

Best,
Danilo

#include <ROOT/RNTupleModel.hxx>
#include <ROOT/RNTupleWriter.hxx>
#include <ROOT/RNTupleReader.hxx>
#include <ROOT/RField.hxx>
#include <TH1D.h>
#include <iostream>

void write_th1d_rntuple(const char *ntupleName, const char *outfile)
{
  // Create model
  auto model = ROOT::RNTupleModel::Create();
  auto histoField = std::make_unique<ROOT::RStreamerField>("histo", "TH1D");
  model->AddField(std::move(histoField));
  
  // Create writer
  auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), ntupleName, outfile);
  
  // Get value pointer and write into it
  std::shared_ptr<TH1D> pHisto = writer->GetModel().GetDefaultEntry().GetPtr<TH1D>("histo");
  for (int i = 0; i < 10; ++i) {
    *pHisto = TH1D(("h" + std::to_string(i)).c_str(), "my histo", 64, 0, 16);
    writer->Fill();
  }

  // RNTuple gets flushed to storage at the end of the scope
}

void read_th1d_rntuple(const char *ntupleName, const char *infile)
{
  // Create reader
  auto reader = ROOT::RNTupleReader::Open(ntupleName, infile);

  // Get value pointer to read from
  std::shared_ptr<TH1D> pHisto = reader->GetModel().GetDefaultEntry().GetPtr<TH1D>("histo");

  // Read the values
  for (auto entry : reader->GetEntryRange()){
    reader->LoadEntry(entry);
    std::cout << "histo has " << pHisto->GetNdivisions() << " divisions\n";
  }
}

int main()
{
  const char *ntplName = "ntpl";
  const char *fileName = "/tmp/foo.root";
  
  write_th1d_rntuple(ntplName, fileName);
  read_th1d_rntuple(ntplName, fileName);
}