Author: [log in to unmask] Date: Mon Dec 15 15:54:53 2014 New Revision: 1738 Log: Add new cluster package to ecal recon. Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/AbstractClusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPEcalClusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterDriver.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterUtilities.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/Clusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClustererFactory.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/EcalClusterIC.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPEcalClusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/LegacyClusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleInnerCalClusterer.java Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/AbstractClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/AbstractClusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/AbstractClusterer.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,21 @@ +package org.hps.recon.ecal.cluster; + +import java.util.List; + +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.Cluster; +import org.lcsim.geometry.subdetector.HPSEcal3; +import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap; + +public abstract class AbstractClusterer implements Clusterer { + + HPSEcal3 ecal; + NeighborMap neighborMap; + + public void setEcalSubdetector(HPSEcal3 ecal) { + this.ecal = ecal; + this.neighborMap = ecal.getNeighborMap(); + } + + public abstract List<Cluster> createClusters(List<CalorimeterHit> hits); +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPEcalClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPEcalClusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPEcalClusterer.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,383 @@ +package org.hps.recon.ecal.cluster; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; + +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.EventHeader; +import org.lcsim.geometry.Detector; +import org.lcsim.geometry.IDDecoder; +import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap; +import org.lcsim.geometry.subdetector.HPSEcal3; +import org.lcsim.util.Driver; +import org.lcsim.lcio.LCIOConstants; + +/** + * Creates clusters from CalorimeterHits in the HPSEcal detector. + * + * The clustering algorithm is from JLab Hall B 6 GeV DVCS Trigger Design doc. + * + * @author Jeremy McCormick <[log in to unmask]> + * @author Tim Nelson <[log in to unmask]> + * @author Sho Uemura <[log in to unmask]> + * @version $Id: CTPEcalClusterer.java,v 1.1 2013/02/25 22:39:24 meeg Exp $ + */ +public class CTPEcalClusterer extends Driver { + + Detector detector = null; + // The calorimeter object. + HPSEcal3 ecal; + IDDecoder dec; + // The identification name for getting the calorimeter object. + String ecalName; + // The collection name for the calorimeter hits. + String ecalCollectionName; + // The collection name in which to store clusters. + String clusterCollectionName = "EcalClusters"; + Set<Long> clusterCenters = null; + Map<Long, Double> hitSums = null; + Map<Long, CalorimeterHit> hitMap = null; + // Map of crystals to their neighbors. + NeighborMap neighborMap = null; + // The time period in which clusters may be formed. A negative value means that all hits + // will always be used in cluster finding, regardless of the time difference between them. + double clusterWindow = -1; + // The minimum energy needed for a hit to be considered. + double addEMin = 0; + + public CTPEcalClusterer() { + } + + public void setAddEMin(double addEMin) { + this.addEMin = addEMin; + } + + public void setClusterWindow(double clusterWindow) { + this.clusterWindow = clusterWindow; + } + + public void setClusterCollectionName(String clusterCollectionName) { + this.clusterCollectionName = clusterCollectionName; + } + + public void setEcalCollectionName(String ecalCollectionName) { + this.ecalCollectionName = ecalCollectionName; + } + + public void setEcalName(String ecalName) { + this.ecalName = ecalName; + } + + @Override + public void startOfData() { + // Make sure that there is a cluster collection name into which clusters may be placed. + if (ecalCollectionName == null) { + throw new RuntimeException("The parameter ecalCollectionName was not set!"); + } + + // Make sure that there is a calorimeter detector. + if (ecalName == null) { + throw new RuntimeException("The parameter ecalName was not set!"); + } + } + + @Override + public void detectorChanged(Detector detector) { + + this.detector = detector; + + // Get the Subdetector. + ecal = (HPSEcal3) detector.getSubdetector(ecalName); + + // Get the decoder for the ECal IDs. + dec = ecal.getIDDecoder(); + + // Cache reference to the neighbor map. + neighborMap = ecal.getNeighborMap(); + + clusterCenters = new HashSet<Long>(); + // Make set of valid cluster centers. + // Exclude edge crystals as good cluster centers. + for (Long cellID : neighborMap.keySet()) { + boolean isValidCenter = true; + Set<Long> neighbors = neighborMap.get(cellID); + for (Long neighborID : neighbors) { + Set<Long> neighborneighbors = new HashSet<Long>(); + neighborneighbors.addAll(neighborMap.get(neighborID)); + neighborneighbors.add(neighborID); + + if (neighborneighbors.containsAll(neighbors)) { + isValidCenter = false; + break; + } + } + if (isValidCenter) { + clusterCenters.add(cellID); + } + } + } + + @Override + public void process(EventHeader event) { + // Make sure that this event has calorimeter hits. + if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) { + // Get the list of calorimeter hits from the event. + List<CalorimeterHit> hits = event.get(CalorimeterHit.class, ecalCollectionName); + + // Define a list of clusters to be filled. + List<HPSEcalCluster> clusters; + + // If there is a cluster window, run the cluster window code. A cluster window is a time + // period in nanoseconds during which hits can be applied to the same cluster. + if (clusterWindow >= 0) { + // Create priority queues. These are sorted by the time variable associated with each hit. + PriorityQueue<CalorimeterHit> futureHits = new PriorityQueue<CalorimeterHit>(10, new TimeComparator()); + PriorityQueue<CalorimeterHit> pastHits = new PriorityQueue<CalorimeterHit>(10, new TimeComparator()); + + // Initialize the cluster list variable. + clusters = new ArrayList<HPSEcalCluster>(); + + // Populate the list of unprocessed hits with the calorimeter hits. These will then be sorted + // by time, from first to last, automatically by the priority queue. + for (CalorimeterHit hit : hits) { + if (hit.getRawEnergy() > addEMin) { + futureHits.add(hit); + } + } + + // We process the unprocessed hits... + while (!futureHits.isEmpty()) { + // Move the first occurring hit from the unprocessed list to the processed list. + CalorimeterHit nextHit = futureHits.poll(); + pastHits.add(nextHit); + + // Add any hits that occurred at the same time as the hit we just added to the processed list. + while (!futureHits.isEmpty() && futureHits.peek().getTime() == nextHit.getTime()) { + pastHits.add(futureHits.poll()); + } + + // Remove hits that happened earlier than the cluster window period. + while (pastHits.peek().getTime() < nextHit.getTime() - clusterWindow) { + pastHits.poll(); + } + + // Calculate the cluster energy for each crystal. This should be the + // total energy for the 3x3 crystal collection sorrounding the center + // crystal. + sumHits(pastHits); + + // Choose which crystal is the appropriate cluster crystal. + clusters.addAll(createClusters()); + } + // If there is no cluster window, then all the hits in the event are visible simultaneously. + } else { + // Calculate the cluster energy of each crystal. + sumHits(hits); + // Generate the clusters. + clusters = createClusters(); + } + + // Put the cluster collection into the event. + int flag = 1 << LCIOConstants.CLBIT_HITS; + event.put(clusterCollectionName, clusters, HPSEcalCluster.class, flag); + } + } + + public void sumHits(Collection<CalorimeterHit> hits) { + // Store the latest hit on each crystal in a map for later reference in + // the clustering algorithm. + hitMap = new HashMap<Long, CalorimeterHit>(); + // Store the cluster energy for each crystal. Cluster energy represents + // the total energy of the 3x3 crystal set. + hitSums = new HashMap<Long, Double>(); + + // Loop over the active calorimeter hits to compute the cluster energies. + for (CalorimeterHit hit : hits) { + // Make a hit map for quick lookup by ID. + hitMap.put(hit.getCellID(), hit); + + // Get the cell ID for the current crystal's neighbors. + Set<Long> neighbors = neighborMap.get(hit.getCellID()); + + // If there are no neighbors, something is rather wrong. + if (neighbors == null) { + throw new RuntimeException("Oops! Set of neighbors is null!"); + } + + // Store the energy of the current calorimeter hit. + Double hitSum; + + // We are only interested in this crystal's cluster energy if it is + // a valid cluster crystal. Edge crystals are not allowed to be clusters, + // so these are ignored. + if (clusterCenters.contains(hit.getCellID())) { + // Check if an energy has been assigned to the crystal. + hitSum = hitSums.get(hit.getCellID()); + + // If not, then the crystal's cluster energy is equal to this hit's energy. + if (hitSum == null) { + hitSums.put(hit.getCellID(), hit.getRawEnergy()); + } + // Otherwise, add the energy of this hit to the total crystal cluster energy. + else { + hitSums.put(hit.getCellID(), hitSum + hit.getRawEnergy()); + } + } + + // Loop over neighbors to add the current hit's energy to the neighbor's + // cluster energy. + for (Long neighborId : neighbors) { + // If the crystal is not an edge crystal, ignore its hit energy. + if (!clusterCenters.contains(neighborId)) { + continue; + } + + // Get the cluster energy of the neighboring crystals. + hitSum = hitSums.get(neighborId); + + // If the neighbor crystal has no cluster energy, then set the + // cluster energy to the current hit's energy. + if (hitSum == null) { + hitSums.put(neighborId, hit.getRawEnergy()); + } + // Otherwise, add the hit's energy to the neighbor's cluster energy. + else { + hitSums.put(neighborId, hitSum + hit.getRawEnergy()); + } + } + } + } + + public List<HPSEcalCluster> createClusters() { + // Create a list of clusters to be added to the event, + List<HPSEcalCluster> clusters = new ArrayList<HPSEcalCluster>(); + + // We examine each crystal with a non-zero cluster energy. + for(Long possibleCluster : hitSums.keySet()) { + // Get the luster energy for the crystal this hit is assocaite with. + Double thisSum = hitSums.get(possibleCluster); + + // Get neighboring crystals' IDs. + Set<Long> neighbors = neighborMap.get(possibleCluster); + + // If there are no neighbors, throw an error. + if (neighbors == null) { + throw new RuntimeException("Oops! Set of neighbors is null!"); + } + + // Get the x/y position of the hit's associated crystal. + dec.setID(possibleCluster); + int x1 = dec.getValue("ix"); + int y1 = dec.getValue("iy"); + + // Store whether it is a valid cluster or not. + boolean isCluster = true; + + // Check to see if any of the crystal's neighbors preclude the current crystal + // from being a proper cluster. The cluster crystal should have the highest + // energy among its neighbors + for (Long neighborId : neighbors) { + // Get the x/y position of the neighbor's associated crystal. + dec.setID(neighborId); + int x2 = dec.getValue("ix"); + int y2 = dec.getValue("iy"); + + // If the neighbor's energy value does not exist, we don't need to perform + // any additional checks for this neighbor. A crystal with no energy can + // not be the center of a cluster. + Double neighborSum = hitSums.get(neighborId); + if (neighborSum == null) { + continue; + } + + // If the neighbor's energy value is greater than this crystal's value, + // then this crystal is not the cluster and we may terminate the check. + if (neighborSum > thisSum) { + isCluster = false; + break; + } + // If the crystals each have the same energy, we choose the crystal that + // is closest to the electron side of the detector. If both crystals are + // equally close, we choose the crystal closest to the beam gap. If the + // neighbor fits these parameters better, this is not a crystal and we + // may skip any further checks. + else if (neighborSum.equals(thisSum) && (x1 > x2 || (x1 == x2 && Math.abs(y1) < Math.abs(y2)))) { + isCluster = false; + break; + } + } + + // If the crystal was not invalidated by the any of the neighboring crystals, + // then it is a cluster crystal and should be processed. + if (isCluster) { + // Make a list to store the hits that are part of this cluster. + List<CalorimeterHit> hits = new ArrayList<CalorimeterHit>(); + + // Store the time at which the cluster occurred. + double clusterTime = Double.NEGATIVE_INFINITY; + + // Get the last hit on this crystal. + CalorimeterHit hit = hitMap.get(possibleCluster); + + // If the hit exists, add it to the list of associated hits. + if (hit != null) { + hits.add(hit); + + // If the latest hit's time is later than the current cluster time, + // set the cluster time to the latest hit's time. + if (hit.getTime() > clusterTime) { + clusterTime = hit.getTime(); + } + } + + // Add all of the neighboring crystals to the cluster, if they have a + // hit associated with them. Crystals with no hits are not actually part + // of a cluster. + for (Long neighborId : neighbors) { + hit = hitMap.get(neighborId); + if (hit != null) { + hits.add(hit); + if (hit.getTime() > clusterTime) { + clusterTime = hit.getTime(); + } + } + } + + // Generate a new cluster seed hit from the above results. + HPSCalorimeterHit seedHit = new HPSCalorimeterHit(0.0, clusterTime, possibleCluster, hits.get(0).getType()); + seedHit.setMetaData(hits.get(0).getMetaData()); + + // Generate a new cluster from the seed hit. + HPSEcalCluster cluster = new HPSEcalCluster(); + cluster.setSeedHit(seedHit); + // Populate the cluster with each of the chosen neighbors. + for (CalorimeterHit clusterHit : hits) { + cluster.addHit(clusterHit); + } + // Add the cluster to the cluster list. + clusters.add(cluster); + } + } + + // Return the list of clusters. + return clusters; + } + + static class TimeComparator implements Comparator<CalorimeterHit> { + // Compare by time with the earlier coming before the later. + public int compare(CalorimeterHit o1, CalorimeterHit o2) { + if (o1.getTime() == o2.getTime()) { + return 0; + } else { + return (o1.getTime() > o2.getTime()) ? 1 : -1; + } + } + } +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterDriver.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterDriver.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterDriver.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,128 @@ +package org.hps.recon.ecal.cluster; + +import java.util.List; + +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.Cluster; +import org.lcsim.event.EventHeader; +import org.lcsim.geometry.Detector; +import org.lcsim.geometry.Subdetector; +import org.lcsim.geometry.subdetector.HPSEcal3; +import org.lcsim.lcio.LCIOConstants; +import org.lcsim.util.Driver; + +/** + * This is a basic Driver that creates Cluster collections through the + * Clusterer interface. + * @author Jeremy McCormick <[log in to unmask]> + */ +public class ClusterDriver extends Driver { + + protected String ecalName = "Ecal"; + protected HPSEcal3 ecal; + protected String outputClusterCollectionName = "EcalClusters"; + protected String inputHitCollectionName = "EcalCalHits"; + protected Clusterer clusterer; + protected boolean createEmptyClusterCollection = true; + protected boolean raiseErrorNoHitCollection = false; + protected boolean skipNoClusterEvents = false; + protected boolean writeClusterCollection = true; + protected boolean storeHits = true; + + protected ClusterDriver() { + } + + public void setEcalName(String ecalName) { + this.ecalName = ecalName; + } + + public void setOutputClusterCollectionName(String outputClusterCollectionName) { + this.outputClusterCollectionName = outputClusterCollectionName; + } + + public void setInputHitCollectionName(String inputHitCollectionName) { + this.inputHitCollectionName = inputHitCollectionName; + } + + public void setSkipNoClusterEvents(boolean skipNoClusterEvents) { + this.skipNoClusterEvents = skipNoClusterEvents; + } + + public void setWriteClusterCollection(boolean writeClusterCollection) { + this.writeClusterCollection = writeClusterCollection; + } + + public void setRaiseErrorNoHitCollection(boolean raiseErrorNoHitCollection) { + this.raiseErrorNoHitCollection = raiseErrorNoHitCollection; + } + + public void setStoreHits(boolean storeHits) { + this.storeHits = storeHits; + } + + public void setClusterer(String name) { + clusterer = ClustererFactory.create(name); + } + + public void setClusterer(Clusterer clusterer) { + this.clusterer = clusterer; + } + + public void setCreateEmptyClusterCollection(boolean createEmptyClusterCollection) { + this.createEmptyClusterCollection = createEmptyClusterCollection; + } + + public void detectorChanged(Detector detector) { + Subdetector subdetector = detector.getSubdetector(ecalName); + if (subdetector == null) { + throw new RuntimeException("There is no subdetector called " + ecalName + " in the detector."); + } + if (!(subdetector instanceof HPSEcal3)) { + throw new RuntimeException("Ther subdetector " + ecalName + " does not have the right type."); + } + ecal = (HPSEcal3) subdetector; + } + + public void startOfData() { + if (this.clusterer == null) { + throw new RuntimeException("The clusterer was never initialized."); + } + if (this.clusterer instanceof AbstractClusterer) { + ((AbstractClusterer)clusterer).setEcalSubdetector(ecal); + } + } + + /** + * This method implements the default clustering procedure based on input parameters. + */ + public void process(EventHeader event) { + if (event.hasCollection(CalorimeterHit.class, inputHitCollectionName)) { + List<CalorimeterHit> hits = event.get(CalorimeterHit.class, inputHitCollectionName); + List<Cluster> clusters = clusterer.createClusters(hits); + if (clusters == null) { + throw new RuntimeException("The clusterer returned a null pointer."); + } + if (clusters.isEmpty() && this.skipNoClusterEvents) { + throw new NextEventException(); + } + if (event.hasCollection(Cluster.class, this.outputClusterCollectionName)) { + throw new RuntimeException("There is already a cluster collection called " + this.outputClusterCollectionName); + } + int flags = 0; + if (this.storeHits) { + flags = 1 << LCIOConstants.CLBIT_HITS; + } + if (!clusters.isEmpty() || this.createEmptyClusterCollection) { + event.put(outputClusterCollectionName, clusters, Cluster.class, flags); + if (!this.writeClusterCollection) { + event.getMetaData(clusters).setTransient(true); + } + } + } else { + this.getLogger().warning("The input hit collection " + this.inputHitCollectionName + " is missing from the event."); + if (this.raiseErrorNoHitCollection) { + throw new RuntimeException("The expected hit collection " + this.inputHitCollectionName + " is missing from the event."); + } + } + } +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterUtilities.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterUtilities.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterUtilities.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,41 @@ +package org.hps.recon.ecal.cluster; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.lcsim.event.CalorimeterHit; +import org.lcsim.geometry.subdetector.HPSEcal3; + +public final class ClusterUtilities { + + private ClusterUtilities() { + } + + public static Map<Long, CalorimeterHit> createHitMap(List<CalorimeterHit> hits) { + Map<Long, CalorimeterHit> hitMap = new LinkedHashMap<Long, CalorimeterHit>(); + for (CalorimeterHit hit : hits) { + hitMap.put(hit.getCellID(), hit); + } + return hitMap; + } + + /** + * Given a hit, find its list of neighboring crystals that have hits and return their IDs. + * @param hit The input hit. + * @param hitMap The hit map with all the collection's hits. + * @return The set of neighboring hit IDs. + */ + public static Set<Long> findNeighborHitIDs(HPSEcal3 ecal, CalorimeterHit hit, Map<Long, CalorimeterHit> hitMap) { + Set<Long> neigbhors = ecal.getNeighborMap().get(hit.getCellID()); + Set<Long> neighborHitIDs = new HashSet<Long>(); + for (long neighborID : neigbhors) { + if (hitMap.containsKey(neighborID)) { + neighborHitIDs.add(neighborID); + } + } + return neighborHitIDs; + } +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/Clusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/Clusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/Clusterer.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,12 @@ +package org.hps.recon.ecal.cluster; + +import java.util.List; + +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.Cluster; +import org.lcsim.geometry.subdetector.HPSEcal3; + +public interface Clusterer { + + List<Cluster> createClusters(List<CalorimeterHit> hits); +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClustererFactory.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClustererFactory.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClustererFactory.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,18 @@ +package org.hps.recon.ecal.cluster; + +public final class ClustererFactory { + + private ClustererFactory() { + } + + public static Clusterer create(String name) { + if (LegacyClusterer.class.getSimpleName().equals(name)) { + return new LegacyClusterer(); + } if (SimpleInnerCalClusterer.class.getSimpleName().equals(name)) { + return new SimpleInnerCalClusterer(); + } else { + throw new IllegalArgumentException("Unknown clusterer: " + name); + } + } + +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/EcalClusterIC.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/EcalClusterIC.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/EcalClusterIC.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,868 @@ +package org.hps.recon.ecal.cluster; +import hep.physics.vec.BasicHep3Vector; +import hep.physics.vec.Hep3Vector; +import hep.physics.vec.VecOp; + +import java.awt.Point; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.lcsim.detector.IGeometryInfo; +import org.lcsim.detector.solids.Trd; +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.EventHeader; +import org.lcsim.geometry.Detector; +import org.lcsim.geometry.subdetector.HPSEcal3; +import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap; +import org.lcsim.lcio.LCIOConstants; +import org.lcsim.util.Driver; + + +/** + * This Driver creates clusters from the CalorimeterHits of an + * {@link org.lcsim.geometry.subdetectur.HPSEcal3} detector. + * + * This clustering logic is based on that from the CLAS-Note-2005-001. + * This sorts hits from highest to lowest energy and build clusters around + * each local maximum/seed hit. Common hits are distributed between clusters + * when minimum between two clusters. There is a threshold cut for minimum + * hit energy, minimum cluster energy, and minimum seed hit energy. There is + * also a timing threshold with respect to the seed hit. All of these parameters + * are tunable and should be refined with more analysis. + * + * + * @author Holly Szumila-Vance <[log in to unmask]> + * @author Kyle McCarty <[log in to unmask]> + * + */ +public class EcalClusterIC extends Driver { + // File writer to output cluster results. + FileWriter writeHits = null; + // LCIO collection name for calorimeter hits. + String ecalCollectionName; + // Name of the calorimeter detector object. + String ecalName = "Ecal"; + // LCIO cluster collection name to which to write. + String clusterCollectionName = "EcalClustersIC"; + // Collection name for rejected hits + String rejectedHitName = "RejectedHits"; + // File path to which to write event display output. + String outfile = null; + // Map of crystals to their neighbors. + NeighborMap neighborMap = null; + // Minimum energy threshold for hits; lower energy hits will be + // excluded from clustering. Units in GeV. + double hitEnergyThreshold = 0.0075; + // Minimum energy threshold for seed hits; if seed hit is below + // cluster is excluded from output. Units in GeV. + double seedEnergyThreshold = 0.1; + // Minimum energy threshold for cluster hits; if total cluster + // energy is below, the cluster is excluded. Units in GeV. + double clusterEnergyThreshold = 0.3; + // A Comparator that sorts CalorimeterHit objects from highest to + // lowest energy. + private static final EnergyComparator ENERGY_COMP = new EnergyComparator(); + // Track the event number for the purpose of outputting to event + // display format. + private int eventNum = -1; + // Apply time cut to hits + boolean timeCut = false; + // Minimum time cut window range. Units in ns. + double minTime = 0.0; + // Maximum time cut window range. Units in ns. + double timeWindow = 20.0; + // Variables for electron energy corrections + static final double ELECTRON_ENERGY_A = -0.0027; + static final double ELECTRON_ENERGY_B = -0.06; + static final double ELECTRON_ENERGY_C = 0.95; + // Variables for positron energy corrections + static final double POSITRON_ENERGY_A = -0.0096; + static final double POSITRON_ENERGY_B = -0.042; + static final double POSITRON_ENERGY_C = 0.94; + // Variables for photon energy corrections + static final double PHOTON_ENERGY_A = 0.0015; + static final double PHOTON_ENERGY_B = -0.047; + static final double PHOTON_ENERGY_C = 0.94; + // Variables for electron position corrections + static final double ELECTRON_POS_A = 0.0066; + static final double ELECTRON_POS_B = -0.03; + static final double ELECTRON_POS_C = 0.028; + static final double ELECTRON_POS_D = -0.45; + static final double ELECTRON_POS_E = 0.465; + // Variables for positron position corrections + static final double POSITRON_POS_A = 0.0072; + static final double POSITRON_POS_B = -0.031; + static final double POSITRON_POS_C = 0.007; + static final double POSITRON_POS_D = 0.342; + static final double POSITRON_POS_E = 0.108; + // Variables for photon position corrections + static final double PHOTON_POS_A = 0.005; + static final double PHOTON_POS_B = -0.032; + static final double PHOTON_POS_C = 0.011; + static final double PHOTON_POS_D = -0.037; + static final double PHOTON_POS_E = 0.294; + + + + public void setClusterCollectionName(String clusterCollectionName) { + this.clusterCollectionName = clusterCollectionName; + } + + public void setEcalCollectionName(String ecalCollectionName) { + this.ecalCollectionName = ecalCollectionName; + } + + public void setEcalName(String ecalName) { + this.ecalName = ecalName; + } + + /** + * Output file name for event display output. + * @param outfile + */ + public void setOutfile(String outfile) { + this.outfile = outfile; + } + + public void setRejectedHitName(String rejectedHitName){ + this.rejectedHitName = rejectedHitName; + } + + /** + * Minimum energy for a hit to be used in a cluster. Default of 0.0075 GeV + * + * @param hitEnergyThreshold + */ + public void sethitEnergyThreshold(double hitEnergyThreshold) { + this.hitEnergyThreshold = hitEnergyThreshold; + } + + /** + * Minimum energy for a seed hit. Default of 0.1 GeV + * + * @param seedEnergyThreshold + */ + public void setseedEnergyThreshold(double seedEnergyThreshold) { + this.seedEnergyThreshold = seedEnergyThreshold; + } + + /** + * Minimum energy for a cluster. Default of 0.3 GeV + * + * @param clusterEnergyThreshold + */ + public void setclusterEnergyThreshold(double clusterEnergyThreshold) { + this.clusterEnergyThreshold = clusterEnergyThreshold; + } + + /** + * Apply time cuts to hits. Defaults to false. + * + * @param timeCut + */ + public void setTimeCut(boolean timeCut) { + this.timeCut = timeCut; + } + + /** + * Minimum hit time, if timeCut is true. Default of 0 ns. + * + * @param minTime + */ + public void setMinTime(double minTime) { + this.minTime = minTime; + } + + /** + * Width of time window, if timeCut is true. Default of 20 ns. + * + * @param timeWindow + */ + public void setTimeWindow(double timeWindow) { + this.timeWindow = timeWindow; + } + + // Make a map for quick calculation of the x-y position of crystal face + public Map<Point, double[]> correctedPositionMap = new HashMap<Point, double[]>(); + + public void startOfData() { + // Make sure that the calorimeter hit collection name is defined. + if (ecalCollectionName == null) { + throw new RuntimeException("The parameter ecalCollectionName was not set!"); + } + + // Make sure the name of calorimeter detector is defined. + if (ecalName == null) { + throw new RuntimeException("The parameter ecalName was not set!"); + } + + if (outfile!=null) { + // Create a file writer and clear the output file, if it exists. + try { + writeHits = new FileWriter(outfile); + writeHits.write(""); + } catch (IOException e) { + } + } + } + + public void detectorChanged(Detector detector) { + // Get the calorimeter. + HPSEcal3 ecal = (HPSEcal3) detector.getSubdetector(ecalName); + + // Store the map of neighbor crystals for the current calorimeter set-up. + neighborMap = ecal.getNeighborMap(); + } + + public void process(EventHeader event) { + // Make sure the current event contains calorimeter hits. + if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) { + + // Generate clusters from the calorimeter hits. + //List<HPSEcalClusterIC> clusterList = null; + try { createClusters(event); } + catch(IOException e) { } + } + } + + public void createClusters(EventHeader event) throws IOException { + + // Create a list to store the event hits in. + List<CalorimeterHit> hitList = new ArrayList<CalorimeterHit>(); + List<CalorimeterHit> baseList = event.get(CalorimeterHit.class, ecalCollectionName); + for(CalorimeterHit r : baseList) { + hitList.add(r); + } + + //Create a list to store the newly created clusters in. + ArrayList<HPSEcalClusterIC> clusterList = new ArrayList<HPSEcalClusterIC>(); + + //Create a list to store the rejected hits in. + ArrayList<CalorimeterHit> rejectedHitList = new ArrayList<CalorimeterHit>(); + + // Sort the list of hits by energy. + Collections.sort(hitList, ENERGY_COMP); + + // Filter the hit list of any hits that fail to pass the + // designated threshold. + filterLoop: + for(int index = hitList.size() - 1; index >= 0; index--) { + // If the hit is below threshold or outside of time window, kill it. + if((hitList.get(index).getCorrectedEnergy() < hitEnergyThreshold)|| + (timeCut && (hitList.get(index).getTime() < minTime || hitList.get(index).getTime() > (minTime + timeWindow)))) { + rejectedHitList.add(hitList.get(index)); + hitList.remove(index); + } + + // Since the hits are sorted by energy from highest to + // lowest, any hit that is above threshold means that all + // subsequent hits will also be above threshold. Continue through + // list to check in time window. + else { continue; } + } + + //Create a map to connect the cell ID of a calorimeter crystal to the hit which occurred in that crystal. + HashMap<Long, CalorimeterHit> hitMap = new HashMap<Long, CalorimeterHit>(); + for (CalorimeterHit hit : hitList) { hitMap.put(hit.getCellID(), hit); } + + //Map a crystal to a list of all clusters in which it is a member. + Map<CalorimeterHit, List<CalorimeterHit>> commonHits = new HashMap<CalorimeterHit, List<CalorimeterHit>>(); + + //Map a crystal to the seed of the cluster of which it is a member. + HashMap<CalorimeterHit, CalorimeterHit> hitSeedMap = new HashMap<CalorimeterHit, CalorimeterHit>(); + + // Loop through all calorimeter hits to locate seeds and perform + // first pass calculations for component and common hits. + for (int ii = 0; ii <= hitList.size() - 1; ii ++){ + CalorimeterHit hit = hitList.get(ii); + // Get the set of all neighboring crystals to the current hit. + Set<Long> neighbors = neighborMap.get(hit.getCellID()); + + // Generate a list to store any neighboring hits in. + ArrayList<CalorimeterHit> neighborHits = new ArrayList<CalorimeterHit>(); + + // Sort through the set of neighbors and, if a hit exists + // which corresponds to a neighbor, add it to the list of + // neighboring hits. + for (Long neighbor : neighbors) { + // Get the neighboring hit. + CalorimeterHit neighborHit = hitMap.get(neighbor); + + // If it exists, add it to the list. + if(neighborHit != null) { neighborHits.add(neighborHit); } + } + + // Track whether the current hit is a seed hit or not. + boolean isSeed = true; + + // Loops through all the neighboring hits to determine if + // the current hit is the local maximum within its set of + // neighboring hits. + seedHitLoop: + for(CalorimeterHit neighbor : neighborHits) { + if(!equalEnergies(hit, neighbor)) { + isSeed = false; + break seedHitLoop; + } + } + // If this hit is a seed hit, just map it to itself. + if (isSeed && hit.getCorrectedEnergy() >= seedEnergyThreshold) { hitSeedMap.put(hit, hit); } + + // If this hit is a local maximum but does not pass seed threshold, + // remove from hit list and do not cluster. + else if (isSeed && hit.getCorrectedEnergy() < seedEnergyThreshold){ + hitList.remove(ii); + rejectedHitList.add(hit); + ii --; + } + + // If this hit is not a seed hit, see if it should be + // attached to any neighboring seed hits. + else { + // Sort through the list of neighboring hits. + for (CalorimeterHit neighborHit : neighborHits) { + // Check whether the neighboring hit is a seed. + if(hitSeedMap.get(neighborHit) == neighborHit) { + // If the neighboring hit is a seed hit and the + // current hit has been associated with a cluster, + // then it is a common hit between its previous + // seed and the neighboring seed. + if (hitSeedMap.containsKey(hit)) { + // Check and see if a list of common seeds + // for this hit already exists or not. + List<CalorimeterHit> commonHitList = commonHits.get(hit); + + // If it does not, make a new one. + if(commonHitList == null) { commonHitList = new ArrayList<CalorimeterHit>(); } + + // Add the neighbors to the seeds to set of + // common seeds. + commonHitList.add(neighborHit); + commonHitList.add(hitSeedMap.get(hit)); + + + // Put the common seed list back into the set. + commonHits.put(hit, commonHitList); + } + + // If the neighboring hit is a seed hit and the + // current hit has not been added to a cluster yet + // associate it with the neighboring seed and note + // that it has been clustered. + else { + hitSeedMap.put(hit, neighborHit); + } + } + } + } + } // End primary seed loop. + + // Performs second pass calculations for component hits. + secondaryHitsLoop: + for (CalorimeterHit secondaryHit : hitList) { + // Look for hits that already have an associated seed/clustering. + if(!hitSeedMap.containsKey(secondaryHit)) { continue secondaryHitsLoop; } + + // Get the secondary hit's neighboring crystals. + Set<Long> secondaryNeighbors = neighborMap.get(secondaryHit.getCellID()); + + // Make a list to store the hits associated with the + // neighboring crystals. + List<CalorimeterHit> secondaryNeighborHits = new ArrayList<CalorimeterHit>(); + + // Loop through the neighboring crystals. + for (Long secondaryNeighbor : secondaryNeighbors) { + // Get the hit associated with the neighboring crystal. + CalorimeterHit secondaryNeighborHit = hitMap.get(secondaryNeighbor); + + // If the neighboring crystal exists and is not already + // in a cluster, add it to the list of neighboring hits. + if (secondaryNeighborHit != null && !hitSeedMap.containsKey(secondaryNeighborHit)) { + secondaryNeighborHits.add(secondaryNeighborHit); + } + } + + // Loop over the secondary neighbor hits. + for (CalorimeterHit secondaryNeighborHit : secondaryNeighborHits) { + // If the neighboring hit is of lower energy than the + // current secondary hit, then associate the neighboring + // hit with the current secondary hit's seed. + if(!equalEnergies(secondaryNeighborHit, secondaryHit)){ + hitSeedMap.put(secondaryNeighborHit, hitSeedMap.get(secondaryHit));} + else {continue;} + } + } // End component hits loop. + + + // Performs second pass calculations for common hits. + commonHitsLoop: + for (CalorimeterHit clusteredHit : hitSeedMap.keySet()) { + + // Seed hits are never common hits and can be skipped. + if(hitSeedMap.get(clusteredHit) == clusteredHit) { continue commonHitsLoop; } + + // Get the current clustered hit's neighboring crystals. + Set<Long> clusteredNeighbors = neighborMap.get(clusteredHit.getCellID()); + + // Store a list of all the clustered hits neighboring + // crystals which themselves contain hits. + List<CalorimeterHit> clusteredNeighborHits = new ArrayList<CalorimeterHit>(); + + // Loop through the neighbors and see if they have hits. + for (Long neighbor : clusteredNeighbors) { + // Get the hit associated with the neighbor. + CalorimeterHit clusteredNeighborHit = hitMap.get(neighbor); + + // If it exists, add it to the neighboring hit list. + + if (clusteredNeighborHit != null && hitSeedMap.get(clusteredNeighborHit) != null) { + clusteredNeighborHits.add(clusteredNeighborHit); + } + } + + // Get the seed hit associated with this clustered hit. + CalorimeterHit clusteredHitSeed = hitSeedMap.get(clusteredHit); + + + // Loop over the clustered neighbor hits. + for (CalorimeterHit clusteredNeighborHit : clusteredNeighborHits) { + // Check to make sure that the clustered neighbor hit + // is not already associated with the current clustered + // hit's seed. + + if ((hitSeedMap.get(clusteredNeighborHit) != clusteredHitSeed)){ + // Check for lowest energy hit and that comparison hit is not already common. + // If already common, this boundary is already accounted for. + if(!equalEnergies(clusteredHit, clusteredNeighborHit) + && !commonHits.containsKey(clusteredNeighborHit)){ + + // Check and see if a list of common seeds + // for this hit already exists or not. + List<CalorimeterHit> commonHitList = commonHits.get(clusteredHit); + + // If it does not, make a new one. + if(commonHitList == null) { commonHitList = new ArrayList<CalorimeterHit>();} + + // Add the neighbors to the seeds to set of + // common seeds. + commonHitList.add(clusteredHitSeed); + commonHitList.add(hitSeedMap.get(clusteredNeighborHit)); + + // Put the common seed list back into the set. + commonHits.put(clusteredHit, commonHitList); + + } + } + } + } // End common hits loop. + + + // Remove any common hits from the clustered hits collection. + for(CalorimeterHit commonHit : commonHits.keySet()) { + hitSeedMap.remove(commonHit); + } + + + /* + * All hits are sorted from above. The next part of the code is for calculating energies and positions. + */ + + //Create map to contain the total energy of each cluster + Map<CalorimeterHit, Double> seedEnergy = new HashMap<CalorimeterHit, Double>(); + + // Get energy of each cluster, excluding common hits + for (CalorimeterHit iSeed : hitList) { + if(hitSeedMap.get(iSeed) == iSeed) { + seedEnergy.put(iSeed, 0.0); + } + } + + //Putting total cluster energies excluding common hit energies into map with seed keys + for (Map.Entry<CalorimeterHit, CalorimeterHit> entry : hitSeedMap.entrySet()) { + CalorimeterHit eSeed = entry.getValue(); + double eEnergy = seedEnergy.get(eSeed); + eEnergy += entry.getKey().getCorrectedEnergy(); + seedEnergy.put(eSeed, eEnergy); + } + + // Create a map to contain final uncorrected cluster energies including common hit distributions. + Map<CalorimeterHit, Double> seedEnergyTot = seedEnergy; + + //Distribute common hit energies with clusters + for (Map.Entry<CalorimeterHit, List<CalorimeterHit>> entry1 : commonHits.entrySet()) { + CalorimeterHit commonCell = entry1.getKey(); + CalorimeterHit seedA = entry1.getValue().get(0); + CalorimeterHit seedB = entry1.getValue().get(1); + double eFractionA = (seedEnergy.get(seedA))/((seedEnergy.get(seedA)+seedEnergy.get(seedB))); + double eFractionB = (seedEnergy.get(seedB))/((seedEnergy.get(seedA)+seedEnergy.get(seedB))); + double currEnergyA = seedEnergyTot.get(seedA); + double currEnergyB = seedEnergyTot.get(seedB); + currEnergyA += eFractionA * commonCell.getCorrectedEnergy(); + currEnergyB += eFractionB * commonCell.getCorrectedEnergy(); + + seedEnergyTot.put(seedA, currEnergyA); + seedEnergyTot.put(seedB, currEnergyB); + } + + + // Make mapping to contain clusters with corrected energy. + Map<CalorimeterHit, Double> seedEnergyCorr = new HashMap<CalorimeterHit, Double>(); + + // Energy Corrections as per HPS Note 2014-001 + // Iterate through known clusters with energies and apply correction. + for (Map.Entry<CalorimeterHit, Double> entryC : seedEnergyTot.entrySet()) { + double rawEnergy = entryC.getValue(); + + // Energy correction for initial guess of electron: + int pdg = 11; + double corrEnergy = enCorrection(pdg, rawEnergy); + + seedEnergyCorr.put(entryC.getKey(), corrEnergy); + }// end of energy corrections + + + // Cluster Position as per HPS Note 2014-001 + // Create map with seed as key to position/centroid value + Map<CalorimeterHit, double[]> rawSeedPosition = new HashMap<CalorimeterHit, double[]>(); + Map<CalorimeterHit, double[]> corrSeedPosition = new HashMap<CalorimeterHit, double[]>(); + + // top level iterates through seeds + for (Map.Entry<CalorimeterHit, Double> entryS : seedEnergyTot.entrySet()) { + //get the seed for this iteration + CalorimeterHit seedP = entryS.getKey(); + + double xCl = 0.0; // calculated cluster x position, prior to correction + double yCl = 0.0; // calculated cluster y position + double eNumX = 0.0; + double eNumY = 0.0; + double eDen = 0.0; + double w0 = 3.1; + + // iterate through hits corresponding to seed + for (Map.Entry<CalorimeterHit, CalorimeterHit> entryP : hitSeedMap.entrySet()) { + if(entryP.getValue() == seedP){ + /////////////////////////////// + // This block fills a map with crystal to center of face of crystal + // Get the hit indices as a Point. + int ix = entryP.getKey().getIdentifierFieldValue("ix"); + int iy = entryP.getKey().getIdentifierFieldValue("iy"); + Point hitIndex = new Point(ix, iy); + + // Get the corrected position for this index pair. + double[] position = correctedPositionMap.get(hitIndex); + + // If the result is null, it hasn't been calculated yet. + if(position == null) { + // Calculate the corrected position. + IGeometryInfo geom = entryP.getKey().getDetectorElement().getGeometry(); + double[] pos = geom.transformLocalToGlobal(VecOp.add(geom.transformGlobalToLocal(geom.getPosition()),(Hep3Vector)new BasicHep3Vector(0,0,-1*((Trd)geom.getLogicalVolume().getSolid()).getZHalfLength()))).v(); + + // Convert the result to a Double[] array. + position = new double[3]; + position[0] = pos[0]; + position[1] = pos[1]; + position[2] = pos[2]; + + // Store the result in the map. + correctedPositionMap.put(hitIndex, position); + } + /////////////////////////////// + + //Use Method 3 weighting scheme described in note: + eNumX += Math.max(0.0,(w0+Math.log(entryP.getKey().getCorrectedEnergy() + /seedEnergyTot.get(seedP))))*(correctedPositionMap.get(hitIndex)[0]/10.0); + eNumY += Math.max(0.0,(w0+Math.log(entryP.getKey().getCorrectedEnergy() + /seedEnergyTot.get(seedP))))*(correctedPositionMap.get(hitIndex)[1]/10.0); + eDen += Math.max(0.0, (w0+Math.log(entryP.getKey().getCorrectedEnergy()/ + seedEnergyTot.get(seedP)))); + } + + } + + xCl = eNumX/eDen; + yCl = eNumY/eDen; + + double[] rawPosition = new double[3]; + rawPosition[0] = xCl*10.0;//mm + rawPosition[1] = yCl*10.0;//mm + int ix = seedP.getIdentifierFieldValue("ix"); + int iy = seedP.getIdentifierFieldValue("iy"); + Point hitIndex = new Point(ix, iy); + rawPosition[2] = correctedPositionMap.get(hitIndex)[2]; + + + + // Apply position correction factors: + // Position correction for electron: + int pdg = 11; + double xCorr = posCorrection(pdg, xCl*10.0, seedEnergyTot.get(seedP)); + + double[] corrPosition = new double[3]; + corrPosition[0] = xCorr*10.0;//mm + corrPosition[1] = yCl*10.0;//mm + corrPosition[2] = correctedPositionMap.get(hitIndex)[2]; + + corrSeedPosition.put(seedP, corrPosition); + rawSeedPosition.put(seedP, rawPosition); + + + }// end of cluster position calculation + + + /* + * Outputs results to cluster collection. + */ + // Only write output if something actually exists. + if (hitMap.size() != 0) { + // Loop over seeds + for (Map.Entry<CalorimeterHit, CalorimeterHit> entry2 : hitSeedMap.entrySet()) { + if (entry2.getKey() == entry2.getValue()){ + if(seedEnergyCorr.get(entry2.getKey())<clusterEnergyThreshold) + { + //Not clustered for not passing cuts + rejectedHitList.add(entry2.getKey()); + } + + else{ + // New cluster + HPSEcalClusterIC cluster = new HPSEcalClusterIC(entry2.getKey()); + clusterList.add(cluster); + // Loop over hits belonging to seeds + for (Map.Entry<CalorimeterHit, CalorimeterHit> entry3 : hitSeedMap.entrySet()) { + if (entry3.getValue() == entry2.getValue()) { + if(rejectedHitList.contains(entry2.getValue())){ + rejectedHitList.add(entry3.getKey()); + } + else{ + // Add hit to cluster + cluster.addHit(entry3.getKey()); + } + } + } + + for (Map.Entry<CalorimeterHit, List<CalorimeterHit>> entry4 : commonHits.entrySet()) { + if (entry4.getValue().contains(entry2.getKey())) { + // Add shared hits for energy distribution between clusters + cluster.addSharedHit(entry4.getKey()); + } + } + + //Input both raw and corrected cluster energies + if (seedEnergyCorr.values().size() > 0){ + cluster.setEnergy(seedEnergyCorr.get(entry2.getKey())); + cluster.setRawEnergy(seedEnergyTot.get(entry2.getKey())); + } + + //Input both uncorrected and corrected cluster positions. + cluster.setCorrPosition(corrSeedPosition.get(entry2.getKey())); + cluster.setRawPosition(rawSeedPosition.get(entry2.getKey())); + + + }// End checking thresholds and write out. + } + } //End cluster loop +// System.out.println("Number of clusters: "+clusterList.size()); + + + } //End event output loop. + int flag = 1 << LCIOConstants.CLBIT_HITS; + event.put(clusterCollectionName, clusterList, HPSEcalClusterIC.class, flag); + event.put(rejectedHitName, rejectedHitList, CalorimeterHit.class, flag); + + + } + + public void endOfData() { + if (writeHits != null) { + // Close the event display output writer. + try { + writeHits.close(); + } catch (IOException e) { + } + } + } + + + private static class EnergyComparator implements Comparator<CalorimeterHit> { + /** + * Compares the first hit with respect to the second. This + * method will compare hits first by energy, and then spatially. + * In the case of equal energy hits, the hit closest to the + * beam gap and closest to the positron side of the detector + * will be selected. If all of these conditions are true, the + * hit with the positive y-index will be selected. Hits with + * all four conditions matching are the same hit. + * @param hit1 The hit to compare. + * @param hit2 The hit with respect to which the first should + * be compared. + */ + public int compare(CalorimeterHit hit1, CalorimeterHit hit2) { + // Hits are sorted on a hierarchy by three conditions. First, + // the hits with the highest energy come first. Next, they + // are ranked by vertical proximity to the beam gap, and + // lastly, they are sorted by horizontal proximity to the + // positron side of the detector. + + // Get the hit energies. + double[] e = { hit1.getCorrectedEnergy(), hit2.getCorrectedEnergy() }; + + // Perform the energy comparison. The higher energy hit + // will be ordered first. + if(e[0] < e[1]) { return 1; } + else if(e[0] > e[1]) { return -1; } + + // If the hits are the same energy, we must perform the + // spatial comparisons. + else { + // Get the position with respect to the beam gap. + int[] iy = { Math.abs(hit1.getIdentifierFieldValue("iy")), Math.abs(hit2.getIdentifierFieldValue("iy")) }; + + // The closest hit is first. + if(iy[0] > iy[1]) { return -1; } + else if(iy[0] < iy[1]) { return 1; } + + // Hits that are identical in vertical distance from + // beam gap and energy are differentiated with distance + // horizontally from the positron side of the detector. + else { + // Get the position from the positron side. + int[] ix = { hit1.getIdentifierFieldValue("ix"), hit2.getIdentifierFieldValue("ix") }; + + // The closest hit is first. + if(ix[0] > ix[1]) { return 1; } + else if(ix[0] < ix[1]) { return -1; } + + // If all of these checks are the same, compare + // the raw value for iy. If these are identical, + // then the two hits are the same. Otherwise, sort + // the numerical value of iy. (This removes the + // issue where hits (x, y) and (x, -y) can have + // the same energy and be otherwise seen as the + // same hit from the above checks. + else { return Integer.compare(hit1.getIdentifierFieldValue("iy"), hit2.getIdentifierFieldValue("iy")); } + } + } + } +} + + + + /** + * Handles pathological case where multiple neighboring crystals have EXACTLY the same energy. + * @param hit + * @param neighbor Neighbor to hit + * @return boolean value of if the hit is a seed + */ + private boolean equalEnergies(CalorimeterHit hit, CalorimeterHit neighbor){ + boolean isSeed = true; + + int hix = hit.getIdentifierFieldValue("ix"); + int hiy = Math.abs(hit.getIdentifierFieldValue("iy")); + int nix = neighbor.getIdentifierFieldValue("ix"); + int niy = Math.abs(neighbor.getIdentifierFieldValue("iy")); + double hE = hit.getCorrectedEnergy(); + double nE = neighbor.getCorrectedEnergy(); + if(hE < nE) { + isSeed = false; + } + else if((hE == nE) && (hiy > niy)) { + isSeed = false; + } + else if((hE == nE) && (hiy == niy) && (hix > nix)) { + isSeed = false; + } + return isSeed; + } + /** + * Calculates energy correction based on cluster raw energy and particle type as per + *<a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a> + * @param pdg Particle id as per PDG + * @param rawEnergy Raw Energy of the cluster (sum of hits with shared hit distribution) + * @return Corrected Energy + */ + public double enCorrection(int pdg, double rawEnergy){ + if (pdg == 11) { // Particle is electron + return energyCorrection(rawEnergy, ELECTRON_ENERGY_A, ELECTRON_ENERGY_B, ELECTRON_ENERGY_C); + } + else if (pdg == -11) { //Particle is positron + return energyCorrection(rawEnergy, POSITRON_ENERGY_A, POSITRON_ENERGY_B, POSITRON_ENERGY_C); + } + else if (pdg == 22) { //Particle is photon + return energyCorrection(rawEnergy, PHOTON_ENERGY_A, PHOTON_ENERGY_B, PHOTON_ENERGY_C); + } + else { //Unknown + double corrEnergy = rawEnergy; + return corrEnergy;} + + } + + /** + * Calculates the energy correction to a cluster given the variables from the fit as per + * <a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a> + * @param rawEnergy Raw energy of the cluster + * @param A,B,C from fitting in note + * @return Corrected Energy + */ + public double energyCorrection(double rawEnergy, double varA, double varB, double varC){ + double corrEnergy = rawEnergy / (varA * rawEnergy + varB / (Math.sqrt(rawEnergy)) + varC); + return corrEnergy; + } + + + /** + * Calculates position correction based on cluster raw energy, x calculated position, + * and particle type as per + * <a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a> + * @param pdg Particle id as per PDG + * @param xCl Calculated x centroid position of the cluster, uncorrected, at face + * @param rawEnergy Raw energy of the cluster (sum of hits with shared hit distribution) + * @return Corrected x position + */ + public double posCorrection(int pdg, double xPos, double rawEnergy){ + double xCl = xPos/10.0;//convert to mm + if (pdg == 11) { //Particle is electron + double xCorr = positionCorrection(xCl, rawEnergy, ELECTRON_POS_A, ELECTRON_POS_B, ELECTRON_POS_C, ELECTRON_POS_D, ELECTRON_POS_E); + return xCorr*10.0; + } + else if (pdg == -11) {// Particle is positron + double xCorr = positionCorrection(xCl, rawEnergy, POSITRON_POS_A, POSITRON_POS_B, POSITRON_POS_C, POSITRON_POS_D, POSITRON_POS_E); + return xCorr*10.0; + } + else if (pdg == 22) {// Particle is photon + double xCorr = positionCorrection(xCl, rawEnergy, PHOTON_POS_A, PHOTON_POS_B, PHOTON_POS_C, PHOTON_POS_D, PHOTON_POS_E); + return xCorr*10.0; + } + else { //Unknown + double xCorr = xCl; + return xCorr*10.0;} + } + + + /** + * Calculates the position correction in cm using the raw energy and variables associated with the fit + * of the particle as described in + * <a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a> + * @param xCl + * @param rawEnergy + * @param varA + * @param varB + * @param varC + * @param varD + * @param varE + * @return + */ + public double positionCorrection(double xCl, double rawEnergy, double varA, double varB, double varC, double varD, double varE){ + double xCorr = xCl-(varA/Math.sqrt(rawEnergy) + varB )*xCl- + (varC*rawEnergy + varD/Math.sqrt(rawEnergy) + varE); + return xCorr; + } + + + } Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPEcalClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPEcalClusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPEcalClusterer.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,382 @@ +package org.hps.recon.ecal.cluster; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.EventHeader; +import org.lcsim.geometry.Detector; +import org.lcsim.geometry.subdetector.HPSEcal3; +import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap; +import org.lcsim.lcio.LCIOConstants; +import org.lcsim.util.Driver; + +/** + * Class <code>GTPCalorimeterClusterer</code> processes events and converts hits + * into clusters, where appropriate. It uses the modified 2014 clustering algorithm.<br/> + * <br/> + * For a hit to be a cluster center, it is required to have an energy above some + * tunable minimum threshold. Additionally, the hit must be a local maximum with + * respect to its neighbors and itself over a tunable (default 2) clock cycles. + * Hits that pass these checks are then required to additional have a total + * cluster energy that exceeds another tunable minimum threshold.<br/> + * <br/> + * A hit is added to a cluster as a component if it has a non-zero energy and + * within the aforementioned tunable time buffer used for clustering and is + * either at the same location as the seed hit or is a neighbor to the seed hit. + * @author Kyle McCarty + * @author Sho Uemura + */ +public class GTPEcalClusterer extends Driver { + Detector detector = null; + + + /** + * <b>calorimeter</b><br/><br/> + * <code>private HPSEcal3 <b>calorimeter</b></code><br/><br/> + * The sub-detector representing the calorimeter. + */ + private HPSEcal3 calorimeter; + /** + * <b>ecalName</b><br/><br/> + * <code>private String <b>ecalName</b></code><br/><br/> + * The name of the calorimeter sub-detector stored in the + * <code> + * Detector</code> object for this run. + */ + private String ecalName; + /** + * <b>clusterCollectionName</b><br/><br/> + * <code>private String <b>clusterCollectionName</b></code><br/><br/> + * The name of the LCIO collection name in which the clusters will be + * stored. + */ + private String clusterCollectionName = "EcalClusters"; + /** + * <b>clusterWindow</b><br/><br/> + * <code>private int <b>clusterWindow</b></code><br/><br/> + * Indicates the number of FADC clock cycles (each cycle is 4 ns) before and + * after a given cycle that should be considered when checking if a cluster + * is a local maximum in space-time. + */ + private int clusterWindow = 2; + /** + * <b>hitBuffer</b><br/><br/> + * <code>private LinkedList<List<CalorimeterHit>> <b>hitBuffer</b></code><br/><br/> + * Stores a set of all the hits occurring in each clock cycle for the number + * of clock cycles that should be considered for clustering. + */ + private LinkedList<Map<Long, CalorimeterHit>> hitBuffer; + /** + * <b>ecalCollectionName</b><br/><br/> + * <code>private String <b>ecalCollectionName</b></code><br/><br/> + * The name of LCIO collection containing the calorimeter hits that are to + * be used for clustering. + */ + private String ecalCollectionName; + /** + * <b>neighborMap</b><br/><br/> + * <code>private NeighborMap <b>neighborMap</b></code><br/><br/> + * Maps the + * <code>long</code> crystal ID to the set of crystal IDs of the crystals + * which are adjacent to the key. + */ + private NeighborMap neighborMap = null; + /** + * <b>seedEnergyThreshold</b><br/><br/> + * <code>private double <b>seedEnergyThreshold</b></code><br/><br/> + * The minimum energy required for a hit to be considered as a cluster + * center. Hits with energy less than this value will be ignored. + */ + private double seedEnergyThreshold = 0.05; + /** + * <b>limitClusterRange</b><br/><br/> + * <code>private boolean <b>limitClusterRange</b></code><br/><br/> + * Whether an asymmetric or symmetric window should be used for + * adding hits to a cluster. + */ + private boolean limitClusterRange = false; + + /** + * Initializes detector-dependent parameters for clustering. Method is + * responsible for determining which crystals are valid cluster centers + * (stored in + * <code>validCrystals</code>), defining the detector object (stored in + * <code>calorimeter</code>), defining the detector ID decoder (stored in + * <code>decoder</code>), and defining the mapping of crystal IDs to the + * crystal's neighbors (defined in + * <code>neighborMap</code>). + * + * @param detector - The new detector to use. + */ + @Override + public void detectorChanged(Detector detector) { + + this.detector = detector; + + // Get the calorimeter object. + calorimeter = (HPSEcal3) detector.getSubdetector(ecalName); + + // Get a map to associate crystals with their neighbors. + neighborMap = calorimeter.getNeighborMap(); + } + + /** + * Generates a list of clusters from the current hit buffer. The "present" + * event is taken to be the list of hits occurring at index + * <code>clusterWindow</code>, which is the middle of the buffer. + * + * @return Returns a <code>List</code> of <code>HPSEcalCluster + * </code> objects generated from the current event. + */ + public List<HPSEcalCluster> getClusters() { + // Generate a list for storing clusters. + List<HPSEcalCluster> clusters = new ArrayList<HPSEcalCluster>(); + + // Get the list of hits at the current time in the event buffer. + Map<Long, CalorimeterHit> currentHits = hitBuffer.get(clusterWindow); + + // For a hit to be a cluster center, it must be a local maximum + // both with respect to its neighbors and itself both in the + // present time and at all times within the event buffer. + seedLoop: + for (Long currentID : currentHits.keySet()) { + // Get the actual hit object. + CalorimeterHit currentHit = currentHits.get(currentID); + + // Store the energy of the current hit. + double currentEnergy = currentHit.getRawEnergy(); + + // If the hit energy is lower than the minimum threshold, + // then we immediately reject this hit as a possible cluster. + if (currentEnergy < seedEnergyThreshold) { + continue seedLoop; + } + + // Store the crystals that are part of this potential cluster, + // starting with the cluster seed candidate. + HPSEcalCluster cluster = new HPSEcalCluster(); + cluster.setSeedHit(currentHit); + cluster.addHit(currentHit); + + // Get the set of neighbors for this hit. + Set<Long> neighbors = neighborMap.get(currentHit.getCellID()); + + // Sort through each event stored in the buffer. + int bufferIndex = 0; + for (Map<Long, CalorimeterHit> bufferHits : hitBuffer) { + // Get the hit energy at the current hit's position in + // the buffer, if it exists. Ignore the current seed candidate. + CalorimeterHit bufferHit = bufferHits.get(currentID); + if (bufferHit != null && bufferHit != currentHit) { + double bufferHitEnergy = bufferHit.getRawEnergy(); + + // Check to see if the hit at this point in the buffer + // is larger than then original hit. If it is, we may + // stop the comparison because this is not a cluster. + if (bufferHitEnergy > currentEnergy) { + continue seedLoop; + } + + // If the buffer hit is smaller, then add its energy + // to the cluster total energy. + else { + if(limitClusterRange && bufferIndex <= clusterWindow + 1) { cluster.addHit(bufferHit); } + else if(!limitClusterRange) { cluster.addHit(bufferHit); } + } + } + + // We must also make sure that the original hit is + // larger than all of the neighboring hits at this + // point in the buffer as well. + for (Long neighborID : neighbors) { + // Get the neighbor hit energy if it exists. + CalorimeterHit neighborHit = bufferHits.get(neighborID); + if (neighborHit != null) { + double neighborHitEnergy = neighborHit.getRawEnergy(); + + // Check to see if the neighbor hit at this point + // in the buffer is larger than then original hit. + // If it is, we may stop the comparison because this + // is not a cluster. + if (neighborHitEnergy > currentEnergy) { + continue seedLoop; + } + + // If the buffer neighbor hit is smaller, then + // add its energy to the cluster total energy. + else { + if(limitClusterRange && bufferIndex <= clusterWindow + 1) { cluster.addHit(neighborHit); } + else if(!limitClusterRange) { cluster.addHit(neighborHit); } + } + } + } + + // Increment the buffer index. + bufferIndex++; + } + + // Add the cluster to the list of clusters. + clusters.add(cluster); + } + + // Return the generated list of clusters. + return clusters; + } + + /** + * Places hits from the current event into the event hit buffer and + * processes the buffer to extract clusters. Clusters are then stored in the + * event object. + * + * @param event - The event to process. + */ + @Override + public void process(EventHeader event) { + // Only run the clusterer if this event has the calorimeter hit collection. + // This ensures this driver makes clusters in sync with the FADC readout cycle. + if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) { + // Get the list of calorimeter hits from the event. + List<CalorimeterHit> hitList = event.get(CalorimeterHit.class, ecalCollectionName); + + // Store each hit in a set by its cell ID so that it may be + // easily acquired later. + HashMap<Long, CalorimeterHit> hitMap = new HashMap<Long, CalorimeterHit>(); + for (CalorimeterHit hit : hitList) { + hitMap.put(hit.getCellID(), hit); + } + + // Remove the last event from the hit buffer and add the new one. + hitBuffer.removeLast(); + hitBuffer.addFirst(hitMap); + + // Run the clustering algorithm on the buffer. + List<HPSEcalCluster> clusterList = getClusters(); + + // Store the cluster list in the LCIO collection. + int flag = 1 << LCIOConstants.CLBIT_HITS; + event.put(clusterCollectionName, clusterList, HPSEcalCluster.class, flag); + } + } + + /** + * Sets the name of the LCIO collection containing the calorimeter hits + * which should be used for clustering. + * + * @param ecalCollectionName - The appropriate collection name. + */ + public void setEcalCollectionName(String ecalCollectionName) { + this.ecalCollectionName = ecalCollectionName; + } + + /** + * Sets the name of the LCIO collection in which the clusters should be + * stored. + * + * @param clusterCollectionName - The desired collection name. + */ + public void setClusterCollectionName(String clusterCollectionName) { + this.clusterCollectionName = clusterCollectionName; + } + + /** + * Sets the name of the calorimeter sub-detector stored in the + * <code>Detector</code> object that is to be used for this run. + * + * @param ecalName - The calorimeter's name. + */ + public void setEcalName(String ecalName) { + this.ecalName = ecalName; + } + + /** + * Sets the number of clock cycles before and after a given cycle that will + * be used when checking whether a given hit is a local maximum in both time + * and space. Note that a value of + * <code>N + * </code> indicates that + * <code>N</code> clock cycles before and + * <code>N</code> clock cycles after will be considered. Thusly, a total of + * <code>2N + 1</code> clock cycles will be used total. + * + * @param clusterWindow - The number of additional clock cycles to include + * in the clustering checks. A negative value will be treated as zero. + */ + public void setClusterWindow(int clusterWindow) { + // The cluster window of must always be at least zero. + if (clusterWindow < 0) { + this.clusterWindow = 0; + } + + // If the cluster window is non-zero, then store it. + else { + this.clusterWindow = clusterWindow; + } + } + + /** + * Sets whether hits should be added to a cluster from the entire + * cluster window or just the "future" hits, plus one clock-cycle + * of "past" hits as a safety buffer to account for time uncertainty. + * + * @param limitClusterRange - <code>true</code> indicates that + * the asymmetric clustering window should be used and <code> + * false</code> that the symmetric window should be used. + */ + public void setLimitClusterRange(boolean limitClusterRange) { + this.limitClusterRange = limitClusterRange; + } + + /** + * Sets the minimum energy threshold below which hits will not be considered + * as cluster centers. + * + * @param seedEnergyThreshold - The minimum energy for a cluster center. + */ + public void setSeedEnergyThreshold(double seedEnergyThreshold) { + // A negative energy threshold is non-physical. All thresholds + // be at least zero. + if (seedEnergyThreshold < 0.0) { + this.seedEnergyThreshold = 0.0; + } // If the energy threshold is valid, then use it. + else { + this.seedEnergyThreshold = seedEnergyThreshold; + } + } + + /** + * Initializes the clusterer. This ensures that the collection name for the + * calorimeter hits from which clusters are to be generated, along with the + * calorimeter name, have been defined. Method also initializes the event + * hit buffer. + */ + @Override + public void startOfData() { + // Make sure that there is a cluster collection name into + // which clusters may be placed. + if (ecalCollectionName == null) { + throw new RuntimeException("The parameter ecalCollectionName was not set!"); + } + + // Make sure that there is a calorimeter detector. + if (ecalName == null) { + throw new RuntimeException("The parameter ecalName was not set!"); + } + + // Initiate the hit buffer. + hitBuffer = new LinkedList<Map<Long, CalorimeterHit>>(); + + // Populate the event buffer with (2 * clusterWindow + 1) + // empty events. These empty events represent the fact that + // the first few events will not have any events in the past + // portion of the buffer. + int bufferSize = (2 * clusterWindow) + 1; + for (int i = 0; i < bufferSize; i++) { + hitBuffer.add(new HashMap<Long, CalorimeterHit>(0)); + } + } +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/LegacyClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/LegacyClusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/LegacyClusterer.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,97 @@ +package org.hps.recon.ecal.cluster; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hps.recon.ecal.ECalUtils; +import org.hps.recon.ecal.HPSEcalCluster; +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.Cluster; + +/** + * This Driver creates clusters from the CalorimeterHits of an + * {@link org.lcsim.geometry.subdetectur.HPSEcal3} detector. + * + * The clustering algorithm is from pages 83 and 84 of the HPS Proposal. + * + * @author Jeremy McCormick <[log in to unmask]> + * @author Tim Nelson <[log in to unmask]> + */ +public class LegacyClusterer extends AbstractClusterer { + + // Minimum E for cluster seed. + double minimumClusterSeedEnergy = 0.05 * ECalUtils.GeV; + + // Minimum E to add hit to cluster. + double minimumHitEnergy = 0.03 * ECalUtils.GeV; + + void setMinimumClusterSeedEnergy(double minimumClusterSeedEnergy) { + this.minimumClusterSeedEnergy = minimumClusterSeedEnergy; + } + + void setMinimumHitEnergy(double minimumHitEnergy) { + this.minimumHitEnergy = minimumHitEnergy; + if (minimumClusterSeedEnergy < minimumHitEnergy) { + minimumClusterSeedEnergy = minimumHitEnergy; + } + } + + public List<Cluster> createClusters(List<CalorimeterHit> hits) { + + Map<Long, CalorimeterHit> hitMap = ClusterUtilities.createHitMap(hits); + + // New Cluster list to be added to event. + List<Cluster> clusters = new ArrayList<Cluster>(); + + // Loop over ECal hits to find cluster seeds. + for (CalorimeterHit hit : hitMap.values()) { + // Cut on min seed E. + if (hit.getRawEnergy() < minimumClusterSeedEnergy) { + continue; + } + + // Get neighbor crystal IDs. + Set<Long> neighbors = neighborMap.get(hit.getCellID()); + + // List for neighboring hits. + List<CalorimeterHit> neighborHits = new ArrayList<CalorimeterHit>(); + + // Loop over neighbors to make hit list for cluster. + boolean isSeed = true; + for (Long neighborId : neighbors) { + // Find the neighbor hit in the event if it exists. + CalorimeterHit neighborHit = hitMap.get(neighborId); + + // Was this neighbor cell hit? + if (neighborHit != null) { + // Check if neighbor cell has more energy. + if (neighborHit.getRawEnergy() > hit.getRawEnergy()) { + // Neighbor has more energy, so cell is not a seed. + isSeed = false; + break; + } + + // Add to cluster if above min E. + if (neighborHit.getRawEnergy() >= minimumHitEnergy) { + neighborHits.add(neighborHit); + } + } + } + + // Did we find a seed? + if (isSeed) { + // Make a cluster from the hit list. + HPSEcalCluster cluster = new HPSEcalCluster(); + cluster.setSeedHit(hit); + cluster.addHit(hit); + for (CalorimeterHit clusHit : neighborHits) { + cluster.addHit(clusHit); + } + clusters.add(cluster); + } + } + return clusters; + } +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleInnerCalClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleInnerCalClusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleInnerCalClusterer.java Mon Dec 15 15:54:53 2014 @@ -0,0 +1,151 @@ +package org.hps.recon.ecal.cluster; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hps.recon.ecal.HPSEcalCluster; +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.Cluster; + +/** + * This Driver creates clusters from the CalorimeterHits of an + * {@link org.lcsim.geometry.subdetector.HPSEcal3} detector. + * + * Uses basic IC clustering algorithm as given in CLAS note 2004-040: no common + * hits (hits are assigned to cluster with largest seed hit energy). + * + * Hit time information is not used, and multiple hits in the same crystal are + * not handled correctly (a warning is printed); optional time cut is applied at + * the beginning to discard hits too far from t0. + * + * @author Holly Szumila-Vance <[log in to unmask]> + * @author Sho Uemura <[log in to unmask]> + * + */ +public class SimpleInnerCalClusterer extends AbstractClusterer { + + //Minimum energy that counts as hit + double Emin = 0.001; + boolean timeCut = false; + double minTime = 0.0; + double timeWindow = 20.0; + + /** + * Minimum energy for a hit to be used in a cluster. Default of 0.001 GeV.. + * @param Emin + */ + public void setEmin(double Emin) { + this.Emin = Emin; + } + + /** + * Apply time cuts to hits. Defaults to false. + * @param timeCut + */ + public void setTimeCut(boolean timeCut) { + this.timeCut = timeCut; + } + + /** + * Minimum hit time, if timeCut is true. Default of 0 ns. + * @param minTime + */ + public void setMinTime(double minTime) { + this.minTime = minTime; + } + + /** + * Width of time window, if timeCut is true. Default of 20 ns. + * @param timeWindow + */ + public void setTimeWindow(double timeWindow) { + this.timeWindow = timeWindow; + } + + public List<Cluster> createClusters(List<CalorimeterHit> allHits) { + + // New Cluster list to be added to event. + List<Cluster> clusters = new ArrayList<Cluster>(); + + //Create a Calorimeter hit list in each event, then sort with highest energy first + ArrayList<CalorimeterHit> sortedHitList = new ArrayList<CalorimeterHit>(allHits.size()); + for (CalorimeterHit h : allHits) { + //reject hits below the energy cut + if (h.getCorrectedEnergy() < Emin) { + continue; + } + //if time cut is being used, reject hits outside the time window + if (timeCut && (h.getTime() < minTime || h.getTime() > minTime + timeWindow)) { + continue; + } + sortedHitList.add(h); + } + + //sort the list, highest energy first + Collections.sort(sortedHitList, Collections.reverseOrder(new EnergyComparator())); + + //map from seed hit to cluster + Map<CalorimeterHit, HPSEcalCluster> seedToCluster = new HashMap<CalorimeterHit, HPSEcalCluster>(); + + //Quick Map to access hits from cell IDs + Map<Long, CalorimeterHit> idToHit = new HashMap<Long, CalorimeterHit>(); + + //map from each hit to its cluster seed + Map<CalorimeterHit, CalorimeterHit> hitToSeed = new HashMap<CalorimeterHit, CalorimeterHit>(); + + //Fill Map with cell ID and hit + for (CalorimeterHit hit : sortedHitList) { + //if (idToHit.containsKey(hit.getCellID())) { + // System.out.println(this.getName() + ": multiple CalorimeterHits in same crystal"); + //} + idToHit.put(hit.getCellID(), hit); + } + + for (CalorimeterHit hit : sortedHitList) { + CalorimeterHit biggestSeed = null; + + for (Long neighbor : neighborMap.get(hit.getCellID())) { + CalorimeterHit neighborHit = idToHit.get(neighbor); + if (neighborHit == null) { + continue; + } + + if (neighborHit.getCorrectedEnergy() > hit.getCorrectedEnergy()) { + CalorimeterHit neighborSeed = hitToSeed.get(neighborHit); + if (biggestSeed == null || neighborHit.getCorrectedEnergy() > biggestSeed.getCorrectedEnergy()) { + biggestSeed = neighborSeed; + } + } + } + if (biggestSeed == null) { //if no neighbors had more energy than this hit, this hit is a seed + hitToSeed.put(hit, hit); + HPSEcalCluster cluster = new HPSEcalCluster(hit.getCellID()); + clusters.add(cluster); + seedToCluster.put(hit, cluster); + } else { + hitToSeed.put(hit, biggestSeed); + } + } + + //add all hits to clusters + for (CalorimeterHit hit : sortedHitList) { + CalorimeterHit seed = hitToSeed.get(hit); + HPSEcalCluster cluster = seedToCluster.get(seed); + cluster.addHit(hit); + } + + return clusters; + } + + private class EnergyComparator implements Comparator<CalorimeterHit> { + + @Override + public int compare(CalorimeterHit o1, CalorimeterHit o2) { + return Double.compare(o1.getCorrectedEnergy(), o2.getCorrectedEnergy()); + } + } +}