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