Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/root2dex/src/podio-file.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * The JSROOT access layer: opens a podio ROOT file and reads single entries out
0003  * of its 'events' TTree.
0004  *
0005  * Partial reading is the point of this class. JSROOT never loads a whole file:
0006  * `openFile` reads the key directory and the streamer info, `readObject('events')`
0007  * reads the TTree metadata, and a `treeProcess` pass reads only the baskets that
0008  * cover the requested entries of the requested branches. A multi-GB file costs a
0009  * handful of byte ranges per event.
0010  *
0011  * All branches an event needs are read in ONE pass (`readEntry`) so JSROOT can
0012  * batch their baskets into as few reads as possible - a pass per field would
0013  * multiply the round trips by the number of columns.
0014  *
0015  * Sources, in order of what JSROOT does with them:
0016  *   - a browser `File` (drag-and-drop / file picker) - sliced with FileReader
0017  *   - an http(s) URL - HTTP Range requests
0018  *   - a `file://` path or plain path under node - positional reads
0019  *   - any object implementing JSROOT's FileProxy contract (`getFileName`,
0020  *     `getFileSize`, `readBuffer(pos, len)`) - the seam a future XRootD
0021  *     byte-range proxy plugs into without touching this package
0022  */
0023 
0024 // jsroot declares its subpath modules (jsroot/io, jsroot/tree) in the types
0025 // of its main entry; the reference loads them without importing the main
0026 // entry, whose module graph is several times the size of the io entry.
0027 /// <reference types="jsroot" />
0028 import { openFile } from 'jsroot/io';
0029 import { treeProcess, TSelector } from 'jsroot/tree';
0030 
0031 /** Anything JSROOT's `openFile` accepts. See the class doc for the list. */
0032 export type RootSource = string | File | Blob | ArrayBuffer | object;
0033 
0034 /** One column of values as JSROOT returns it (typed array, or Array for 64-bit ints). */
0035 export type Column = ArrayLike<number> & Iterable<number>;
0036 
0037 /** The columns read by one `readEntry` call, keyed by full branch name. */
0038 export type ColumnBag = Record<string, Column>;
0039 
0040 /** The podio data model of an 'events' tree. */
0041 export type PodioModel = 'edm4eic' | 'edm4hep';
0042 
0043 const EDM4EIC_HIT_TYPE = 'vector<edm4eic::TrackerHitData>';
0044 const EDM4HEP_SIM_HIT_TYPE = 'vector<edm4hep::SimTrackerHitData>';
0045 
0046 interface JsrootBranch {
0047   fName: string;
0048   fClassName?: string;
0049   fBranches?: { arr?: JsrootBranch[] };
0050 }
0051 
0052 interface JsrootTree {
0053   fEntries: number;
0054   fBranches?: { arr?: JsrootBranch[] };
0055 }
0056 
0057 interface JsrootFile {
0058   readObject(name: string): Promise<unknown>;
0059   getFileName?(): string;
0060 }
0061 
0062 /**
0063  * An opened podio 'events' tree, kept open across conversions so the tree
0064  * metadata is read once.
0065  */
0066 export class PodioEventFile {
0067   /** Full names of every branch and sub-branch, e.g. 'SiBarrelHits.position.x'. */
0068   private readonly branchNames = new Set<string>();
0069   /** Top-level collection name -> its podio C++ type. */
0070   private readonly collectionTypes = new Map<string, string>();
0071 
0072   private constructor(
0073     private readonly file: JsrootFile,
0074     private readonly tree: JsrootTree,
0075     readonly sourceName: string,
0076   ) {
0077     for (const branch of tree.fBranches?.arr ?? []) {
0078       this.collectionTypes.set(branch.fName, branch.fClassName ?? '');
0079       this.collectBranchNames(branch);
0080     }
0081   }
0082 
0083   private collectBranchNames(branch: JsrootBranch): void {
0084     this.branchNames.add(branch.fName);
0085     for (const child of branch.fBranches?.arr ?? []) {
0086       this.collectBranchNames(child);
0087     }
0088   }
0089 
0090   /** Opens `source` and reads the 'events' tree metadata (no event data yet). */
0091   static async open(source: RootSource, treeName = 'events'): Promise<PodioEventFile> {
0092     const file = (await openFile(source as never)) as JsrootFile;
0093     // JSROOT rejects a missing key with a bare "Key not found events"; the
0094     // message goes straight to the user, so say what was actually wrong
0095     const tree = (await file
0096       .readObject(treeName)
0097       .catch(() => null)) as JsrootTree | null;
0098     if (!tree || typeof tree.fEntries !== 'number') {
0099       throw new Error(
0100         `No '${treeName}' TTree found in the ROOT file - this does not look like an ` +
0101           `EDM4eic/EDM4hep event file (a detector geometry file, for instance, has none)`,
0102       );
0103     }
0104     const name =
0105       typeof source === 'string'
0106         ? source
0107         : ((source as File).name ?? file.getFileName?.() ?? 'unknown.root');
0108     return new PodioEventFile(file, tree, name);
0109   }
0110 
0111   /** Number of entries (events) in the tree. */
0112   get entryCount(): number {
0113     return this.tree.fEntries;
0114   }
0115 
0116   /** True when a branch or sub-branch with this exact full name exists. */
0117   hasBranch(name: string): boolean {
0118     return this.branchNames.has(name);
0119   }
0120 
0121   /** Top-level collection names whose podio C++ type is exactly `typeName`. */
0122   collectionsOfType(typeName: string): string[] {
0123     const names: string[] = [];
0124     for (const [name, type] of this.collectionTypes) {
0125       if (type === typeName) names.push(name);
0126     }
0127     return names;
0128   }
0129 
0130   /**
0131    * Detects the data model by branch types, the same rule pyrobird uses:
0132    * reconstructed edm4eic collections win over the sim hits that eicrecon
0133    * files also carry.
0134    */
0135   detectModel(): PodioModel {
0136     if (this.collectionsOfType(EDM4EIC_HIT_TYPE).length > 0) return 'edm4eic';
0137     if (this.collectionsOfType(EDM4HEP_SIM_HIT_TYPE).length > 0) return 'edm4hep';
0138     throw new Error(
0139       `Cannot detect file type: no '${EDM4EIC_HIT_TYPE}' or '${EDM4HEP_SIM_HIT_TYPE}' ` +
0140         `branches found in the 'events' tree`,
0141     );
0142   }
0143 
0144   /**
0145    * Reads the given branches for ONE entry in a single pass.
0146    *
0147    * Names that do not exist in the tree are dropped silently - callers ask for
0148    * optional fields (edm4hep renamed 'EDep' to 'eDep', relation branches come
0149    * and go between podio versions) and check the result. Every returned column
0150    * holds the values of that one entry, flattened out of the per-event vector.
0151    */
0152   async readEntry(names: Iterable<string>, entry: number): Promise<ColumnBag> {
0153     const wanted = [...new Set(names)].filter(name => this.hasBranch(name));
0154     const bag: ColumnBag = {};
0155     if (wanted.length === 0) return bag;
0156 
0157     if (!Number.isInteger(entry) || entry < 0 || entry >= this.entryCount) {
0158       throw new RangeError(
0159         `Entry ${entry} is outside of the tree: it holds ${this.entryCount} entries`,
0160       );
0161     }
0162 
0163     const selector = new TSelector();
0164     // Read into slot names ('b0', 'b1', ...): JSROOT would otherwise treat the
0165     // dots in 'SiBarrelHits.position.x' as a member path in the target object.
0166     wanted.forEach((name, index) => selector.addBranch(name, `b${index}`));
0167     selector.Process = function (this: { tgtobj: Record<string, Column> }) {
0168       wanted.forEach((name, index) => {
0169         bag[name] = this.tgtobj[`b${index}`];
0170       });
0171     };
0172 
0173     await treeProcess(this.tree, selector, { firstentry: entry, numentries: 1 });
0174     return bag;
0175   }
0176 
0177   /**
0178    * Reads the podio collectionID of `collectionName` from the file's
0179    * 'podio_metadata' tree, or null when the metadata is unavailable (trimmed
0180    * test files have none).
0181    */
0182   async readCollectionId(collectionName: string): Promise<number | null> {
0183     try {
0184       const metadata = (await this.file.readObject('podio_metadata')) as JsrootTree | null;
0185       if (!metadata) return null;
0186       const base = 'events___CollectionTypeInfo';
0187       const selector = new TSelector();
0188       selector.addBranch(`${base}.collectionID`, 'ids');
0189       selector.addBranch(`${base}.name`, 'names');
0190       let ids: ArrayLike<number> = [];
0191       let names: ArrayLike<string> = [];
0192       selector.Process = function (this: { tgtobj: Record<string, unknown> }) {
0193         ids = this.tgtobj['ids'] as ArrayLike<number>;
0194         names = this.tgtobj['names'] as ArrayLike<string>;
0195       };
0196       await treeProcess(metadata, selector, { firstentry: 0, numentries: 1 });
0197       for (let i = 0; i < names.length; i++) {
0198         if (names[i] === collectionName) return Number(ids[i]);
0199       }
0200     } catch {
0201       // No podio metadata in this file - the caller skips the consistency check
0202     }
0203     return null;
0204   }
0205 }
0206 
0207 /** Reads a column out of a bag, or an empty array when the branch was absent. */
0208 export function column(bag: ColumnBag, name: string): Column {
0209   return bag[name] ?? [];
0210 }
0211 
0212 /** Reads a column as a plain number array (JSROOT hands back typed arrays). */
0213 export function numbers(bag: ColumnBag, name: string): number[] {
0214   const values = bag[name];
0215   return values ? Array.from(values, Number) : [];
0216 }