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());
+ }
+ }
+}
|