Print

Print


Author: [log in to unmask]
Date: Mon Jan  5 19:17:08 2015
New Revision: 1878

Log:
Port GTPOnlineClusterer Driver to new clustering package.

Added:
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPOnlineClusterer.java
Modified:
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ClustererFactory.java

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	Mon Jan  5 19:17:08 2015
@@ -13,10 +13,11 @@
  * this class implements the {@link Clusterer} interface and will throw an error if it does not.
  * 
  * @see Clusterer
- * @see ReconClusterer
  * @see DualThresholdCosmicClusterer
+ * @see GTPOnlineClusterer
  * @see LegacyClusterer 
  * @see NearestNeighborClusterer
+ * @see ReconClusterer
  * @see SimpleReconClusterer
  * @see SimpleCosmicClusterer
  * 
@@ -51,6 +52,8 @@
             clusterer = new DualThresholdCosmicClusterer();
         } else if (SimpleCosmicClusterer.class.getSimpleName().equals(name)) {
             clusterer = new SimpleCosmicClusterer();
+        } else if (GTPOnlineClusterer.class.getSimpleName().equals(name)) {
+            clusterer = new GTPOnlineClusterer();
         } 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/GTPOnlineClusterer.java
 =============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPOnlineClusterer.java	(added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/GTPOnlineClusterer.java	Mon Jan  5 19:17:08 2015
@@ -0,0 +1,357 @@
+package org.hps.recon.ecal.cluster;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.hps.recon.ecal.HPSEcalCluster;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.Cluster;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.base.BaseCluster;
+
+/**
+ * Class <code>GTPOnlineClusterer</code> is an implementation of the
+ * GTP clustering algorithm for EVIO readout data for use in either
+ * online reconstruction/diagnostics or for general analysis of EVIO
+ * readout data.<br/>
+ * <br/>
+ * The GTP algorithm searches the set of hits in a readout event and
+ * compares them to select those that are a maximum in their 3x3 window
+ * and across a period of time that can be set. Hits that are maxima
+ * are declared "cluster seeds" and a cluster is created from them.
+ * All hits within the clustering window of the seed time are then
+ * added to the cluster and it is written to the event stream.<br/>
+ * <br/>
+ * The GTP algorithm uses three time windows. THe verification window
+ * is the time around the seed hit in which it is required to have more
+ * energy than any other hits in the 3x3 spatial window surrounding it.
+ * This is always symmetric. The other two windows combined define the
+ * clustering window. The clustering window is composed of a window
+ * before and a window after the seed time. These may be defined using
+ * different values. The window after should be as long or longer than
+ * the window before to make physical sense. The verification window is
+ * then defined as the larger of the two constituents of the clustering
+ * window. This is required for clustering to be consistent.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+// TODO: Handle time window arguments.
+public class GTPOnlineClusterer extends AbstractClusterer {
+
+    // The size of the temporal window in nanoseconds. By default,
+    // this is 1 clock-cycle before and 3 clock-cycles after.
+    private double timeBefore = 4;
+    private double timeAfter = 12;
+    private double timeWindow = 12;
+
+    // Cluster formation energy thresholds. Currently, the hardware
+    // only supports a lower bound seed energy. Units are in GeV.
+    private double seedThreshold = 0.050;
+
+    // Internal variables.
+    private boolean verbose = false;
+    
+    public GTPOnlineClusterer() {
+        super(new String[] { "seedThreshold" }, 
+                new double[] { 0.050 });
+    }
+
+    public void initialize() {
+        seedThreshold = getCuts().getValue("seedThreshold");
+    }
+
+    /**
+     * Reads in hits and processes them into clusters as per the GTP
+     * clustering algorithm implemented in the hardware.
+     * @param event - The object containing event data.
+     */
+    @Override
+    public List<Cluster> createClusters(EventHeader event, List<CalorimeterHit> hitList) {
+
+        // Track the valid clusters.
+        List<Cluster> clusterList = new ArrayList<Cluster>();
+
+        // VERBOSE :: Indicate whether the event has hits.
+        //if(verbose) { System.out.printf("Event %7d :: Has hits [%5b]%n", event.getEventNumber(), hasHits); }
+
+        // Sort the hits by time in reverse order.
+        Collections.sort(hitList, new Comparator<CalorimeterHit>() {
+            @Override
+            public int compare(CalorimeterHit firstHit, CalorimeterHit secondHit) {
+                return Double.compare(secondHit.getTime(), firstHit.getTime());
+            }
+        }); 
+
+        // VERBOSE :: Print the hit information.
+        if(verbose) { 
+            for(CalorimeterHit hit : hitList) {
+                int ix = hit.getIdentifierFieldValue("ix");
+                int iy = hit.getIdentifierFieldValue("iy");
+                double energy = hit.getCorrectedEnergy();
+                double time = hit.getTime();
+
+                System.out.printf("\tHit --> %.3f GeV at (%3d, %3d) and at t = %.2f%n", energy, ix, iy, time);
+            }
+        }
+
+        // A seed hit is a hit that is the largest both within its
+        // spatial range (+/- 1 in the ix and iy direction) and
+        // within a certain temporal window. If a hit is a seed, all
+        // hits within the 3x3 spatial range around it that are also
+        // within the temporal window are considered part of the
+        // cluster.
+
+        // Iterate over each hit and see if it qualifies as a seed hit.
+        seedLoop:
+            for(CalorimeterHit seed : hitList) {
+                // Check whether the potential seed passes the seed
+                // energy cut.
+                if(seed.getCorrectedEnergy() < seedThreshold) {
+                    continue seedLoop;
+                }
+
+                // Create a cluster for the potential seed.
+                BaseCluster protoCluster = new HPSEcalCluster(); // FIXME: Should be changed to BaseCluster but needs prop calculations for now.
+                protoCluster.addHit(seed);
+
+                // Iterate over the other hits and if the are within
+                // the clustering spatiotemporal window, compare their
+                // energies.
+                for(CalorimeterHit hit : hitList) {
+                    // Do not perform the comparison if the hit is the
+                    // current potential seed.
+                    if (hit != seed) {
+                        // Check if the hit is within the spatiotemporal
+                        // clustering window.
+                        if (withinTimeVerificationWindow(seed, hit) && withinSpatialWindow(seed, hit)) {
+                            // Check if the hit invalidates the potential
+                            // seed.
+                            if (isValidSeed(seed, hit)) {
+                                // Make sure that the hit is also within
+                                // the hit add window; this may not be
+                                // the same as the verification window
+                                // if the asymmetric window is active.
+                                if(withinTimeClusteringWindow(seed, hit)) {
+                                    protoCluster.addHit(hit);
+                                }
+                            }
+
+                            // If it is not, then skip the rest of the
+                            // loop; the potential seed is not really
+                            // a seed.
+                            else { continue seedLoop; }
+                        }
+                    }
+                }
+
+                // If this point is reached, then the seed was not
+                // invalidated by any of the other hits and is really
+                // a cluster center. Add the cluster to the list.
+                clusterList.add(protoCluster);
+            }
+
+        // VERBOSE :: Print out all the clusters in the event.
+        if(verbose) { 
+            for(Cluster cluster : clusterList) {
+                CalorimeterHit seedHit = cluster.getCalorimeterHits().get(0);
+                int ix = seedHit.getIdentifierFieldValue("ix");
+                int iy = seedHit.getIdentifierFieldValue("iy");
+                double energy = cluster.getEnergy();
+                double time = seedHit.getTime();
+
+                System.out.printf("\tCluster --> %.3f GeV at (%3d, %3d) and at t = %.2f%n", energy, ix, iy, time);
+
+                for(CalorimeterHit hit : cluster.getCalorimeterHits()) {
+                    int hix = hit.getIdentifierFieldValue("ix");
+                    int hiy = hit.getIdentifierFieldValue("iy");
+                    double henergy = hit.getCorrectedEnergy();
+                    double htime = hit.getTime();
+                    System.out.printf("\t\tCompHit --> %.3f GeV at (%3d, %3d) and at t = %.2f%n", henergy, hix, hiy, htime);
+                }
+            }
+        }
+
+
+        // VERBOSE :: Print a new line.
+        if(verbose) { System.out.println(); }
+
+        return clusterList;
+    }
+
+    /**
+     * Checks whether the hit <code>hit</code> keeps the hit <code>seed
+     * </code> from meeting the criteria for being a seed hit. Note that
+     * this does not check to see if the two hits are within the valid
+     * spatiotemporal window of one another.
+     * @param seed - The potential seed hit.
+     * @param hit - The hit to compare with the seed.
+     * @return Returns <code>true</code> if either the two hits are the
+     * same hit or if the hit does not invalidate the potential seed.
+     * Returns <code>false</code> otherwise.
+     */
+    private boolean isValidSeed(CalorimeterHit seed, CalorimeterHit hit) {
+        // Get the hit and seed energies.
+        double henergy = hit.getCorrectedEnergy();
+        double senergy = seed.getCorrectedEnergy();
+
+        // If the hit energy is less than the seed, the seed is valid.
+        if(henergy < senergy) {
+            return true;
+        }
+
+        // If the hit energy is the same as the seed energy, spatial
+        // comparisons are used to ensure the uniqueness of the seed.
+        if(henergy == senergy) {
+            // Get the x-indices of the hits.
+            int six = seed.getIdentifierFieldValue("ix");
+            int hix = hit.getIdentifierFieldValue("ix");
+
+            // The hit closest to the electron-side of the detector
+            // is considered the seed.
+            if(six < hix) { return true; }
+            else if(six > hix) { return false; }
+
+            // If both hits are at the same x-index, compare how close
+            // they are to the beam gap.
+            else {
+                // Get the y-indices. The absolute values are used
+                // because closeness to iy = 0 represents closeness
+                // to the beam gap.
+                int siy = Math.abs(seed.getIdentifierFieldValue("iy"));
+                int hiy = Math.abs(seed.getIdentifierFieldValue("iy"));
+
+                // If the seed is closer, it is valid.
+                if(siy < hiy) { return true; }
+                else if(siy > hiy) { return false; }
+
+                // If the y-index is the same, these are the same hit.
+                // This case shouldn't really ever happen, but for the
+                // compiler's sake, it returns true. A hit can not render
+                // itself invalid for the purpose of being a seed.
+                else { return true; }
+            }
+        }
+
+        // Otherwise, the seed is invalid.
+        else { return false; }
+    }
+
+    /**
+     * Checks whether the hit <code>hit</code> falls within the spatial
+     * window of the hit <code>Seed</code>. This is defined as within
+     * 1 index of the seed's x-index and similarly for the seed's
+     * y-index. 
+     * @param seed - The seed hit.
+     * @param hit - The comparison hit.
+     * @return Returns <code>true</code> if either both hits are the
+     * the same hit or if the comparison hit is within 1 index of the
+     * seed's x-index and within 1 index of the seed's y-index. Returns
+     * <code>false</code> otherwise.
+     */
+    private boolean withinSpatialWindow(CalorimeterHit seed, CalorimeterHit hit) {
+        // Get the x-indices of each hit.
+        int six = seed.getIdentifierFieldValue("ix");
+        int hix = hit.getIdentifierFieldValue("ix");
+
+        // Check that the x indices are either the same or within a
+        // range of one of one another.
+        if((six == hix) || (six + 1 == hix) || (six - 1 == hix)) {
+            // Get the y-indices of each hit.
+            int siy = seed.getIdentifierFieldValue("iy");
+            int hiy = hit.getIdentifierFieldValue("iy");
+
+            // Ensure that the y-indices are either the same or are
+            // within one of one another.
+            return (siy == hiy) || (siy + 1 == hiy) || (siy - 1 == hiy);
+        }
+
+        // If the x-index comparison fails, return false.
+        return false;
+    }
+
+    /**
+     * Checks whether the hit <code>hit</code> is within the temporal
+     * window of the hit <code>seed</code> for the purpose of seed
+     * verification.
+     * @param seed - The seed hit.
+     * @param hit - The comparison hit.
+     * @return Returns <code>true</code> if the comparison hit is within
+     * the temporal window of the seed hit and <code>false</code>
+     * otherwise.
+     */
+    private boolean withinTimeVerificationWindow(CalorimeterHit seed, CalorimeterHit hit) {
+        // If the hit is within the hit time window, it is valid.
+        if(Math.abs(seed.getTime() - hit.getTime()) <= timeWindow) {
+            return true;
+        }
+
+        // Otherwise, they are not.
+        else { return false; }
+    }
+
+    /**
+     * Checks whether the hit <code>hit</code> is within the temporal
+     * window of the hit <code>seed</code> for the purpose of adding
+     * a hit to a cluster.
+     * @param seed - The seed hit.
+     * @param hit - The comparison hit.
+     * @return Returns <code>true</code> if the comparison hit is within
+     * the temporal window of the seed hit and <code>false</code>
+     * otherwise.
+     */
+    private boolean withinTimeClusteringWindow(CalorimeterHit seed, CalorimeterHit hit) {
+        // Get the hit time and seed time.
+        double hitTime = hit.getTime();
+        double seedTime = seed.getTime();
+
+        // If the hit is before the seed, use the before window.
+        if(hitTime < seedTime) {
+            return (seedTime - hitTime) <= timeBefore;
+        }
+
+        // If the hit occurs after the seed, use the after window.
+        else if(hitTime > seedTime) {
+            return (hitTime - seedTime) <= timeAfter;
+        }
+
+        // If the times are the same, the are within the window.
+        if(hitTime == seedTime) { return true; }
+
+        // Otherwise, one or both times is undefined and should not be
+        // treated as within time.
+        else { return false; }
+    }
+ 
+    /**
+     * Sets the minimum energy a hit must have before it will be
+     * considered for cluster formation.
+     * @param seedThreshold - The seed threshold in GeV.
+     */
+    public void setSeedLowThreshold(double seedThreshold) {
+        this.seedThreshold = seedThreshold;
+    }
+
+    /**
+     * Sets the number of clock-cycles to include in the clustering
+     * window before the seed hit. One clock-cycle is four nanoseconds.
+     * @param cyclesBefore - The length of the clustering window before
+     * the seed hit in clock cycles.
+     */
+    public void setWindowBefore(int cyclesBefore) {
+        timeBefore = cyclesBefore * 4;
+        timeWindow = Math.max(timeBefore, timeAfter);
+    }
+
+    /**
+     * Sets the number of clock-cycles to include in the clustering
+     * window after the seed hit. One clock-cycle is four nanoseconds.
+     * @param cyclesAfter - The length of the clustering window after
+     * the seed hit in clock cycles.
+     */
+    public void setWindowAfter(int cyclesAfter) {
+        timeAfter = cyclesAfter * 4;
+        timeWindow = Math.max(timeBefore, timeAfter);
+    }
+}