Print

Print


Author: [log in to unmask]
Date: Tue Dec 16 15:15:39 2014
New Revision: 1761

Log:
Snapshot of work on new clustering package including better doc and API additions.  Several simple Clusterer algorithms are implemented now and seem to basically work.

Added:
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleClasInnerCalClusterer.java
      - copied, changed from r1759, java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleInnerCalClusterer.java
Removed:
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleInnerCalClusterer.java
Modified:
    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/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/LegacyClusterer.java

Modified: 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	(original)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/AbstractClusterer.java	Tue Dec 16 15:15:39 2014
@@ -2,20 +2,131 @@
 
 import java.util.List;
 
+import org.hps.conditions.database.DatabaseConditionsManager;
+import org.lcsim.conditions.ConditionsEvent;
 import org.lcsim.event.CalorimeterHit;
 import org.lcsim.event.Cluster;
+import org.lcsim.event.EventHeader;
 import org.lcsim.geometry.subdetector.HPSEcal3;
 import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap;
 
+/**
+ * This is an abstract class that {@link Clusterer} classes should implement
+ * to perform a clustering algorithm on a <code>CalorimeterHit</code> collection.
+ * The sub-class should implement {@link #createClusters(List)} which is 
+ * the method that should perform the clustering.
+ * 
+ * @author Jeremy McCormick <[log in to unmask]>
+ */
 public abstract class AbstractClusterer implements Clusterer {
     
-    HPSEcal3 ecal;
-    NeighborMap neighborMap;
+    protected HPSEcal3 ecal;
+    protected NeighborMap neighborMap;
+    protected double[] cuts;
+    protected double[] defaultCuts;
+    protected String[] cutNames;
     
-    public void setEcalSubdetector(HPSEcal3 ecal) {
-        this.ecal = ecal;
+    /**
+     * This is the primary method for sub-classes to implement their clustering algorithm.
+     * @param hits
+     * @return
+     */
+    public abstract List<Cluster> createClusters(EventHeader event, List<CalorimeterHit> hits);
+    
+    /**
+     * Detector setup performed here to get reference to ECAL subdetector and neighbor mapping.
+     */
+    @Override
+    public void conditionsChanged(ConditionsEvent event) {
+        // Default setup of ECAL subdetector.
+        this.ecal = (HPSEcal3) DatabaseConditionsManager.getInstance().getDetectorObject().getSubdetector("Ecal");
         this.neighborMap = ecal.getNeighborMap();
     }
     
-    public abstract List<Cluster> createClusters(List<CalorimeterHit> hits);    
+    /**
+     * By default nothing is done in this method, but start of job initialization can happen here like reading
+     * cut settings into instance variables for convenience.  This is called in the <code>startOfData</code>
+     * method of {@link ClusterDriver}.
+     */
+    public void initialize() {
+    }    
+        
+    /**
+     * Default constructor which takes names of cuts and their default values.
+     * Even if there are no cuts, these should be arrays of length 0 instead of null.
+     * @param cutNames The names of the cuts for this clustering algorithm.
+     * @param defaultCuts The default cut values for the algorithm matching the cutNames ordering.
+     * @throw IllegalArgumentException if the arguments are null or the arrays are different lengths.
+     */
+    protected AbstractClusterer(String cutNames[], double[] defaultCuts) {
+        if (cutNames == null) {
+            throw new IllegalArgumentException("The cutNames is set to null.");
+        }
+        if (defaultCuts == null) {
+            throw new IllegalArgumentException("The defaultCuts is set to null.");
+        }
+        if (cutNames.length != defaultCuts.length) {
+            throw new IllegalArgumentException("The cutNames and defaultCuts are not the same length.");
+        }
+        this.cutNames = cutNames;
+        this.defaultCuts = defaultCuts;
+        this.cuts = defaultCuts;
+    }
+            
+    public void setCuts(double[] cuts) {
+        if (cuts.length != this.cutNames.length) {
+            throw new IllegalArgumentException("The cuts array has the wrong length: " + cuts.length);
+        }
+        this.cuts = cuts;
+    }
+    
+    public double[] getCuts() {
+        return cuts;
+    }
+    
+    public double getCut(String name) {
+         int index = indexFromName(name);
+         if (index == -1) {
+             throw new IllegalArgumentException("There is no cut called " + name + " defined by this clusterer.");
+         }
+         return getCut(index);
+    }
+    
+    public double getCut(int index) {
+        if (index > cuts.length || index < 0) {
+            throw new IndexOutOfBoundsException("The index " + index + " is out of bounds for cuts array.");
+        }
+        return cuts[index];
+    }
+         
+    public String[] getCutNames() {
+        return cutNames;
+    }    
+    
+    @Override
+    public void setCut(int index, double value) {
+        cuts[index] = value;
+    }
+
+    public boolean isDefaultCuts() {
+        return cuts == defaultCuts;
+    }
+    
+    public double[] getDefaultCuts() {
+        return defaultCuts;
+    }
+    
+    public void setCut(String name, double value) {
+        int index = indexFromName(name);
+        cuts[index] = value;
+    }
+        
+    protected int indexFromName(String name) {
+        for (int index = 0; index < cuts.length; index++) {
+            if (getCutNames()[index] == name) {
+                return index;
+            }                 
+        }
+        return -1;
+    }       
 }

Modified: 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	(original)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterDriver.java	Tue Dec 16 15:15:39 2014
@@ -1,6 +1,7 @@
 package org.hps.recon.ecal.cluster;
 
 import java.util.List;
+import java.util.logging.Logger;
 
 import org.lcsim.event.CalorimeterHit;
 import org.lcsim.event.Cluster;
@@ -10,10 +11,18 @@
 import org.lcsim.geometry.subdetector.HPSEcal3;
 import org.lcsim.lcio.LCIOConstants;
 import org.lcsim.util.Driver;
+import org.lcsim.util.log.LogUtil;
+import org.lcsim.util.log.BasicFormatter;
 
 /**
- * This is a basic Driver that creates Cluster collections through the
- * Clusterer interface.
+ * <p>
+ * This is a basic Driver that creates ECAL <code>Cluster</code> collections 
+ * through the {@link Clusterer} interface.
+ * <p>
+ * A specific clustering engine can be created with the {@link #setClusterer(String)} method
+ * which will use a factory to create it by name.  The cuts of the {@link Clusterer}
+ * can be set generically with the {@link #setCuts(double[])} method.  
+ * 
  * @author Jeremy McCormick <[log in to unmask]>
  */
 public class ClusterDriver extends Driver {
@@ -28,28 +37,35 @@
     protected boolean skipNoClusterEvents = false;
     protected boolean writeClusterCollection = true;
     protected boolean storeHits = true;
+    protected double[] cuts;
+    protected Logger logger = LogUtil.create(ClusterDriver.class, new BasicFormatter(ClusterDriver.class.getSimpleName()));
     
     protected ClusterDriver() {
+        logger.config("initializing");
     }
     
     public void setEcalName(String ecalName) {
         this.ecalName = ecalName;
     }
     
-    public void setOutputClusterCollectionName(String outputClusterCollectionName) {
+    public void setOutputClusterCollectionName(String outputClusterCollectionName) {        
         this.outputClusterCollectionName = outputClusterCollectionName;
+        this.getLogger().config("outputClusterCollectionName = " + this.outputClusterCollectionName);
     }
     
     public void setInputHitCollectionName(String inputHitCollectionName) {
         this.inputHitCollectionName = inputHitCollectionName;
+        this.getLogger().config("inputClusterCollectionName = " + this.inputHitCollectionName);
     }
     
     public void setSkipNoClusterEvents(boolean skipNoClusterEvents) {
-        this.skipNoClusterEvents = skipNoClusterEvents;
+        this.skipNoClusterEvents = skipNoClusterEvents;       
+        this.getLogger().config("skipNoClusterEvents = " + this.skipNoClusterEvents);
     }
     
     public void setWriteClusterCollection(boolean writeClusterCollection) {
         this.writeClusterCollection = writeClusterCollection;
+        this.getLogger().config("writeClusterCollection = " + this.writeClusterCollection);
     }
     
     public void setRaiseErrorNoHitCollection(boolean raiseErrorNoHitCollection) {
@@ -72,7 +88,12 @@
         this.createEmptyClusterCollection = createEmptyClusterCollection;
     }
     
+    public void setCuts(double[] cuts) {
+        this.cuts = cuts;
+    }
+    
     public void detectorChanged(Detector detector) {
+        logger.fine("detectorChanged");
         Subdetector subdetector = detector.getSubdetector(ecalName);
         if (subdetector == null) {
             throw new RuntimeException("There is no subdetector called " + ecalName + " in the detector.");
@@ -84,45 +105,62 @@
     }
     
     public void startOfData() {
+        logger.fine("startOfData");
         if (this.clusterer == null) {
             throw new RuntimeException("The clusterer was never initialized.");
         }
-        if (this.clusterer instanceof AbstractClusterer) {
-            ((AbstractClusterer)clusterer).setEcalSubdetector(ecal);
-        }
+        if (this.cuts != null) {
+            logger.config("setting cuts on clusterer");
+            this.clusterer.setCuts(cuts);
+            for (int cutIndex = 0; cutIndex < clusterer.getCuts().length; cutIndex++) {
+                logger.config("  " + this.clusterer.getCutNames()[cutIndex] + " = " + this.clusterer.getCut(cutIndex));
+            }            
+        } 
+        logger.config("initializing clusterer");
+        this.clusterer.initialize();
     }
     
     /**
      * This method implements the default clustering procedure based on input parameters.
      */
     public void process(EventHeader event) {
+        this.getLogger().fine("processing event #" + event.getEventNumber());
         if (event.hasCollection(CalorimeterHit.class, inputHitCollectionName)) {       
             List<CalorimeterHit> hits = event.get(CalorimeterHit.class, inputHitCollectionName);
-            List<Cluster> clusters = clusterer.createClusters(hits);
+            this.getLogger().fine("Input hit collection " + inputHitCollectionName + " has " + hits.size() + " hits.");
+            List<Cluster> clusters = clusterer.createClusters(event, hits);
             if (clusters == null) {
-                throw new RuntimeException("The clusterer returned a null pointer.");
+                throw new RuntimeException("The clusterer returned null from its createClusters method.");
             }
             if (clusters.isEmpty() && this.skipNoClusterEvents) {
+                logger.finer("Skipping event with no clusters.");
                 throw new NextEventException();
             }
             if (event.hasCollection(Cluster.class, this.outputClusterCollectionName)) {
-                throw new RuntimeException("There is already a cluster collection called " + this.outputClusterCollectionName);
+                this.getLogger().severe("There is already a cluster collection called " + this.outputClusterCollectionName);
+                throw new RuntimeException("Cluster collection already exists in event.");
             }
             int flags = 0;
             if (this.storeHits) {
                 flags = 1 << LCIOConstants.CLBIT_HITS;
             }
             if (!clusters.isEmpty() || this.createEmptyClusterCollection) {
+                logger.finer("writing " + clusters.size() + " clusters to collection " + outputClusterCollectionName);
                 event.put(outputClusterCollectionName, clusters, Cluster.class, flags);
                 if (!this.writeClusterCollection) {
+                    logger.finer("Collection is set to transient and will not be persisted.");
                     event.getMetaData(clusters).setTransient(true);
                 }
             }
         } else {
-            this.getLogger().warning("The input hit collection " + this.inputHitCollectionName + " is missing from the event.");
+            this.getLogger().severe("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.");
+                throw new RuntimeException("The expected input hit collection is missing from the event.");
             }
         }
     }
+    
+    public Logger getLogger() {
+       return logger;
+    }
 }

Modified: 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	(original)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClusterUtilities.java	Tue Dec 16 15:15:39 2014
@@ -1,5 +1,6 @@
 package org.hps.recon.ecal.cluster;
 
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -7,13 +8,21 @@
 import java.util.Set;
 
 import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.Cluster;
+import org.lcsim.event.base.BaseCluster;
 import org.lcsim.geometry.subdetector.HPSEcal3;
 
+/**
+ * This is a set of simple clustering utility methods.
+ */
 public final class ClusterUtilities {
     
     private ClusterUtilities() {        
     }
 
+    /**
+     * Create a map of IDs to their hits.
+     */
     public static Map<Long, CalorimeterHit> createHitMap(List<CalorimeterHit> hits) {
         Map<Long, CalorimeterHit> hitMap = new LinkedHashMap<Long, CalorimeterHit>();
         for (CalorimeterHit hit : hits) {
@@ -23,7 +32,7 @@
     }
     
     /**
-     * Given a hit, find its list of neighboring crystals that have hits and return their IDs.
+     * Given a hit, find the list of neighboring crystal IDs that also have hits.
      * @param hit The input hit.
      * @param hitMap The hit map with all the collection's hits.
      * @return The set of neighboring hit IDs.
@@ -38,4 +47,30 @@
         }
         return neighborHitIDs;
     }
+    
+    /**
+     * Create a basic cluster from a list of hits.
+     * @param clusterHits The list of hits.
+     * @return The basic cluster.
+     */
+    protected static Cluster createBasicCluster(List<CalorimeterHit> clusterHits) {
+        BaseCluster cluster = new BaseCluster();
+        double totalEnergy = 0;
+        for (CalorimeterHit clusterHit : clusterHits) {
+            cluster.addHit(clusterHit);
+            totalEnergy += clusterHit.getCorrectedEnergy();
+        }
+        cluster.setEnergy(totalEnergy);        
+        return cluster;
+    }
+    
+    /**
+     * Compare CalorimeterHit objects by their energy.
+     */
+    public static class HitEnergyComparator implements Comparator<CalorimeterHit> {
+        @Override
+        public int compare(CalorimeterHit o1, CalorimeterHit o2) {
+            return Double.compare(o1.getCorrectedEnergy(), o2.getCorrectedEnergy());
+        }
+    }    
 }

Modified: 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	(original)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/Clusterer.java	Tue Dec 16 15:15:39 2014
@@ -2,11 +2,85 @@
 
 import java.util.List;
 
+import org.lcsim.conditions.ConditionsListener;
 import org.lcsim.event.CalorimeterHit;
 import org.lcsim.event.Cluster;
-import org.lcsim.geometry.subdetector.HPSEcal3;
+import org.lcsim.event.EventHeader;
 
-public interface Clusterer {
+/**
+ * This is an interface for creating clusters and providing cut values
+ * to the clustering algorithms in a generic fashion.
+ */
+public interface Clusterer extends ConditionsListener {
 
-    List<Cluster> createClusters(List<CalorimeterHit> hits);
+    /**
+     * Create a list of output clusters from input hits.
+     * @param event The current LCSim event.
+     * @param hits The list of hits.
+     * @return The output clusters.
+     */
+    List<Cluster> createClusters(EventHeader event, List<CalorimeterHit> hits);
+    
+    /**
+     * Perform start of job intialization on this object.
+     */
+    void initialize();
+    
+    /**
+     * Get the list of numerical cuts.
+     * @return The list of numerical cuts.
+     */
+    double[] getCuts();
+    
+    /**
+     * Get the default cut values.
+     * @return The default cut values.
+     */
+    double[] getDefaultCuts();
+    
+    /**
+     * True if algorithm is using its default cuts.
+     * @return True if using the default cuts.
+     */
+    boolean isDefaultCuts();
+    
+    /**
+     * Set numerical cuts array.
+     * @param cuts The numerical cuts.
+     */
+    void setCuts(double[] cuts);
+                 
+    /**
+     * Get a cut value by its index.
+     * @param index The index of the cut.
+     * @return The cut value at index.
+     */
+    double getCut(int index);
+    
+    /**
+     * Get a cut value by name.
+     * @param name The name of the cut.
+     * @return The named cut.
+     */
+    double getCut(String name);
+    
+    /**
+     * Set a cut value by name.
+     * @param name The name of the cut.
+     * @param value The value of the cut.
+     */
+    void setCut(String name, double value);
+    
+    /**
+     * Set a cut value by index.
+     * @param index The index of the cut.
+     * @param value The value of the cut.
+     */
+    void setCut(int index, double value);
+    
+    /**
+     * Get the names of the cuts.
+     * @return The names of the cuts.
+     */
+    String[] getCutNames();       
 }

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	Tue Dec 16 15:15:39 2014
@@ -1,18 +1,49 @@
 package org.hps.recon.ecal.cluster;
 
+import org.lcsim.conditions.ConditionsListener;
+import org.lcsim.conditions.ConditionsManager;
+
+/**
+ * This is a convenience class for creating different kinds of clustering algorithms.
+ * @author Jeremy McCormick <[log in to unmask]>
+ */
 public final class ClustererFactory {
     
     private ClustererFactory() {        
     }
     
+    /**
+     * Create a clustering algorithm with a set of cuts.
+     * @param name The name of the clustering algorithm.
+     * @param cuts The set of cuts (can be null).
+     * @return The clustering algorithm.
+     * @throw IllegalArgumentException if there is no Clusterer found with name.
+     */
+    public static Clusterer create(String name, double[] cuts) {
+        Clusterer clusterer;
+        System.out.println("simple name:" + LegacyClusterer.class.getSimpleName());
+        if (LegacyClusterer.class.getSimpleName().equals(name)) {            
+            clusterer = new LegacyClusterer();
+        } else if (SimpleClasInnerCalClusterer.class.getSimpleName().equals(name)) {
+            clusterer = new SimpleClasInnerCalClusterer();
+        } else {
+            throw new IllegalArgumentException("Unknown clusterer type: " + name);
+        }
+        if (clusterer instanceof ConditionsListener) {
+            ConditionsManager.defaultInstance().addConditionsListener((ConditionsListener) clusterer);
+        }
+        if (cuts != null) {
+            clusterer.setCuts(cuts);
+        }
+        return clusterer;
+    }
+    
+    /**
+     * Create a clustering algorithm with default cut values.
+     * @param name The name of the clustering algorithm.
+     * @return The clustering algorithm.
+     */
     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);
-        }
+        return create(name, null);
     }
-
 }

Modified: 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	(original)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/LegacyClusterer.java	Tue Dec 16 15:15:39 2014
@@ -9,36 +9,35 @@
 import org.hps.recon.ecal.HPSEcalCluster;
 import org.lcsim.event.CalorimeterHit;
 import org.lcsim.event.Cluster;
+import org.lcsim.event.EventHeader;
 
 /**
- * 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.
+ * <p>
+ * This Driver creates clusters from a CalorimeterHit input collection.
+ * <p>
+ * The clustering algorithm is implemented according to the description in pages 83 and 84 of the 
+ * <a href="https://confluence.slac.stanford.edu/download/attachments/86676777/HPSProposal-FINAL_Rev2.pdf">HPS Proposal document</a>.
+ * <p>
+ * This is a simple algorithm that is obsolete!  The current IC or hardware algorithm clustering algorithms should generally be used instead.
  *
  * @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;
-        }
+    
+    double minClusterSeedEnergy;
+    double minHitEnergy;
+    
+    LegacyClusterer() {
+        super(new String[] { "minClusterSeedEnergy", "minHitEnergy" }, new double[] { 0.05 * ECalUtils.GeV, 0.03 * ECalUtils.GeV });
     }
     
-    public List<Cluster> createClusters(List<CalorimeterHit> hits) {
+    public void initialize() {
+        minClusterSeedEnergy = this.getCut("minClusterSeedEnergy");
+        minHitEnergy = this.getCut("minHitEnergy");
+    }
+                 
+    public List<Cluster> createClusters(EventHeader event, List<CalorimeterHit> hits) {
 
         Map<Long, CalorimeterHit> hitMap = ClusterUtilities.createHitMap(hits);
         
@@ -48,7 +47,7 @@
         // Loop over ECal hits to find cluster seeds.
         for (CalorimeterHit hit : hitMap.values()) {
             // Cut on min seed E.
-            if (hit.getRawEnergy() < minimumClusterSeedEnergy) {
+            if (hit.getRawEnergy() < minClusterSeedEnergy) {
                 continue;
             }
 
@@ -74,7 +73,7 @@
                     }
 
                     // Add to cluster if above min E.
-                    if (neighborHit.getRawEnergy() >= minimumHitEnergy) {
+                    if (neighborHit.getRawEnergy() >= minHitEnergy) {
                         neighborHits.add(neighborHit);
                     }
                 }

Copied: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleClasInnerCalClusterer.java (from r1759, 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	(original)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/SimpleClasInnerCalClusterer.java	Tue Dec 16 15:15:39 2014
@@ -2,7 +2,6 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -10,98 +9,83 @@
 import org.hps.recon.ecal.HPSEcalCluster;
 import org.lcsim.event.CalorimeterHit;
 import org.lcsim.event.Cluster;
+import org.lcsim.event.EventHeader;
 
 /**
- * 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.
+ * <p>
+ * This clustering algorithm creates clusters from an input CalorimeterHit collection.
+ * <p>
+ * It uses the basic Inner Calorimeter (IC) clustering algorithm as described in 
+ * <a href="https://misportal.jlab.org/ul/Physics/Hall-B/clas/viewFile.cfm/2005-001.pdf?documentId=6">CLAS Note 2004-040</a>.
+ * <p> 
+ * Hits are assigned to a cluster with the largest seed hit energy.  Time information is not used, and multiple hits in the same 
+ * crystal are not handled correctly so an exception is throw if this occurs.  An optional cut can be applied to discard hits
+ * with a time that is too far from t0.
  *
  * @author Holly Szumila-Vance <[log in to unmask]>
  * @author Sho Uemura <[log in to unmask]>
- *
+ * @author Jeremy McCormick <[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;
+public class SimpleClasInnerCalClusterer extends AbstractClusterer {
+    
+    double minEnergy;
+    double minTime;
+    double timeWindow;
+    boolean timeCut;
 
     /**
-     * Minimum energy for a hit to be used in a cluster. Default of 0.001 GeV..
-     * @param Emin
+     * Initialize the algorithm with default cuts.
      */
-    public void setEmin(double Emin) {
-        this.Emin = Emin;
+    public SimpleClasInnerCalClusterer() {
+        super(new String[] { "minEnergy", "minTime", "timeWindow", "timeCut" }, new double[] { 0., 0.001, 0.0, 20.0 });
     }
 
-    /**
-     * Apply time cuts to hits. Defaults to false.
-     * @param timeCut
-     */
-    public void setTimeCut(boolean timeCut) {
-        this.timeCut = timeCut;
+    public void initialize() {
+        // Setup class variables from cuts.
+        timeCut = (this.getCut("timeCut") == 1.0);
+        minEnergy = this.getCut("minEnergy");
+        minTime = this.getCut("minTime");
+        timeWindow = this.getCut("timeWindow");
     }
 
-    /**
-     * 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) {
+    public List<Cluster> createClusters(EventHeader event, List<CalorimeterHit> hitCollection) {
 
         // 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) {
+        // Create a Calorimeter hit list in each event, then sort with highest energy first
+        ArrayList<CalorimeterHit> sortedHitList = new ArrayList<CalorimeterHit>(hitCollection.size());
+        for (CalorimeterHit h : hitCollection) {
+            // reject hits below the energy cut
+            if (h.getCorrectedEnergy() < this.minEnergy) {
                 continue;
             }
-            //if time cut is being used, reject hits outside the time window
+            // 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
+        // sort the list, highest energy first
+        Collections.sort(sortedHitList, Collections.reverseOrder(new CalorimeterHit.CalorimeterHitEnergyComparator()));
+
+        // map from seed hit to cluster
         Map<CalorimeterHit, HPSEcalCluster> seedToCluster = new HashMap<CalorimeterHit, HPSEcalCluster>();
 
-        //Quick Map to access hits from cell IDs
+        // 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 from each hit to its cluster seed
         Map<CalorimeterHit, CalorimeterHit> hitToSeed = new HashMap<CalorimeterHit, CalorimeterHit>();
 
-        //Fill Map with cell ID and hit
+        // 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");
-            //}
+            if (idToHit.containsKey(hit.getCellID())) {
+                //System.out.println(this.getName() + ": multiple CalorimeterHits in same crystal");
+                // Make this an error for now.
+                throw new RuntimeException("Multiple CalorimeterHits found in same crystal.");
+            }
             idToHit.put(hit.getCellID(), hit);
         }
 
@@ -121,7 +105,7 @@
                     }
                 }
             }
-            if (biggestSeed == null) { //if no neighbors had more energy than this hit, this hit is a seed
+            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);
@@ -131,7 +115,7 @@
             }
         }
 
-        //add all hits to clusters
+        // add all hits to clusters
         for (CalorimeterHit hit : sortedHitList) {
             CalorimeterHit seed = hitToSeed.get(hit);
             HPSEcalCluster cluster = seedToCluster.get(seed);
@@ -140,12 +124,4 @@
 
         return clusters;
     }
-
-    private class EnergyComparator implements Comparator<CalorimeterHit> {
-
-        @Override
-        public int compare(CalorimeterHit o1, CalorimeterHit o2) {
-            return Double.compare(o1.getCorrectedEnergy(), o2.getCorrectedEnergy());
-        }
-    }
 }