diff --git a/src/backend/datasources/filters/ROOTFilter.h b/src/backend/datasources/filters/ROOTFilter.h --- a/src/backend/datasources/filters/ROOTFilter.h +++ b/src/backend/datasources/filters/ROOTFilter.h @@ -30,11 +30,14 @@ #include "backend/datasources/filters/AbstractFileFilter.h" +#include +#include +#include + class ROOTFilterPrivate; class QStringList; class QIODevice; - /// Manages the importing of histograms from ROOT files class ROOTFilter : public AbstractFileFilter { Q_OBJECT @@ -61,44 +64,56 @@ /// List names of histograms contained in ROOT file QStringList listHistograms(const QString& fileName); + /// List names of trees contained in ROOT file + QStringList listTrees(const QString& fileName); + /// List names of leaves contained in ROOT tree + QVector listLeaves(const QString& fileName, const QString& treeName); /// Set the current histograms, which is one out of listHistograms - void setCurrentHistogram(const QString&); + void setCurrentObject(const QString&); /// Get the name of the currently set histogram - const QString currentHistogram() const; + const QString currentObject() const; /// Get preview data of the currently set histogram - QVector previewCurrentHistogram(const QString& fileName, + QVector previewCurrentObject(const QString& fileName, int first, int last); /// Get the number of bins in the current histogram - int binsInCurrentHistogram(const QString& fileName); + int rowsInCurrentObject(const QString& fileName); /** - * @brief Set the first bin of the histogram to be read + * @brief Set the first bin of the object to be read * - * The default of -1 skips the underflow bin with index 0 + * -1 skips the underflow bin of histograms */ - void setStartBin(const int bin); - /// Get the index of the first bin to be read - int startBin() const; + void setStartRow(const int bin); + /// Get the index of the first row to be read + int startRow() const; /** * @brief Set the last bin of the histogram to be read * - * The default of -1 skips the overflow bin + * -1 skips the overflow row of histograms */ - void setEndBin(const int bin); + void setEndRow(const int bin); /// Get the index of the last bin to be read - int endBin() const; + int endRow() const; /** * @brief Set the first column of the histogram to be read * * The following columns are available: Bin Center, Content, Error */ - void setColumns(const int columns); - /// Get the index of the first column to be read - int columns() const; + void setColumns(const QVector& columns); + /** + * @brief Get the colums to be read + * + * For histograms, the identifiers for location, content and error are given + * as the first part, the corresponding translation as the second part. + * For trees, the branch name and the leaf name are returned. + * + * @return A pair of strings with different content depending on the object type + */ + QVector columns() const; /// Save bin limitation settings void save(QXmlStreamWriter*) const override; diff --git a/src/backend/datasources/filters/ROOTFilter.cpp b/src/backend/datasources/filters/ROOTFilter.cpp --- a/src/backend/datasources/filters/ROOTFilter.cpp +++ b/src/backend/datasources/filters/ROOTFilter.cpp @@ -33,6 +33,8 @@ #include +#include + #ifdef HAVE_ZIP #include @@ -68,81 +70,82 @@ Q_UNUSED(filterName); } -void ROOTFilter::setCurrentHistogram(const QString& histogram) { - d->currentHistogram = histogram; +void ROOTFilter::setCurrentObject(const QString& object) { + d->currentObject = object; } -const QString ROOTFilter::currentHistogram() const { - return d->currentHistogram; +const QString ROOTFilter::currentObject() const { + return d->currentObject; } QStringList ROOTFilter::listHistograms(const QString& fileName) { return d->listHistograms(fileName); } -QVector ROOTFilter::previewCurrentHistogram(const QString& fileName, - int first, int last) { - return d->previewCurrentHistogram(fileName, first, last); +QStringList ROOTFilter::listTrees(const QString& fileName) { + return d->listTrees(fileName); +} + +QVector ROOTFilter::listLeaves(const QString& fileName, const QString& treeName) { + return d->listLeaves(fileName, treeName); } -int ROOTFilter::binsInCurrentHistogram(const QString& fileName) { - return d->binsInCurrentHistogram(fileName); +QVector ROOTFilter::previewCurrentObject(const QString& fileName, + int first, int last) { + return d->previewCurrentObject(fileName, first, last); } -void ROOTFilter::setStartBin(const int s) { - d->startBin = s; +int ROOTFilter::rowsInCurrentObject(const QString& fileName) { + return d->rowsInCurrentObject(fileName); } -int ROOTFilter::startBin() const { - return d->startBin; +void ROOTFilter::setStartRow(const int s) { + d->startRow = s; } -void ROOTFilter::setEndBin(const int e) { - d->endBin = e; +int ROOTFilter::startRow() const { + return d->startRow; } -int ROOTFilter::endBin() const { - return d->endBin; +void ROOTFilter::setEndRow(const int e) { + d->endRow = e; } -void ROOTFilter::setColumns(const int columns) { +int ROOTFilter::endRow() const { + return d->endRow; +} + +void ROOTFilter::setColumns(const QVector& columns) { d->columns = columns; } -int ROOTFilter::columns() const { +QVector ROOTFilter::columns() const { return d->columns; } void ROOTFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("rootFilter"); - writer->writeAttribute("startBin", QString::number(d->startBin) ); - writer->writeAttribute("endBin", QString::number(d->endBin) ); - writer->writeAttribute("columns", QString::number(d->columns) ); + writer->writeAttribute("startRow", QString::number(d->startRow) ); + writer->writeAttribute("endRow", QString::number(d->endRow) ); writer->writeEndElement(); } bool ROOTFilter::load(XmlStreamReader* reader) { QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); // read attributes - QString str = attribs.value("startBin").toString(); + QString str = attribs.value("startRow").toString(); if (str.isEmpty()) - reader->raiseWarning(attributeWarning.arg("'startBin'")); + reader->raiseWarning(attributeWarning.arg("'startRow'")); else - d->startBin = str.toInt(); + d->startRow = str.toInt(); - str = attribs.value("endBin").toString(); + str = attribs.value("endRow").toString(); if (str.isEmpty()) - reader->raiseWarning(attributeWarning.arg("'endBin'")); + reader->raiseWarning(attributeWarning.arg("'endRow'")); else - d->endBin = str.toInt(); - - str = attribs.value("columns").toString(); - if (str.isEmpty()) - reader->raiseWarning(attributeWarning.arg("'columns'")); - else - d->columns = str.toInt(); + d->endRow = str.toInt(); return true; } @@ -152,177 +155,295 @@ ROOTFilterPrivate::ROOTFilterPrivate() = default; void ROOTFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, - AbstractFileFilter::ImportMode importMode) { + AbstractFileFilter::ImportMode importMode) { DEBUG("ROOTFilterPrivate::readDataFromFile()"); setFile(fileName); - auto bins = readHistogram(); - const int nbins = (int)bins.size(); - - // skip underflow and overflow bins by default - int first = qMax(startBin, 0); - int last = qMin(endBin, nbins - 1); - - QStringList colNames = createHeaders(); - - QVector dataContainer; - const int columnOffset = dataSource->prepareImport(dataContainer, importMode, last - first + 1, colNames.size(), - colNames, QVector(colNames.size(), AbstractColumn::Numeric)); - - // read data - DEBUG("reading " << first - last + 1 << " lines"); + QStringList typeobject = currentObject.split(':'); + if (typeobject.size() < 2) + return; + if (typeobject.first() == QStringLiteral("Hist")) { + typeobject.removeFirst(); + auto bins = readHistogram(typeobject.join(':')); + const int nbins = static_cast(bins.size()); + + // skip underflow and overflow bins by default + int first = qMax(qAbs(startRow), 0); + int last = endRow < 0 ? nbins - 1 : qMax(first - 1, qMin(endRow, nbins - 1)); + + QStringList headers; + for (const auto& l : columns) { + headers << l.last(); + } - int c = 0; - Spreadsheet* spreadsheet = dynamic_cast(dataSource); - if (columns & ROOTFilter::Center) { - if (spreadsheet) - spreadsheet->column(columnOffset)->setPlotDesignation(Column::X); + QVector dataContainer; + const int columnOffset = dataSource->prepareImport(dataContainer, importMode, last - first + 1, columns.size(), + headers, QVector(columns.size(), + AbstractColumn::Numeric)); + + // read data + DEBUG(" reading " << first - last + 1 << " lines"); + + int c = 0; + Spreadsheet* spreadsheet = dynamic_cast(dataSource); + + for (const auto& l : columns) { + QVector& container = *static_cast*>(dataContainer[c]); + if (l.first() == QStringLiteral("center")) { + if (spreadsheet) + spreadsheet->column(columnOffset + c)->setPlotDesignation(Column::X); + for (int i = first; i <= last; ++i) + container[i - first] = (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) + : i == 0 ? bins.front().lowedge // -infinity + : -bins.front().lowedge; // +infinity + } else if (l.first() == QStringLiteral("low")) { + if (spreadsheet) + spreadsheet->column(columnOffset + c)->setPlotDesignation(Column::X); + for (int i = first; i <= last; ++i) + container[i - first] = bins[i].lowedge; + } else if (l.first() == QStringLiteral("content")) { + if (spreadsheet) + spreadsheet->column(columnOffset + c)->setPlotDesignation(Column::Y); + for (int i = first; i <= last; ++i) + container[i - first] = bins[i].content; + } else if (l.first() == QStringLiteral("error")) { + if (spreadsheet) + spreadsheet->column(columnOffset + c)->setPlotDesignation(Column::YError); + for (int i = first; i <= last; ++i) + container[i - first] = std::sqrt(bins[i].sumw2); + } + ++c; + } - QVector& container = *static_cast*>(dataContainer[c]); - for (int i = first; i <= last; ++i) - container[i - first] = (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) - : i == 0 ? bins.front().lowedge // -infinity - : -bins.front().lowedge; // +infinity - c++; - } - if (columns & ROOTFilter::Low) { - if (spreadsheet) - spreadsheet->column(columnOffset + c)->setPlotDesignation(Column::X); - - QVector& container = *static_cast*>(dataContainer[c]); - for (int i = first; i <= last; ++i) - container[i - first] = bins[i].lowedge; - c++; - } - if (columns & ROOTFilter::Content) { - if (spreadsheet) - spreadsheet->column(columnOffset + c)->setPlotDesignation(Column::Y); - - QVector& container = *static_cast*>(dataContainer[c]); - for (int i = first; i <= last; ++i) - container[i - first] = bins[i].content; - c++; - } - if (columns & ROOTFilter::Error) { - if (spreadsheet) - spreadsheet->column(columnOffset + c)->setPlotDesignation(Column::XError); + dataSource->finalizeImport(columnOffset, 0, columns.size() - 1, -1, QString(), importMode); + } else if (typeobject.first() == QStringLiteral("Tree")) { + typeobject.removeFirst(); + const QString treeName = typeobject.join(':'); + const int nentries = static_cast(currentROOTData->treeEntries(treeName.toStdString())); + + int first = qMax(qAbs(startRow), 0); + int last = qMax(first - 1, qMin(endRow, nentries - 1)); + + QStringList headers; + for (const auto& l : columns) { + QString lastelement = l.back(), leaf = l.front(); + bool isArray = false; + if (lastelement.front() == '[' && lastelement.back() == ']') { + lastelement.mid(1, lastelement.length() - 2).toUInt(&isArray); + } + if (!isArray || l.count() == 2) + headers << l.join(isArray ? QString() : QString(':')); + else + headers << l.first() + QChar(':') + l.at(1) + l.back(); + } - QVector& container = *static_cast*>(dataContainer[c]); - for (int i = first; i <= last; ++i) - container[i - first] = std::sqrt(bins[i].sumw2); - } + QVector dataContainer; + const int columnOffset = dataSource->prepareImport(dataContainer, importMode, last - first + 1, columns.size(), + headers, QVector(columns.size(), + AbstractColumn::Numeric)); + + int c = 0; + for (const auto& l : columns) { + size_t element = 0; + QString lastelement = l.back(), leaf = l.front(); + bool isArray = false; + if (lastelement.front() == '[' && lastelement.back() == ']') { + element = lastelement.mid(1, lastelement.length() - 2).toUInt(&isArray); + if (!isArray) + element = 0; + if (l.count() > 2) + leaf = l.at(1); + } else if (l.count() > 1) + leaf = l.at(1); + + QVector& container = *static_cast*>(dataContainer[c++]); + auto data = readTree(treeName, l.first(), leaf, element, last); + for (int i = first; i <= last; ++i) + container[i - first] = data[i]; + } - dataSource->finalizeImport(columnOffset, 0, colNames.size() - 1, -1, QString(), importMode); + dataSource->finalizeImport(columnOffset, 0, columns.size() - 1, -1, QString(), importMode); + } } void ROOTFilterPrivate::write(const QString& fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); - //TODO: writing ROOT not implemented yet } QStringList ROOTFilterPrivate::listHistograms(const QString& fileName) { setFile(fileName); QStringList histList; - for (const auto& hist : currentROOTHist->listHistograms()) { + for (const auto& hist : currentROOTData->listHistograms()) { histList << QString::fromStdString(hist); } return histList; } -QVector ROOTFilterPrivate::previewCurrentHistogram(const QString& fileName, int first, int last) { - DEBUG("previewCurrentHistogram()"); - +QStringList ROOTFilterPrivate::listTrees(const QString& fileName) { setFile(fileName); - auto bins = readHistogram(); - const int nbins = (int)bins.size(); - - last = qMin(nbins - 1, last); + QStringList treeList; + for (const auto& tree : currentROOTData->listTrees()) { + treeList << QString::fromStdString(tree); + } - QVector preview(qMax(last - first + 2, 1)); - preview.last() = createHeaders(); + return treeList; +} - // read data - DEBUG("reading " << preview.size() << " lines"); +QVector ROOTFilterPrivate::listLeaves(const QString& fileName, const QString& treeName) { + setFile(fileName); - if (columns & ROOTFilter::Center) { - for (int i = first; i <= last; ++i) - preview[i - first] << QString::number( - (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) - : i == 0 ? bins.front().lowedge // -infinity - : -bins.front().lowedge); // +infinity - } - if (columns & ROOTFilter::Low) { - for (int i = first; i <= last; ++i) - preview[i - first] << QString::number(bins[i].lowedge); - } - if (columns & ROOTFilter::Content) { - for (int i = first; i <= last; ++i) - preview[i - first] << QString::number(bins[i].content); - } - if (columns & ROOTFilter::Error) { - for (int i = first; i <= last; ++i) - preview[i - first] << QString::number(std::sqrt(bins[i].sumw2)); + QVector leafList; + for (const auto& leaf : currentROOTData->listLeaves(treeName.toStdString())) { + leafList << QStringList(QString::fromStdString(leaf.branch)); + if (leaf.branch != leaf.leaf) + leafList.last() << QString::fromStdString(leaf.leaf); + if (leaf.elements > 1) + leafList.last() << QString("[%1]").arg(leaf.elements); } - return preview; + return leafList; } -int ROOTFilterPrivate::binsInCurrentHistogram(const QString& fileName) { +QVector ROOTFilterPrivate::previewCurrentObject(const QString& fileName, int first, int last) { + DEBUG("ROOTFilterPrivate::previewCurrentObject()"); + setFile(fileName); - QStringList nameindex = currentHistogram.split(';'); - bool ok = nameindex.size() > 1; - int cycle = ok ? nameindex.last().toInt(&ok) : 1; - if (ok) { - nameindex.removeLast(); - } else { - cycle = 1; - } + QStringList typeobject = currentObject.split(':'); + if (typeobject.size() < 2) + return QVector(1, QStringList()); + + if (typeobject.first() == QStringLiteral("Hist")) { + typeobject.removeFirst(); + auto bins = readHistogram(typeobject.join(':')); + const int nbins = static_cast(bins.size()); + + last = qMin(nbins - 1, last); + + QVector preview(qMax(last - first + 2, 1)); + DEBUG(" reading " << preview.size() - 1 << " lines"); + + // set headers + for (const auto& l : columns) { + preview.last() << l.last(); + } + + // read data + for (const auto& l : columns) { + if (l.first() == QStringLiteral("center")) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number( + (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) + : i == 0 ? bins.front().lowedge // -infinity + : -bins.front().lowedge); // +infinity + } else if (l.first() == QStringLiteral("low")) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number(bins[i].lowedge); + } else if (l.first() == QStringLiteral("content")) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number(bins[i].content); + } else if (l.first() == QStringLiteral("error")) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number(std::sqrt(bins[i].sumw2)); + } + } + + return preview; + } else if (typeobject.first() == QStringLiteral("Tree")) { + typeobject.removeFirst(); + const QString treeName = typeobject.join(':'); + last = qMin(last, currentROOTData->treeEntries(treeName.toStdString()) - 1); + + QVector preview(qMax(last - first + 2, 1)); + DEBUG(" reading " << preview.size() - 1 << " lines"); + + // read data leaf by leaf and set headers + for (const auto& l : columns) { + size_t element = 0; + QString lastelement = l.back(), leaf = l.front(); + bool isArray = false; + if (lastelement.front() == '[' && lastelement.back() == ']') { + element = lastelement.mid(1, lastelement.length() - 2).toUInt(&isArray); + if (!isArray) + element = 0; + if (l.count() > 2) + leaf = l.at(1); + } else if (l.count() > 1) + leaf = l.at(1); + + auto data = readTree(treeName, l.first(), leaf, element, last); + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number(data[i]); + if (!isArray || l.count() == 2) + preview.last() << l.join(isArray ? QString() : QString(':')); + else + preview.last() << l.first() + QChar(':') + l.at(1) + l.back(); + } - return currentROOTHist->histogramBins(nameindex.join(';').toStdString(), cycle); + return preview; + } else + return QVector(1, QStringList()); } -QStringList ROOTFilterPrivate::createHeaders() { - QStringList colNames; - if (columns & ROOTFilter::Center) - colNames << i18n("Bin Center"); - if (columns & ROOTFilter::Low) - colNames << i18n("Low Edge"); - if (columns & ROOTFilter::Content) - colNames << i18n("Content"); - if (columns & ROOTFilter::Error) - colNames << i18n("Error"); - return colNames; +int ROOTFilterPrivate::rowsInCurrentObject(const QString& fileName) { + setFile(fileName); + + QStringList typeobject = currentObject.split(':'); + if (typeobject.size() < 2) + return 0; + if (typeobject.first() == QStringLiteral("Hist")) { + typeobject.removeFirst(); + QStringList nameindex = typeobject.join(':').split(';');; + bool ok = nameindex.size() > 1; + int cycle = ok ? nameindex.last().toInt(&ok) : 1; + if (ok) { + nameindex.removeLast(); + } else { + cycle = 1; + } + + return currentROOTData->histogramBins(nameindex.join(';').toStdString(), cycle); + } else if (typeobject.first() == QStringLiteral("Tree")) { + typeobject.removeFirst(); + return currentROOTData->treeEntries(typeobject.join(':').toStdString()); + } else + return 0; } void ROOTFilterPrivate::setFile(const QString& fileName) { - if (!currentROOTHist || fileName != currentFile) { + if (!currentROOTData || fileName != currentFile) { currentFile = fileName; - currentROOTHist.reset(new ROOTHist(fileName.toStdString())); + currentROOTData.reset(new ROOTData(fileName.toStdString())); } } -std::vector ROOTFilterPrivate::readHistogram() { - QStringList nameindex = currentHistogram.split(';'); +std::vector ROOTFilterPrivate::readHistogram(const QString& histName) { + QStringList nameindex = histName.split(';'); bool ok = nameindex.size() > 1; int cycle = ok ? nameindex.last().toInt(&ok) : 1; if (ok) { nameindex.removeLast(); } else { cycle = 1; } - return currentROOTHist->readHistogram(nameindex.join(';').toStdString(), cycle); + return currentROOTData->readHistogram(nameindex.join(';').toStdString(), cycle); +} + +std::vector ROOTFilterPrivate::readTree(const QString& treeName, const QString& branchName, const QString& leafName, int element, int last) +{ + return currentROOTData->listEntries(treeName.toStdString(), branchName.toStdString(), leafName.toStdString(), element, last + 1); } -/******************** ROOTHist implementation ************************/ -namespace ROOTHistHelpers { +/******************** ROOTData implementation ************************/ + +namespace ROOTDataHelpers { /// Read value from stream template @@ -352,11 +473,18 @@ return r.val; } +/// Read value from buffer and cast to U +template +U readcast(char*& s) { + return static_cast(read(s)); +} + /// Get version of ROOT object, obtain number of bytes in object short Version(char*& buffer, size_t& count) { + // root/io/io/src/TBufferFile.cxx -> ReadVersion count = read(buffer); short version = (count & 0x40000000) ? read(buffer) : read(buffer -= 4); - count &= (count & 0x40000000) ? (~0x40000000) : 0; + count = (count & 0x40000000) ? (count & ~0x40000000) - 2 : 2; return version; } @@ -369,10 +497,9 @@ /// Skip ROOT object void Skip(char*& buffer, const size_t& n) { for (size_t i = 0; i < n; ++i) { - char* nbuf = buffer + 4; size_t count; Version(buffer, count); - buffer = nbuf + count; + buffer += count; } } @@ -384,6 +511,7 @@ /// Get TString std::string String(char*& buffer) { + // root/io/io/src/TBufferFile.cxx -> ReadTString size_t s = *(buffer++); if (s == 0) return std::string(); @@ -395,163 +523,277 @@ } } +/// Get the header of an object in TObjArray +std::string readObject(char*& buf, char* const buf0, std::map& tags) +{ + // root/io/io/src/TBufferFile.cxx -> ReadObjectAny + std::string clname; + unsigned int tag = read(buf); + if (tag & 0x40000000) { + tag = read(buf); + if (tag == 0xFFFFFFFF) { + tags[buf - buf0 - 2] = clname = buf; + buf += clname.size() + 1; + } else { + clname = tags[tag & ~0x80000000]; + } + } + + return clname; +} + } -using namespace ROOTHistHelpers; +using namespace ROOTDataHelpers; -ROOTHist::ROOTHist(const std::string& filename) : filename(filename) { +ROOTData::ROOTData(const std::string& filename) : filename(filename) { // The file structure is described in root/io/io/src/TFile.cxx std::ifstream is(filename, std::ifstream::binary); std::string root(4, 0); is.read(const_cast(root.data()), 4); if (root != "root") return; - is.seekg(8); // skip version - int pos = read(is); - is.seekg(16); - int lastpos = read(is); + int fileVersion = read(is); + long int pos = read(is); + long int endpos = fileVersion < 1000000 ? read(is) : read(is); is.seekg(33); - compression = read(is); + int compression = read(is); compression = compression > 0 ? compression : 0; - while (is.good() && pos < lastpos) { + while (is.good() && pos < endpos) { is.seekg(pos); - size_t lcdata = read(is); - is.seekg(2, is.cur); // short version = read(is); + int lcdata = read(is); + if (lcdata == 0) { + break; + } + if (lcdata < 0) { + pos -= lcdata; + continue; + } + short version = read(is); size_t ldata = read(is); - is.seekg(4, is.cur); + is.seekg(4, is.cur); // skip the date size_t lkey = read(is); short cycle = read(is); - is.seekg(8, is.cur); + is.seekg(version > 1000 ? 16 : 8, is.cur); // skip seek positions std::string cname(read(is), 0); is.read(&cname[0], cname.size()); + std::string name(read(is), 0); + is.read(&name[0], name.size()); + std::string title(read(is), 0); + is.read(&title[0], title.size()); + + ContentType type = Invalid; if (cname.size() == 4 && cname.substr(0, 3) == "TH1") { - KeyBuffer::ContentType type; - switch (cname[3]) { - case 'D': - type = KeyBuffer::ContentType::Double; - break; - case 'F': - type = KeyBuffer::ContentType::Float; + type = histType(cname[3]); + } else if (cname == "TTree") + type = Tree; + else if (cname.substr(0, 7) == "TNtuple") + type = NTuple; + else if (cname == "TBasket") + type = Basket; + else if (cname == "TList" && name == "StreamerInfo") + type = Streamer; + + if (type) { + if (type == Basket) + is.seekg(19, std::ifstream::cur); // TODO read info instead? + KeyBuffer buffer; + buffer.type = Invalid; + // see root/io/io/src/TKey.cxx for reference + int complib = 0; + if (compression) { + // Default: compression level + // ZLIB: 100 + compression level + // LZ4: 400 + compression level + // do not rely on this, but read the header + std::string lib(2, 0); + is.read(&lib[0], 2); + complib = lib == "ZL" ? 1 : + lib == "XZ" ? 2 : + lib == "CS" ? 3 : + lib == "L4" ? 4 : 0; + } + if (complib > 0) { +# ifdef HAVE_ZIP + // see root/core/zip/src/RZip.cxx -> R__unzip + char method = is.get(); + size_t chcdata = is.get(); + chcdata |= (is.get() << 8); + chcdata |= (is.get() << 16); + size_t chdata = is.get(); + chdata |= (is.get() << 8); + chdata |= (is.get() << 16); + + if (chcdata == lcdata - lkey - 9 && chdata == ldata) { + if (complib == 1 && method == Z_DEFLATED) { + buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::zlib, + pos + lkey + 9, chcdata, chdata, 0}; + } else if (complib == 4 && method == LZ4_versionNumber() / 10000) { + buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::lz4, + pos + lkey + 9 + 8, chcdata - 8, chdata, 0}; + } + } +# endif + } else { + buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::none, + pos + lkey, ldata, ldata, 0}; + } + switch (buffer.type) { + case Basket: + basketkeys.emplace(pos, buffer); break; - case 'I': - type = KeyBuffer::ContentType::Int; + case Tree: + case NTuple: { + auto it = treekeys.find(name); + if (it != treekeys.end()) { + // TTrees may be written several times, only consider last cycle + if (buffer.cycle > it->second.cycle) { + it->second = buffer; + } + } else + treekeys.emplace(name, buffer); break; - case 'S': - type = KeyBuffer::ContentType::Short; + } case Streamer: + readStreamerInfo(buffer); break; - case 'C': - type = KeyBuffer::ContentType::Byte; + case Double: case Float: case Int: case Short: case Byte: + histkeys.emplace(name + ';' + std::to_string(cycle), buffer); break; - default: - type = KeyBuffer::ContentType::Invalid; + case Invalid: case Long: case Bool: case CString: break; } - if (type) { - std::string name(read(is), 0); - is.read(&name[0], name.size()); - std::string title(read(is), 0); - is.read(&title[0], title.size()); - - auto it = histkeys.end(); - // see root/io/io/src/TKey.cxx for reference - if (compression && ldata > 256) { -# ifdef HAVE_ZIP - // Default: compression level - // ZLIB: 100 + compression level - // LZ4: 400 + compression level - if (compression / 100 <= 1 || compression / 100 == 4) { - // see root/core/zip/src/RZip.cxx -> R__unzip - std::string lib(2, 0); - is.read(&lib[0], 2); - char method = is.get(); - size_t chcdata = is.get(); - chcdata |= (is.get() << 8); - chcdata |= (is.get() << 16); - size_t chdata = is.get(); - chdata |= (is.get() << 8); - chdata |= (is.get() << 16); - - if (chcdata == lcdata - lkey - 9 && chdata == ldata) { - if (lib == "ZL" && method == Z_DEFLATED) { - it = histkeys.emplace(name + ';' + std::to_string(cycle), - KeyBuffer{name, title, cycle, type, KeyBuffer::zlib, - pos + lkey + 9, chcdata, chdata, 0} - ).first; - } else if (lib == "L4" && method == LZ4_versionNumber() / (100 * 100)) { - it = histkeys.emplace(name + ';' + std::to_string(cycle), - KeyBuffer{name, title, cycle, type, KeyBuffer::lz4, - pos + lkey + 9 + 8, chcdata - 8, chdata, 0} - ).first; - } - } - } -# endif - } else { - it = histkeys.emplace(name + ';' + std::to_string(cycle), - KeyBuffer{name, title, cycle, type, KeyBuffer::none, - pos + lkey, ldata, ldata, 0} - ).first; - } - if (it != histkeys.end()) - readNBins(it->second); - } } - pos += (int)lcdata; + pos += lcdata; + } + + // Create default object structures if no StreamerInfo was found. + // Obtained by running the following in ROOT with a file passed as an argument: + // + // _file0->GetStreamerInfoList()->Print() + // + // auto l = (TStreamerInfo*)_file0->GetStreamerInfoList()->At(ENTRYNUMBER); + // l->Print(); + // for (int i=0; iGetNelement(); ++i) { + // auto e = l->GetElement(i); + // e->Print(); + // cout << e->GetFullName() << " " << e->GetTypeName() << " " << e->GetSize() << endl; + // } + + static const StreamerInfo dummyobject{"Object", 0, std::string(), false, false}; + if (!treekeys.empty()) { + if (!streamerInfo.count("TTree")) { + streamerInfo["TTree"] = {dummyobject, dummyobject, dummyobject, dummyobject, + StreamerInfo{"fEntries", 8, std::string(), false, false}, + StreamerInfo{std::string(), 5 * 8 + 4 * 4, std::string(), false, false}, + StreamerInfo{"fNClusterRange", 4, std::string(), true, false}, + StreamerInfo{std::string(), 6 * 8, std::string(), false, false}, + StreamerInfo{"fNClusterRangeEnd", 8, "fNClusterRange", false, true}, + StreamerInfo{"fNClusterSize", 8, "fNClusterRange", false, true}, + StreamerInfo{"fBranches", 0, std::string(), false, false} + }; + } + if (!streamerInfo.count("TBranch")) { + streamerInfo["TBranch"] = {StreamerInfo{"TNamed", 0, std::string(), false, false}, dummyobject, + StreamerInfo{std::string(), 3 * 4, std::string(), false, false}, + StreamerInfo{"fWriteBasket", 4, std::string(), false, false}, + StreamerInfo{std::string(), 8 + 4, std::string(), false, false}, + StreamerInfo{"fMaxBaskets", 4, std::string(), true, false}, + StreamerInfo{std::string(), 4 + 4 * 8, std::string(), false, false}, + StreamerInfo{"fBranches", 0, std::string(), false, false}, + StreamerInfo{"fLeaves", 0, std::string(), false, false}, + StreamerInfo{"fBaskets", 0, std::string(), false, false}, + StreamerInfo{"fBasketBytes", 4, "fMaxBaskets", false, true}, + StreamerInfo{"fBasketEntry", 8, "fMaxBaskets", false, true}, + StreamerInfo{"fBasketSeek", 8, "fMaxBaskets", false, true} + }; + } + } + if (!histkeys.empty()) { + if (!streamerInfo.count("TH1")) { + streamerInfo["TH1"] = {dummyobject, dummyobject, dummyobject, dummyobject, + StreamerInfo{"fNcells", 4, std::string(), false, false}, + StreamerInfo{"fXaxis", 0, std::string(), false, false}, + StreamerInfo{"fYaxis", 0, std::string(), false, false}, + StreamerInfo{"fZaxis", 0, std::string(), false, false}, + StreamerInfo{std::string(), 2 * 2 + 8 * 8, std::string(), false, false}, + dummyobject, + StreamerInfo{"fSumw2", 0, std::string(), false, false}}; + } + if (!streamerInfo.count("TAxis")) { + streamerInfo["TAxis"] = {dummyobject, dummyobject, + StreamerInfo{"fNbins", 4, std::string(), false, false}, + StreamerInfo{"fXmin", 8, std::string(), false, false}, + StreamerInfo{"fXmax", 8, std::string(), false, false}, + StreamerInfo{"fXbins", 0, std::string(), false, false}}; + } + } + + for (auto& tree : treekeys) + readNEntries(tree.second); + for (auto& hist : histkeys) + readNBins(hist.second); +} + +void ROOTData::readNBins(ROOTData::KeyBuffer& kbuffer) { + std::string buffer = data(kbuffer); + if (!buffer.empty()) { + char* buf = &buffer[0]; + std::map counts; + Version(buf); // TH1(D/F/I/S/C) + Version(buf); // TH1 + advanceTo(buf, streamerInfo.find("TH1")->second, std::string(), "fNcells", counts); + kbuffer.nrows = read(buf); // fNcells } } -std::vector ROOTHist::listHistograms() const { +std::vector ROOTData::listHistograms() const { std::vector l; - for (auto& n : histkeys) + for (auto& n : histkeys) { l.emplace_back(n.first); - + } return l; } -std::vector ROOTHist::readHistogram(const std::string& name, int cycle) { +std::vector ROOTData::readHistogram(const std::string& name, int cycle) { auto it = histkeys.find(name + ';' + std::to_string(cycle)); if (it == histkeys.end()) - return std::vector(); + return std::vector(); std::string buffer = data(it->second); if (!buffer.empty()) { - // The object structure can be retrieved from TFile::GetStreamerInfoList(). - // Every ROOT object contains a version number which may include the byte count - // for the object. The latter is currently assumed to be present to skip unused - // objects. No checks are performed. The corresponding ROOT code is quite nested - // but the actual readout is straight forward. - // Files for reference: - // root/io/io/src/TBufferFile.cxx -> ReadVersion, ReadTString, ReadObjectAny - // root/core/cont/src/TArrayD.cxx -> Streamer - // root/hist/hist/src/TH1.cxx -> Streamer - char *buf = &buffer[0]; + char* buf = &buffer[0]; + std::map counts; + auto& streamerTH1 = streamerInfo.find("TH1")->second; + auto& streamerTAxis = streamerInfo.find("TAxis")->second; + size_t count; - Version(buf, count); // TH1(D/F/I/S/C) - char* dbuf = buf + 4; + Version(buf); // TH1(D/F/I/S/C) Version(buf, count); // TH1 - dbuf += count; - Skip(buf, 4); // skip TNamed, TAttLine, TAttFill, TAttMarker + char* const dbuf = buf + count; + advanceTo(buf, streamerTH1, std::string(), "fNcells", counts); std::vector r(read(buf)); // fNcells if (r.size() < 3) return std::vector(); r.front().lowedge = -std::numeric_limits::infinity(); + advanceTo(buf, streamerTH1, "fNcells", "fXaxis", counts); // x-Axis - char* nbuf = buf + 4; Version(buf, count); // TAxis - nbuf += count; - Skip(buf, 2); // skip TNamed, TAttAxis - - int nbins = read(buf); - double xmin = read(buf); - double xmax = read(buf); - const size_t nborders = read(buf); - + char* const nbuf = buf + count; + advanceTo(buf, streamerTAxis, std::string(), "fNbins", counts); + const int nbins = read(buf); + advanceTo(buf, streamerTAxis, "fNbins", "fXmin", counts); + const double xmin = read(buf); + advanceTo(buf, streamerTAxis, "fXmin", "fXmax", counts); + const double xmax = read(buf); + advanceTo(buf, streamerTAxis, "fXmax", "fXbins", counts); + const size_t nborders = read(buf); // TArrayD + // root/core/cont/src/TArrayD.cxx -> Streamer if (nborders == r.size() - 1) { for (size_t i = 0; i < nborders; ++i) { r[i + 1].lowedge = read(buf); @@ -563,81 +805,344 @@ r[i + 1].lowedge = static_cast(i) * scale + xmin; } } - buf = nbuf; - Skip(buf, 2); // skip y-Axis and z-Axis; - buf += 68; // skip 2 shorts and 8 doubles - buf += read(buf) * 8; // skip fContour array + buf = nbuf; // go beyond x-Axis - if (static_cast(read(buf)) == r.size()) { + advanceTo(buf, streamerTH1, "fXaxis", "fSumw2", counts); + if (static_cast(read(buf)) == r.size()) { // TArrayD for (auto& b : r) b.sumw2 = read(buf); // always double } - buf = dbuf; // skip to contents of TH1(D/F/I/S/C) if (static_cast(read(buf)) == r.size()) { - switch (it->second.type) { - case KeyBuffer::ContentType::Double: - for (auto& b : r) - b.content = read(buf); - break; - case KeyBuffer::ContentType::Float: - for (auto& b : r) - b.content = read(buf); - break; - case KeyBuffer::ContentType::Int: - for (auto& b : r) - b.content = read(buf); - break; - case KeyBuffer::ContentType::Short: - for (auto& b : r) - b.content = read(buf); - break; - case KeyBuffer::ContentType::Byte: - for (auto& b : r) - b.content = read(buf); - break; - case KeyBuffer::ContentType::Invalid: // never reached - default: - break; - } + auto readf = readType(it->second.type); + for (auto& b : r) + b.content = readf(buf); } return r; } else return std::vector(); } -void ROOTHist::readNBins(ROOTHist::KeyBuffer& kbuffer) { +void ROOTData::readNEntries(ROOTData::KeyBuffer& kbuffer) { std::string buffer = data(kbuffer); if (!buffer.empty()) { - // The object structure can be retrieved from TFile::GetStreamerInfoList(). - // Every ROOT object contains a version number which may include the byte count - // for the object. The latter is currently assumed to be present to skip unused - // objects. No checks are performed. The corresponding ROOT code is quite nested - // but the actual readout is straight forward. - // Files for reference: - // root/io/io/src/TBufferFile.cxx -> ReadVersion, ReadTString, ReadObjectAny - // root/core/cont/src/TArrayD.cxx -> Streamer - // root/hist/hist/src/TH1.cxx -> Streamer - char *buf = &buffer[0]; + char* buf = &buffer[0]; + std::map counts; + if (kbuffer.type == NTuple) + Version(buf); // TNtuple(D) + Version(buf); // TTree + advanceTo(buf, streamerInfo.find("TTree")->second, std::string(), "fEntries", counts); + kbuffer.nrows = read(buf); // fEntries + } +} + +std::vector ROOTData::listTrees() const { + std::vector l; + for (auto& n : treekeys) { + l.emplace_back(n.first); + } + return l; +} + +std::vector ROOTData::listLeaves(const std::string& treename) const { + std::vector leaves; + + auto it = treekeys.find(treename); + if (it == treekeys.end()) + return leaves; + + std::ifstream is(filename, std::ifstream::binary); + std::string datastring = data(it->second, is); + if (datastring.empty()) + return leaves; + + char* buf = &datastring[0]; + char* const buf0 = buf - it->second.keylength; + std::map counts; + auto& streamerTBranch = streamerInfo.find("TBranch")->second; + + if (it->second.type == NTuple) + Version(buf); // TNtuple(D) + Version(buf); // TTree + advanceTo(buf, streamerInfo.find("TTree")->second, std::string(), "fBranches", counts); + + // read the list of branches + Version(buf); // TObjArray + SkipObject(buf); + String(buf); + const size_t nbranches = read(buf); + const size_t lowb = read(buf); + std::map tags; + for (size_t i = 0; i < nbranches; ++i) { + std::string clname = readObject(buf, buf0, tags); size_t count; - Version(buf, count); // TH1(D/F/I/S/C) - char* dbuf = buf + 4; - Version(buf, count); // TH1 - dbuf += count; - Skip(buf, 4); // skip TNamed, TAttLine, TAttFill, TAttMarker + Version(buf, count); // TBranch or TBranchElement + char* const nbuf = buf + count; + if (i >= lowb) { + if (clname == "TBranchElement") { + Version(buf); // TBranch + } + advanceTo(buf, streamerTBranch, std::string(), "TNamed", counts); + Version(buf); // TNamed + SkipObject(buf); + const std::string branch = String(buf); + String(buf); + // TODO add reading of nested branches (fBranches) + advanceTo(buf, streamerTBranch, "TNamed", "fLeaves", counts); + + // fLeaves + Version(buf); // TObjArray + SkipObject(buf); + String(buf); + const size_t nleaves = read(buf); + const size_t lowb = read(buf); + for (size_t i = 0; i < nleaves; ++i) { + std::string clname = readObject(buf, buf0, tags); + Version(buf, count); // TLeaf(D/F/B/S/I/L/C/O) + char* nbuf = buf + count; + if (i >= lowb && clname.size() == 6 && clname.compare(0, 5, "TLeaf") == 0) { + Version(buf); // TLeaf + Version(buf); // TNamed + SkipObject(buf); + const std::string leafname = String(buf); + const std::string leaftitle = String(buf); + size_t elements = read(buf); + int bytes = read(buf); + if ((leafType(clname.back()) & 0xF) != bytes) + qDebug() << "ROOTData: type " << clname.back() << " does not match its size!"; + buf += 5; + leaves.emplace_back(LeafInfo{branch, leafname, leafType(clname.back()), !read(buf), elements}); + } + + buf = nbuf; + } + } - kbuffer.nbins = read(buf); // fNcells + buf = nbuf; } + return leaves; } -std::string ROOTHist::data(const ROOTHist::KeyBuffer& buffer) const { +template +std::vector ROOTData::listEntries(const std::string& treename, const std::string& branchname, const std::string& leafname, const size_t element, const size_t nentries) const { + std::vector entries; + + auto it = treekeys.find(treename); + if (it == treekeys.end()) + return entries; + + std::ifstream is(filename, std::ifstream::binary); + std::string datastring = data(it->second, is); + if (datastring.empty()) + return entries; + + char* buf = &datastring[0]; + char* const buf0 = buf - it->second.keylength; + std::map counts; + auto& streamerTTree = streamerInfo.find("TTree")->second; + auto& streamerTBranch = streamerInfo.find("TBranch")->second; + + if (it->second.type == NTuple) + Version(buf); // TNtuple(D) + Version(buf); // TTree + advanceTo(buf, streamerTTree, std::string(), "fEntries", counts); + entries.reserve(std::min(static_cast(read(buf)), nentries)); // reserve space (maximum for number of entries) + advanceTo(buf, streamerTTree, "fEntries", "fBranches", counts); + + // read the list of branches + Version(buf); // TObjArray + SkipObject(buf); + String(buf); + const size_t nbranches = read(buf); + const size_t lowb = read(buf); + std::map tags; + for (size_t i = 0; i < nbranches; ++i) { + std::string clname = readObject(buf, buf0, tags); + size_t count; + Version(buf, count); // TBranch or TBranchElement + char* const nbuf = buf + count; + if (i >= lowb) { + if (clname == "TBranchElement") { + Version(buf); + } + Version(buf); // TNamed + SkipObject(buf); + const std::string currentbranch = String(buf); + String(buf); + + advanceTo(buf, streamerTBranch, "TNamed", "fWriteBasket", counts); + int fWriteBasket = read(buf); + // TODO add reading of nested branches (fBranches) + advanceTo(buf, streamerTBranch, "fWriteBasket", "fLeaves", counts); + + // fLeaves + Version(buf); // TObjArray + SkipObject(buf); + String(buf); + const size_t nleaves = read(buf); + const size_t lowb = read(buf); + int leafoffset = 0, leafcount = 0, leafcontent = 0, leafsize = 0; + bool leafsign = false; + ContentType leaftype = Invalid; + for (size_t i = 0; i < nleaves; ++i) { + std::string clname = readObject(buf, buf0, tags); + Version(buf, count); // TLeaf(D/F/L/I/S/B/O/C/Element) + char* nbuf = buf + count; + if (currentbranch == branchname) { + if (i >= lowb && clname.size() >= 5 && clname.compare(0, 5, "TLeaf") == 0) { + Version(buf); // TLeaf + Version(buf); // TNamed + SkipObject(buf); + const bool istheleaf = (clname.size() == 6 && leafname == String(buf)); + String(buf); + const int len = read(buf); + const int size = read(buf); + if (istheleaf) { + leafoffset = leafcount; + leafsize = size; + leaftype = leafType(clname.back()); + } + leafcount += len * size; + if (istheleaf) { + leafcontent = leafcount - leafoffset; + buf += 1; + leafsign = !read(buf); + } + } + } + + buf = nbuf; + } + if (leafcontent == 0) { + buf = nbuf; + continue; + } + + if (static_cast(element) * leafsize >= leafcontent) { + qDebug() << "ROOTData: " << leafname.c_str() << " only contains " << leafcontent / leafsize << " elements."; + break; + } + + advanceTo(buf, streamerTBranch, "fLeaves", "fBaskets", counts); + // fBaskets (probably empty) + Version(buf, count); // TObjArray + char* const basketsbuf = buf += count + 1; // TODO there is one byte to be skipped in fBaskets, why is that? + + advanceTo(buf, streamerTBranch, "fBaskets", "fBasketEntry", counts); + for (int i = 0; i <= fWriteBasket; ++i) { + if (static_cast(read(buf)) > nentries) { + fWriteBasket = i; + break; + } + } + // rewind to the end of fBaskets and look for the fBasketSeek array + advanceTo(buf = basketsbuf, streamerTBranch, "fBaskets", "fBasketSeek", counts); + auto readf = readType(leaftype, leafsign); + for (int i = 0; i < fWriteBasket; ++i) { + size_t pos = read(buf); + auto it = basketkeys.find(pos); + if (it != basketkeys.end()) { + std::string basketbuffer = data(it->second); + if (!basketbuffer.empty()) { + char* bbuf = &basketbuffer[0]; + char* const bufend = bbuf + basketbuffer.size(); + while (bbuf + leafcount <= bufend && entries.size() < nentries) { + bbuf += leafoffset + leafsize * element; + entries.emplace_back(readf(bbuf)); + bbuf += leafcount - leafsize * (element + 1) - leafoffset; + } + } + } else { + qDebug() << "ROOTData: fBasketSeek(" << i << "): " << pos << " (not available)"; + } + } + } + + buf = nbuf; + } + + return entries; +} + +ROOTData::ContentType ROOTData::histType(const char type) +{ + switch (type) { + case 'D': + return Double; + case 'F': + return Float; + case 'I': + return Int; + case 'S': + return Short; + case 'C': + return Byte; + default: + return Invalid; + } +} + +ROOTData::ContentType ROOTData::leafType(const char type) +{ + switch (type) { + case 'D': + return Double; + case 'F': + return Float; + case 'L': + return Long; + case 'I': + return Int; + case 'S': + return Short; + case 'B': + return Byte; + case 'O': + return Bool; + case 'C': + return CString; + default: + return Invalid; + } +} + +template +T (*ROOTData::readType(ROOTData::ContentType type, bool sign) const)(char*&) +{ + switch (type) { + case Double: + return readcast; + case Float: + return readcast; + case Long: + return sign ? readcast : readcast; + case Int: + return sign ? readcast : readcast; + case Short: + return sign ? readcast : readcast; + case Byte: + return sign ? readcast : readcast; + case Bool: + return readcast; + case CString: + case Tree: + case NTuple: + case Basket: + case Streamer: + case Invalid: + break; + } + return readcast; +} + +std::string ROOTData::data(const ROOTData::KeyBuffer& buffer) const { std::ifstream is(filename, std::ifstream::binary); return data(buffer, is); } -std::string ROOTHist::data(const ROOTHist::KeyBuffer& buffer, std::ifstream& is) const { +std::string ROOTData::data(const ROOTData::KeyBuffer& buffer, std::ifstream& is) const { std::string data(buffer.count, 0); is.seekg(buffer.start); if (buffer.compression == KeyBuffer::none) { @@ -661,7 +1166,159 @@ return std::string(); } -// needs to be after ROOTHistHelpers namespace declaration +void ROOTData::readStreamerInfo(const ROOTData::KeyBuffer& buffer) +{ + std::ifstream is(filename, std::ifstream::binary); + std::string datastring = data(buffer, is); + if (!datastring.empty()) { + char* buf = &datastring[0]; + char* const buf0 = buf - buffer.keylength; + Version(buf); + SkipObject(buf); // TCollection + String(buf); + const int nobj = read(buf); + std::map tags; + for (int i = 0; i < nobj; ++i) { + std::string clname = readObject(buf, buf0, tags); + size_t count; + Version(buf, count); + char* const nbuf = buf + count; + if (clname == "TStreamerInfo") { + Version(buf); + SkipObject(buf); + std::vector& sinfo = streamerInfo[String(buf)]; + String(buf); + buf += 8; // skip check sum and version + + clname = readObject(buf, buf0, tags); + Version(buf, count); + if (clname != "TObjArray") { + buf += count; + continue; + } + + SkipObject(buf); // TObjArray + String(buf); + const int nobj = read(buf); + const int lowb = read(buf); + for (int i = 0; i < nobj; ++i) { + std::string clname = readObject(buf, buf0, tags); + Version(buf, count); + char* const nbuf = buf + count; + + const bool isbasicpointer = clname == "TStreamerBasicPointer"; + const bool ispointer = isbasicpointer || clname == "TStreamerObjectPointer"; + if (i >= lowb) { + if (ispointer || + clname == "TStreamerBase" || + clname == "TStreamerBasicType" || + clname == "TStreamerObject" || + clname == "TStreamerObjectAny" || + clname == "TStreamerString" || + clname == "TStreamerSTL") + { + Version(buf); // TStreamerXXX + Version(buf); // TStreamerElement + SkipObject(buf); + const std::string name = String(buf); + const std::string title = String(buf); + int type = read(buf); + size_t size = read(buf); + + if (clname.compare(0, 15, "TStreamerObject") == 0) + size = 0; + std::string counter; + bool iscounter = false; + if (ispointer) { + if (!title.empty() && title.front() == '[') { + const size_t endref = title.find(']', 1); + if (endref != title.npos) { + counter = title.substr(1, endref - 1); + } + } + if (isbasicpointer) { + // see root/io/io/inc/TStreamerInfo.h -> TStreamerInfo::EReadWrite + switch(type - 40) { + case 1: // char + case 11: // unsigned char + size = 1; + break; + case 2: // short + case 12: // unsigned short + case 19: // float16 + size = 2; + break; + case 3: // int + case 5: // float + case 9: // double32 + case 13: // unsigned int + size = 4; + break; + case 4: // long + case 8: // double + case 14: // unsigned long + case 16: // long + case 17: // unsigned long + size = 8; + break; + } + } + } else if (clname == "TStreamerBasicType") { + iscounter = type == 6; // see root/io/io/inc/TStreamerInfo.h -> TStreamerInfo::EReadWrite + } + sinfo.emplace_back(StreamerInfo{name, size, counter, iscounter, ispointer}); + } + } + buf = nbuf; + } + } else + buf = nbuf; + buf += 1; // trailing zero of TObjArray* + } + } else + qDebug() << "ROOTData: Inflation failed!"; +} + +bool ROOTData::advanceTo(char*& buf, const std::vector& objects, const std::string& current, const std::string& target, std::map& counts) +{ + // The object structure can be retrieved from TFile::GetStreamerInfoList(). + // Every ROOT object contains a version number which may include the byte count + // for the object. The latter is currently assumed to be present to skip unused + // objects. No checks are performed. The corresponding ROOT code is quite nested + // but the actual readout is straight forward. + auto it = objects.begin(); + if (!current.empty()) { + for (; it != objects.end(); ++it) { + if (it->name == target) { + return false; // target lies before current buffer position + } else if (it->name == current) { + ++it; + break; + } + } + } + + for (; it != objects.end(); ++it) { + if (it->name == target) + return true; + + if (it->size == 0) + Skip(buf, 1); + else if (it->iscounter) + counts[it->name] = read(buf); + else if (it->ispointer) { + if (it->counter.empty()) + buf += it->size + 1; + else + buf += it->size * counts[it->counter] + 1; + } else + buf += it->size; + } + + return false; +} + +// needs to be after ROOTDataHelpers namespace declaration QString ROOTFilter::fileInfoString(const QString& fileName) { DEBUG("ROOTFilter::fileInfoString()"); @@ -676,16 +1333,16 @@ return i18n("Not a ROOT file"); } - int version = ROOTHistHelpers::read(is); + int version = read(is); info += i18n("File format version: %1", QString::number(version)); info += QLatin1String("
"); is.seekg(20); - int freeBytes = ROOTHistHelpers::read(is); - int freeRecords = ROOTHistHelpers::read(is); - int namedBytes = ROOTHistHelpers::read(is); - char pointerBytes = ROOTHistHelpers::read(is); + int freeBytes = read(is); + int freeRecords = read(is); + int namedBytes = read(is); + char pointerBytes = read(is); info += i18n("FREE data record size: %1 bytes", QString::number(freeBytes)); info += QLatin1String("
"); info += i18n("Number of free data records: %1", QString::number(freeRecords)); @@ -695,13 +1352,13 @@ info += i18n("Size of file pointers: %1 bytes", QString::number(pointerBytes)); info += QLatin1String("
"); - int compression = ROOTHistHelpers::read(is); + int compression = read(is); compression = compression > 0 ? compression : 0; info += i18n("Compression level and algorithm: %1", QString::number(compression)); info += QLatin1String("
"); is.seekg(41); - int infoBytes = ROOTHistHelpers::read(is); + int infoBytes = read(is); info += i18n("Size of TStreamerInfo record: %1 bytes", QString::number(infoBytes)); info += QLatin1String("
"); diff --git a/src/backend/datasources/filters/ROOTFilterPrivate.h b/src/backend/datasources/filters/ROOTFilterPrivate.h --- a/src/backend/datasources/filters/ROOTFilterPrivate.h +++ b/src/backend/datasources/filters/ROOTFilterPrivate.h @@ -28,7 +28,6 @@ #ifndef ROOTFILTERPRIVATE_H #define ROOTFILTERPRIVATE_H -#include #include #include #include @@ -44,31 +43,94 @@ class AbstractColumn; /** - * @brief Read TH1 histograms from ROOT files without depending on ROOT libraries + * @brief Read TH1 histograms and TTrees from ROOT files without depending on ROOT libraries */ -class ROOTHist { +class ROOTData + { public: /** - * @brief Open ROOT file and save file positions of histograms + * @brief Open ROOT file and save file positions of histograms and trees * * Also checks for the compression level. Currently the default ZLIB and LZ4 compression - * types are supported. The TH1 object structure is hard coded, TStreamerInfo is not read. + * types are supported. The TStreamerInfo is read if it is available, otherwise the + * data structure as of ROOT v6.15 is used. No tests were performed with data written + * prior to ROOT v5.34. * * @param[in] filename ROOT file to be read */ - explicit ROOTHist(const std::string& filename); + explicit ROOTData (const std::string& filename); + /// Parameters to describe a bin struct BinPars { double content; double sumw2; double lowedge; }; + /** + * @brief Identifiers for different data types + * + * Histograms are identified by their bin type. The lowest byte indicates the size + * of the numeric types for cross checks during the import. + */ + enum ContentType {Invalid = 0, Tree = 0x10, NTuple = 0x11, Basket = 0x20, + Streamer = 0x30, + Double = 0x48, Float = 0x54, + Long = 0x68, Int = 0x74, Short = 0x82, Byte = 0x91, + Bool = 0xA1, CString = 0xB0}; + + /// Information about leaf contents + struct LeafInfo { + std::string branch; + std::string leaf; + ContentType type; + bool issigned; + size_t elements; + }; + /** * @brief List available histograms in the ROOT file */ std::vector listHistograms() const; + /** + * @brief List available trees in the ROOT file + */ + std::vector listTrees() const; + + /** + * @brief List information about data contained in leaves + * + * @param[in] treename Name of the tree + */ + std::vector listLeaves(const std::string& treename) const; + + /** + * @brief Get entries of a leaf + * + * @param[in] treename Name of the tree + * @param[in] branchname Name of the branch + * @param[in] leafname Name of the leaf + * @param[in] element Index, if leaf is an array + * @param[in] nentries Maximum number of entries to be read + */ + template + std::vector listEntries(const std::string& treename, const std::string& branchname, const std::string& leafname, + const size_t element = 0, const size_t nentries = std::numeric_limits::max()) const; + /** + * @brief Get entries of a leaf with the same name as its branch + * + * @param[in] treename Name of the tree + * @param[in] branchname Name of the branch + * @param[in] nentries Maximum number of entries to be read + */ + template + std::vector listEntries(const std::string& treename, const std::string& branchname, + const size_t element = 0, const size_t nentries = std::numeric_limits::max()) const + { + return listEntries(treename, branchname, branchname, element, nentries); + } + /** * @brief Read histogram from file * @@ -90,7 +152,7 @@ */ std::string histogramTitle(const std::string& name, int cycle = 1) { - auto it = histkeys.find(name + ";" + std::to_string(cycle)); + auto it = histkeys.find(name + ';' + std::to_string(cycle)); if (it != histkeys.end()) return it->second.title; else @@ -107,35 +169,87 @@ */ int histogramBins(const std::string& name, int cycle = 1) { - auto it = histkeys.find(name + ";" + std::to_string(cycle)); + auto it = histkeys.find(name + ';' + std::to_string(cycle)); if (it != histkeys.end()) - return it->second.nbins; + return it->second.nrows; + else + return 0; + } + + /** + * @brief Get number of entries in tree + * + * The number of entriees is stored in the buffer. No file access required. + * + * @param[in] name Tree name + */ + int treeEntries(const std::string& name) + { + auto it = treekeys.find(name); + if (it != treekeys.end()) + return it->second.nrows; else return 0; } private: struct KeyBuffer { + ContentType type; std::string name; std::string title; int cycle; - enum ContentType { Invalid = 0, Double, Float, Int, Short, Byte } type; + size_t keylength; enum CompressionType { none, zlib, lz4 } compression; size_t start; size_t compressed_count; size_t count; - int nbins; + int nrows; }; - /// Get the number of bins contained in the histogram - void readNBins(ROOTHist::KeyBuffer& buffer); + struct StreamerInfo + { + std::string name; + size_t size; + std::string counter; + bool iscounter; + bool ispointer; + }; + + /// Get data type from histogram identifier + static ContentType histType(const char type); + /// Get data type from leaf identifier + static ContentType leafType(const char type); + /// Get function to read a buffer of the specified type + template + T (*readType(ContentType type, bool sign = true) const)(char*&); + + /// Get the number of bins contained in a histogram + void readNBins(KeyBuffer& buffer); + /// Get the number of entries contained in a tree + void readNEntries(KeyBuffer& buffer); /// Get buffer from file content at histogram position - std::string data(const ROOTHist::KeyBuffer& buffer) const; + std::string data(const KeyBuffer& buffer) const; /// Get buffer from file content at histogram position, uses already opened stream - std::string data(const ROOTHist::KeyBuffer& buffer, std::ifstream& is) const; + std::string data(const KeyBuffer& buffer, std::ifstream& is) const; + /// Load streamer information + void readStreamerInfo(const KeyBuffer& buffer); + /** + * @brief Advance to an object inside a class according to streamer information + * + * The number of entriees is stored in the buffer. No file access required. + * + * @param[in] buf Pointer to the current position in the class object + * @param[in] objects A list of objects in the class defined by the streamer information + * @param[in] current The name of the current object + * @param[in] target The name of the object to be advanced to + * @param[in] counts A list of the number of entries in objects of dynamic length; updated while reading + */ + static bool advanceTo(char*& buf, const std::vector& objects, const std::string& current, const std::string& target, std::map& counts); std::string filename; - std::map histkeys; - int compression; + std::map histkeys, treekeys; + std::map basketkeys; + + std::map > streamerInfo; }; class ROOTFilterPrivate { @@ -155,34 +269,38 @@ /// List names of histograms contained in ROOT file QStringList listHistograms(const QString& fileName); + /// List names of trees contained in ROOT file + QStringList listTrees(const QString& fileName); + /// List names of leaves contained in ROOT tree + QVector listLeaves(const QString& fileName, const QString& treeName); /// Get preview data of the currently set histogram - QVector previewCurrentHistogram(const QString& fileName, + QVector previewCurrentObject(const QString& fileName, int first, int last); /// Get the number of bins in the current histogram - int binsInCurrentHistogram(const QString& fileName); + int rowsInCurrentObject(const QString& fileName); /// Identifier of the current histogram - QString currentHistogram; - /// Start bin to read (can be -1, skips the underflow bin 0) - int startBin = -1; - /// End bin to read (can be -1, skips the overflow bin) - int endBin = -1; + QString currentObject; + /// First row to read (can be -1, skips the underflow bin 0) + int startRow = -1; + /// Last row to read (can be -1, skips the overflow bin) + int endRow = -1; /// Start column to read - int columns = 0; + QVector columns; private: - /// Create headers from set columns - QStringList createHeaders(); /// Checks and updates the current ROOT file path void setFile(const QString& fileName); /// Calls ReadHistogram from ROOTHist - std::vector readHistogram(); + std::vector readHistogram(const QString& histName); + /// Calls listEntries from ROOTHist + std::vector readTree(const QString& treeName, const QString& branchName, const QString& leafName, int element, int last); /// Currently set ROOT file path QString currentFile; /// ROOTHist instance kept alive while currentFile does not change - std::unique_ptr currentROOTHist; + std::unique_ptr currentROOTData; }; #endif diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -283,7 +283,7 @@ static_cast(filter)->setCurrentVarName(names[i]); break; case AbstractFileFilter::ROOT: - static_cast(filter)->setCurrentHistogram(names[i]); + static_cast(filter)->setCurrentObject(names[i]); break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -124,7 +124,7 @@ #endif ui.cbFileType->addItem(i18n("JSON data"), AbstractFileFilter::JSON); #ifdef HAVE_ZIP - ui.cbFileType->addItem(i18n("ROOT (CERN) Histograms"), AbstractFileFilter::ROOT); + ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); @@ -704,10 +704,10 @@ auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedROOTNames(); if (!names.isEmpty()) - filter->setCurrentHistogram(names.first()); + filter->setCurrentObject(names.first()); - filter->setStartBin( m_rootOptionsWidget->startBin() ); - filter->setEndBin( m_rootOptionsWidget->endBin() ); + filter->setStartRow( m_rootOptionsWidget->startRow() ); + filter->setEndRow( m_rootOptionsWidget->endRow() ); filter->setColumns( m_rootOptionsWidget->columns() ); break; @@ -1918,13 +1918,12 @@ case AbstractFileFilter::ROOT: { auto filter = static_cast(currentFileFilter()); lines = m_rootOptionsWidget->lines(); - - m_rootOptionsWidget->setNBins(filter->binsInCurrentHistogram(fileName)); - importedStrings = filter->previewCurrentHistogram( + m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(fileName)); + importedStrings = filter->previewCurrentObject( fileName, - m_rootOptionsWidget->startBin(), - qMin(m_rootOptionsWidget->startBin() + m_rootOptionsWidget->lines() - 1, - m_rootOptionsWidget->endBin()) + m_rootOptionsWidget->startRow(), + qMin(m_rootOptionsWidget->startRow() + m_rootOptionsWidget->lines() - 1, + m_rootOptionsWidget->endRow()) ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names diff --git a/src/kdefrontend/datasources/ROOTOptionsWidget.h b/src/kdefrontend/datasources/ROOTOptionsWidget.h --- a/src/kdefrontend/datasources/ROOTOptionsWidget.h +++ b/src/kdefrontend/datasources/ROOTOptionsWidget.h @@ -45,19 +45,24 @@ /// Return a list of selected histograms const QStringList selectedROOTNames() const; int lines() const { return ui.sbPreviewLines->value(); } - int startBin() const { return ui.sbFirst->value(); } - int endBin() const { return ui.sbLast->value(); } - int columns() const; - void setNBins(int nbins); + int startRow() const { return ui.sbFirst->value(); } + int endRow() const { return ui.sbLast->value(); } + QVector columns() const; + void setNRows(int nrows); QTableWidget* previewWidget() const { return ui.twPreview; } private: Ui::ROOTOptionsWidget ui; + QTreeWidgetItem* histItem; + QTreeWidgetItem* treeItem; + QHash > leaves; + ImportFileWidget* m_fileWidget; + bool histselected = false; private slots: /// Updates the selected data set of a ROOT file when a new item is selected - void rootListWidgetSelectionChanged(); + void rootObjectSelectionChanged(); }; #endif diff --git a/src/kdefrontend/datasources/ROOTOptionsWidget.cpp b/src/kdefrontend/datasources/ROOTOptionsWidget.cpp --- a/src/kdefrontend/datasources/ROOTOptionsWidget.cpp +++ b/src/kdefrontend/datasources/ROOTOptionsWidget.cpp @@ -34,65 +34,152 @@ ROOTOptionsWidget::ROOTOptionsWidget(QWidget* parent, ImportFileWidget* fileWidget) : QWidget(parent), m_fileWidget(fileWidget) { ui.setupUi(parent); + histItem = new QTreeWidgetItem(ui.twContent, QStringList(i18n("Histograms"))); + histItem->setFlags(Qt::ItemIsEnabled); + treeItem = new QTreeWidgetItem(ui.twContent, QStringList(i18n("Trees and Tuples"))); + treeItem->setFlags(Qt::ItemIsEnabled); - connect(ui.lwContent, &QListWidget::itemSelectionChanged, this, &ROOTOptionsWidget::rootListWidgetSelectionChanged); + connect(ui.twContent, &QTreeWidget::itemSelectionChanged, this, &ROOTOptionsWidget::rootObjectSelectionChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, fileWidget, &ImportFileWidget::refreshPreview); } void ROOTOptionsWidget::clear() { - ui.lwContent->clear(); + qDeleteAll(histItem->takeChildren()); + qDeleteAll(treeItem->takeChildren()); ui.twPreview->clearContents(); } void ROOTOptionsWidget::updateContent(ROOTFilter *filter, const QString& fileName) { DEBUG("updateContent()"); - ui.lwContent->clear(); - ui.lwContent->addItems(filter->listHistograms(fileName)); + + qDeleteAll(histItem->takeChildren()); + qDeleteAll(treeItem->takeChildren()); + leaves.clear(); + for (const QString& s : filter->listHistograms(fileName)) + new QTreeWidgetItem(histItem, QStringList(s)); + for (const QString& s : filter->listTrees(fileName)) { + new QTreeWidgetItem(treeItem, QStringList(s)); + leaves[s] = filter->listLeaves(fileName, s); + } } -void ROOTOptionsWidget::rootListWidgetSelectionChanged() { - DEBUG("rootListWidgetSelectionChanged()"); - auto items = ui.lwContent->selectedItems(); +void ROOTOptionsWidget::rootObjectSelectionChanged() { + DEBUG("rootObjectSelectionChanged()"); + auto items = ui.twContent->selectedItems(); QDEBUG("SELECTED ITEMS =" << items); + ui.twColumns->clear(); + if (items.isEmpty()) return; + QTreeWidgetItem* const p = items.first()->parent(); + if (p == histItem) { + ui.twColumns->setColumnCount(1); + + auto center = new QTreeWidgetItem(ui.twColumns, QStringList(i18n("Bin Center"))); + center->setData(0, Qt::UserRole, QStringList(QStringLiteral("center"))); + center->setSelected(true); + center->setFirstColumnSpanned(true); + auto low = new QTreeWidgetItem(ui.twColumns, QStringList(i18n("Low Edge"))); + low->setData(0, Qt::UserRole, QStringList(QStringLiteral("low"))); + low->setFirstColumnSpanned(true); + auto content = new QTreeWidgetItem(ui.twColumns, QStringList(i18n("Content"))); + content->setData(0, Qt::UserRole, QStringList(QStringLiteral("content"))); + content->setSelected(true); + content->setFirstColumnSpanned(true); + auto error = new QTreeWidgetItem(ui.twColumns, QStringList(i18n("Error"))); + error->setData(0, Qt::UserRole, QStringList(QStringLiteral("error"))); + error->setSelected(true); + error->setFirstColumnSpanned(true); + + if (!histselected) { + histselected = true; + ui.sbFirst->setMaximum(0); + ui.sbLast->setMaximum(0); + } + } else if (p == treeItem) { + ui.twColumns->setColumnCount(2); + + for (const auto& l : leaves[items.first()->text(0)]) { + auto leaf = new QTreeWidgetItem(ui.twColumns, l); + bool ok = false; + if (l.count() > 1) { + QString index(l.back()); + if (index.front() == '[' && index.back() == ']') { + size_t elements = index.mid(1, index.length() - 2).toUInt(&ok); + if (ok) { + leaf->setFlags(Qt::ItemIsEnabled); + QStringList elname({l.at(l.count() - 2), QString()}); + QStringList eldata(elname); + if (l.count() > 2) + eldata.prepend(l.front()); + for (size_t i = 0; i < elements; ++i) { + eldata.last() = elname.last() = QString("[%1]").arg(i); + auto el = new QTreeWidgetItem(leaf, elname); + el->setData(0, Qt::UserRole, eldata); + } + } + } + } else + leaf->setFirstColumnSpanned(true); + + if (!ok) { + leaf->setData(0, Qt::UserRole, l); + } + } + if (histselected) { + histselected = false; + ui.sbFirst->setMaximum(0); + ui.sbLast->setMaximum(0); + } + } + m_fileWidget->refreshPreview(); } const QStringList ROOTOptionsWidget::selectedROOTNames() const { QStringList names; - for (const QListWidgetItem* const item : ui.lwContent->selectedItems()) - names << item->text(); + for (const QTreeWidgetItem* const item : ui.twContent->selectedItems()) { + if (item->parent() == histItem) + names << QStringLiteral("Hist:") + item->text(0); + else if (item->parent() == treeItem) + names << QStringLiteral("Tree:") + item->text(0); + } return names; } -int ROOTOptionsWidget::columns() const { - int cols = ui.checkCenter->isChecked() ? ROOTFilter::Center : 0; - cols |= ui.checkLow->isChecked() ? ROOTFilter::Low : 0; - cols |= ui.checkContent->isChecked() ? ROOTFilter::Content : 0; - cols |= ui.checkError->isChecked() ? ROOTFilter::Error : 0; +QVector ROOTOptionsWidget::columns() const { + QVector cols; + + auto item = ui.twColumns->topLevelItem(0); + while (item) { + if (item->isSelected()) { + cols << item->data(0,Qt::UserRole).toStringList(); + } + item = ui.twColumns->itemBelow(item); + } return cols; } -void ROOTOptionsWidget::setNBins(int nbins) { +void ROOTOptionsWidget::setNRows(int nrows) { // try to retain the range settings: - // - if nbins was not 0, keep start bin, + // - if nrows was not 0, keep start row, // else set it to one after underflow - // - if nbins didn't change, keep end bin, + // - if nrows didn't change, keep end row, // else set it to one before overflow - const int max = qMax(nbins - 1, 0); + const int max = qMax(nrows - 1, 0); int firstval = ui.sbFirst->value(); if (ui.sbFirst->maximum() == 0) - firstval = qMin(nbins - 1, 1); + firstval = qMin(nrows - 1, histselected ? 1 : 0); ui.sbFirst->setMaximum(max); ui.sbFirst->setValue(firstval); - int lastval = max == ui.sbLast->maximum() ? ui.sbLast->value() : qMax(max - 1, 0); + int lastval = max == ui.sbLast->maximum() ? ui.sbLast->value() + : qMax(max - (histselected ? 1 : 0), 0); ui.sbLast->setMaximum(max); ui.sbLast->setValue(lastval); } diff --git a/src/kdefrontend/ui/datasources/rootoptionswidget.ui b/src/kdefrontend/ui/datasources/rootoptionswidget.ui --- a/src/kdefrontend/ui/datasources/rootoptionswidget.ui +++ b/src/kdefrontend/ui/datasources/rootoptionswidget.ui @@ -6,190 +6,176 @@ 0 0 - 516 + 702 242 - - - + + + Qt::Horizontal + + + + + 1 + 0 + + + + Shows the content of the selected ROOT file + + + QAbstractItemView::SingleSelection + + + false + + - Number of bins to preview: + 1 - - - - - - 1 - - - 10000 - - - 100 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Refresh - - - - - - - - - - - - - - Data to import: - - - - - - - - - Bin Center - - - true - - - - - - - Error - - - true + + + + + + + + + 0 + 0 + - - - - - - Low Edge + + QAbstractItemView::MultiSelection + + false + + + + 1 + + - - - - Content - - - true - - + + + + + + First row: + + + + + + + 100 + + + 1 + + + + + + + Last row: + + + + + + + 100 + + + 99 + + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 100 - - - 1 - - + + + + + + + + + Number of rows to preview: + + + + + + + 1 + + + 10000 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Refresh + + + + .. + + + + - - - - 100 + + + + + 0 + 0 + - - 99 - - - - - - - First Bin: - - - - - - - Last Bin: + + QAbstractItemView::NoEditTriggers + + true + + + false + - - + + - - - - - Qt::Horizontal - - - - Shows the content of the selected ROOT file - - - QAbstractItemView::ExtendedSelection - - - - - QAbstractItemView::NoEditTriggers - - - true - - - false - - - - - + diff --git a/tests/import_export/ROOT/data/advanced_zlib.root b/tests/import_export/ROOT/data/advanced_zlib.root index 197c563658d1a003f7bfa462ef052c1375b98277..46f8dd01c4abcdda9d8e9699094af71d2f372c52 GIT binary patch literal 11567 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 borders; + for (size_t i = 0; i < 101; ++i) + borders.push_back(0.09 * i - 5. + 1.e-4 * i * i); + + TH1D variableBinHist("variableBinHist", "", 100, borders.data()); + variableBinHist.Sumw2(); + variableBinHist.FillRandom("gaus", 10000); + variableBinHist.Write(); + variableBinHist.FillRandom("gaus", 1000000); + variableBinHist.Write(); + + TTree tree("tree", "TTree title"); + double d; + int i; + struct { + int a[2]; + Double_t d; + float f; + } s; + tree.Branch("doubleTest", &d, "doubleTest/D"); + tree.Branch("intTest", &i, "intTest/I"); + tree.Branch("structTest", &s, "array[2]/I:double/D:float/F"); + + for (size_t j = 0; j < 10; ++j) { + d = j; + i = j; + s.f = 9 - j; + s.a[0] = j; s.a[1] = 2 * j; + s.d = s.f * s.f; + tree.Fill(); + } + tree.Write(); + + TNtuple tuple("tuple", "TNtuple title", "x:y:z"); + tuple.Fill(1., 2., 3.); + tuple.Write(); + tuple.Fill(3., 4., 5.); + tuple.Write(); + + fAdvanced.Close(); + + ifstream infile("basic_lz4.root"); + ofstream outfile("broken_basic.root"); + + char buffer[3000]; + infile.read(buffer, 3000); + outfile.write(buffer, 3000); +} diff --git a/tests/import_export/ROOT/data/samplehists.C b/tests/import_export/ROOT/data/samplehists.C deleted file mode 100644 --- a/tests/import_export/ROOT/data/samplehists.C +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************** -File : samplehists.C -Project : LabPlot -Description : ROOT script to create test files for ROOT importer -------------------------------------------------------------------------- -Copyright : (C) 2018 by Christoph Roick (chrisito@gmx.de) -***************************************************************************/ - -/*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -* This program is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* GNU General Public License for more details. * -* * -* You should have received a copy of the GNU General Public License * -* along with this program; if not, write to the Free Software * -* Foundation, Inc., 51 Franklin Street, Fifth Floor, * -* Boston, MA 02110-1301 USA * -* * -***************************************************************************/ - -// ROOT test files are regenerated by calling 'root samplehists.C'. -// Note that creation date and random numbers will be different in the new files. - -void samplehists() -{ - TFile fBasic("basic_lz4.root", "RECREATE"); - fBasic.SetCompressionAlgorithm(ROOT::kLZ4); - - TH1D doubleHist("doubleHist", "", 100, -5., 5.); - doubleHist.Sumw2(); - doubleHist.FillRandom("gaus", 10000); - doubleHist.Write(); - - TH1F floatHist("floatHist", "", 100, -5., 5.); - floatHist.Sumw2(); - floatHist.FillRandom("gaus", 10000); - floatHist.Write(); - - TH1I intHist("intHist", "", 100, -5., 5.); - intHist.Sumw2(); - intHist.FillRandom("gaus", 10000); - intHist.Write(); - - TH1S shortHist("shortHist", "", 100, -5., 5.); - shortHist.Sumw2(); - shortHist.FillRandom("gaus", 10000); - shortHist.Write(); - - TH1C charHist("charHist", "", 100, -5., 5.); - charHist.Sumw2(); - charHist.FillRandom("gaus", 1000); - charHist.Write(); - - fBasic.Close(); - - TFile fAdvanced("advanced_zlib.root", "RECREATE"); - fAdvanced.SetCompressionAlgorithm(ROOT::kZLIB); - - vector borders; - for (size_t i = 0; i < 101; ++i) - borders.push_back(0.09 * i - 5. + 1.e-4 * i * i); - - TH1D variableBinHist("variableBinHist", "", 100, borders.data()); - variableBinHist.Sumw2(); - variableBinHist.FillRandom("gaus", 10000); - variableBinHist.Write(); - variableBinHist.FillRandom("gaus", 1000000); - variableBinHist.Write(); - - fAdvanced.Close(); - - ifstream infile("basic_lz4.root"); - ofstream outfile("broken_basic.root"); - - char buffer[3000]; - infile.read(buffer, 3000); - outfile.write(buffer, 3000); -}