File

src/loaders/physlite-loader.ts

Description

Loader for ATLAS PHYSLITE (DAOD_PHYSLITE) ROOT files.

Uses jsroot to read the CollectionTree TTree and converts xAOD auxiliary-data branches into Phoenix event data format.

Extends

PhoenixLoader

Index

Properties
Methods

Constructor

constructor(maxEvents: number)

Create a PHYSLITE loader.

Parameters :
Name Type Optional Description
maxEvents number No

Maximum number of events to read from the file.

Properties

Private collectionDefs
Type : PHYSLITECollectionDef[]

Collection definitions describing which branches to read.

Private maxEvents
Type : number

Maximum number of events to load from the file.

Protected eventData
Type : PhoenixEventData
Inherited from PhoenixLoader
Defined in PhoenixLoader:35

Event data processed by the loader.

Private graphicsLibrary
Type : ThreeManager
Inherited from PhoenixLoader
Defined in PhoenixLoader:31

ThreeService to perform three.js related functions.

Protected labelsObject
Type : literal type
Default value : {}
Inherited from PhoenixLoader
Defined in PhoenixLoader:41

Object containing event object labels.

Protected loadingManager
Type : LoadingManager
Inherited from PhoenixLoader
Defined in PhoenixLoader:37

Loading manager for loadable resources

Protected stateManager
Type : StateManager
Inherited from PhoenixLoader
Defined in PhoenixLoader:39

Loading manager for loadable resources

Private ui
Type : UIManager
Inherited from PhoenixLoader
Defined in PhoenixLoader:33

UIService to perform UI related functions.

Methods

Private convertCaloClusters
convertCaloClusters(keys: literal type, tgt: any)

Convert egammaClusters branch data into Phoenix CaloClusterParams array.

Parameters :
Name Type Optional
keys literal type No
tgt any No
Returns : any[]
Private convertCollection
convertCollection(def: PHYSLITECollectionDef, keys: literal type, tgt: any)

Convert one collection's branch data for a single event entry into an array of Phoenix-format objects.

Parameters :
Name Type Optional
def PHYSLITECollectionDef No
keys literal type No
tgt any No
Returns : [] | null
Private convertCompoundObjects
convertCompoundObjects(keys: literal type, tgt: any, isCharged: boolean)

Convert compound objects (Electrons, Muons, Photons). These are rendered as extrapolated tracks (charged) or clusters (neutral). Energy is computed from pt, eta, and mass: E = sqrt((pt*cosh(eta))^2 + m^2).

Parameters :
Name Type Optional
keys literal type No
tgt any No
isCharged boolean No
Returns : any[]
Private convertJets
convertJets(keys: literal type, tgt: any)

Convert jet branch data into Phoenix JetParams array. Energy computed from pt, eta, m: E = sqrt((pt*cosh(eta))^2 + m^2).

Parameters :
Name Type Optional
keys literal type No
tgt any No
Returns : any[]
Private convertMET
convertMET(keys: literal type, tgt: any)

Convert MET branch data into Phoenix MissingEnergyParams array.

Parameters :
Name Type Optional
keys literal type No
tgt any No
Returns : any[]
Private convertTracks
convertTracks(keys: literal type, tgt: any)

Convert InDetTrackParticles branch data into Phoenix TrackParams array. Uses dparams [d0, z0, phi, theta, qOverP] for Runge-Kutta extrapolation.

Parameters :
Name Type Optional
keys literal type No
tgt any No
Returns : any[]
Private convertVertices
convertVertices(keys: literal type, tgt: any)

Convert PrimaryVertices branch data into Phoenix VertexParams array.

Parameters :
Name Type Optional
keys literal type No
tgt any No
Returns : any[]
Async getEventData
getEventData(fileSource: File | string)

Open a PHYSLITE ROOT file and return all events as a PhoenixEventsData object (keyed by event name).

Parameters :
Name Type Optional Description
fileSource File | string No

File object or URL of the .root file.

Returns : Promise<any>

Promise resolving to the events data.

Private toArray
toArray(val: any)

Ensure a value is a plain array (jsroot may return typed arrays).

Parameters :
Name Type Optional
val any No
Returns : [] | null
Private addCollection
addCollection(objectCollection: any, collectionName: string, getObject: (object: any,typeName: string) => void, typeName: string, objectGroup: Group, concatonateObjs: boolean)
Inherited from PhoenixLoader
Defined in PhoenixLoader:315

Adds to the event display all the objects inside a collection.

Parameters :
Name Type Optional Description
objectCollection any No

Contains the params for every object of the collection.

collectionName string No

Label to UNIQUELY identify the collection.

getObject function No

Handles reconstructing the objects of the collection.

typeName string No
objectGroup Group No

Group containing the collections of the same object type.

concatonateObjs boolean No

If true, don't process objects individually, but process as a group (e.g. for point hits).

Returns : void
Public addLabelToEventObject
addLabelToEventObject(label: string, collection: string, indexInCollection: number)
Inherited from PhoenixLoader
Defined in PhoenixLoader:532

Add label of event object to the labels object.

Parameters :
Name Type Optional Description
label string No

Label to be saved.

collection string No

Collection the event object is a part of.

indexInCollection number No

Event object's index in collection.

Returns : string

A unique label ID string.

Protected addObjectType
addObjectType(object: any, getObject: any, typeName: string, concatonateObjs: boolean, cuts?: Cut[], extendEventDataTypeUI?: (typeFolder?: GUI,typeFolderPM?: PhoenixMenuNode) => void)
Inherited from PhoenixLoader
Defined in PhoenixLoader:234

Adds to the event display all collections of a given object type.

Parameters :
Name Type Optional Default value Description
object any No

Contains all collections of a given type (Tracks, Jets, CaloClusters...).

getObject any No

Function that handles of reconstructing objects of the given type.

typeName string No

Label for naming the object type.

concatonateObjs boolean No false

If true, don't process objects individually, but process as a group (e.g. for point hits). Default is false.

cuts Cut[] Yes

Filters that can be applied to the objects.

extendEventDataTypeUI function Yes

A callback to add more options to event data type UI folder.

Returns : void
Public addScaleOptions
addScaleOptions(configKey: string, configLabel: string, scaleFunction: (value: number) => void)
Inherited from PhoenixLoader
Defined in PhoenixLoader:569

Get function to add options to scale event object type by.

Parameters :
Name Type Optional Description
configKey string No

Key of the scale configuration option (for dat.GUI menu).

configLabel string No

Label of the scale configuration option.

scaleFunction function No

Function to scale the objects by.

Function which adds scale options to Phoenix menu and dat.GUI menu.

Public buildEventData
buildEventData(eventData: PhoenixEventData, graphicsLibrary: ThreeManager, ui: UIManager, infoLogger: InfoLogger)
Inherited from PhoenixLoader
Defined in PhoenixLoader:59

Takes an object that represents ONE event and takes care of adding the different objects to the graphics library and the UI controls.

Parameters :
Name Type Optional Description
eventData PhoenixEventData No

Object representing the event.

graphicsLibrary ThreeManager No

Service containing functionality to draw the 3D objects.

ui UIManager No

Service for showing menus and controls to manipulate the geometries.

infoLogger InfoLogger No

Service for logging data to the information panel.

Returns : void
Public getCollection
getCollection(collectionName: string)
Inherited from PhoenixLoader
Defined in PhoenixLoader:132

Get the collection with the given collection name from the event data.

Parameters :
Name Type Optional Description
collectionName string No

Name of the collection to get.

Returns : any

An object containing the collection.

Public getCollections
getCollections()
Inherited from PhoenixLoader
Defined in PhoenixLoader:111

Get list of collections in the event data.

Returns : literal type

List of all collection names.

Protected getCompound
getCompound(params: any, name: string, objectIsTrack: boolean, objectIsCluster: boolean)
Inherited from PhoenixLoader
Defined in PhoenixLoader:380

Process the compound object (e.g. Muon, Electron, Photon) from the given parameters and get it as a group. FIXME. This is currently here and not in PhoenixObjects because we need to handle linked objects.

Parameters :
Name Type Optional Default value Description
params any No

Parameters of the Muon.

name string No ''
objectIsTrack boolean No false
objectIsCluster boolean No false
Returns : Object3D

Muon group containing Clusters and Tracks.

Protected getCompoundCluster
getCompoundCluster(params: any, name: string)
Inherited from PhoenixLoader
Defined in PhoenixLoader:370

Process the compound object of cluster type (e.g. Photon, ..) from the given parameters and get it as a group.

Parameters :
Name Type Optional Default value
params any No
name string No ''
Returns : Object3D
Protected getCompoundTrack
getCompoundTrack(params: any, name: string)
Inherited from PhoenixLoader
Defined in PhoenixLoader:364

Process the compound object of track type (e.g. Muon, Electron, ..) from the given parameters and get it as a group.

Parameters :
Name Type Optional Default value
params any No
name string No ''
Returns : Object3D
getEventMetadata
getEventMetadata()
Inherited from PhoenixLoader
Defined in PhoenixLoader:478

Get metadata associated to the event (experiment info, time, run, event...).

Returns : any[]

Metadata of the event.

Public getEventsList
getEventsList(eventsData: PhoenixEventsData)
Inherited from PhoenixLoader
Defined in PhoenixLoader:95

Get the list of event names from the event data.

Parameters :
Name Type Optional Description
eventsData PhoenixEventsData No

Object containing all event data.

Returns : string[]

List of event names.

Public getLabelsObject
getLabelsObject()
Inherited from PhoenixLoader
Defined in PhoenixLoader:558

Get the object containing labels.

Returns : literal type

The labels object.

Private getObjectTypeCollections
getObjectTypeCollections(object: any)
Inherited from PhoenixLoader
Defined in PhoenixLoader:350

Get collection names of a given object type.

Parameters :
Name Type Optional Description
object any No

Contains all collections of a given type (Tracks, Jets, CaloClusters etc.).

Returns : string[]

List of collection names of an object type (Tracks, Jets, CaloClusters etc.).

Protected getObjectTypeConfigs
getObjectTypeConfigs()
Inherited from PhoenixLoader
Defined in PhoenixLoader:154

Get the object type configs for this loader. Override in subclasses to add experiment-specific types or modify defaults.

Returns : ObjectTypeConfig[]
Protected loadObjectTypes
loadObjectTypes(eventData: PhoenixEventData)
Inherited from PhoenixLoader
Defined in PhoenixLoader:162

Load all object types from event data using the registry configs.

Parameters :
Name Type Optional Description
eventData PhoenixEventData No

One event in Phoenix format.

Returns : void
import { PhoenixLoader } from './phoenix-loader';
import { openFile, settings as jsrootSettings } from 'jsroot';
import { TSelector, treeProcess } from 'jsroot/tree';
import { CoordinateHelper } from '../helpers/coordinate-helper';

/**
 * Branch mapping for a PHYSLITE collection.
 * Each entry describes which ROOT branches to read
 * and how to convert them into Phoenix event data.
 */
interface PHYSLITECollectionDef {
  /** Branch prefix in the ROOT file (e.g. 'AnalysisElectronsAuxDyn'). */
  prefix: string;
  /** Branch suffixes to read (e.g. ['pt', 'eta', 'phi', 'm']). */
  fields: string[];
  /** Phoenix object type this maps to. */
  phoenixType: string;
  /** Name for the Phoenix collection. */
  collectionName: string;
}

/**
 * Default PHYSLITE branch mapping for ATLAS Open Data.
 * Branch names verified against the official get_json_phoenix.py script.
 */
const DEFAULT_COLLECTIONS: PHYSLITECollectionDef[] = [
  {
    prefix: 'AnalysisElectronsAuxDyn',
    fields: ['pt', 'eta', 'phi', 'm'],
    phoenixType: 'Electrons',
    collectionName: 'AnalysisElectrons',
  },
  {
    prefix: 'AnalysisMuonsAuxDyn',
    fields: ['pt', 'eta', 'phi'],
    phoenixType: 'Muons',
    collectionName: 'AnalysisMuons',
  },
  {
    prefix: 'AnalysisPhotonsAuxDyn',
    fields: ['pt', 'eta', 'phi', 'm'],
    phoenixType: 'Photons',
    collectionName: 'AnalysisPhotons',
  },
  {
    prefix: 'AnalysisJetsAuxDyn',
    fields: ['pt', 'eta', 'phi', 'm'],
    phoenixType: 'Jets',
    collectionName: 'AnalysisJets',
  },
  {
    prefix: 'AnalysisLargeRJetsAuxDyn',
    fields: ['pt', 'eta', 'phi', 'm'],
    phoenixType: 'Jets',
    collectionName: 'AnalysisLargeRJets',
  },
  {
    prefix: 'InDetTrackParticlesAuxDyn',
    fields: ['d0', 'z0', 'theta', 'phi', 'qOverP'],
    phoenixType: 'Tracks',
    collectionName: 'InDetTrackParticles',
  },
  {
    prefix: 'MuonSpectrometerTrackParticlesAuxDyn',
    fields: ['d0', 'z0', 'theta', 'phi', 'qOverP'],
    phoenixType: 'Tracks',
    collectionName: 'MuonSpectrometerTrackParticles',
  },
  {
    prefix: 'CombinedMuonTrackParticlesAuxDyn',
    fields: ['d0', 'z0', 'theta', 'phi', 'qOverP'],
    phoenixType: 'Tracks',
    collectionName: 'CombinedMuonTrackParticles',
  },
  {
    prefix: 'ExtrapolatedMuonTrackParticlesAuxDyn',
    fields: ['d0', 'z0', 'theta', 'phi', 'qOverP'],
    phoenixType: 'Tracks',
    collectionName: 'ExtrapolatedMuonTrackParticles',
  },
  {
    prefix: 'GSFTrackParticlesAuxDyn',
    fields: ['d0', 'z0', 'theta', 'phi', 'qOverP'],
    phoenixType: 'Tracks',
    collectionName: 'GSFTrackParticles',
  },
  {
    prefix: 'MET_Core_AnalysisMETAuxDyn',
    fields: ['mpx', 'mpy'],
    phoenixType: 'MissingEnergy',
    collectionName: 'MET',
  },
  {
    prefix: 'PrimaryVerticesAuxDyn',
    fields: ['x', 'y', 'z'],
    phoenixType: 'Vertices',
    collectionName: 'PrimaryVertices',
  },
  {
    prefix: 'egammaClustersAuxDyn',
    fields: ['calE', 'calEta', 'calPhi'],
    phoenixType: 'CaloClusters',
    collectionName: 'egammaClusters',
  },
];

/**
 * Loader for ATLAS PHYSLITE (DAOD_PHYSLITE) ROOT files.
 *
 * Uses jsroot to read the CollectionTree TTree and converts
 * xAOD auxiliary-data branches into Phoenix event data format.
 */
export class PHYSLITELoader extends PhoenixLoader {
  /** Maximum number of events to load from the file. */
  private maxEvents: number;
  /** Collection definitions describing which branches to read. */
  private collectionDefs: PHYSLITECollectionDef[];

  /**
   * Create a PHYSLITE loader.
   * @param maxEvents Maximum number of events to read from the file.
   */
  constructor(maxEvents: number = 100) {
    super();
    this.maxEvents = maxEvents;
    this.collectionDefs = DEFAULT_COLLECTIONS;
  }

  /**
   * Open a PHYSLITE ROOT file and return all events as a
   * PhoenixEventsData object (keyed by event name).
   * @param fileSource File object or URL of the .root file.
   * @returns Promise resolving to the events data.
   */
  async getEventData(fileSource: File | string): Promise<any> {
    jsrootSettings.UseStamp = false;

    const file = await openFile(fileSource as any);
    const tree: any = await file.readObject('CollectionTree');

    if (!tree) {
      throw new Error(
        'No CollectionTree found in this ROOT file. It may not be a PHYSLITE file.',
      );
    }

    const nEntries: number = tree.fEntries ?? 0;
    const nToProcess = Math.min(nEntries, this.maxEvents);

    if (nToProcess === 0) {
      throw new Error('CollectionTree has no entries.');
    }

    // --- Add branches to the selector, skipping missing collections ---
    // jsroot throws if a non-existent branch is added to treeProcess.
    // AuxDyn branches are stored as flat top-level entries (e.g.
    // "AnalysisJetsAuxDyn.pt"), so we check exact names before adding.
    const topLevelNames = new Set<string>(
      tree.fBranches.arr.map((b: any) => b.fName as string),
    );

    const activeDefs: {
      def: PHYSLITECollectionDef;
      selectorKeys: { [field: string]: string };
    }[] = [];

    const selector = new TSelector();

    for (const def of this.collectionDefs) {
      // Check that at least the first required field branch exists
      const firstBranch = `${def.prefix}.${def.fields[0]}`;
      if (!topLevelNames.has(firstBranch)) {
        continue;
      }

      const keys: { [field: string]: string } = {};
      let allFound = true;

      for (const field of def.fields) {
        const branchName = `${def.prefix}.${field}`;
        if (!topLevelNames.has(branchName)) {
          allFound = false;
          break;
        }
        const key = `${def.prefix}__${field}`;
        keys[field] = key;
      }

      if (!allFound) continue;

      // All branches verified — add them to the selector
      for (const field of def.fields) {
        const branchName = `${def.prefix}.${field}`;
        selector.addBranch(branchName, keys[field]);
      }

      activeDefs.push({ def, selectorKeys: keys });
    }

    // Event info branches (metadata)
    const eventNumberKey = 'evtinfo_eventNumber';
    const runNumberKey = 'evtinfo_runNumber';
    selector.addBranch('EventInfoAuxDyn.eventNumber', eventNumberKey);
    selector.addBranch('EventInfoAuxDyn.runNumber', runNumberKey);

    // --- Process entries ---
    const eventsData: any = {};
    let eventIndex = 0;

    selector.Process = (entry: number) => {
      if (eventIndex >= nToProcess) {
        selector.Abort();
        return;
      }

      const tgt = selector.tgtobj;

      const eventNumber = eventNumberKey ? tgt[eventNumberKey] : eventIndex;
      const runNumber = runNumberKey ? tgt[runNumberKey] : 0;

      // Pre-initialize all active collection types so Phoenix registers
      // them from the first event (even if empty for that event).
      const eventData: any = {
        'event number': eventNumber,
        'run number': runNumber,
      };
      for (const { def } of activeDefs) {
        if (!eventData[def.phoenixType]) {
          eventData[def.phoenixType] = {};
        }
      }

      for (const { def, selectorKeys } of activeDefs) {
        const collection = this.convertCollection(def, selectorKeys, tgt);
        if (collection && collection.length > 0) {
          eventData[def.phoenixType][def.collectionName] = collection;
        }
      }

      const eventKey = `Event ${eventNumber}`;
      eventsData[eventKey] = eventData;
      eventIndex++;
    };

    await treeProcess(tree, selector, { numentries: nToProcess });

    return eventsData;
  }

  /**
   * Convert one collection's branch data for a single event entry
   * into an array of Phoenix-format objects.
   */
  private convertCollection(
    def: PHYSLITECollectionDef,
    keys: { [field: string]: string },
    tgt: any,
  ): any[] | null {
    switch (def.phoenixType) {
      case 'Electrons':
      case 'Muons':
        return this.convertCompoundObjects(keys, tgt, true);
      case 'Photons':
        return this.convertCompoundObjects(keys, tgt, false);
      case 'Jets':
        return this.convertJets(keys, tgt);
      case 'Tracks':
        return this.convertTracks(keys, tgt);
      case 'MissingEnergy':
        return this.convertMET(keys, tgt);
      case 'Vertices':
        return this.convertVertices(keys, tgt);
      case 'CaloClusters':
        return this.convertCaloClusters(keys, tgt);
      default:
        return null;
    }
  }

  /**
   * Convert compound objects (Electrons, Muons, Photons).
   * These are rendered as extrapolated tracks (charged) or clusters (neutral).
   * Energy is computed from pt, eta, and mass: E = sqrt((pt*cosh(eta))^2 + m^2).
   */
  private convertCompoundObjects(
    keys: { [field: string]: string },
    tgt: any,
    isCharged: boolean,
  ): any[] {
    const ptArr = keys['pt'] ? this.toArray(tgt[keys['pt']]) : null;
    const etaArr = keys['eta'] ? this.toArray(tgt[keys['eta']]) : null;
    const phiArr = keys['phi'] ? this.toArray(tgt[keys['phi']]) : null;
    const mArr = keys['m'] ? this.toArray(tgt[keys['m']]) : null;

    if (!etaArr || !phiArr) return [];

    const n = etaArr.length;
    const objects: any[] = [];

    for (let i = 0; i < n; i++) {
      const eta = etaArr[i];
      const phi = phiArr[i];
      const pt = ptArr ? ptArr[i] : 0; // MeV
      const m = mArr ? mArr[i] : 0;

      // E = sqrt((pt*cosh(eta))^2 + m^2)
      const p = pt * Math.cosh(eta);
      const energy = Math.sqrt(p * p + m * m);

      const obj: any = {
        eta,
        phi,
        pt,
        energy,
      };

      // For charged particles, derive sign from charge if available,
      // otherwise assume negative (electron/muon convention)
      if (isCharged) {
        // PHYSLITE muons don't have explicit pt — they are linked to tracks.
        // For electrons, charge can be inferred from track qOverP sign.
        // Default to negative charge convention for track curvature.
        obj.pdgId = -1;
      }

      objects.push(obj);
    }

    return objects;
  }

  /**
   * Convert jet branch data into Phoenix JetParams array.
   * Energy computed from pt, eta, m: E = sqrt((pt*cosh(eta))^2 + m^2).
   */
  private convertJets(keys: { [field: string]: string }, tgt: any): any[] {
    const ptArr = keys['pt'] ? this.toArray(tgt[keys['pt']]) : null;
    const etaArr = keys['eta'] ? this.toArray(tgt[keys['eta']]) : null;
    const phiArr = keys['phi'] ? this.toArray(tgt[keys['phi']]) : null;
    const mArr = keys['m'] ? this.toArray(tgt[keys['m']]) : null;

    if (!etaArr || !phiArr) return [];

    const n = etaArr.length;
    const jets: any[] = [];

    for (let i = 0; i < n; i++) {
      const pt = ptArr ? ptArr[i] : 0;
      const eta = etaArr[i];
      const m = mArr ? mArr[i] : 0;
      const p = pt * Math.cosh(eta);
      const energy = Math.sqrt(p * p + m * m);

      jets.push({
        eta,
        phi: phiArr[i],
        energy,
      });
    }

    return jets;
  }

  /**
   * Convert InDetTrackParticles branch data into Phoenix TrackParams array.
   * Uses dparams [d0, z0, phi, theta, qOverP] for Runge-Kutta extrapolation.
   */
  private convertTracks(keys: { [field: string]: string }, tgt: any): any[] {
    const d0Arr = keys['d0'] ? this.toArray(tgt[keys['d0']]) : null;
    const z0Arr = keys['z0'] ? this.toArray(tgt[keys['z0']]) : null;
    const thetaArr = keys['theta'] ? this.toArray(tgt[keys['theta']]) : null;
    const phiArr = keys['phi'] ? this.toArray(tgt[keys['phi']]) : null;
    const qOverPArr = keys['qOverP'] ? this.toArray(tgt[keys['qOverP']]) : null;

    if (!phiArr || !thetaArr || !qOverPArr) return [];

    const n = phiArr.length;
    const tracks: any[] = [];

    for (let i = 0; i < n; i++) {
      const d0 = d0Arr ? d0Arr[i] : 0;
      const z0 = z0Arr ? z0Arr[i] : 0;
      const phi = phiArr[i];
      const theta = thetaArr[i];
      const qOverP = qOverPArr[i];

      // Skip tracks with invalid parameters to avoid NaN in Runge-Kutta
      if (
        !qOverP ||
        !isFinite(1.0 / qOverP) ||
        theta <= 0 ||
        theta >= Math.PI
      ) {
        continue;
      }

      const p = Math.abs(1.0 / qOverP);
      const pt = p * Math.sin(theta);
      const eta = CoordinateHelper.thetaToEta(theta);

      tracks.push({
        dparams: [d0, z0, phi, theta, qOverP],
        phi,
        eta,
        pT: pt,
        d0,
        z0,
      });
    }

    return tracks;
  }

  /**
   * Convert MET branch data into Phoenix MissingEnergyParams array.
   */
  private convertMET(keys: { [field: string]: string }, tgt: any): any[] {
    const mpx = keys['mpx'] ? tgt[keys['mpx']] : null;
    const mpy = keys['mpy'] ? tgt[keys['mpy']] : null;

    if (mpx == null || mpy == null) return [];

    // MET may be a single value or an array with one element
    return [
      {
        etx: typeof mpx === 'number' ? mpx : (mpx[0] ?? 0),
        ety: typeof mpy === 'number' ? mpy : (mpy[0] ?? 0),
      },
    ];
  }

  /**
   * Convert PrimaryVertices branch data into Phoenix VertexParams array.
   */
  private convertVertices(keys: { [field: string]: string }, tgt: any): any[] {
    const xArr = keys['x'] ? this.toArray(tgt[keys['x']]) : null;
    const yArr = keys['y'] ? this.toArray(tgt[keys['y']]) : null;
    const zArr = keys['z'] ? this.toArray(tgt[keys['z']]) : null;

    if (!xArr || !yArr || !zArr) return [];

    const n = xArr.length;
    const vertices: any[] = [];

    for (let i = 0; i < n; i++) {
      vertices.push({
        x: xArr[i],
        y: yArr[i],
        z: zArr[i],
      });
    }

    return vertices;
  }

  /**
   * Convert egammaClusters branch data into Phoenix CaloClusterParams array.
   */
  private convertCaloClusters(
    keys: { [field: string]: string },
    tgt: any,
  ): any[] {
    const eArr = keys['calE'] ? this.toArray(tgt[keys['calE']]) : null;
    const etaArr = keys['calEta'] ? this.toArray(tgt[keys['calEta']]) : null;
    const phiArr = keys['calPhi'] ? this.toArray(tgt[keys['calPhi']]) : null;

    if (!eArr || !etaArr || !phiArr) return [];

    const n = eArr.length;
    const clusters: any[] = [];

    for (let i = 0; i < n; i++) {
      clusters.push({
        energy: eArr[i],
        eta: etaArr[i],
        phi: phiArr[i],
      });
    }

    return clusters;
  }

  /**
   * Ensure a value is a plain array (jsroot may return typed arrays).
   */
  private toArray(val: any): number[] | null {
    if (val == null) return null;
    if (Array.isArray(val)) return val;
    if (ArrayBuffer.isView(val)) return Array.from(val as any);
    if (typeof val === 'number') return [val];
    return null;
  }
}

results matching ""

    No results matching ""