Back to home page

EIC code displayed by LXR

 
 

    


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

0001 /**
0002  * EDM4eic (eicrecon reconstruction output) -> Firebird DEX.
0003  *
0004  * Mirrors pyrobird's `pyrobird/edm4eic.py` piece for piece, so the same input
0005  * entry produces the same DEX document whether it went through the python CLI
0006  * or through this package in the browser.
0007  *
0008  * Collections converted:
0009  *   vector<edm4eic::TrackerHitData>   -> one BoxHit piece per collection
0010  *   edm4eic::TrackSegmentData + its TrackPoints -> one PointTrajectory piece
0011  *
0012  * Empty collections are kept as count-0 pieces (pyrobird does the same here;
0013  * the edm4hep path drops them instead).
0014  */
0015 
0016 import { PIECE_VERSION, type DexEvent, type DexPiece } from './dex';
0017 import { numbers, type ColumnBag, type PodioEventFile } from './podio-file';
0018 import {
0019   mcParticlesBranchNames,
0020   mcParticlesToTrajectories,
0021   type McParticlesOptions,
0022 } from './mc-particles';
0023 
0024 export const EDM4EIC_TRACKER_HIT_TYPE = 'vector<edm4eic::TrackerHitData>';
0025 
0026 /** The TrackSegment collection pyrobird converts; see EICrecon issue #1730. */
0027 const SEGMENT_COLLECTION = 'CentralTrackSegments';
0028 const TRACK_PARAMS_COLLECTION = 'CentralCKFTrackParameters';
0029 
0030 /** Which collection groups to convert. Defaults to all of them. */
0031 export type Edm4eicCollection = 'tracker_hits' | 'tracks' | 'mc_particles';
0032 export const EDM4EIC_DEFAULT_COLLECTIONS: Edm4eicCollection[] = [
0033   'tracker_hits',
0034   'tracks',
0035   'mc_particles',
0036 ];
0037 
0038 export interface Edm4eicOptions extends McParticlesOptions {
0039   collections?: Edm4eicCollection[];
0040 }
0041 
0042 const HIT_FIELDS = [
0043   'position.x',
0044   'position.y',
0045   'position.z',
0046   'positionError.xx',
0047   'positionError.yy',
0048   'positionError.zz',
0049   'time',
0050   'timeError',
0051   'edep',
0052   'edepError',
0053 ];
0054 
0055 const POINT_FIELDS = [
0056   'position.x',
0057   'position.y',
0058   'position.z',
0059   'time',
0060   'timeError',
0061   'positionError.xx',
0062   'positionError.yy',
0063   'positionError.zz',
0064 ];
0065 
0066 const TRACK_PARAM_FIELDS = ['theta', 'phi', 'qOverP', 'loc.a', 'loc.b', 'time'];
0067 
0068 /** positionError.ii is a variance (sigma^2); the box spans +- one sigma. */
0069 function boxWidth(variance: number): number {
0070   return variance > 0 ? 2 * Math.sqrt(variance) : 0.0;
0071 }
0072 
0073 /**
0074  * Every branch an edm4eic entry conversion reads. Collected up front so one
0075  * `readEntry` pass covers the whole event - see PodioEventFile.
0076  */
0077 export function edm4eicBranchNames(
0078   file: PodioEventFile,
0079   collections: Edm4eicCollection[] = EDM4EIC_DEFAULT_COLLECTIONS,
0080   options: Edm4eicOptions = {},
0081 ): string[] {
0082   const names: string[] = [];
0083   if (collections.includes('tracker_hits')) {
0084     for (const name of file.collectionsOfType(EDM4EIC_TRACKER_HIT_TYPE)) {
0085       for (const field of HIT_FIELDS) names.push(`${name}.${field}`);
0086     }
0087   }
0088   if (collections.includes('tracks') && file.hasBranch(SEGMENT_COLLECTION)) {
0089     names.push(`${SEGMENT_COLLECTION}.points_begin`, `${SEGMENT_COLLECTION}.points_end`);
0090     const points = `_${SEGMENT_COLLECTION}_points`;
0091     for (const field of POINT_FIELDS) names.push(`${points}.${field}`);
0092     for (const field of TRACK_PARAM_FIELDS) names.push(`${TRACK_PARAMS_COLLECTION}.${field}`);
0093   }
0094   if (collections.includes('mc_particles')) {
0095     names.push(...mcParticlesBranchNames(options));
0096   }
0097   return names;
0098 }
0099 
0100 /** Converts one vector<edm4eic::TrackerHitData> collection to a BoxHit piece. */
0101 export function trackerHitsToBoxHits(bag: ColumnBag, name: string): DexPiece {
0102   // The hit count comes from position.x; pyrobird reads cellID for it, which
0103   // this path skips - it is not written to DEX and costs a basket read.
0104   const posX = numbers(bag, `${name}.position.x`);
0105   const posY = numbers(bag, `${name}.position.y`);
0106   const posZ = numbers(bag, `${name}.position.z`);
0107   const errX = numbers(bag, `${name}.positionError.xx`);
0108   const errY = numbers(bag, `${name}.positionError.yy`);
0109   const errZ = numbers(bag, `${name}.positionError.zz`);
0110 
0111   const pos: number[] = [];
0112   const dim: number[] = [];
0113   for (let i = 0; i < posX.length; i++) {
0114     pos.push(posX[i], posY[i], posZ[i]);
0115     dim.push(boxWidth(errX[i]), boxWidth(errY[i]), boxWidth(errZ[i]));
0116   }
0117 
0118   return {
0119     name,
0120     type: 'BoxHit',
0121     version: PIECE_VERSION,
0122     origin: { type: 'edm4eic::TrackerHitData', name },
0123     count: posX.length,
0124     columns: {
0125       pos,
0126       dim,
0127       time: numbers(bag, `${name}.time`),
0128       timeError: numbers(bag, `${name}.timeError`),
0129       edep: numbers(bag, `${name}.edep`),
0130       edepError: numbers(bag, `${name}.edepError`),
0131     },
0132   };
0133 }
0134 
0135 /**
0136  * Converts edm4eic::TrackSegmentData plus its TrackPoints into one
0137  * PointTrajectory piece: each segment becomes one trajectory whose points run
0138  * from points_begin to points_end of the shared point collection.
0139  *
0140  * Track parameters come from CentralCKFTrackParameters positionally, which is
0141  * what pyrobird does - the one-to-one relation to Track cannot be followed
0142  * (EICrecon issue #1730).
0143  */
0144 export function trackSegmentsToTrajectories(
0145   file: PodioEventFile,
0146   bag: ColumnBag,
0147   name: string,
0148   onWarning?: (message: string) => void,
0149 ): DexPiece {
0150   const piece: DexPiece = {
0151     name,
0152     type: 'PointTrajectory',
0153     version: PIECE_VERSION,
0154     origin: ['edm4eic::TrackPoint', 'edm4eic::TrackSegmentData'],
0155     count: 0,
0156     columns: {},
0157     pointColumns: ['x', 'y', 'z', 't', 'dx', 'dy', 'dz', 'dt'],
0158     points: [],
0159   };
0160 
0161   const beginIndex = numbers(bag, `${name}.points_begin`);
0162   const endIndex = numbers(bag, `${name}.points_end`);
0163 
0164   const pointsCollection = `_${name}_points`;
0165   if (!file.hasBranch(pointsCollection)) {
0166     // The file organizes the points differently, or there are none
0167     return piece;
0168   }
0169 
0170   const pointField = (field: string) => numbers(bag, `${pointsCollection}.${field}`);
0171   const px = pointField('position.x');
0172   const py = pointField('position.y');
0173   const pz = pointField('position.z');
0174   const pt = pointField('time');
0175   const pTimeError = pointField('timeError');
0176   const pErrXX = pointField('positionError.xx');
0177   const pErrYY = pointField('positionError.yy');
0178   const pErrZZ = pointField('positionError.zz');
0179 
0180   const paramsExist = file.hasBranch(TRACK_PARAMS_COLLECTION);
0181   const param = (field: string) =>
0182     paramsExist ? numbers(bag, `${TRACK_PARAMS_COLLECTION}.${field}`) : [];
0183   const theta = param('theta');
0184   const phi = param('phi');
0185   const qOverP = param('qOverP');
0186   const locA = param('loc.a');
0187   const locB = param('loc.b');
0188   const paramTime = param('time');
0189 
0190   const segmentCount = beginIndex.length;
0191   if (paramsExist && segmentCount !== theta.length) {
0192     onWarning?.(
0193       `len(${TRACK_PARAMS_COLLECTION}) != len(${name}). ` +
0194         `Might be a sign of format change or broken tree`,
0195     );
0196   }
0197 
0198   const allPoints: number[][][] = [];
0199   for (let segment = 0; segment < segmentCount; segment++) {
0200     const segmentPoints: number[][] = [];
0201     for (let i = beginIndex[segment]; i < endIndex[segment]; i++) {
0202       // pointColumns => [x, y, z, t, dx, dy, dz, dt]; the position errors are
0203       // variances, so the point extent is +- one sigma
0204       segmentPoints.push([
0205         px[i],
0206         py[i],
0207         pz[i],
0208         pt[i],
0209         boxWidth(pErrXX[i]),
0210         boxWidth(pErrYY[i]),
0211         boxWidth(pErrZZ[i]),
0212         pTimeError[i],
0213       ]);
0214     }
0215     allPoints.push(segmentPoints);
0216   }
0217 
0218   piece.count = segmentCount;
0219   piece.points = allPoints;
0220   if (paramsExist) {
0221     // trajectory id == index in every column; a missing tail (parameter list
0222     // shorter than segments) is padded with null rather than dropped
0223     const padded = (values: number[]) =>
0224       Array.from({ length: segmentCount }, (_, i) => (i < values.length ? values[i] : null));
0225     piece.columns = {
0226       theta: padded(theta),
0227       phi: padded(phi),
0228       q_over_p: padded(qOverP),
0229       loc_a: padded(locA),
0230       loc_b: padded(locB),
0231       time: padded(paramTime),
0232     };
0233   }
0234   return piece;
0235 }
0236 
0237 /** Converts one entry of an edm4eic 'events' tree to a DEX event object. */
0238 export async function edm4eicEntryToDex(
0239   file: PodioEventFile,
0240   entry: number,
0241   options: Edm4eicOptions = {},
0242 ): Promise<DexEvent> {
0243   const collections = options.collections?.length
0244     ? options.collections
0245     : EDM4EIC_DEFAULT_COLLECTIONS;
0246   const bag = await file.readEntry(edm4eicBranchNames(file, collections, options), entry);
0247 
0248   const pieces: DexPiece[] = [];
0249 
0250   if (collections.includes('tracker_hits')) {
0251     for (const name of file.collectionsOfType(EDM4EIC_TRACKER_HIT_TYPE)) {
0252       pieces.push(trackerHitsToBoxHits(bag, name));
0253     }
0254   }
0255 
0256   if (collections.includes('tracks') && file.hasBranch(SEGMENT_COLLECTION)) {
0257     pieces.push(trackSegmentsToTrajectories(file, bag, SEGMENT_COLLECTION, options.onWarning));
0258   }
0259 
0260   if (collections.includes('mc_particles')) {
0261     const piece = mcParticlesToTrajectories(file, bag, options);
0262     if (piece.count > 0) pieces.push(piece);
0263   }
0264 
0265   return { id: entry, pieces };
0266 }