Back to home page

EIC code displayed by LXR

 
 

    


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

0001 /**
0002  * The public conversion entry points: open a podio ROOT file and turn selected
0003  * entries into a Firebird DEX document.
0004  *
0005  * `Root2DexConverter` keeps the file open so a UI can convert one event, then
0006  * another, paying the tree-metadata read only once; `convertRootToDex` is the
0007  * one-shot form used by scripts and tests.
0008  */
0009 
0010 import { makeDex, type DexDocument, type DexEvent } from './dex';
0011 import { PodioEventFile, type PodioModel, type RootSource } from './podio-file';
0012 import {
0013   edm4eicEntryToDex,
0014   EDM4EIC_DEFAULT_COLLECTIONS,
0015   type Edm4eicCollection,
0016 } from './edm4eic';
0017 import { EDM4HEP_DEFAULT_COLLECTIONS, edm4hepEntryToDex, type Edm4hepOptions } from './edm4hep';
0018 import { parseEntryRanges, selectEntries, type EntryRange } from './entries';
0019 
0020 export interface ConvertOptions extends Omit<Edm4hepOptions, 'collections'> {
0021   /**
0022    * Collection groups to convert. edm4eic knows 'tracker_hits', 'tracks' and
0023    * 'mc_particles'; edm4hep knows 'tracker_hits', 'mc_trajectories' and
0024    * 'mc_particles'. Empty means all.
0025    */
0026   collections?: string[];
0027   /** Overrides model auto-detection. */
0028   model?: PodioModel;
0029   /** Overrides the "file" field of the DEX origin block. */
0030   sourceName?: string;
0031 }
0032 
0033 /**
0034  * Parses entry numbers written as '3', '1-5', or '1,2-5,8' into a list of
0035  * integers. Arrays of numbers pass through.
0036  *
0037  * This expands without bounds; to convert from a file, select with
0038  * `parseEntryRanges()` and `selectEntries()` first, which reject oversized
0039  * selections and clamp the ranges to the file before expanding.
0040  */
0041 export function parseEntryNumbers(value: string | number | Iterable<number>): number[] {
0042   if (typeof value === 'number') return [Math.trunc(value)];
0043   if (typeof value !== 'string') return [...value].map(v => Math.trunc(v));
0044   const entries: number[] = [];
0045   for (const [start, end] of parseEntryRanges(value)) {
0046     for (let entry = start; entry <= end; entry++) entries.push(entry);
0047   }
0048   return entries;
0049 }
0050 
0051 /**
0052  * The collection groups a model's conversion knows, in conversion order. This
0053  * is what a UI offers as per-group conversion checkboxes; the names match
0054  * `pyrobird convert --collections` and the convert endpoint's `collections`
0055  * query parameter.
0056  */
0057 export function collectionGroupsFor(model: PodioModel): string[] {
0058   return model === 'edm4hep'
0059     ? [...EDM4HEP_DEFAULT_COLLECTIONS]
0060     : [...EDM4EIC_DEFAULT_COLLECTIONS];
0061 }
0062 
0063 /**
0064  * An open podio ROOT file that converts entries on demand.
0065  *
0066  * Only the bytes of the requested entry are read - see PodioEventFile for how
0067  * that works and which sources are supported.
0068  */
0069 export class Root2DexConverter {
0070   private mcCollectionId: number | null | undefined;
0071 
0072   private constructor(
0073     readonly file: PodioEventFile,
0074     readonly model: PodioModel,
0075     private readonly options: ConvertOptions,
0076   ) {}
0077 
0078   static async open(source: RootSource, options: ConvertOptions = {}): Promise<Root2DexConverter> {
0079     const file = await PodioEventFile.open(source);
0080     const model = options.model ?? file.detectModel();
0081     return new Root2DexConverter(file, model, options);
0082   }
0083 
0084   get entryCount(): number {
0085     return this.file.entryCount;
0086   }
0087 
0088   get sourceName(): string {
0089     return this.options.sourceName ?? this.file.sourceName;
0090   }
0091 
0092   /** Converts one entry to a DEX event object (no document wrapper). */
0093   async convertEntry(entry: number, overrides: ConvertOptions = {}): Promise<DexEvent> {
0094     const options = { ...this.options, ...overrides };
0095     if (this.model === 'edm4hep') {
0096       if (this.mcCollectionId === undefined) {
0097         this.mcCollectionId = await this.file.readCollectionId(options.mcBranch ?? 'MCParticles');
0098       }
0099       return edm4hepEntryToDex(this.file, entry, {
0100         ...options,
0101         collections: options.collections as Edm4hepOptions['collections'],
0102         mcCollectionId: this.mcCollectionId,
0103       });
0104     }
0105     return edm4eicEntryToDex(this.file, entry, {
0106       ...options,
0107       collections: options.collections as Edm4eicCollection[] | undefined,
0108     });
0109   }
0110 
0111   /** Converts entries to a complete DEX document with an origin block. */
0112   async convert(entries: number[] | number, overrides: ConvertOptions = {}): Promise<DexDocument> {
0113     const list = typeof entries === 'number' ? [entries] : entries;
0114     const events: DexEvent[] = [];
0115     for (const entry of list) {
0116       events.push(await this.convertEntry(entry, overrides));
0117     }
0118     return makeDex(events, {
0119       file: overrides.sourceName ?? this.sourceName,
0120       entries_count: this.entryCount,
0121       file_type: this.model,
0122     });
0123   }
0124 }
0125 
0126 /**
0127  * Opens `source`, converts `entries` and returns the DEX document. Equivalent
0128  * to `pyrobird convert <file> -e <entries>`: requested entries outside the
0129  * file are dropped before anything is converted, with one warning through
0130  * `options.onWarning` (console.warn without it).
0131  *
0132  * @throws EntrySelectionError when no requested entry is in the file.
0133  */
0134 export async function convertRootToDex(
0135   source: RootSource,
0136   entries: string | number | number[] = 0,
0137   options: ConvertOptions = {},
0138 ): Promise<DexDocument> {
0139   const converter = await Root2DexConverter.open(source, options);
0140   const ranges: EntryRange[] = typeof entries === 'string'
0141     ? parseEntryRanges(entries)
0142     : (typeof entries === 'number' ? [entries] : entries).map(entry => [Math.trunc(entry), Math.trunc(entry)] as const);
0143   const selection = selectEntries(ranges, converter.entryCount);
0144   if (selection.warning) {
0145     (options.onWarning ?? console.warn)(selection.warning);
0146   }
0147   return converter.convert(selection.entries);
0148 }