Exemplo n.º 1
0
def nearPhotonInfo(trackingHitList,
                   g_trajectory,
                   returnLayer=True,
                   returnNumber=True):

    layer = 33
    n = 0
    for hit in trackingHitList:

        # Near the photn trajectory
        if physTools.dist(
                physTools.pos(hit)[:2],
                g_trajectory[physTools.ecal_layer(hit)]) < physTools.cellWidth:
            n += 1

            # Earliest layer
            if physTools.ecal_layer(hit) < layer:
                layer = physTools.ecal_layer(hit)

    # Prepare and return desired output
    out = []
    if returnLayer: out.append(layer)
    if returnNumber: out.append(n)

    return out
Exemplo n.º 2
0
def event_process(self):

    # Initialize BDT input variables w/ defaults
    feats = next(iter(self.tfMakers.values())).resetFeats()

    # Assign pre-computed variables
    feats['nReadoutHits']       = self.ecalVeto.getNReadoutHits()
    feats['summedDet']          = self.ecalVeto.getSummedDet()
    feats['summedTightIso']     = self.ecalVeto.getSummedTightIso()
    feats['maxCellDep']         = self.ecalVeto.getMaxCellDep()
    feats['showerRMS']          = self.ecalVeto.getShowerRMS()
    feats['xStd']               = self.ecalVeto.getXStd()
    feats['yStd']               = self.ecalVeto.getYStd()
    feats['avgLayerHit']        = self.ecalVeto.getAvgLayerHit()
    feats['stdLayerHit']        = self.ecalVeto.getStdLayerHit()
    feats['deepestLayerHit']    = self.ecalVeto.getDeepestLayerHit() 
    feats['ecalBackEnergy']     = self.ecalVeto.getEcalBackEnergy()
    
    ###################################
    # Determine event type
    ###################################

    # Get e position and momentum from EcalSP
    e_ecalHit = physTools.electronEcalSPHit(self.ecalSPHits)
    if e_ecalHit != None:
        e_ecalPos, e_ecalP = e_ecalHit.getPosition(), e_ecalHit.getMomentum()

    # Photon Info from targetSP
    e_targetHit = physTools.electronTargetSPHit(self.targetSPHits)
    if e_targetHit != None:
        g_targPos, g_targP = physTools.gammaTargetInfo(e_targetHit)
    else:  # Should about never happen -> division by 0 in g_traj
        print('no e at targ!')
        g_targPos = g_targP = np.zeros(3)

    # Get electron and photon trajectories
    e_traj = g_traj = None

    if e_ecalHit != None:
        e_traj = physTools.layerIntercepts(e_ecalPos, e_ecalP)

    if e_targetHit != None:
        g_traj = physTools.layerIntercepts(g_targPos, g_targP)

    # Fiducial categories (filtered into different output trees)
    if self.separate:
        e_fid = g_fid = False

        if e_traj != None:
            for cell in cellMap:
                if physTools.dist( cell[1:], e_traj[0] ) <= physTools.cell_radius:
                    e_fid = True
                    break

        if g_traj != None:
            for cell in cellMap:
                if physTools.dist( cell[1:], g_traj[0] ) <= physTools.cell_radius:
                    g_fid = True
                    break

    ###################################
    # Compute extra BDT input variables
    ###################################

    # Find epSep and epDot, and prepare electron and photon trajectory vectors
    if e_traj != None and g_traj != None:

        # Create arrays marking start and end of each trajectory
        e_traj_ends = [np.array([e_traj[0][0], e_traj[0][1], physTools.ecal_layerZs[0]    ]),
                       np.array([e_traj[-1][0], e_traj[-1][1], physTools.ecal_layerZs[-1] ])]
        g_traj_ends = [np.array([g_traj[0][0], g_traj[0][1], physTools.ecal_layerZs[0]    ]),
                       np.array([g_traj[-1][0], g_traj[-1][1], physTools.ecal_layerZs[-1] ])]

        # Unused epDot and epSep
        #e_norm  = physTools.unit( e_traj_ends[1] - e_traj_ends[0] )
        #g_norm  = physTools.unit( g_traj_ends[1] - g_traj_ends[0] )
        #feats['epSep'] = physTools.dist( e_traj_ends[0], g_traj_ends[0] )
        #feats['epDot'] = physTools.dot(e_norm,g_norm)

    else:

        # Electron trajectory is missing so all hits in Ecal are okay to use
        # Pick trajectories so they won'trestrict tracking, far outside the Ecal

        e_traj_ends   = [np.array([999 ,999 ,0   ]), np.array([999 ,999 ,999 ]) ]
        g_traj_ends   = [np.array([1000,1000,0   ]), np.array([1000,1000,1000]) ]

        #feats['epSep'] = 10.0 + 1.0 # Don't cut on these in this case
        #feats['epDot'] = 3.0 + 1.0

    # Territory setup (consider missing case)
    gToe    = physTools.unit( e_traj_ends[0] - g_traj_ends[0] )
    origin  = g_traj_ends[0] + 0.5*8.7*gToe

    # Recoil electron momentum magnitude and angle with z-axis
    recoilPMag  = physTools.mag(  e_ecalP )                 if e_ecalHit != None else -1.0
    recoilTheta = physTools.angle(e_ecalP, units='radians') if recoilPMag > 0    else -1.0

    # Set electron RoC binnings
    e_radii = physTools.radius68_thetalt10_plt500
    if recoilTheta < 10 and recoilPMag >= 500: e_radii = physTools.radius68_thetalt10_pgt500
    elif recoilTheta >= 10 and recoilTheta < 20: e_radii = physTools.radius68_theta10to20
    elif recoilTheta >= 20: e_radii = physTools.radius68_thetagt20

    # Always use default binning for photon RoC
    g_radii = physTools.radius68_thetalt10_plt500

    # Big data
    trackingHitList = []

    # Major ECal loop
    for hit in self.ecalRecHits:
        
        if hit.getEnergy() > 0:

            layer = physTools.ecal_layer(hit)
            xy_pair = ( hit.getXPos(), hit.getYPos() )

            # Territory selections
            hitPrime = physTools.pos(hit) - origin
            if np.dot(hitPrime, gToe) > 0: feats['fullElectronTerritoryHits'] += 1
            else: feats['fullPhotonTerritoryHits'] += 1

            # Distance to electron trajectory
            if e_traj != None:
                xy_e_traj = ( e_traj[layer][0], e_traj[layer][1] )
                distance_e_traj = physTools.dist(xy_pair, xy_e_traj)
            else: distance_e_traj = -1.0

            # Distance to photon trajectory
            if g_traj != None:
                xy_g_traj = ( g_traj[layer][0], g_traj[layer][1] )
                distance_g_traj = physTools.dist(xy_pair, xy_g_traj)
            else: distance_g_traj = -1.0

            # Decide which longitudinal segment the hit is in and add to sums
            for i in range(1, physTools.nSegments + 1):

                if (physTools.segLayers[i - 1] <= layer)\
                  and (layer <= physTools.segLayers[i] - 1):
                    feats['energy_s{}'.format(i)] += hit.getEnergy()
                    feats['nHits_s{}'.format(i)] += 1
                    feats['xMean_s{}'.format(i)] += xy_pair[0]*hit.getEnergy()
                    feats['yMean_s{}'.format(i)] += xy_pair[1]*hit.getEnergy()
                    feats['layerMean_s{}'.format(i)] += layer*hit.getEnergy()

                    # Decide which containment region the hit is in and add to sums
                    for j in range(1, physTools.nRegions + 1):

                        if ((j - 1)*e_radii[layer] <= distance_e_traj)\
                          and (distance_e_traj < j*e_radii[layer]):
                            feats['eContEnergy_x{}_s{}'.format(j,i)] += hit.getEnergy()
                            feats['eContNHits_x{}_s{}'.format(j,i)] += 1
                            feats['eContXMean_x{}_s{}'.format(j,i)] +=\
                                                                xy_pair[0]*hit.getEnergy()
                            feats['eContYMean_x{}_s{}'.format(j,i)] +=\
                                                                xy_pair[1]*hit.getEnergy()
                            feats['eContLayerMean_x{}_s{}'.format(j,i)] +=\
                                                                layer*hit.getEnergy()

                        if ((j - 1)*g_radii[layer] <= distance_g_traj)\
                          and (distance_g_traj < j*g_radii[layer]):
                            feats['gContEnergy_x{}_s{}'.format(j,i)] += hit.getEnergy()
                            feats['gContNHits_x{}_s{}'.format(j,i)] += 1
                            feats['gContXMean_x{}_s{}'.format(j,i)] +=\
                                                                xy_pair[0]*hit.getEnergy()
                            feats['gContYMean_x{}_s{}'.format(j,i)] +=\
                                                                xy_pair[1]*hit.getEnergy()
                            feats['gContLayerMean_x{}_s{}'.format(j,i)] +=\
                                                                layer*hit.getEnergy()

                        if (distance_e_traj > j*e_radii[layer])\
                          and (distance_g_traj > j*g_radii[layer]):
                            feats['oContEnergy_x{}_s{}'.format(j,i)] += hit.getEnergy()
                            feats['oContNHits_x{}_s{}'.format(j,i)] += 1
                            feats['oContXMean_x{}_s{}'.format(j,i)] +=\
                                                                xy_pair[0]*hit.getEnergy()
                            feats['oContYMean_x{}_s{}'.format(j,i)] +=\
                                                                xy_pair[1]*hit.getEnergy()
                            feats['oContLayerMean_x{}_s{}'.format(j,i)] +=\
                                                                layer*hit.getEnergy()

            # Build MIP tracking hit list; (outside electron region or electron missing)
            if distance_e_traj >= e_radii[layer] or distance_e_traj == -1.0:
                trackingHitList.append(hit) 

    # If possible, quotient out the total energy from the means
    for i in range(1, physTools.nSegments + 1):

        if feats['energy_s{}'.format(i)] > 0:
            feats['xMean_s{}'.format(i)] /= feats['energy_s{}'.format(i)]
            feats['yMean_s{}'.format(i)] /= feats['energy_s{}'.format(i)]
            feats['layerMean_s{}'.format(i)] /= feats['energy_s{}'.format(i)]

        for j in range(1, physTools.nRegions + 1):

            if feats['eContEnergy_x{}_s{}'.format(j,i)] > 0:
                feats['eContXMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['eContEnergy_x{}_s{}'.format(j,i)]
                feats['eContYMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['eContEnergy_x{}_s{}'.format(j,i)]
                feats['eContLayerMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['eContEnergy_x{}_s{}'.format(j,i)]

            if feats['gContEnergy_x{}_s{}'.format(j,i)] > 0:
                feats['gContXMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['gContEnergy_x{}_s{}'.format(j,i)]
                feats['gContYMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['gContEnergy_x{}_s{}'.format(j,i)]
                feats['gContLayerMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['gContEnergy_x{}_s{}'.format(j,i)]

            if feats['oContEnergy_x{}_s{}'.format(j,i)] > 0:
                feats['oContXMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['oContEnergy_x{}_s{}'.format(j,i)]
                feats['oContYMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['oContEnergy_x{}_s{}'.format(j,i)]
                feats['oContLayerMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['oContEnergy_x{}_s{}'.format(j,i)]

    # Loop over hits again to calculate the standard deviations
    for hit in self.ecalRecHits:

        layer = physTools.ecal_layer(hit)
        xy_pair = (hit.getXPos(), hit.getYPos())

        # Distance to electron trajectory
        if e_traj != None:
            xy_e_traj = (e_traj[layer][0], e_traj[layer][1])
            distance_e_traj = physTools.dist(xy_pair, xy_e_traj)
        else:
            distance_e_traj = -1.0

        # Distance to photon trajectory
        if g_traj != None:
            xy_g_traj = (g_traj[layer][0], g_traj[layer][1])
            distance_g_traj = physTools.dist(xy_pair, xy_g_traj)
        else:
            distance_g_traj = -1.0

        # Decide which longitudinal segment the hit is in and add to sums
        for i in range(1, physTools.nSegments + 1):

            if (physTools.segLayers[i - 1] <= layer) and\
                    (layer <= physTools.segLayers[i] - 1):
                feats['xStd_s{}'.format(i)] += ((xy_pair[0] -\
                        feats['xMean_s{}'.format(i)])**2)*hit.getEnergy()
                feats['yStd_s{}'.format(i)] += ((xy_pair[1] -\
                        feats['yMean_s{}'.format(i)])**2)*hit.getEnergy()
                feats['layerStd_s{}'.format(i)] += ((layer -\
                        feats['layerMean_s{}'.format(i)])**2)*hit.getEnergy()

                # Decide which containment region the hit is in and add to sums
                for j in range(1, physTools.nRegions + 1):

                    if ((j - 1)*e_radii[layer] <= distance_e_traj)\
                      and (distance_e_traj < j*e_radii[layer]):
                        feats['eContXStd_x{}_s{}'.format(j,i)] += ((xy_pair[0] -\
                                feats['eContXMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['eContYStd_x{}_s{}'.format(j,i)] += ((xy_pair[1] -\
                                feats['eContYMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['eContLayerStd_x{}_s{}'.format(j,i)] += ((layer -\
                            feats['eContLayerMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()

                    if ((j - 1)*g_radii[layer] <= distance_g_traj)\
                      and (distance_g_traj < j*g_radii[layer]):
                        feats['gContXStd_x{}_s{}'.format(j,i)] += ((xy_pair[0] -\
                                feats['gContXMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['gContYStd_x{}_s{}'.format(j,i)] += ((xy_pair[1] -\
                                feats['gContYMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['gContLayerStd_x{}_s{}'.format(j,i)] += ((layer -\
                            feats['gContLayerMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()

                    if (distance_e_traj > j*e_radii[layer])\
                      and (distance_g_traj > j*g_radii[layer]):
                        feats['oContXStd_x{}_s{}'.format(j,i)] += ((xy_pair[0] -\
                                feats['oContXMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['oContYStd_x{}_s{}'.format(j,i)] += ((xy_pair[1] -\
                                feats['oContYMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['oContLayerStd_x{}_s{}'.format(j,i)] += ((layer -\
                            feats['oContLayerMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()

    # Quotient out the total energies from the standard deviations if possible and take root
    for i in range(1, physTools.nSegments + 1):

        if feats['energy_s{}'.format(i)] > 0:
            feats['xStd_s{}'.format(i)] = math.sqrt(feats['xStd_s{}'.format(i)]/\
                    feats['energy_s{}'.format(i)])
            feats['yStd_s{}'.format(i)] = math.sqrt(feats['yStd_s{}'.format(i)]/\
                    feats['energy_s{}'.format(i)])
            feats['layerStd_s{}'.format(i)] = math.sqrt(feats['layerStd_s{}'.format(i)]/\
                    feats['energy_s{}'.format(i)])

        for j in range(1, physTools.nRegions + 1):

            if feats['eContEnergy_x{}_s{}'.format(j,i)] > 0:
                feats['eContXStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['eContXStd_x{}_s{}'.format(j,i)]/\
                        feats['eContEnergy_x{}_s{}'.format(j,i)])
                feats['eContYStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['eContYStd_x{}_s{}'.format(j,i)]/\
                        feats['eContEnergy_x{}_s{}'.format(j,i)])
                feats['eContLayerStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['eContLayerStd_x{}_s{}'.format(j,i)]/\
                        feats['eContEnergy_x{}_s{}'.format(j,i)])

            if feats['gContEnergy_x{}_s{}'.format(j,i)] > 0:
                feats['gContXStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['gContXStd_x{}_s{}'.format(j,i)]/\
                        feats['gContEnergy_x{}_s{}'.format(j,i)])
                feats['gContYStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['gContYStd_x{}_s{}'.format(j,i)]/\
                        feats['gContEnergy_x{}_s{}'.format(j,i)])
                feats['gContLayerStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['gContLayerStd_x{}_s{}'.format(j,i)]/\
                        feats['gContEnergy_x{}_s{}'.format(j,i)])

            if feats['oContEnergy_x{}_s{}'.format(j,i)] > 0:
                feats['oContXStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['oContXStd_x{}_s{}'.format(j,i)]/\
                        feats['oContEnergy_x{}_s{}'.format(j,i)])
                feats['oContYStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['oContYStd_x{}_s{}'.format(j,i)]/\
                        feats['oContEnergy_x{}_s{}'.format(j,i)])
                feats['oContLayerStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['oContLayerStd_x{}_s{}'.format(j,i)]/\
                        feats['oContEnergy_x{}_s{}'.format(j,i)])


    # Find the first layer of the ECal where a hit near the projected photon trajectory
    # AND the total number of hits around the photon trajectory
    if g_traj != None: # If no photon trajectory, leave this at the default

        # First currently unusued; pending further study; performance drop from  v9 and v12
        #print(trackingHitList, g_traj)
        feats['firstNearPhLayer'], feats['nNearPhHits'] = mipTracking.nearPhotonInfo(
                                                            trackingHitList, g_traj )
    else: feats['nNearPhHits'] = feats['nReadoutHits']


    # Territories limited to trackingHitList
    if e_traj != None:
        for hit in trackingHitList:
            hitPrime = physTools.pos(hit) - origin
            if np.dot(hitPrime, gToe) > 0: feats['electronTerritoryHits'] += 1
            else: feats['photonTerritoryHits'] += 1
    else:
        feats['photonTerritoryHits'] = feats['nReadoutHits']
        feats['TerritoryRatio'] = 10
        feats['fullTerritoryRatio'] = 10
    if feats['electronTerritoryHits'] != 0:
        feats['TerritoryRatio'] = feats['photonTerritoryHits']/feats['electronTerritoryHits']
    if feats['fullElectronTerritoryHits'] != 0:
        feats['fullTerritoryRatio'] = feats['fullPhotonTerritoryHits']/\
                                            feats['fullElectronTerritoryHits']


    # Find MIP tracks
    feats['straight4'], trackingHitList = mipTracking.findStraightTracks(
                                trackingHitList, e_traj_ends, g_traj_ends,
                                mst = 4, returnHitList = True)

    # Fill the tree (according to fiducial category) with values for this event
    if not self.separate:
        self.tfMakers['unsorted'].fillEvent(feats)
    else:
        if e_fid and g_fid: self.tfMakers['egin'].fillEvent(feats)
        elif e_fid and not g_fid: self.tfMakers['ein'].fillEvent(feats)
        elif not e_fid and g_fid: self.tfMakers['gin'].fillEvent(feats)
        else: self.tfMakers['none'].fillEvent(feats)
Exemplo n.º 3
0
def event_process(self):

    # Initialize BDT input variables w/ defaults
    feats = next(iter(self.tfMakers.values())).resetFeats()

    evtNum = self.eventHeader.getEventNumber()
    feats['eventNumber'] = evtNum

    #############################################
    # Assign pre-computed BDT variables
    #############################################

    feats['nReadoutHits'] = self.ecalVeto.getNReadoutHits()
    feats['summedDet'] = self.ecalVeto.getSummedDet()
    feats['summedTightIso'] = self.ecalVeto.getSummedTightIso()
    feats['maxCellDep'] = self.ecalVeto.getMaxCellDep()
    feats['showerRMS'] = self.ecalVeto.getShowerRMS()
    feats['xStd'] = self.ecalVeto.getXStd()
    feats['yStd'] = self.ecalVeto.getYStd()
    feats['avgLayerHit'] = self.ecalVeto.getAvgLayerHit()
    feats['stdLayerHit'] = self.ecalVeto.getStdLayerHit()
    feats['deepestLayerHit'] = self.ecalVeto.getDeepestLayerHit()
    feats['ecalBackEnergy'] = self.ecalVeto.getEcalBackEnergy()

    for i in range(0, physTools.nRegions):
        feats['electronContainmentEnergy_x{}'.format(
            i + 1)] = self.ecalVeto.getElectronContainmentEnergy()[i]
        feats['photonContainmentEnergy_x{}'.format(
            i + 1)] = self.ecalVeto.getPhotonContainmentEnergy()[i]
        feats['outsideContainmentEnergy_x{}'.format(
            i + 1)] = self.ecalVeto.getOutsideContainmentEnergy()[i]
        feats['outsideContainmentNHits_x{}'.format(
            i + 1)] = self.ecalVeto.getOutsideContainmentNHits()[i]
        feats['outsideContainmentXStd_x{}'.format(
            i + 1)] = self.ecalVeto.getOutsideContainmentXStd()[i]
        feats['outsideContainmentYStd_x{}'.format(
            i + 1)] = self.ecalVeto.getOutsideContainmentYStd()[i]

    ###################################
    # Determine event type
    ###################################

    # Get e position and momentum from EcalSP
    e_ecalHit = physTools.electronEcalSPHit(self.ecalSPHits)
    if e_ecalHit != None:
        e_ecalPos, e_ecalP = e_ecalHit.getPosition(), e_ecalHit.getMomentum()
    else:
        print('No electron at ECal scoring plane for event {}!'.format(evtNum))

    # Photon Info from targetSP
    e_targetHit = physTools.electronTargetSPHit(self.targetSPHits)
    if e_targetHit != None:
        g_targPos, g_targP = physTools.gammaTargetInfo(e_targetHit)
    else:  # Should about never happen -> division by 0 in g_traj
        print(
            'No electron at target scoring plane for event {}!'.format(evtNum))
        g_targPos = g_targP = np.zeros(3)

    if e_ecalHit != None and e_targetHit == None:
        print('Event {} is anomalous!'.format(evtNum))

    # Get electron and photon trajectories
    e_traj = g_traj = None

    if e_ecalHit != None:
        e_traj = physTools.layerIntercepts(e_ecalPos, e_ecalP)

    if e_targetHit != None:
        g_traj = physTools.layerIntercepts(g_targPos, g_targP)

    # Fiducial categories (filtered into different output trees)
    if self.separate:
        e_fid = g_fid = False

        if e_traj != None:
            for cell in cellMap:
                if physTools.dist(cell[1:],
                                  e_traj[0]) <= physTools.cell_radius:
                    e_fid = True
                    break

        if g_traj != None:
            for cell in cellMap:
                if physTools.dist(cell[1:],
                                  g_traj[0]) <= physTools.cell_radius:
                    g_fid = True
                    break

    #############################################
    # Compute extra BDT input variables
    #############################################

    # Find epSep and epDot, and prepare electron and photon trajectory vectors
    if e_traj != None and g_traj != None:

        # Create arrays marking start and end of each trajectory
        e_traj_ends = [
            np.array([e_traj[0][0], e_traj[0][1], physTools.ecal_layerZs[0]]),
            np.array(
                [e_traj[-1][0], e_traj[-1][1], physTools.ecal_layerZs[-1]])
        ]
        g_traj_ends = [
            np.array([g_traj[0][0], g_traj[0][1], physTools.ecal_layerZs[0]]),
            np.array(
                [g_traj[-1][0], g_traj[-1][1], physTools.ecal_layerZs[-1]])
        ]

        e_norm = physTools.unit(e_traj_ends[1] - e_traj_ends[0])
        g_norm = physTools.unit(g_traj_ends[1] - g_traj_ends[0])
        feats['epSep'] = physTools.dist(e_traj_ends[0], g_traj_ends[0])
        feats['epDot'] = physTools.dot(e_norm, g_norm)

    else:

        # Electron trajectory is missing so all hits in Ecal are okay to use
        # Pick trajectories so they won'trestrict tracking, far outside the Ecal

        e_traj_ends = [np.array([999, 999, 0]), np.array([999, 999, 999])]
        g_traj_ends = [np.array([1000, 1000, 0]), np.array([1000, 1000, 1000])]

        feats['epSep'] = 10.0 + 1.0  # Don't cut on these in this case
        feats['epDot'] = 3.0 + 1.0

    # Territory setup (consider missing case)
    gToe = physTools.unit(e_traj_ends[0] - g_traj_ends[0])
    origin = g_traj_ends[0] + 0.5 * 8.7 * gToe

    # Recoil electron momentum magnitude and angle with z-axis
    recoilPMag = physTools.mag(e_ecalP) if e_ecalHit != None else -1.0
    recoilTheta = physTools.angle(e_ecalP,
                                  units='radians') if recoilPMag > 0 else -1.0

    # Set electron RoC binnings
    e_radii = physTools.radius68_thetalt10_plt500
    if recoilTheta < 10 and recoilPMag >= 500:
        e_radii = physTools.radius68_thetalt10_pgt500
    elif recoilTheta >= 10 and recoilTheta < 20:
        e_radii = physTools.radius68_theta10to20
    elif recoilTheta >= 20:
        e_radii = physTools.radius68_thetagt20

    # Always use default binning for photon RoC
    g_radii = physTools.radius68_thetalt10_plt500

    # Big data
    trackingHitList = []

    # Major ECal loop
    for hit in self.ecalRecHits:

        if hit.getEnergy() <= 0:
            continue

        layer = physTools.ecal_layer(hit)
        xy_pair = (hit.getXPos(), hit.getYPos())

        # Territory selections
        hitPrime = physTools.pos(hit) - origin
        if np.dot(hitPrime, gToe) > 0: feats['fullElectronTerritoryHits'] += 1
        else: feats['fullPhotonTerritoryHits'] += 1

        # Distance to electron trajectory
        if e_traj != None:
            xy_e_traj = (e_traj[layer][0], e_traj[layer][1])
            distance_e_traj = physTools.dist(xy_pair, xy_e_traj)
        else:
            distance_e_traj = -1.0

        # Distance to photon trajectory
        if g_traj != None:
            xy_g_traj = (g_traj[layer][0], g_traj[layer][1])
            distance_g_traj = physTools.dist(xy_pair, xy_g_traj)
        else:
            distance_g_traj = -1.0

        # Decide which longitudinal segment the hit is in and add to sums
        for i in range(1, physTools.nSegments + 1):

            if (physTools.segLayers[i - 1] <= layer)\
              and (layer <= physTools.segLayers[i] - 1):
                feats['energy_s{}'.format(i)] += hit.getEnergy()
                feats['nHits_s{}'.format(i)] += 1
                feats['xMean_s{}'.format(i)] += xy_pair[0] * hit.getEnergy()
                feats['yMean_s{}'.format(i)] += xy_pair[1] * hit.getEnergy()
                feats['layerMean_s{}'.format(i)] += layer * hit.getEnergy()

                # Decide which containment region the hit is in and add to sums
                for j in range(1, physTools.nRegions + 1):

                    if ((j - 1)*e_radii[layer] <= distance_e_traj)\
                      and (distance_e_traj < j*e_radii[layer]):
                        feats['eContEnergy_x{}_s{}'.format(
                            j, i)] += hit.getEnergy()
                        feats['eContNHits_x{}_s{}'.format(j, i)] += 1
                        feats['eContXMean_x{}_s{}'.format(j,i)] +=\
                                                            xy_pair[0]*hit.getEnergy()
                        feats['eContYMean_x{}_s{}'.format(j,i)] +=\
                                                            xy_pair[1]*hit.getEnergy()
                        feats['eContLayerMean_x{}_s{}'.format(j,i)] +=\
                                                            layer*hit.getEnergy()

                    if ((j - 1)*g_radii[layer] <= distance_g_traj)\
                      and (distance_g_traj < j*g_radii[layer]):
                        feats['gContEnergy_x{}_s{}'.format(
                            j, i)] += hit.getEnergy()
                        feats['gContNHits_x{}_s{}'.format(j, i)] += 1
                        feats['gContXMean_x{}_s{}'.format(j,i)] +=\
                                                            xy_pair[0]*hit.getEnergy()
                        feats['gContYMean_x{}_s{}'.format(j,i)] +=\
                                                            xy_pair[1]*hit.getEnergy()
                        feats['gContLayerMean_x{}_s{}'.format(j,i)] +=\
                                                            layer*hit.getEnergy()

                    if (distance_e_traj > j*e_radii[layer])\
                      and (distance_g_traj > j*g_radii[layer]):
                        feats['oContEnergy_x{}_s{}'.format(
                            j, i)] += hit.getEnergy()
                        feats['oContNHits_x{}_s{}'.format(j, i)] += 1
                        feats['oContXMean_x{}_s{}'.format(j,i)] +=\
                                                            xy_pair[0]*hit.getEnergy()
                        feats['oContYMean_x{}_s{}'.format(j,i)] +=\
                                                            xy_pair[1]*hit.getEnergy()
                        feats['oContLayerMean_x{}_s{}'.format(j,i)] +=\
                                                            layer*hit.getEnergy()

        # Build MIP tracking hit list; (outside electron region or electron missing)
        if distance_e_traj >= e_radii[layer] or distance_e_traj == -1.0:
            trackingHitList.append(hit)

    # If possible, quotient out the total energy from the means
    for i in range(1, physTools.nSegments + 1):

        if feats['energy_s{}'.format(i)] > 0:
            feats['xMean_s{}'.format(i)] /= feats['energy_s{}'.format(i)]
            feats['yMean_s{}'.format(i)] /= feats['energy_s{}'.format(i)]
            feats['layerMean_s{}'.format(i)] /= feats['energy_s{}'.format(i)]

        for j in range(1, physTools.nRegions + 1):

            if feats['eContEnergy_x{}_s{}'.format(j, i)] > 0:
                feats['eContXMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['eContEnergy_x{}_s{}'.format(j,i)]
                feats['eContYMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['eContEnergy_x{}_s{}'.format(j,i)]
                feats['eContLayerMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['eContEnergy_x{}_s{}'.format(j,i)]

            if feats['gContEnergy_x{}_s{}'.format(j, i)] > 0:
                feats['gContXMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['gContEnergy_x{}_s{}'.format(j,i)]
                feats['gContYMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['gContEnergy_x{}_s{}'.format(j,i)]
                feats['gContLayerMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['gContEnergy_x{}_s{}'.format(j,i)]

            if feats['oContEnergy_x{}_s{}'.format(j, i)] > 0:
                feats['oContXMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['oContEnergy_x{}_s{}'.format(j,i)]
                feats['oContYMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['oContEnergy_x{}_s{}'.format(j,i)]
                feats['oContLayerMean_x{}_s{}'.format(j,i)] /=\
                                                    feats['oContEnergy_x{}_s{}'.format(j,i)]

    # Loop over hits again to calculate the standard deviations
    for hit in self.ecalRecHits:

        if hit.getEnergy() <= 0:
            continue

        layer = physTools.ecal_layer(hit)
        xy_pair = (hit.getXPos(), hit.getYPos())

        # Distance to electron trajectory
        if e_traj != None:
            xy_e_traj = (e_traj[layer][0], e_traj[layer][1])
            distance_e_traj = physTools.dist(xy_pair, xy_e_traj)
        else:
            distance_e_traj = -1.0

        # Distance to photon trajectory
        if g_traj != None:
            xy_g_traj = (g_traj[layer][0], g_traj[layer][1])
            distance_g_traj = physTools.dist(xy_pair, xy_g_traj)
        else:
            distance_g_traj = -1.0

        # Decide which longitudinal segment the hit is in and add to sums
        for i in range(1, physTools.nSegments + 1):

            if (physTools.segLayers[i - 1] <= layer) and\
                    (layer <= physTools.segLayers[i] - 1):
                feats['xStd_s{}'.format(i)] += ((xy_pair[0] -\
                        feats['xMean_s{}'.format(i)])**2)*hit.getEnergy()
                feats['yStd_s{}'.format(i)] += ((xy_pair[1] -\
                        feats['yMean_s{}'.format(i)])**2)*hit.getEnergy()
                feats['layerStd_s{}'.format(i)] += ((layer -\
                        feats['layerMean_s{}'.format(i)])**2)*hit.getEnergy()

                # Decide which containment region the hit is in and add to sums
                for j in range(1, physTools.nRegions + 1):

                    if ((j - 1)*e_radii[layer] <= distance_e_traj)\
                      and (distance_e_traj < j*e_radii[layer]):
                        feats['eContXStd_x{}_s{}'.format(j,i)] += ((xy_pair[0] -\
                                feats['eContXMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['eContYStd_x{}_s{}'.format(j,i)] += ((xy_pair[1] -\
                                feats['eContYMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['eContLayerStd_x{}_s{}'.format(j,i)] += ((layer -\
                            feats['eContLayerMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()

                    if ((j - 1)*g_radii[layer] <= distance_g_traj)\
                      and (distance_g_traj < j*g_radii[layer]):
                        feats['gContXStd_x{}_s{}'.format(j,i)] += ((xy_pair[0] -\
                                feats['gContXMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['gContYStd_x{}_s{}'.format(j,i)] += ((xy_pair[1] -\
                                feats['gContYMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['gContLayerStd_x{}_s{}'.format(j,i)] += ((layer -\
                            feats['gContLayerMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()

                    if (distance_e_traj > j*e_radii[layer])\
                      and (distance_g_traj > j*g_radii[layer]):
                        feats['oContXStd_x{}_s{}'.format(j,i)] += ((xy_pair[0] -\
                                feats['oContXMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['oContYStd_x{}_s{}'.format(j,i)] += ((xy_pair[1] -\
                                feats['oContYMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()
                        feats['oContLayerStd_x{}_s{}'.format(j,i)] += ((layer -\
                            feats['oContLayerMean_x{}_s{}'.format(j,i)])**2)*hit.getEnergy()

    # Quotient out the total energies from the standard deviations if possible and take root
    for i in range(1, physTools.nSegments + 1):

        if feats['energy_s{}'.format(i)] > 0:
            feats['xStd_s{}'.format(i)] = math.sqrt(feats['xStd_s{}'.format(i)]/\
                    feats['energy_s{}'.format(i)])
            feats['yStd_s{}'.format(i)] = math.sqrt(feats['yStd_s{}'.format(i)]/\
                    feats['energy_s{}'.format(i)])
            feats['layerStd_s{}'.format(i)] = math.sqrt(feats['layerStd_s{}'.format(i)]/\
                    feats['energy_s{}'.format(i)])

        for j in range(1, physTools.nRegions + 1):

            if feats['eContEnergy_x{}_s{}'.format(j, i)] > 0:
                feats['eContXStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['eContXStd_x{}_s{}'.format(j,i)]/\
                        feats['eContEnergy_x{}_s{}'.format(j,i)])
                feats['eContYStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['eContYStd_x{}_s{}'.format(j,i)]/\
                        feats['eContEnergy_x{}_s{}'.format(j,i)])
                feats['eContLayerStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['eContLayerStd_x{}_s{}'.format(j,i)]/\
                        feats['eContEnergy_x{}_s{}'.format(j,i)])

            if feats['gContEnergy_x{}_s{}'.format(j, i)] > 0:
                feats['gContXStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['gContXStd_x{}_s{}'.format(j,i)]/\
                        feats['gContEnergy_x{}_s{}'.format(j,i)])
                feats['gContYStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['gContYStd_x{}_s{}'.format(j,i)]/\
                        feats['gContEnergy_x{}_s{}'.format(j,i)])
                feats['gContLayerStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['gContLayerStd_x{}_s{}'.format(j,i)]/\
                        feats['gContEnergy_x{}_s{}'.format(j,i)])

            if feats['oContEnergy_x{}_s{}'.format(j, i)] > 0:
                feats['oContXStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['oContXStd_x{}_s{}'.format(j,i)]/\
                        feats['oContEnergy_x{}_s{}'.format(j,i)])
                feats['oContYStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['oContYStd_x{}_s{}'.format(j,i)]/\
                        feats['oContEnergy_x{}_s{}'.format(j,i)])
                feats['oContLayerStd_x{}_s{}'.format(j,i)] =\
                        math.sqrt(feats['oContLayerStd_x{}_s{}'.format(j,i)]/\
                        feats['oContEnergy_x{}_s{}'.format(j,i)])

    # Find the first layer of the ECal where a hit near the projected photon trajectory
    # AND the total number of hits around the photon trajectory
    if g_traj != None:  # If no photon trajectory, leave this at the default

        # First currently unusued; pending further study; performance drop from  v9 and v12
        #print(trackingHitList, g_traj)
        feats['firstNearPhLayer'], feats[
            'nNearPhHits'] = mipTracking.nearPhotonInfo(
                trackingHitList, g_traj)
    else:
        feats['nNearPhHits'] = feats['nReadoutHits']

    # Territories limited to trackingHitList
    if e_traj != None:
        for hit in trackingHitList:
            hitPrime = physTools.pos(hit) - origin
            if np.dot(hitPrime, gToe) > 0: feats['electronTerritoryHits'] += 1
            else: feats['photonTerritoryHits'] += 1
    else:
        feats['photonTerritoryHits'] = feats['nReadoutHits']
        feats['TerritoryRatio'] = 10
        feats['fullTerritoryRatio'] = 10
    if feats['electronTerritoryHits'] != 0:
        feats['TerritoryRatio'] = feats['photonTerritoryHits'] / feats[
            'electronTerritoryHits']
    if feats['fullElectronTerritoryHits'] != 0:
        feats['fullTerritoryRatio'] = feats['fullPhotonTerritoryHits']/\
                                            feats['fullElectronTerritoryHits']

    # Find MIP tracks
    feats['straight4'], trackingHitList = mipTracking.findStraightTracks(
        trackingHitList, e_traj_ends, g_traj_ends, mst=4, returnHitList=True)

    #######################################################
    # Other quantities needed for sample analysis
    #######################################################

    # Kinematic information for electron
    if e_ecalHit != None:
        feats['electronESPXPos'] = e_ecalPos[0]
        feats['electronESPYPos'] = e_ecalPos[1]
        feats['electronESPZPos'] = e_ecalPos[2]
        feats['electronESPXMom'] = e_ecalP[0]
        feats['electronESPYMom'] = e_ecalP[1]
        feats['electronESPZMom'] = e_ecalP[2]
        feats['electronESPMagMom'] = physTools.mag(e_ecalP)
        feats['electronESPThetaMom'] = physTools.angle(e_ecalP,
                                                       units='degrees')
        feats['isAtESP'] = 1

    if e_targetHit != None:
        e_targPos, e_targP = e_targetHit.getPosition(
        ), e_targetHit.getMomentum()
        feats['electronTSPXPos'] = e_targPos[0]
        feats['electronTSPYPos'] = e_targPos[1]
        feats['electronTSPZPos'] = e_targPos[2]
        feats['electronTSPXMom'] = e_targP[0]
        feats['electronTSPYMom'] = e_targP[1]
        feats['electronTSPZMom'] = e_targP[2]
        feats['electronTSPMagMom'] = physTools.mag(e_targP)
        feats['electronTSPThetaMom'] = physTools.angle(e_targP,
                                                       units='degrees')
        feats['isAtTSP'] = 1

    fiducial = 0
    if e_ecalHit != None:
        slopeXZ = 99999
        if e_ecalP[0] != 0:
            slopeXZ = e_ecalP[2] / e_ecalP[0]
        fX = (physTools.ecal_layerZs[0] -
              physTools.ecal_front_z) / slopeXZ + e_ecalPos[0]

        slopeYZ = 99999
        if e_ecalP[1] != 0:
            slopeYZ = e_ecalP[2] / e_ecalP[1]
        fY = (physTools.ecal_layerZs[0] -
              physTools.ecal_front_z) / slopeYZ + e_ecalPos[1]

        for cell in cellMap:
            xDist = fY - cell[2]
            yDist = fX - cell[1]
            cellDist = physTools.mag([xDist, yDist])
            if cellDist <= physTools.cell_radius:
                fiducial = 1
                break

    feats['isFiducial'] = fiducial

    # Kinematic information for inferred photon
    g_ecalHit = physTools.gammaEcalSPHit(self.ecalSPHits)
    if g_ecalHit != None:
        g_ecalPos = g_ecalHit.getPosition()
        feats['photonESPXPos'] = g_ecalPos[0]
        feats['photonESPYPos'] = g_ecalPos[1]
        feats['photonESPZPos'] = g_ecalPos[2]

    if e_targetHit != None:
        feats['photonTSPXPos'] = g_targPos[0]
        feats['photonTSPYPos'] = g_targPos[1]
        feats['photonTSPZPos'] = g_targPos[2]
        feats['photonXMom'] = g_targP[0]
        feats['photonYMom'] = g_targP[1]
        feats['photonZMom'] = g_targP[2]
        feats['photonMagMom'] = physTools.mag(g_targP)
        feats['photonThetaMom'] = physTools.angle(g_targP, units='degrees')

    # ECal rec hit information
    feats['totalRecAmplitude'] = sum(
        [recHit.getAmplitude() for recHit in self.ecalRecHits])
    feats['totalRecEnergy'] = sum(
        [recHit.getEnergy() for recHit in self.ecalRecHits])
    feats['nRecHits'] = len([recHit for recHit in self.ecalRecHits])

    # ECal sim hit information
    feats['totalSimEDep'] = sum(
        [simHit.getEdep() for simHit in self.ecalSimHits])
    feats['nSimHits'] = len([simHit for simHit in self.ecalSimHits])

    # Sort the ECal rec hits and sim hits by hit ID
    ecalRecHitsSorted = [hit for hit in self.ecalRecHits]
    ecalRecHitsSorted.sort(key=lambda hit: hit.getID())
    ecalSimHitsSorted = [hit for hit in self.ecalSimHits]
    ecalSimHitsSorted.sort(key=lambda hit: hit.getID())

    # ECal noise information
    for recHit in ecalRecHitsSorted:

        # If the noise flag is set, count the hit as a noise hit
        if recHit.isNoise():
            feats['totalNoiseEnergy'] += recHit.getEnergy()
            feats['nNoiseHits'] += 1

        # Otherwise, check for a sim hit whose hitID matches
        nSimHitMatch = 0
        for simHit in ecalSimHitsSorted:

            if simHit.getID() == recHit.getID():
                nSimHitMatch += 1

            elif simHit.getID() > recHit.getID():
                break

        # If no matching sim hit exists, count the hit as a noise hit
        if (not recHit.isNoise()) and (nSimHitMatch == 0):
            feats['totalNoiseEnergy'] += recHit.getEnergy()
            feats['nNoiseHits'] += 1

    # Rec hit information
    for recHit in self.ecalRecHits:
        recHitBranches['amplitude']['address'][0] = recHit.getAmplitude()
        recHitBranches['energy']['address'][0] = recHit.getEnergy()
        recHitBranches['xPos']['address'][0] = recHit.getXPos()
        recHitBranches['yPos']['address'][0] = recHit.getYPos()
        recHitBranches['zPos']['address'][0] = recHit.getZPos()

        matchingSimEDep = 0
        for simHit in self.ecalSimHits:

            if simHit.getID() == recHit.getID():
                matchingSimEDep += simHit.getEdep()

        recHitBranches['matchingSimEDep']['address'][0] = matchingSimEDep

        for varName in branches_info:
            recHitBranches[varName]['address'][0] = feats[varName]

        self.recHitInfo.Fill()

    # Sim hit information
    for simHit in self.ecalSimHits:
        simHitBranches['eDep']['address'][0] = simHit.getEdep()
        simHitBranches['xPos']['address'][0] = simHit.getPosition()[0]
        simHitBranches['yPos']['address'][0] = simHit.getPosition()[1]
        simHitBranches['zPos']['address'][0] = simHit.getPosition()[2]

        for varName in branches_info:
            simHitBranches[varName]['address'][0] = feats[varName]

        self.simHitInfo.Fill()

    # Sim particle information
    for trackID, simParticle in self.simParticles:
        simParticleBranches['energy']['address'][0] = simParticle.getEnergy()
        simParticleBranches['trackID']['address'][0] = trackID
        simParticleBranches['pdgID']['address'][0] = simParticle.getPdgID()
        simParticleBranches['vertXPos']['address'][0] = simParticle.getVertex(
        )[0]
        simParticleBranches['vertYPos']['address'][0] = simParticle.getVertex(
        )[1]
        simParticleBranches['vertZPos']['address'][0] = simParticle.getVertex(
        )[2]
        simParticleBranches['endXPos']['address'][0] = simParticle.getEndPoint(
        )[0]
        simParticleBranches['endYPos']['address'][0] = simParticle.getEndPoint(
        )[1]
        simParticleBranches['endZPos']['address'][0] = simParticle.getEndPoint(
        )[2]
        simParticleBranches['xMom']['address'][0] = simParticle.getMomentum(
        )[0]
        simParticleBranches['yMom']['address'][0] = simParticle.getMomentum(
        )[1]
        simParticleBranches['zMom']['address'][0] = simParticle.getMomentum(
        )[2]
        simParticleBranches['mass']['address'][0] = simParticle.getMass()
        simParticleBranches['charge']['address'][0] = simParticle.getCharge()
        simParticleBranches['nDaughters']['address'][0] = len(
            simParticle.getDaughters())
        simParticleBranches['nParents']['address'][0] = len(
            simParticle.getParents())

        for varName in branches_info:
            simParticleBranches[varName]['address'][0] = feats[varName]

        self.simParticleInfo.Fill()

    # Fill the tree (according to fiducial category) with values for this event
    if not self.separate:
        self.tfMakers['unsorted'].fillEvent(feats)
    else:
        if e_fid and g_fid: self.tfMakers['egin'].fillEvent(feats)
        elif e_fid and not g_fid: self.tfMakers['ein'].fillEvent(feats)
        elif not e_fid and g_fid: self.tfMakers['gin'].fillEvent(feats)
        else: self.tfMakers['none'].fillEvent(feats)