Author: [log in to unmask] Date: Fri Jan 9 16:24:39 2015 New Revision: 1912 Log: Add several more clustering algorithm implementations to the ecal.cluster package. Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPClusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterDriver.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterer.java Modified: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/CTPEcalClusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/GTPEcalClusterer.java java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClustererFactory.java java/trunk/ecal-recon/src/test/java/org/hps/recon/ecal/cluster/ClustererTest.java Modified: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/CTPEcalClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/CTPEcalClusterer.java (original) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/CTPEcalClusterer.java Fri Jan 9 16:24:39 2015 @@ -28,8 +28,10 @@ * @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 $ + * + * @deprecated Use the {@link org.hps.recon.ecal.cluster.CTPClusterer} with the {@link org.hps.recon.ecal.cluster.ClusterDriver}. */ +@Deprecated public class CTPEcalClusterer extends Driver { Detector detector = null; Modified: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/GTPEcalClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/GTPEcalClusterer.java (original) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/GTPEcalClusterer.java Fri Jan 9 16:24:39 2015 @@ -30,7 +30,10 @@ * either at the same location as the seed hit or is a neighbor to the seed hit. * @author Kyle McCarty * @author Sho Uemura + * + * @deprecated Use the {@link org.hps.recon.ecal.cluster.GTPClusterDriver} instead. */ +@Deprecated public class GTPEcalClusterer extends Driver { Detector detector = null; Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPClusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/CTPClusterer.java Fri Jan 9 16:24:39 2015 @@ -0,0 +1,332 @@ +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.hps.recon.ecal.CalorimeterHitUtilities; +import org.lcsim.conditions.ConditionsEvent; +import org.lcsim.event.CalorimeterHit; +import org.lcsim.event.Cluster; +import org.lcsim.event.EventHeader; +import org.lcsim.event.base.BaseCalorimeterHit; +import org.lcsim.event.base.BaseCluster; +import org.lcsim.geometry.IDDecoder; + +/** + * Creates clusters from CalorimeterHits in the HPSEcal detector. + * + * The clustering algorithm is from JLab Hall B 6 GeV DVCS Trigger Design doc. + * + * @author Kyle McCarty + * @author Sho Uemura <[log in to unmask]> + * @author Jeremy McCormick <[log in to unmask]> + */ +public class CTPClusterer extends AbstractClusterer { + + IDDecoder dec; + + Set<Long> clusterCenters = null; + Map<Long, Double> hitSums = null; + Map<Long, CalorimeterHit> hitMap = 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; + + CTPClusterer() { + super(new String[] { "addEMin", "clusterWindow"}, new double[] { 0., -1. }); + } + + public void initialize() { + addEMin = getCuts().getValue("addEMin"); + clusterWindow = getCuts().getValue("clusterWindow"); + } + + @Override + public void conditionsChanged(ConditionsEvent event) { + super.conditionsChanged(event); + + // Get the decoder for the ECal IDs. + dec = ecal.getIDDecoder(); + + // Make set of valid cluster centers. + // Exclude edge crystals as good cluster centers. + clusterCenters = new HashSet<Long>(); + 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 List<Cluster> createClusters(EventHeader event, List<CalorimeterHit> hits) { + + // Define a list of clusters to be filled. + List<Cluster> 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<Cluster>(); + + // 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(); + } + + return clusters; + } + + private 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()); + } + } + } + } + + private List<Cluster> createClusters() { + // Create a list of clusters to be added to the event, + List<Cluster> clusters = new ArrayList<Cluster>(); + + // 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. + CalorimeterHit seedHit = (BaseCalorimeterHit)CalorimeterHitUtilities.create(0.0, clusterTime, possibleCluster, hits.get(0).getMetaData()); + + // Generate a new cluster from the seed hit. + BaseCluster cluster = new BaseCluster(); + cluster.addHit(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; + } + } + } +} Modified: 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 (original) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClustererFactory.java Fri Jan 9 16:24:39 2015 @@ -13,13 +13,15 @@ * this class implements the {@link Clusterer} interface and will throw an error if it does not. * * @see Clusterer + * @see CTPClusterer * @see DualThresholdCosmicClusterer + * @see GTPClusterer * @see GTPOnlineClusterer * @see LegacyClusterer * @see NearestNeighborClusterer * @see ReconClusterer * @see SimpleReconClusterer - * @see SimpleCosmicClusterer + * @see SimpleCosmicClusterer * * @author Jeremy McCormick <[log in to unmask]> */ @@ -54,6 +56,10 @@ clusterer = new SimpleCosmicClusterer(); } else if (GTPOnlineClusterer.class.getSimpleName().equals(name)) { clusterer = new GTPOnlineClusterer(); + } else if (GTPClusterer.class.getSimpleName().equals(name)) { + clusterer = new GTPClusterer(); + } else if (CTPClusterer.class.getSimpleName().equals(name)) { + clusterer = new CTPClusterer(); } else { // Try to instantiate a Clusterer object from the name argument, assuming it is a canonical class name. try { Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterDriver.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterDriver.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterDriver.java Fri Jan 9 16:24:39 2015 @@ -0,0 +1,31 @@ +package org.hps.recon.ecal.cluster; + +/** + * This is a Driver to wrap the GTPClusterer algorithm, + * allowing the <code>limitClusterRange</code> to be + * set publicly. + * + * @see GTPClusterer + * + * @author Jeremy McCormick <[log in to unmask]> + */ +public class GTPClusterDriver extends ClusterDriver { + + public GTPClusterDriver() { + clusterer = ClustererFactory.create("GTPClusterer"); + } + + /** + * 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. + */ + void setLimitClusterRange(boolean limitClusterRange) { + GTPClusterer gtpClusterer = getClusterer(); + gtpClusterer.setLimitClusterRange(limitClusterRange); + } +} Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterer.java ============================================================================= --- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterer.java (added) +++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPClusterer.java Fri Jan 9 16:24:39 2015 @@ -0,0 +1,271 @@ +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.Cluster; +import org.lcsim.event.EventHeader; +import org.lcsim.event.base.BaseCluster; + +/** + * 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 GTPClusterer extends AbstractClusterer { + + /** + * <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; + + /** + * <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; + + /** + * <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>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; + + GTPClusterer() { + super(new String[] { "seedEnergyThreshold", "clusterWindow" }, new double[] { 0.05, 2.}); + } + + public void initialize() { + + // Set cuts. + setSeedEnergyThreshold(getCuts().getValue("seedEnergyThreshold")); + setClusterWindow((int) getCuts().getValue("clusterWindow")); + + // 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)); + } + } + + /** + * 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. + */ + private List<Cluster> getClusters() { + // Generate a list for storing clusters. + List<Cluster> clusters = new ArrayList<Cluster>(); + + // 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. + BaseCluster cluster = new BaseCluster(); + 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. + */ + public List<Cluster> createClusters(EventHeader event, List<CalorimeterHit> hits) { + + // 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 : hits) { + 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<Cluster> clusterList = getClusters(); + + return clusterList; + } + + /** + * 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. + */ + 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. + */ + 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. + */ + 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; + } + } +} Modified: java/trunk/ecal-recon/src/test/java/org/hps/recon/ecal/cluster/ClustererTest.java ============================================================================= --- java/trunk/ecal-recon/src/test/java/org/hps/recon/ecal/cluster/ClustererTest.java (original) +++ java/trunk/ecal-recon/src/test/java/org/hps/recon/ecal/cluster/ClustererTest.java Fri Jan 9 16:24:39 2015 @@ -35,7 +35,7 @@ */ public class ClustererTest extends TestCase { - static int nEvents = 1000; + static int nEvents = 100; static final String fileLocation = "http://www.lcsim.org/test/hps-java/MockDataReconTest.slcio"; File inputFile; File testOutputDir; @@ -48,11 +48,11 @@ throw new RuntimeException(e); } - // Create test output dir. + // Create test output directory. testOutputDir = new TestOutputFile(getClass().getSimpleName()); testOutputDir.mkdir(); - // Initialize conditions system. + // Initialize the conditions system. new DatabaseConditionsManager(); DatabaseConditionsManager.getInstance().setLogLevel(Level.WARNING); } @@ -79,6 +79,14 @@ public void testGTPOnlineClusterer() { runClustererTest("GTPOnlineClusterer", null, true, true); + } + + public void testCTPClusterer() { + runClustererTest("CTPClusterer", null, true, false); + } + + public void testGTPClusterer() { + runClustererTest("GTPClusterer", null, true, false); } /**