Commit in java/sandbox/ecal-recon on MAIN
pom.xml+43added 346
src/main/java/org/hps/recon/ecal/CTPEcalClusterer.java+377added 346
                                /ECalUtils.java+77added 346
                                /EcalClusterIC.java+372added 346
                                /EcalClusterer.java+171added 346
                                /EcalConditions.java+351added 346
                                /EcalConverterDriver.java+86added 346
                                /EcalCrystalFilter.java+1048added 346
                                /EcalEdepToTriggerConverterDriver.java+208added 346
                                /EcalRawConverter.java+117added 346
                                /EcalRawConverterDriver.java+165added 346
                                /EcalReadoutToTriggerConverterDriver.java+189added 346
                                /EcalTriggerFilterDriver.java+108added 346
                                /FADCConverterDriver.java+101added 346
                                /GTPEcalClusterer.java+365added 346
                                /HPSCalorimeterHit.java+72added 346
                                /HPSEcalCluster.java+422added 346
                                /HPSRawCalorimeterHit.java+60added 346
                                /TriggerData.java+111added 346
+4443
19 added files
New ecal-recon module in the sandbox.

java/sandbox/ecal-recon
pom.xml added at 346
--- java/sandbox/ecal-recon/pom.xml	                        (rev 0)
+++ java/sandbox/ecal-recon/pom.xml	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>hps-ecal-recon</artifactId>
+    <name>hps-ecal-recon</name>
+    <description>HPS ECAL reconstruction module</description>
+    
+    <parent>
+        <groupId>org.hps</groupId>
+        <artifactId>hps-parent</artifactId>
+        <relativePath>../parent/pom.xml</relativePath>
+        <version>3.0.2-SNAPSHOT</version>
+    </parent>
+    
+    <scm>
+        <url>http://java.freehep.org/svn/repos/hps/list/java/trunk/ecal-recon/</url>
+        <connection>scm:svn:svn://svn.freehep.org/hps/java/trunk/ecal-recon/</connection>
+        <developerConnection>scm:svn:svn://svn.freehep.org/hps/java/trunk/ecal-recon/</developerConnection>
+    </scm>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.lcsim</groupId>
+            <artifactId>lcsim-distribution</artifactId>
+            <version>${lcsimVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.hps</groupId>
+            <artifactId>hps-conditions</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hps</groupId>
+            <artifactId>hps-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math3</artifactId>
+            <version>3.2</version>
+        </dependency>
+    </dependencies>
+    
+</project>

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
CTPEcalClusterer.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/CTPEcalClusterer.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/CTPEcalClusterer.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,377 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.geometry.Detector;
+import org.lcsim.geometry.IDDecoder;
+import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap;
+import org.lcsim.geometry.subdetector.HPSEcal3;
+import org.lcsim.util.Driver;
+import org.lcsim.lcio.LCIOConstants;
+
+/**
+ * Creates clusters from CalorimeterHits in the HPSEcal detector.
+ *
+ * The clustering algorithm is from JLab Hall B 6 GeV DVCS Trigger Design doc.
+ *
+ * @author Jeremy McCormick <[log in to unmask]>
+ * @author Tim Nelson <[log in to unmask]>
+ * @author Sho Uemura <[log in to unmask]>
+ * @version $Id: CTPEcalClusterer.java,v 1.1 2013/02/25 22:39:24 meeg Exp $
+ */
+public class CTPEcalClusterer extends Driver {
+    // The calorimeter object.
+    HPSEcal3 ecal;
+    IDDecoder dec;
+    // The identification name for getting the calorimeter object.
+    String ecalName;
+    // The collection name for the calorimeter hits.
+    String ecalCollectionName;
+    // The collection name in which to store clusters.
+    String clusterCollectionName = "EcalClusters";
+    Set<Long> clusterCenters = null;
+    Map<Long, Double> hitSums = null;
+    Map<Long, CalorimeterHit> hitMap = null;
+    // Map of crystals to their neighbors.
+    NeighborMap neighborMap = null;
+    // The time period in which clusters may be formed. A negative value means that all hits
+    // will always be used in cluster finding, regardless of the time difference between them.
+    double clusterWindow = -1;
+    // The minimum energy needed for a hit to be considered.
+    double addEMin = 0;
+    
+    public CTPEcalClusterer() {
+    }
+    
+    public void setAddEMin(double addEMin) {
+        this.addEMin = addEMin;
+    }
+    
+    public void setClusterWindow(double clusterWindow) {
+        this.clusterWindow = clusterWindow;
+    }
+    
+    public void setClusterCollectionName(String clusterCollectionName) {
+        this.clusterCollectionName = clusterCollectionName;
+    }
+    
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+    
+    public void setEcalName(String ecalName) {
+        this.ecalName = ecalName;
+    }
+    
+    public void startOfData() {
+        // Make sure that there is a cluster collection name into which clusters may be placed.
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+        
+        // Make sure that there is a calorimeter detector.
+        if (ecalName == null) {
+            throw new RuntimeException("The parameter ecalName was not set!");
+        }
+    }
+    
+    @Override
+    public void detectorChanged(Detector detector) {
+        // Get the Subdetector.
+        ecal = (HPSEcal3) detector.getSubdetector(ecalName);
+        
+        // Get the decoder for the ECal IDs.
+        dec = ecal.getIDDecoder();
+        
+        // Cache reference to the neighbor map.
+        neighborMap = ecal.getNeighborMap();
+        
+        clusterCenters = new HashSet<Long>();
+        // Make set of valid cluster centers.
+        // Exclude edge crystals as good cluster centers.
+        for (Long cellID : neighborMap.keySet()) {
+            boolean isValidCenter = true;
+            Set<Long> neighbors = neighborMap.get(cellID);
+            for (Long neighborID : neighbors) {
+                Set<Long> neighborneighbors = new HashSet<Long>();
+                neighborneighbors.addAll(neighborMap.get(neighborID));
+                neighborneighbors.add(neighborID);
+                
+                if (neighborneighbors.containsAll(neighbors)) {
+                    isValidCenter = false;
+                    break;
+                }
+            }
+            if (isValidCenter) {
+                clusterCenters.add(cellID);
+            }
+        }
+    }
+    
+    public void process(EventHeader event) {
+        // Make sure that this event has calorimeter hits.
+        if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) {
+            // Get the list of calorimeter hits from the event.
+            List<CalorimeterHit> hits = event.get(CalorimeterHit.class, ecalCollectionName);
+            
+            // Define a list of clusters to be filled.
+            List<HPSEcalCluster> clusters;
+            
+            // If there is a cluster window, run the clsuter window code. A cluster window is a time
+            // period in nanoseconds during which hits can be applied to the same cluster.
+            if (clusterWindow >= 0) {
+                // Create priority queues. These are sorted by the time variable associated with each hit.
+                PriorityQueue<CalorimeterHit> futureHits = new PriorityQueue<CalorimeterHit>(10, new TimeComparator());
+                PriorityQueue<CalorimeterHit> pastHits = new PriorityQueue<CalorimeterHit>(10, new TimeComparator());
+                
+                // Initialize the cluster list variable.
+                clusters = new ArrayList<HPSEcalCluster>();
+                
+                // Populate the list of unprocessed hits with the calorimeter hits. These will then be sorted
+                // by time, from first to last, automatically by the priority queue.
+                for (CalorimeterHit hit : hits) {
+                    if (hit.getRawEnergy() > addEMin) {
+                        futureHits.add(hit);
+                    }
+                }
+                
+                // We process the unprocessed hits...
+                while (!futureHits.isEmpty()) {
+                    // Move the first occurring hit from the unprocessed list to the processed list.
+                    CalorimeterHit nextHit = futureHits.poll();
+                    pastHits.add(nextHit);
+                    
+                    // Add any hits that occurred at the same time as the hit we just added to the processed list.
+                    while (!futureHits.isEmpty() && futureHits.peek().getTime() == nextHit.getTime()) {
+                        pastHits.add(futureHits.poll());
+                    }
+                    
+                    // Remove hits that happened earlier than the cluster window period.
+                    while (pastHits.peek().getTime() < nextHit.getTime() - clusterWindow) {
+                        pastHits.poll();
+                    }
+                    
+                    // Calculate the cluster energy for each crystal. This should be the
+                    // total energy for the 3x3 crystal collection sorrounding the center
+                    // crystal.
+                    sumHits(pastHits);
+                    
+                    // Choose which crystal is the appropriate cluster crystal.
+                    clusters.addAll(createClusters());
+                }
+            // If there is no cluster window, then all the hits in the event are visible simultaneously.
+            } else {
+                // Calculate the cluster energy of each crystal.
+                sumHits(hits);
+                // Generate the clusters.
+                clusters = createClusters();
+            }
+            
+            // Put the cluster collection into the event.
+            int flag = 1 << LCIOConstants.CLBIT_HITS;
+            event.put(clusterCollectionName, clusters, HPSEcalCluster.class, flag);
+        }
+    }
+    
+    public void sumHits(Collection<CalorimeterHit> hits) {
+        // Store the latest hit on each crystal in a map for later reference in
+        // the clustering algorithm.
+        hitMap = new HashMap<Long, CalorimeterHit>();
+        // Store the cluster energy for each crystal. Cluster energy represents
+        // the total energy of the 3x3 crystal set.
+        hitSums = new HashMap<Long, Double>();
+        
+        // Loop over the active calorimeter hits to compute the cluster energies.
+        for (CalorimeterHit hit : hits) {
+            // Make a hit map for quick lookup by ID.
+            hitMap.put(hit.getCellID(), hit);
+            
+            // Get the cell ID for the current crystal's neighbors.
+            Set<Long> neighbors = neighborMap.get(hit.getCellID());
+            
+            // If there are no neighbors, something is rather wrong.
+            if (neighbors == null) {
+                throw new RuntimeException("Oops!  Set of neighbors is null!");
+            }
+            
+            // Store the energy of the current calorimeter hit.
+            Double hitSum;
+            
+            // We are only interested in this crystal's cluster energy if it is
+            // a valid cluster crystal. Edge crystals are not allowed to be clusters,
+            // so these are ignored.
+            if (clusterCenters.contains(hit.getCellID())) {
+                // Check if an energy has been assigned to the crystal.
+                hitSum = hitSums.get(hit.getCellID());
+                
+                // If not, then the crystal's cluster energy is equal to this hit's energy.
+                if (hitSum == null) {
+                    hitSums.put(hit.getCellID(), hit.getRawEnergy());
+                }
+                // Otherwise, add the energy of this hit to the total crystal cluster energy.
+                else {
+                    hitSums.put(hit.getCellID(), hitSum + hit.getRawEnergy());
+                }
+            }
+            
+            // Loop over neighbors to add the current hit's energy to the neighbor's
+            // cluster energy.
+            for (Long neighborId : neighbors) {
+                // If the crystal is not an edge crystal, ignore its hit energy.
+                if (!clusterCenters.contains(neighborId)) {
+                    continue;
+                }
+                
+                // Get the cluster energy of the neighboring crystals.
+                hitSum = hitSums.get(neighborId);
+                
+                // If the neighbor crystal has no cluster energy, then set the
+                // cluster energy to the current hit's energy.
+                if (hitSum == null) {
+                    hitSums.put(neighborId, hit.getRawEnergy());
+                }
+                // Otherwise, add the hit's energy to the neighbor's cluster energy.
+                else {
+                    hitSums.put(neighborId, hitSum + hit.getRawEnergy());
+                }
+            }
+        }
+    }
+    
+    public List<HPSEcalCluster> createClusters() {
+        // Create a list of clusters to be added to the event,
+        List<HPSEcalCluster> clusters = new ArrayList<HPSEcalCluster>();
+        
+        // We examine each crystal with a non-zero cluster energy.
+        for(Long possibleCluster : hitSums.keySet()) {
+            // Get the luster energy for the crystal this hit is assocaite with.
+            Double thisSum = hitSums.get(possibleCluster);
+            
+            // Get neighboring crystals' IDs.
+            Set<Long> neighbors = neighborMap.get(possibleCluster);
+            
+            // If there are no neighbors, throw an error.
+            if (neighbors == null) {
+                throw new RuntimeException("Oops!  Set of neighbors is null!");
+            }
+            
+            // Get the x/y position of the hit's associated crystal.
+            dec.setID(possibleCluster);
+            int x1 = dec.getValue("ix");
+            int y1 = dec.getValue("iy");
+            
+            // Store whether it is a valid cluster or not.
+            boolean isCluster = true;
+            
+            // Check to see if any of the crystal's neighbors preclude the current crystal
+            // from being a proper cluster. The cluster crystal should have the highest
+            // energy among its neighbors
+            for (Long neighborId : neighbors) {
+                // Get the x/y position of the neighbor's associated crystal.
+                dec.setID(neighborId);
+                int x2 = dec.getValue("ix");
+                int y2 = dec.getValue("iy");
+                
+                // If the neighbor's energy value does not exist, we don't need to perform
+                // any additional checks for this neighbor. A crystal with no energy can
+                // not be the center of a cluster.
+                Double neighborSum = hitSums.get(neighborId);
+                if (neighborSum == null) {
+                    continue;
+                }
+                
+                // If the neighbor's energy value is greater than this crystal's value,
+                // then this crystal is not the cluster and we may terminate the check.
+                if (neighborSum > thisSum) {
+                    isCluster = false;
+                    break;
+                }
+                // If the crystals each have the same energy, we choose the crystal that
+                // is closest to the electron side of the detector. If both crystals are
+                // equally close, we choose the crystal closest to the beam gap. If the
+                // neighbor fits these parameters better, this is not a crystal and we
+                // may skip any further checks.
+                else if (neighborSum.equals(thisSum) && (x1 > x2 || (x1 == x2 && Math.abs(y1) < Math.abs(y2)))) {
+                    isCluster = false;
+                    break;
+                }
+            }
+            
+            // If the crystal was not invalidated by the any of the neighboring crystals,
+            // then it is a cluster crystal and should be processed.
+            if (isCluster) {
+                // Make a list to store the hits that are part of this cluster.
+                List<CalorimeterHit> hits = new ArrayList<CalorimeterHit>();
+                
+                // Store the time at which the cluster occurred.
+                double clusterTime = Double.NEGATIVE_INFINITY;
+                
+                // Get the last hit on this crystal.
+                CalorimeterHit hit = hitMap.get(possibleCluster);
+                
+                // If the hit exists, add it to the list of associated hits.
+                if (hit != null) {
+                    hits.add(hit);
+                    
+                    // If the latest hit's time is later than the current cluster time,
+                    // set the cluster time to the latest hit's time.
+                    if (hit.getTime() > clusterTime) {
+                        clusterTime = hit.getTime();
+                    }
+                }
+                
+                // Add all of the neighboring crystals to the cluster, if they have a
+                // hit associated with them. Crystals with no hits are not actually part
+                // of a cluster.
+                for (Long neighborId : neighbors) {
+                    hit = hitMap.get(neighborId);
+                    if (hit != null) {
+                        hits.add(hit);
+                        if (hit.getTime() > clusterTime) {
+                            clusterTime = hit.getTime();
+                        }
+                    }
+                }
+                
+                // Generate a new cluster seed hit from the above results.
+                CalorimeterHit seedHit = new HPSCalorimeterHit(0.0, clusterTime, possibleCluster, hits.get(0).getType());
+                seedHit.setMetaData(hits.get(0).getMetaData());
+                
+                // Generate a new cluster from the seed hit.
+                HPSEcalCluster cluster = new HPSEcalCluster(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;
+            }
+        }
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
ECalUtils.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/ECalUtils.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/ECalUtils.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,77 @@
+package org.hps.recon.ecal;
+
+import org.lcsim.event.CalorimeterHit;
+
+/**
+ *
+ * @author Sho Uemura <[log in to unmask]>
+ * @version $Id: ECalUtils.java,v 1.1 2013/02/25 22:39:24 meeg Exp $
+ */
+public class ECalUtils {
+
+    public static final double GeV = 1.0;
+    public static final double MeV = 0.001;
+    //parameters for 2014 APDs and preamp
+    public static final double riseTime = 10.0; //10 pulse rise time in ns
+    public static final double fallTime = 17.0; //17 pulse fall time in ns
+    public static final double lightYield = 120. / MeV; // number of photons per GeV
+    public static final double quantumEff = 0.7;  // quantum efficiency of the APD
+    public static final double surfRatio = (10. * 10.) / (16 * 16); // surface ratio between APD and crystals
+    public static final double gainAPD = 150.; // Gain of the APD
+    public static final double elemCharge = 1.60217657e-19;
+    public static final double gainPreAmpl = 525e12; // Gain of the preamplifier in pC/pC, true value is higher but does not take into account losses
+    public static final int nBit = 12;  //number of bits used by the fADC to code a value
+    public static final double maxVolt = 2.0;   //maximum volt intput of the fADC
+    public static final double Req = 1.0 / 27.5; // equivalent resistance of the amplification chain
+    public static final double adcResolution = 2.0 / (Math.pow(2, nBit) - 1); //volts per ADC count
+    public static final double readoutGain = Req * lightYield * quantumEff * surfRatio * gainAPD * gainPreAmpl * elemCharge;// = 15.0545 volt-seconds/GeV
+    public static final double gainFactor = adcResolution / readoutGain;
+    public static final double ecalReadoutPeriod = 4.0; // readout period in ns, it is hardcoded in the public declaration of EcalReadoutDriver. 
+
+    /**
+     * Returns the quadrant which contains the ECal cluster
+     *
+     * @param ecalCluster : ECal cluster
+     * @return Quadrant number
+     */
+    public static int getQuadrant(HPSEcalCluster ecalCluster) {
+        return getQuadrant(ecalCluster.getSeedHit());
+    }
+
+    public static int getQuadrant(CalorimeterHit hit) {
+        int ix = hit.getIdentifierFieldValue("ix");
+        int iy = hit.getIdentifierFieldValue("iy");
+        return getQuadrant(ix, iy);
+    }
+
+    public static int getQuadrant(int x, int y) {
+        if (x > 0) {
+            if (y > 0) {
+                return 1;
+            } else {
+                return 4;
+            }
+        } else {
+            if (y > 0) {
+                return 2;
+            } else {
+                return 3;
+            }
+        }
+    }
+
+    public static int getHVGroup(int x, int y) {
+        int absy = Math.abs(y);
+        if (x > 0 || x <= -8) {
+            return (23 - Math.abs(x)) / 2 + 1;
+        } else {
+            if (x == -7 && absy == 5) {
+                return 8;
+            } else if (x >= -4) {
+                return 12 - Math.max(x + 4, absy - 2);
+            } else {
+                return 12 - Math.max(-5 - x, absy - 2);
+            }
+        }
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalClusterIC.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalClusterIC.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalClusterIC.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,372 @@
+package org.hps.recon.ecal;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.geometry.Detector;
+import org.lcsim.geometry.subdetector.HPSEcal3;
+import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap;
+import org.lcsim.lcio.LCIOConstants;
+import org.lcsim.util.Driver;
+
+/**
+ * This Driver creates clusters from the CalorimeterHits of an
+ * {@link org.lcsim.geometry.subdetectur.HPSEcal3} detector.
+ *
+ *
+ * @author Holly Szumila-Vance <[log in to unmask]>
+ *
+ */
+public class EcalClusterIC extends Driver {
+	FileWriter writeHits;
+    HPSEcal3 ecal;
+    String ecalCollectionName;
+    String ecalName = "Ecal";
+    String clusterCollectionName = "EcalClusters";
+    // Map of crystals to their neighbors.
+    NeighborMap neighborMap = null;
+    //Minimum energy that counts as hit
+    double Emin = 0.001;
+    
+
+    public EcalClusterIC() {
+    }
+
+    public void setClusterCollectionName(String clusterCollectionName) {
+        this.clusterCollectionName = clusterCollectionName;
+    }
+
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    public void setEcalName(String ecalName) {
+        this.ecalName = ecalName;
+    }
+
+    public void startOfData() {
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+
+        if (ecalName == null) {
+            throw new RuntimeException("The parameter ecalName was not set!");
+        }
+        
+    	try{
+    		writeHits = new FileWriter("cluster-hit-IC.txt");
+    		writeHits.write("");
+    	}
+    	catch(IOException e) {};
+    }
+
+    public void detectorChanged(Detector detector) {
+        // Get the Subdetector.
+        ecal = (HPSEcal3) detector.getSubdetector(ecalName);
+
+        // Cache ref to neighbor map.
+        neighborMap = ecal.getNeighborMap();
+    }
+
+    public void process(EventHeader event) {
+
+
+    	
+        if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) {
+            // Get the list of raw ECal hits.
+            List<CalorimeterHit> hits = event.get(CalorimeterHit.class, ecalCollectionName);
+
+            // Make a hit map for quick lookup by ID.
+            Map<Long, CalorimeterHit> hitMap = new HashMap<Long, CalorimeterHit>();
+            
+            for (CalorimeterHit hit : hits) {
+                hitMap.put(hit.getCellID(), hit);
+            }
+            
+            //System.out.println("Number of ECal hits: "+hitMap.size());
+            
+            // Put Cluster collection into event.
+            int flag = 1 << LCIOConstants.CLBIT_HITS;
+            try {
+
+            	event.put(clusterCollectionName, createClusters(hitMap), HPSEcalCluster.class, flag);
+            }
+            catch(IOException e) { }
+        }
+    }
+
+    public List<HPSEcalCluster> createClusters(Map<Long, CalorimeterHit> map) throws IOException {
+
+        // New Cluster list to be added to event.
+        List<HPSEcalCluster> clusters = new ArrayList<HPSEcalCluster>();
+             
+        //Create a Calorimeter hit list in each event, then sort with highest energy first
+        ArrayList<CalorimeterHit> chitList = new ArrayList<CalorimeterHit>(map.size());
+        for(CalorimeterHit h : map.values()) {
+        	if(h.getCorrectedEnergy()>Emin){
+        	chitList.add(h);}
+        
+        	Collections.sort(chitList, new EnergyComparator());}
+        
+        //New Seed list containing each local maximum energy hit
+        List<CalorimeterHit> seedHits = new ArrayList<CalorimeterHit>();
+
+      	//Create map to contain common hits for evaluation later, key is crystal and values are seed
+      	Map<CalorimeterHit, List<CalorimeterHit>> commonHits = new HashMap<CalorimeterHit, List<CalorimeterHit>>();
+      	
+      	//Created map to contain seeds with listed hits, key is crystal, and value is seed
+      	Map<CalorimeterHit, CalorimeterHit> clusterHits = new HashMap<CalorimeterHit, CalorimeterHit>();
+      	
+      	//Create map to contain the total energy of each cluster
+      	Map<CalorimeterHit, Double> seedEnergy = new HashMap<CalorimeterHit,Double>();
+      	
+      	//Quick Map to access hits from cell IDs
+      	Map<Long, CalorimeterHit> hitID = new HashMap<Long, CalorimeterHit>();
+      	
+      	//List to contain all hits already put into clusters
+      	List<CalorimeterHit> clusterHitList = new ArrayList<CalorimeterHit>();
+      	
+        //Fill Map with cell ID and hit
+      	for (CalorimeterHit hit : chitList){
+      		hitID.put(hit.getCellID(), hit);
+      	}
+        
+        for (CalorimeterHit hit : chitList) { 
+        Set<Long> neighbors = neighborMap.get(hit.getCellID()); //get all neighbors of hit
+        List<CalorimeterHit> neighborHits = new ArrayList<CalorimeterHit>();
+        for(Long neighbor : neighbors){
+        	if(hitID.containsKey(neighbor)){//map contains (neighbor's cell id, neighbor hit)
+        		neighborHits.add(hitID.get(neighbor));
+        	}
+        }
+        
+    	Collections.sort(neighborHits, new EnergyComparator());
+        
+        boolean highestE = true;
+        for(CalorimeterHit neighborHit : neighborHits){//check for seed hit
+        	if(hit.getCorrectedEnergy()>neighborHit.getCorrectedEnergy()){
+        		continue;
+        			}
+        	else {
+        		highestE = false;
+        		break;
+        	}	
+        }
+        
+        if (highestE == true){//seed hit, local maximum
+        	seedHits.add(hit);
+        	clusterHits.put(hit, hit);
+        	clusterHitList.add(hit);
+        }
+        
+        //not seed hit
+        else { 
+        	//builds immediate clusters around seed hits
+        	for(CalorimeterHit neighborHit : neighborHits){
+        		//if neighbor to seed, add to seed
+        		if (seedHits.contains(neighborHit)&&!clusterHits.containsKey(hit)){  
+					CalorimeterHit seed = clusterHits.get(neighborHit);
+        			clusterHits.put(hit, seed);
+        			clusterHitList.add(hit);
+        		}
+        		//if neighbor to two seeds, add to common hits
+        		else if (seedHits.contains(neighborHit)&&clusterHits.containsKey(hit)){
+        			CalorimeterHit prevSeed = clusterHits.get(neighborHit);
+        			CalorimeterHit currSeed = clusterHits.get(hit);
+        			List<CalorimeterHit> commonHitList = new ArrayList<CalorimeterHit>();
+        			commonHitList.add(prevSeed);
+        			commonHitList.add(currSeed);
+        			commonHits.put(hit, commonHitList);
+        		}
+        	}
+        }
+        }
+        
+        //loop over hit list, find neighbors, compare energies, add to list 
+        for(CalorimeterHit nHit : chitList){
+        	Set<Long> neighbors2 = neighborMap.get(nHit.getCellID()); //get all neighbors of hit
+            List<CalorimeterHit> neighborHits2 = new ArrayList<CalorimeterHit>();
+            for(Long neighbor : neighbors2){
+            	if(hitID.containsKey(neighbor)){//map contains (neighbor's cell id, neighbor hit)
+            		if(!clusterHitList.contains(hitID.get(neighbor))){
+            			neighborHits2.add(hitID.get(neighbor));
+            		}
+            	}
+            }
+            
+        	Collections.sort(neighborHits2, new EnergyComparator());
+        	for(CalorimeterHit neighbor : neighborHits2){
+        		if(neighbor.getCorrectedEnergy()<nHit.getCorrectedEnergy()){
+        			CalorimeterHit seed = clusterHits.get(nHit);
+					clusterHits.put(neighbor, seed);
+					clusterHitList.add(neighbor);
+        		}
+        		else{continue;}
+        	}
+        }
+        //loop over cluster hits, compare energies of neighbors with diff seeds, if min->add to commonHits
+        for (CalorimeterHit mHit : clusterHitList){
+        	//exclude seed hits as possible common hits
+        	if(clusterHits.get(mHit) != mHit){
+        		
+        		Set<Long> neighbors3 = neighborMap.get(mHit.getCellID()); //get all neighbors of hit
+                List<CalorimeterHit> neighborHits3 = new ArrayList<CalorimeterHit>();
+                for(Long neighbor : neighbors3){
+                	if(hitID.containsKey(neighbor)){//map contains (neighbor's cell id, neighbor hit)
+                		neighborHits3.add(hitID.get(neighbor));
+                		
+                	}
+                }
+                
+            	Collections.sort(neighborHits3, new EnergyComparator());
+            	
+            	CalorimeterHit compSeed = clusterHits.get(mHit);
+            	for(CalorimeterHit neighbor : neighborHits3){
+            		if(clusterHits.get(neighbor)!=compSeed){//borders clusters
+            			if(mHit.getCorrectedEnergy() < neighbor.getCorrectedEnergy()){
+            				//add to common hits, 
+            				List<CalorimeterHit> commonHitList = new ArrayList<CalorimeterHit>();
+                			commonHitList.add(compSeed);
+                			commonHitList.add(clusterHits.get(neighbor));
+                			commonHits.put(mHit, commonHitList);
+            			}	
+            		}
+            	}	
+        	}
+        }
+        
+        //remove common hits from cluster hits list
+        for(CalorimeterHit commHit : clusterHitList){
+        	if(clusterHitList.contains(commHit)&&commonHits.containsKey(commHit)){
+        		clusterHits.remove(commHit);
+        	}
+        	else{
+        		continue;
+        	}
+        }
+        
+
+        		       	
+        //Get energy of each cluster, excluding common hits
+        for(CalorimeterHit iSeed : seedHits){
+        seedEnergy.put(iSeed, 0.0);
+        }
+        //Putting total cluster energies excluding common hit energies into map with seed keys    
+        for (Map.Entry<CalorimeterHit, CalorimeterHit> entry : clusterHits.entrySet()) {
+        	CalorimeterHit eSeed = entry.getValue();
+        	double eEnergy = seedEnergy.get(eSeed);
+        	eEnergy += entry.getKey().getRawEnergy();
+        	seedEnergy.put(eSeed, eEnergy);
+        }
+        
+        //Distribute common hit energies with clusters
+        Map<CalorimeterHit, Double> seedEnergyTot = seedEnergy;
+        for (Map.Entry<CalorimeterHit, List<CalorimeterHit>> entry1 : commonHits.entrySet()) {
+        	CalorimeterHit commonCell = entry1.getKey();
+        	List<CalorimeterHit> commSeedList = entry1.getValue();
+        	CalorimeterHit seedA = commSeedList.get(0);
+        	CalorimeterHit seedB = commSeedList.get(1);
+        	double eFractionA = seedEnergy.get(seedA)/(seedEnergy.get(seedA)+seedEnergy.get(seedB));
+        	double eFractionB = seedEnergy.get(seedB)/(seedEnergy.get(seedA)+seedEnergy.get(seedB));
+        	double currEnergyA = seedEnergyTot.get(seedA);
+        	double currEnergyB = seedEnergyTot.get(seedB);
+        	currEnergyA += eFractionA*commonCell.getCorrectedEnergy();
+        	currEnergyB += eFractionB*commonCell.getCorrectedEnergy();
+        	
+        	seedEnergyTot.put(seedA, currEnergyA);
+        	seedEnergyTot.put(seedB, currEnergyB);
+        }
+                   
+       
+        //Do some system.out for number of crystals in each cluster, energy of each cluster
+        for (Map.Entry<CalorimeterHit, Double> entryEnergy : seedEnergyTot.entrySet()){
+    //    	System.out.println(entryEnergy.getKey().getCellID()+"\t"+entryEnergy.getKey().getCorrectedEnergy()+
+    //    			"\t"+entryEnergy.getValue());
+        }
+        
+   //     System.out.println("Number of clusters: "+seedHits.size());    
+
+        
+      	if (map.size()!=0){
+          	writeHits.append("Event"+"\t"+"1"+"\n");
+          	for (CalorimeterHit n : chitList){
+          		writeHits.append("EcalHit" + "\t" + n.getIdentifierFieldValue("ix") + "\t" + n.getIdentifierFieldValue("iy")
+          		+ "\t" +n.getCorrectedEnergy() + "\n");
+          	}
+          	
+      		for (Map.Entry<CalorimeterHit, CalorimeterHit> entry2 : clusterHits.entrySet()){
+      			if (entry2.getKey()==entry2.getValue()){//seed
+      				writeHits.append("Cluster" + "\t" + entry2.getKey().getIdentifierFieldValue("ix")+
+        				"\t"+entry2.getKey().getIdentifierFieldValue("iy")+"\t"+seedEnergyTot.get(entry2.getKey())+"\n");
+                  
+      				HPSEcalCluster cluster = new HPSEcalCluster(entry2.getKey());
+                    cluster.addHit(entry2.getKey());
+      				
+                    for (Map.Entry<CalorimeterHit, CalorimeterHit> entry3 : clusterHits.entrySet()){
+      					if (entry3.getValue() == entry2.getValue()){
+      						writeHits.append("CompHit" + "\t" + entry3.getKey().getIdentifierFieldValue("ix")+ "\t"
+      			        	+entry3.getKey().getIdentifierFieldValue("iy") + "\n");
+      						
+      						cluster.addHit(entry3.getKey());
+      					}
+      				}
+      				for (Map.Entry<CalorimeterHit, List<CalorimeterHit>> entry4 : commonHits.entrySet()){
+      					if (entry4.getValue().contains(entry2.getKey())){      						
+      						writeHits.append("SharHit" + "\t" + entry4.getKey().getIdentifierFieldValue("ix")+ "\t"
+      						+entry4.getKey().getIdentifierFieldValue("iy") + "\n");
+      						
+      						cluster.addHit(entry4.getKey());
+      						
+      					}
+      				}
+      				
+      				clusters.add(cluster);
+      			}	
+      		}
+      		writeHits.append("EndEvent\n");
+      		
+      	}
+      	
+      	 //Clear all maps for next event iteration
+        hitID.clear();
+        clusterHits.clear();
+        seedHits.clear();
+        commonHits.clear();
+        chitList.clear();
+        seedEnergy.clear();
+ 
+        return clusters;
+      	
+      	
+      	
+
+      }
+    
+    public void endOfData() {
+    	try{
+    	writeHits.close();
+    	}
+    	catch (IOException e){ }
+    }
+
+    private class EnergyComparator implements Comparator<CalorimeterHit>{
+
+		@Override
+		public int compare(CalorimeterHit o1, CalorimeterHit o2) {
+			// TODO Auto-generated method stub
+			double diff = o1.getCorrectedEnergy()-o2.getCorrectedEnergy();
+			if(diff < 0) { return 1; }
+			if(diff > 0) { return -1; } 
+			else { return 0; }
+		}		
+    }   
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalClusterer.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalClusterer.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalClusterer.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,171 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.geometry.Detector;
+import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap;
+import org.lcsim.geometry.subdetector.HPSEcal3;
+import org.lcsim.util.Driver;
+import org.lcsim.lcio.LCIOConstants;
+
+/**
+ * This Driver creates clusters from the CalorimeterHits of an
+ * {@link org.lcsim.geometry.subdetectur.HPSEcal3} detector.
+ *
+ * The clustering algorithm is from pages 83 and 84 of the HPS Proposal.
+ *
+ * @author Jeremy McCormick <[log in to unmask]>
+ * @author Tim Nelson <[log in to unmask]>
+ *
+ * @version $Id: EcalClusterer.java,v 1.1 2013/02/25 22:39:24 meeg Exp $
+ */
+public class EcalClusterer extends Driver {
+
+    HPSEcal3 ecal;
+    String ecalCollectionName;
+    String ecalName = "Ecal";
+    String clusterCollectionName = "EcalClusters";
+    // Minimum E for cluster seed.
+    double seedEMin = .05 * ECalUtils.GeV;
+    // Minimum E to add hit to cluster.
+    double addEMin = .03 * ECalUtils.GeV;
+    // Odd or even number of crystals in X.
+    boolean oddX;
+    // Map of crystals to their neighbors.
+    NeighborMap neighborMap = null;
+
+    public EcalClusterer() {
+    }
+
+    public void setClusterCollectionName(String clusterCollectionName) {
+        this.clusterCollectionName = clusterCollectionName;
+    }
+
+    public void setSeedEMin(double seedEMin) {
+        this.seedEMin = seedEMin;
+    }
+
+    public void setAddEMin(double addEMin) {
+        this.addEMin = addEMin;
+        if (seedEMin < addEMin) {
+            seedEMin = addEMin;
+        }
+    }
+
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    public void setEcalName(String ecalName) {
+        this.ecalName = ecalName;
+    }
+
+    public void startOfData() {
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+
+        if (ecalName == null) {
+            throw new RuntimeException("The parameter ecalName was not set!");
+        }
+    }
+
+    public void detectorChanged(Detector detector) {
+        // Get the Subdetector.
+        ecal = (HPSEcal3) detector.getSubdetector(ecalName);
+
+        // Cache ref to neighbor map.
+        neighborMap = ecal.getNeighborMap();
+
+        //System.out.println(ecal.getName());
+        //System.out.println("  nx="+ecal.nx());
+        //System.out.println("  ny="+ecal.ny());
+        //System.out.println("  beamgap="+ecal.beamGap());
+        //System.out.println("  dface="+ecal.distanceToFace());
+
+        //System.out.println(neighborMap.toString());
+    }
+
+    public void process(EventHeader event) {
+        //System.out.println(this.getClass().getCanonicalName() + " - process");
+
+        if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) {
+            // Get the list of raw ECal hits.
+            List<CalorimeterHit> hits = event.get(CalorimeterHit.class, ecalCollectionName);
+
+            // Make a hit map for quick lookup by ID.
+            Map<Long, CalorimeterHit> hitMap = new HashMap<Long, CalorimeterHit>();
+            for (CalorimeterHit hit : hits) {
+                hitMap.put(hit.getCellID(), hit);
+            }
+
+            // Put Cluster collection into event.
+            int flag = 1 << LCIOConstants.CLBIT_HITS;
+            event.put(clusterCollectionName, createClusters(hitMap), HPSEcalCluster.class, flag);
+        }
+    }
+
+    public List<HPSEcalCluster> createClusters(Map<Long, CalorimeterHit> map) {
+
+        // New Cluster list to be added to event.
+        List<HPSEcalCluster> clusters = new ArrayList<HPSEcalCluster>();
+
+        // Loop over ECal hits to find cluster seeds.
+        for (CalorimeterHit hit : map.values()) {
+            // Cut on min seed E.
+            if (hit.getRawEnergy() < seedEMin) {
+                continue;
+            }
+
+            // Get neighbor crystal IDs.
+            Set<Long> neighbors = neighborMap.get(hit.getCellID());
+
+            if (neighbors == null) {
+                throw new RuntimeException("Oops!  Set of neighbors is null!");
+            }
+
+            // List for neighboring hits.
+            List<CalorimeterHit> neighborHits = new ArrayList<CalorimeterHit>();
+
+            // Loop over neighbors to make hit list for cluster.
+            boolean isSeed = true;
+            for (Long neighborId : neighbors) {
+                // Find the neighbor hit in the event if it exists.
+                CalorimeterHit neighborHit = map.get(neighborId);
+
+                // Was this neighbor cell hit?
+                if (neighborHit != null) {
+                    // Check if neighbor cell has more energy.
+                    if (neighborHit.getRawEnergy() > hit.getRawEnergy()) {
+                        // Neighbor has more energy, so cell is not a seed.
+                        isSeed = false;
+                        break;
+                    }
+
+                    // Add to cluster if above min E.
+                    if (neighborHit.getRawEnergy() >= addEMin) {
+                        neighborHits.add(neighborHit);
+                    }
+                }
+            }
+
+            // Did we find a seed?
+            if (isSeed) {
+                // Make a cluster from the hit list.
+                HPSEcalCluster cluster = new HPSEcalCluster(hit);
+                cluster.addHit(hit);
+                for (CalorimeterHit clusHit : neighborHits) {
+                    cluster.addHit(clusHit);
+                }
+                clusters.add(cluster);
+            }
+        }
+        return clusters;
+    }
+}
\ No newline at end of file

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalConditions.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalConditions.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalConditions.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,351 @@
+package org.hps.recon.ecal;
+
+import java.io.BufferedReader;
+import org.lcsim.geometry.compact.Subdetector;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.StringTokenizer;
+import org.lcsim.conditions.ConditionsManager;
+import org.lcsim.detector.identifier.ExpandedIdentifier;
+import org.lcsim.detector.identifier.IExpandedIdentifier;
+import org.lcsim.detector.identifier.IIdentifierHelper;
+import org.lcsim.geometry.Detector;
+import org.lcsim.util.Driver;
+
+/**
+ *
+ * @author meeg
+ * @version $Id: EcalConditions.java,v 1.2 2013/10/24 20:01:54 meeg Exp $
+ */
+public class EcalConditions extends Driver {
+
+    //DAQ channel map
+    private static HashMap<Long, Long> daqToPhysicalMap = new HashMap<Long, Long>();
+    private static HashMap<Long, Long> physicalToDaqMap = new HashMap<Long, Long>();
+    //pedestals
+    private static HashMap<Long, Double> daqToPedestalMap = new HashMap<Long, Double>();
+    private static HashMap<Long, Double> daqToNoiseMap = new HashMap<Long, Double>();
+    //set of bad channels to ignore
+    private static HashSet<Long> badChannelsSet = new HashSet<Long>();
+    private static boolean badChannelsLoaded = false;
+    private static IIdentifierHelper helper = null;
+    //gain
+    private static HashMap<Long, Double> physicalToGainMap = new HashMap<Long, Double>();
+    //subdetector name (for when this is used as a driver)
+    private String subdetectorName = "Ecal";
+    private static Subdetector subdetector;
+    private static boolean debug = false;
+    private static boolean calibrationLoaded = false;
+    private static String gainFilename = "default.gain";
+
+    public EcalConditions() {
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+        detectorChanged(detector, subdetectorName);
+    }
+
+    public static void detectorChanged(Detector detector, String ecalName) {
+        subdetector = detector.getSubdetector(ecalName);
+        if (subdetector == null) {
+            throw new RuntimeException("Subdetector " + ecalName + " not found");
+        }
+        helper = subdetector.getDetectorElement().getIdentifierHelper();
+    }
+
+    public static boolean calibrationLoaded() {
+        return calibrationLoaded;
+    }
+
+    public static void loadDaqMap(Detector detector, String ecalName) {
+        detectorChanged(detector, ecalName);
+        fillDaqCellMap(subdetector);
+    }
+
+    public static void loadCalibration() {
+        fillDaqCellMap(subdetector);
+        loadBadChannels(subdetector);
+        loadGains();
+        loadPedestals();
+        calibrationLoaded = true;
+    }
+    
+    public static void setGainFilename(String name) {
+    	gainFilename = name;
+    }
+
+    public void setSubdetectorName(String subdetectorName) {
+        this.subdetectorName = subdetectorName;
+    }
+
+    public static IIdentifierHelper getHelper() {
+        return helper;
+    }
+
+    public static Subdetector getSubdetector() {
+        return subdetector;
+    }
+
+    public static boolean badChannelsLoaded() {
+        return badChannelsLoaded;
+    }
+
+    public static void loadPedestals() {
+        ConditionsManager conditions = ConditionsManager.defaultInstance();
+        try {
+            Reader pedestalsReader = conditions.getRawConditions("calibECal/default01.ped").getReader();
+            loadPedestals(pedestalsReader, 1);
+            pedestalsReader = conditions.getRawConditions("calibECal/default02.ped").getReader();
+            loadPedestals(pedestalsReader, 2);
+        } catch (IOException e) {
+            throw new RuntimeException("couldn't get pedestals file", e);
+        }
+    }
+
+    public static void loadPedestals(Reader reader, int crate) {
+
+        System.out.println("reading pedestals for ECal");
+
+        BufferedReader bufferedReader = new BufferedReader(reader);
+        String line;
+        while (true) {
+            try {
+                line = bufferedReader.readLine();
+            } catch (IOException e) {
+                throw new RuntimeException("couldn't parse pedestals file", e);
+            }
+            if (line == null) {
+                break;
+            }
+
+            if (line.indexOf("#") != -1) {
+                line = line.substring(0, line.indexOf("#"));
+            }
+
+            StringTokenizer lineTok = new StringTokenizer(line);
+
+            if (lineTok.countTokens() != 0) {
+                if (lineTok.countTokens() != 4) {
+                    throw new RuntimeException("Invalid line in pedestals file: " + line);
+                } else {
+                    short slot = Short.valueOf(lineTok.nextToken());
+                    short channel = Short.valueOf(lineTok.nextToken());
+                    double pedestal = Double.valueOf(lineTok.nextToken());
+                    double noise = Double.valueOf(lineTok.nextToken());
+                    long daqid = getDaqID(crate, slot, channel);
+                    daqToPedestalMap.put(daqid, pedestal);
+                    daqToNoiseMap.put(daqid, noise);
+                    if (debug) {
+                        System.out.printf("Channel %d: pede %.2f noise %.2f (crate %d slot %d channel %d)\n", daqid,pedestal,noise,crate,slot,channel);
+                    }
+                }
+            }
+        }
+    }
+
+    private static void loadBadChannels(Subdetector ecal) {
+
+        System.out.println("reading ECal bad channels");
+
+        IExpandedIdentifier expId = new ExpandedIdentifier(helper.getIdentifierDictionary().getNumberOfFields());
+        expId.setValue(helper.getFieldIndex("system"), ecal.getSystemID());
+        ConditionsManager conditions = ConditionsManager.defaultInstance();
+        BufferedReader bufferedReader;
+        try {
+            bufferedReader = new BufferedReader(conditions.getRawConditions("daqmap/ecal.badchannels").getReader());
+        } catch (IOException e) {
+            throw new RuntimeException("couldn't get ECal bad channels from conditions manager", e);
+        }
+        String line;
+        while (true) {
+            try {
+                line = bufferedReader.readLine();
+            } catch (IOException e) {
+                throw new RuntimeException("couldn't parse ECal bad channels", e);
+            }
+            if (line == null) {
+                break;
+            }
+
+            if (line.indexOf("#") != -1) {
+                line = line.substring(0, line.indexOf("#"));
+            }
+
+            StringTokenizer lineTok = new StringTokenizer(line);
+
+            if (lineTok.countTokens() != 0) {
+                if (lineTok.countTokens() != 2) {
+                    throw new RuntimeException("Invalid line in ECal bad channels: " + line);
+                } else {
+                    int x = Integer.valueOf(lineTok.nextToken());
+                    int y = Integer.valueOf(lineTok.nextToken());
+                    expId.setValue(helper.getFieldIndex("ix"), x);
+                    expId.setValue(helper.getFieldIndex("iy"), y);
+                    badChannelsSet.add(helper.pack(expId).getValue());
+                    if (debug) {
+                        System.out.printf("Channel %d is bad (x=%d y=%d)\n", helper.pack(expId).getValue(),x,y);
+                    }
+                }
+            }
+        }
+        badChannelsLoaded = true;
+    }
+
+    public static void loadGains() {
+        if (debug) {
+            System.out.println("Loading gains");
+        }
+        BufferedReader bufferedReader;
+        ConditionsManager conditions = ConditionsManager.defaultInstance();
+        try {
+        	bufferedReader = new BufferedReader(conditions.getRawConditions("calibECal/"+EcalConditions.gainFilename).getReader());
+        } catch (IOException e) {
+            throw new RuntimeException("couldn't get gain file("+gainFilename+") ", e);
+        }
+
+        String line;
+        while (true) {
+            try {
+                line = bufferedReader.readLine();
+            } catch (IOException e) {
+                throw new RuntimeException("couldn't parse gain file", e);
+            }
+            if (line == null) {
+                break;
+            }
+
+            if (line.indexOf("#") != -1) {
+                line = line.substring(0, line.indexOf("#"));
+            }
+
+            StringTokenizer lineTok = new StringTokenizer(line);
+
+            if (lineTok.countTokens() != 0) {
+                if (lineTok.countTokens() != 3) {
+                    throw new RuntimeException("Invalid line in gain file: " + line);
+                } else {
+                    int x = Integer.valueOf(lineTok.nextToken());
+                    int y = Integer.valueOf(lineTok.nextToken());
+                    double gain = Double.valueOf(lineTok.nextToken());
+                    physicalToGainMap.put(makePhysicalID(x, y), gain);
+                    if (debug) {
+                        System.out.printf("Channel %d: gain %.2f (x=%d y=%d)\n", makePhysicalID(x, y),gain,x,y);
+                    }
+                }
+            }
+        }
+    }
+
+    public static boolean isBadChannel(long id) {
+        return badChannelsSet.contains(id);
+    }
+
+    private static void fillDaqCellMap(Subdetector ecal) {
+
+        System.out.println("reading ECal DAQ map");
+
+        IExpandedIdentifier expId = new ExpandedIdentifier(helper.getIdentifierDictionary().getNumberOfFields());
+        expId.setValue(helper.getFieldIndex("system"), ecal.getSystemID());
+
+        ConditionsManager conditions = ConditionsManager.defaultInstance();
+        BufferedReader bufferedReader;
+        try {
+            bufferedReader = new BufferedReader(conditions.getRawConditions("daqmap/ecal.txt").getReader());
+        } catch (IOException e) {
+            throw new RuntimeException("couldn't get DAQ map from conditions manager", e);
+        }
+        String line;
+        while (true) {
+            try {
+                line = bufferedReader.readLine();
+            } catch (IOException e) {
+                throw new RuntimeException("couldn't parse ECal DAQ map", e);
+            }
+            if (line == null) {
+                break;
+            }
+
+            if (line.indexOf("#") != -1) {
+                line = line.substring(0, line.indexOf("#"));
+            }
+
+            StringTokenizer lineTok = new StringTokenizer(line);
+
+            if (lineTok.countTokens() != 0) {
+                if (lineTok.countTokens() != 5) {
+                    throw new RuntimeException("Invalid line in ECal DAQ map: " + line);
+                } else {
+                    int x = Integer.valueOf(lineTok.nextToken());
+                    int y = Integer.valueOf(lineTok.nextToken());
+//                    if (x>0 && y>0) x = 24-x;
+                    expId.setValue(helper.getFieldIndex("ix"), x);
+                    expId.setValue(helper.getFieldIndex("iy"), y);
+                    int crate = Integer.valueOf(lineTok.nextToken());
+                    short slot = Short.valueOf(lineTok.nextToken());
+                    short channel = Short.valueOf(lineTok.nextToken());
+                    addMapEntry(helper.pack(expId).getValue(), getDaqID(crate, slot, channel));
+                }
+            }
+        }
+    }
+
+    public static long makePhysicalID(int ix, int iy) {
+        IExpandedIdentifier expId = new ExpandedIdentifier(helper.getIdentifierDictionary().getNumberOfFields());
+        expId.setValue(helper.getFieldIndex("system"), subdetector.getSystemID());
+        expId.setValue(helper.getFieldIndex("ix"), ix);
+        expId.setValue(helper.getFieldIndex("iy"), iy);
+        return helper.pack(expId).getValue();
+    }
+
+    private static void addMapEntry(long physicalID, long daqID) {
+        daqToPhysicalMap.put(daqID, physicalID);
+        physicalToDaqMap.put(physicalID, daqID);
+    }
+
+    public static long getDaqID(int crate, short slot, short channel) {
+        return (((long) crate) << 32) | ((long) slot << 16) | (long) channel;
+    }
+
+    public static Long daqToPhysicalID(int crate, short slot, short channel) {
+        return daqToPhysicalMap.get(getDaqID(crate, slot, channel));
+    }
+
+    public static int getCrate(long daqID) {
+        return (int) (daqID >>> 32);
+    }
+
+    public static short getSlot(long daqID) {
+        return (short) ((daqID >>> 16) & 0xFFFF);
+    }
+
+    public static short getChannel(long daqID) {
+        return (short) (daqID & 0xFFFF);
+    }
+
+    public static Long physicalToDaqID(long physicalID) {
+        return physicalToDaqMap.get(physicalID);
+    }
+
+    public static Long daqToPhysicalID(long daqID) {
+        return daqToPhysicalMap.get(daqID);
+    }
+
+    public static Double daqToPedestal(long daqID) {
+        return daqToPedestalMap.get(daqID);
+    }
+
+    public static Double physicalToPedestal(long physicalID) {
+        return daqToPedestalMap.get(physicalToDaqMap.get(physicalID));
+    }
+
+    public static Double physicalToNoise(long physicalID) {
+        return daqToNoiseMap.get(physicalToDaqMap.get(physicalID));
+    }
+
+    public static Double physicalToGain(long physicalID) {
+        return physicalToGainMap.get(physicalID);
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalConverterDriver.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalConverterDriver.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalConverterDriver.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,86 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.RawCalorimeterHit;
+import org.lcsim.util.Driver;
+import org.lcsim.lcio.LCIOConstants;
+
+/**
+ *
+ * @author Sho Uemura <[log in to unmask]>
+ * @version $Id: EcalConverterDriver.java,v 1.1 2013/02/25 22:39:24 meeg Exp $
+ */
+public class EcalConverterDriver extends Driver {
+
+    String rawCollectionName;
+    String ecalReadoutName = "EcalHits";
+    String ecalCollectionName = "EcalCorrectedHits";
+    int flags;
+    double scale = 1.0;
+//    double pedestal = 0.0;
+    double period = 4.0;
+    double dt = 0.0;
+
+    public EcalConverterDriver() {
+        flags = 0;
+        flags += 1 << LCIOConstants.CHBIT_LONG; //store position
+        flags += 1 << LCIOConstants.RCHBIT_ID1; //store cell ID
+    }
+
+//    public void setPedestal(double pedestal) {
+//        this.pedestal = pedestal;
+//    }
+    public void setScale(double scale) {
+        this.scale = scale;
+    }
+
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    public void setRawCollectionName(String rawCollectionName) {
+        this.rawCollectionName = rawCollectionName;
+    }
+
+    @Override
+    public void startOfData() {
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+    }
+
+    @Override
+    public void process(EventHeader event) {
+        if (event.hasCollection(HPSRawCalorimeterHit.class, rawCollectionName)) {
+            // Get the list of ECal hits.
+            List<HPSRawCalorimeterHit> hits = event.get(HPSRawCalorimeterHit.class, rawCollectionName);
+
+            ArrayList<CalorimeterHit> newHits = new ArrayList<CalorimeterHit>();
+
+            for (HPSRawCalorimeterHit hit : hits) {
+                newHits.add(HitDtoA(hit));
+            }
+
+            event.put(ecalCollectionName, newHits, CalorimeterHit.class, flags, ecalReadoutName);
+        }
+    }
+
+//    private int AtoD(double amplitude, long cellID) {
+//        return (int) Math.round(amplitude / scale);
+//    }
+
+    private double DtoA(int amplitude, long cellID) {
+        return scale * amplitude;
+    }
+
+    private CalorimeterHit HitDtoA(RawCalorimeterHit hit) {
+        return new HPSCalorimeterHit(DtoA(hit.getAmplitude(), hit.getCellID()), period * hit.getTimeStamp() + dt, hit.getCellID(), 0);
+    }
+
+//    private RawCalorimeterHit HitAtoD(CalorimeterHit hit) {
+//        return new HPSFADCCalorimeterHit(hit.getCellID(), AtoD(hit.getRawEnergy(), hit.getCellID()), (int) Math.round(hit.getTime() / period), 0);
+//    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalCrystalFilter.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalCrystalFilter.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalCrystalFilter.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,1048 @@
+package org.hps.recon.ecal;
+
+import hep.aida.IHistogram1D;
+import hep.aida.IHistogram2D;
+import hep.aida.IPlotter;
+import hep.aida.IPlotterStyle;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+
+import org.lcsim.detector.identifier.ExpandedIdentifier;
+import org.lcsim.detector.identifier.IExpandedIdentifier;
+import org.lcsim.detector.identifier.IIdentifier;
+import org.lcsim.detector.identifier.IIdentifierHelper;
+import org.lcsim.detector.identifier.Identifier;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.RawTrackerHit;
+import org.lcsim.event.base.BaseRawCalorimeterHit;
+import org.lcsim.geometry.Detector;
+import org.lcsim.hps.util.AIDAFrame;
+import org.lcsim.hps.util.Resettable;
+import org.lcsim.hps.util.Redrawable;
+import org.lcsim.util.Driver;
+import org.lcsim.util.aida.AIDA;
+
+public class EcalCrystalFilter extends Driver implements Resettable, ActionListener, Redrawable {
+
+    private String inputCollection;
+    private String outputPlotFileName;
+    private IPlotter plotter;
+    private IPlotter plotter2;
+    private IPlotter plotter3;
+    private IPlotter plotter4;
+    private IPlotter plotterTop;
+    private IPlotter plotterTop2;
+    private IPlotter plotterTop3;
+    private IPlotter plotterTop4;
+    private IPlotter plotterBot;
+    private IPlotter plotterBot2;
+    private IPlotter plotterBot3;
+    private IPlotter plotterBot4;
+    private AIDA aida = AIDA.defaultInstance();
+    private AIDAFrame plotterFrame;
+    private AIDAFrame plotterFrameTop;
+    private AIDAFrame plotterFrameBot;
+    private IHistogram1D aMeanPlot;
+    private IHistogram1D aSigmaPlot;
+    private IHistogram1D tMeanPlot;
+    private IHistogram1D tSigmaPlot;
+    private IHistogram1D aTMeanPlot;
+    private IHistogram1D aTSigmaPlot;
+    private IHistogram1D tTMeanPlot;
+    private IHistogram1D tTSigmaPlot;
+    private IHistogram1D aBMeanPlot;
+    private IHistogram1D aBSigmaPlot;
+    private IHistogram1D tBMeanPlot;
+    private IHistogram1D tBSigmaPlot;
+    private IHistogram2D aTOutMeanPlot;
+    private IHistogram2D aTOutSigmaPlot;
+    private IHistogram2D tTOutMeanPlot;
+    private IHistogram2D tTOutSigmaPlot;
+    private IHistogram2D aTTOutMeanPlot;
+    private IHistogram2D aTTOutSigmaPlot;
+    private IHistogram2D tTTOutMeanPlot;
+    private IHistogram2D tTTOutSigmaPlot;
+    private IHistogram2D aBTOutMeanPlot;
+    private IHistogram2D aBTOutSigmaPlot;
+    private IHistogram2D tBTOutMeanPlot;
+    private IHistogram2D tBTOutSigmaPlot;
+    private IHistogram1D[][] aPlots = new IHistogram1D[47][11];
+    private IHistogram1D[][] tPlots = new IHistogram1D[47][11];
+    private IHistogram1D[][] aTPlots = new IHistogram1D[47][11];
+    private IHistogram1D[][] tTPlots = new IHistogram1D[47][11];
+    private IHistogram1D[][] aBPlots = new IHistogram1D[47][11];
+    private IHistogram1D[][] tBPlots = new IHistogram1D[47][11];
+    private JLabel xLabel, yLabel;
+    private JComboBox xCombo;
+    private JComboBox yCombo;
+    private JButton blankButton;
+    private JLabel xLabelTop, yLabelTop;
+    private JComboBox xComboTop;
+    private JComboBox yComboTop;
+    private JButton blankButtonTop;
+    private JLabel xLabelBot, yLabelBot;
+    private JComboBox xComboBot;
+    private JComboBox yComboBot;
+    private JButton blankButtonBot;
+    private static final Integer[] xList = new Integer[46];
+    private static final Integer[] yList = new Integer[10];
+    private static final Integer[] xListTop = new Integer[46];
+    private static final Integer[] yListTop = new Integer[10];
+    private static final Integer[] xListBot = new Integer[46];
+    private static final Integer[] yListBot = new Integer[10];
+    int eventRefreshRate = 1;
+    int eventn = 0;
+    boolean hide = false;
+    int calWindow = 0;
+    double maxE = 1000;
+    double tTOutNSigmaThr = 5.0;
+    String hotCrystalFileName = "ecal_hotcrystals.txt";
+    FileWriter fWriter;
+    PrintWriter pWriter;
+    int[] _trigger = new int[2];
+
+    public EcalCrystalFilter() {
+        int count = 0;
+        for (int i = -23; i <= 23; i++) {
+            if (i != 0) {
+                xList[count] = i;
+                xListTop[count] = i;
+                xListBot[count] = i;
+
+                count++;
+            }
+        }
+        count = 0;
+        for (int i = -5; i <= 5; i++) {
+            if (i != 0) {
+                yList[count] = i;
+                yListTop[count] = i;
+                yListBot[count] = i;
+                count++;
+            }
+        }
+        try {
+            fWriter = new FileWriter(hotCrystalFileName);
+            pWriter = new PrintWriter(fWriter);
+        } catch (IOException ex) {
+            Logger.getLogger(EcalCrystalFilter.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+
+        outputPlotFileName = "";
+
+    }
+
+    public void closeFile() throws IOException {
+        pWriter.close();
+        fWriter.close();
+    }
+
+    public void setMaxE(double maxE) {
+        this.maxE = maxE;
+    }
+
+    public void setCalWindow(int calWindow) {
+        this.calWindow = calWindow;
+    }
+
+    public void setHide(boolean hide) {
+        this.hide = hide;
+    }
+
+    public void setInputCollection(String inputCollection) {
+        this.inputCollection = inputCollection;
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+        if (inputCollection == null) {
+            throw new RuntimeException("The inputCollection parameter was not set.");
+        }
+
+        aida = AIDA.defaultInstance();
+        aida.tree().cd("/");
+
+        aSigmaPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Sigma> (Amplitude) Filter", 50, 0, 200);
+        aMeanPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Mean> (Amplitude) Filter", 50, 0, 1000);
+        tSigmaPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Sigma> (Time) Filter", 50, 0, 50);
+        tMeanPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Mean> (Time) Filter", 50, 0, 100);
+
+        aTSigmaPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Sigma> (Amplitude) Top Trig Filter", 50, 0, 200);
+        aTMeanPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Mean> (Amplitude) Top Trig Filter", 50, 0, 1000);
+        tTSigmaPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Sigma> (Time) Top Trig Filter", 50, 0, 50);
+        tTMeanPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Mean> (Time) Top Trig Filter", 50, 0, 100);
+
+        aBSigmaPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Sigma> (Amplitude) Bottom Trig Filter", 50, 0, 200);
+        aBMeanPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Mean> (Amplitude) Bottom Trig Filter", 50, 0, 1000);
+        tBSigmaPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Sigma> (Time) Bottom Trig Filter", 50, 0, 50);
+        tBMeanPlot = aida.histogram1D(detector.getDetectorName() + " : " + inputCollection + " : <Mean> (Time) Bottom Trig Filter", 50, 0, 100);
+
+
+        aTOutSigmaPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Sigma (Amplitude) Time Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        aTOutMeanPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Mean (Amplitude) Time Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        tTOutSigmaPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Sigma (Time) Time Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        tTOutMeanPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Mean (Time) Time Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+
+        aTTOutSigmaPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Sigma (Amplitude) Time Bot Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        aTTOutMeanPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Mean (Amplitude) Time Bot Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        tTTOutSigmaPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Sigma (Time) Time Bot Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        tTTOutMeanPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Mean (Time) Time Bot Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+
+        aBTOutSigmaPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Sigma (Amplitude) Time Top Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        aBTOutMeanPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Mean (Amplitude) Time Top Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        tBTOutSigmaPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Sigma (Time) Time Top Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+        tBTOutMeanPlot = aida.histogram2D(detector.getDetectorName() + " : " + inputCollection + " : Mean (Time) Time Top Outliers", 47, -23.5, 23.5, 11, -5.5, 5.5);
+
+
+        for (int x = -23; x <= 23; x++) { // slot
+            for (int y = -5; y <= 5; y++) { // crate                
+                //System.out.println("creating plot: " + "ECAL: Crate " + j + "; Slot " + i + " in region " + region);
+//                IHistogram1D hist = aida.histogram1D("ECAL: x=" + i + "; y=" + j, 1000, 0, 16);
+//                plots[x + 23][y + 5] = aida.cloud1D("ECAL: x=" + x + "; y=" + y);
+                if (calWindow == 0) {
+                    aPlots[x + 23][y + 5] = aida.histogram1D("ECAL Amplitudes: x=" + x + "; y=" + y, 500, -0.1, maxE);
+                    aTPlots[x + 23][y + 5] = aida.histogram1D("Top ECAL Amplitudes: x=" + x + "; y=" + y, 500, -0.1, maxE);
+                    aBPlots[x + 23][y + 5] = aida.histogram1D("Bottom ECAL Amplitudes: x=" + x + "; y=" + y, 500, -0.1, maxE);
+
+                } else {
+                    aPlots[x + 23][y + 5] = aida.histogram1D("ECAL Amplitudes: x=" + x + "; y=" + y, 1024, -0.5, 1023.5);
+                    aTPlots[x + 23][y + 5] = aida.histogram1D("Top Trig CAL Amplitudes: x=" + x + "; y=" + y, 1024, -0.5, 1023.5);
+                    aBPlots[x + 23][y + 5] = aida.histogram1D("Bottom Trig CAL Amplitudes: x=" + x + "; y=" + y, 1024, -0.5, 1023.5);
+                }
+                tPlots[x + 23][y + 5] = aida.histogram1D("ECAL Times: x=" + x + "; y=" + y, 100, 0, 100);
+                tTPlots[x + 23][y + 5] = aida.histogram1D("Top Trig ECAL Times: x=" + x + "; y=" + y, 100, 0, 100);
+                tBPlots[x + 23][y + 5] = aida.histogram1D("Bottom Trig ECAL Times: x=" + x + "; y=" + y, 100, 0, 100);
+            }
+        }
+
+        plotterFrame = new AIDAFrame();
+        plotterFrame.setTitle("HPS ECal Crystal Filter Plots");
+
+        xCombo = new JComboBox(xList);
+        xCombo.addActionListener(this);
+        xLabel = new JLabel("x");
+        xLabel.setLabelFor(xCombo);
+        plotterFrame.getControlsPanel().add(xLabel);
+        plotterFrame.getControlsPanel().add(xCombo);
+        yCombo = new JComboBox(yList);
+        yCombo.addActionListener(this);
+        yLabel = new JLabel("y");
+        yLabel.setLabelFor(yCombo);
+        plotterFrame.getControlsPanel().add(yLabel);
+        plotterFrame.getControlsPanel().add(yCombo);
+        blankButton = new JButton("Hide histogram");
+        plotterFrame.getControlsPanel().add(blankButton);
+        blankButton.addActionListener(this);
+
+        // Setup the plotter.
+        plotter = aida.analysisFactory().createPlotterFactory().create();
+        plotter.setTitle("HPS ECal Amplitude");
+        plotterFrame.addPlotter(plotter);
+        plotter.createRegions(1, 3);
+
+        plotter.style().statisticsBoxStyle().setVisible(false);
+        plotter.style().dataStyle().errorBarStyle().setVisible(false);
+        plotter.style().zAxisStyle().setParameter("allowZeroSuppression", "true");
+        IPlotterStyle style = plotter.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotter.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotter.region(0).plot(aSigmaPlot);
+        plotter.region(1).plot(aMeanPlot);
+        plotter.style().dataStyle().fillStyle().setColor("yellow");
+
+        // Setup the plotter.
+        plotter2 = aida.analysisFactory().createPlotterFactory().create();
+        plotter2.setTitle("HPS ECal Hit Time ");
+        plotterFrame.addPlotter(plotter2);
+        plotter2.createRegions(1, 3);
+
+        plotter2.style().statisticsBoxStyle().setVisible(true);
+        plotter2.style().dataStyle().errorBarStyle().setVisible(false);
+        plotter2.style().zAxisStyle().setParameter("allowZeroSuppression", "true");
+        style = plotter2.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotter2.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotter2.region(0).plot(tSigmaPlot);
+        plotter2.region(1).plot(tMeanPlot);
+        plotter2.style().dataStyle().fillStyle().setColor("green");
+
+
+        // Setup the plotter.
+        plotter3 = aida.analysisFactory().createPlotterFactory().create();
+        plotter3.setTitle("HPS ECal for Time Outliers ");
+        plotterFrame.addPlotter(plotter3);
+        plotter3.createRegions(1, 3);
+
+        plotter3.style().statisticsBoxStyle().setVisible(false);
+        plotter3.style().dataStyle().errorBarStyle().setVisible(false);
+        plotter3.style().zAxisStyle().setParameter("allowZeroSuppression", "true");
+        style = plotter3.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotter3.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotter3.region(0).plot(tTOutSigmaPlot);
+        plotter3.region(1).plot(tTOutMeanPlot);
+        plotter3.style().dataStyle().fillStyle().setColor("green");
+
+        // Setup the plotter.
+        plotter4 = aida.analysisFactory().createPlotterFactory().create();
+        plotter4.setTitle("HPS ECal Amplitude for Time Outliers ");
+        plotterFrame.addPlotter(plotter4);
+        plotter4.createRegions(1, 3);
+
+        plotter4.style().statisticsBoxStyle().setVisible(false);
+        plotter4.style().dataStyle().errorBarStyle().setVisible(false);
+        plotter4.style().zAxisStyle().setParameter("allowZeroSuppression", "true");
+        style = plotter4.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotter4.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotter4.region(0).plot(aTOutSigmaPlot);
+        plotter4.region(1).plot(aTOutMeanPlot);
+        plotter4.style().dataStyle().fillStyle().setColor("green");
+
+        plotter.region(2).plot(aPlots[-5 + 23][2 + 5 - 1]);
+        plotter2.region(2).plot(tPlots[-5 + 23][2 + 5 - 1]);
+        plotter3.region(2).plot(tPlots[-5 + 23][2 + 5 - 1]);
+        plotter4.region(2).plot(aPlots[-5 + 23][2 + 5 - 1]);
+
+
+
+
+
+
+
+
+        plotterFrameTop = new AIDAFrame();
+        plotterFrameTop.setTitle("HPS Top Trig ECal Crystal Filter Plots");
+
+        xComboTop = new JComboBox(xListTop);
+        xComboTop.addActionListener(this);
+        xLabelTop = new JLabel("xT");
+        xLabelTop.setLabelFor(xComboTop);
+        plotterFrameTop.getControlsPanel().add(xLabelTop);
+        plotterFrameTop.getControlsPanel().add(xComboTop);
+        yComboTop = new JComboBox(yListTop);
+        yComboTop.addActionListener(this);
+        yLabelTop = new JLabel("yT");
+        yLabelTop.setLabelFor(yComboTop);
+
+
+        plotterFrameTop.getControlsPanel().add(yLabelTop);
+        plotterFrameTop.getControlsPanel().add(yComboTop);
+        blankButtonTop = new JButton("Hide histogram (Top)");
+        plotterFrameTop.getControlsPanel().add(blankButtonTop);
+        blankButtonTop.addActionListener(this);
+
+
+        // Setup the plotterTop.
+        plotterTop = aida.analysisFactory().createPlotterFactory().create();
+        plotterTop.setTitle("HPS ECal Amplitude");
+        plotterFrameTop.addPlotter(plotterTop);
+        plotterTop.createRegions(1, 3);
+        plotterTop.setStyle(plotter.style());
+        style = plotterTop.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterTop.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterTop.region(0).plot(aTSigmaPlot);
+        plotterTop.region(1).plot(aTMeanPlot);
+        plotterTop.style().dataStyle().fillStyle().setColor("yellow");
+
+        // Setup the plotterTop.
+        plotterTop2 = aida.analysisFactory().createPlotterFactory().create();
+        plotterTop2.setTitle("HPS ECal Hit Time ");
+        plotterFrameTop.addPlotter(plotterTop2);
+        plotterTop2.createRegions(1, 3);
+
+        plotterTop2.setStyle(plotter2.style());
+        style = plotterTop2.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterTop2.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterTop2.region(0).plot(tTSigmaPlot);
+        plotterTop2.region(1).plot(tTMeanPlot);
+
+
+        // Setup the plotter.
+        plotterTop3 = aida.analysisFactory().createPlotterFactory().create();
+        plotterTop3.setTitle("HPS ECal for Time Outliers ");
+        plotterFrameTop.addPlotter(plotterTop3);
+        plotterTop3.createRegions(1, 3);
+
+        plotterTop3.setStyle(plotter3.style());
+        style = plotterTop3.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterTop3.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterTop3.region(0).plot(tTTOutSigmaPlot);
+        plotterTop3.region(1).plot(tTTOutMeanPlot);
+
+        // Setup the plotter.
+        plotterTop4 = aida.analysisFactory().createPlotterFactory().create();
+        plotterTop4.setTitle("HPS ECal Amplitude for Time Outliers ");
+        plotterFrameTop.addPlotter(plotterTop4);
+        plotterTop4.createRegions(1, 3);
+
+        plotterTop4.setStyle(plotter4.style());
+        style = plotterTop4.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterTop4.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterTop4.region(0).plot(aTTOutSigmaPlot);
+        plotterTop4.region(1).plot(aTTOutMeanPlot);
+
+        plotterTop.region(2).plot(aTPlots[-5 + 23][2 + 5 - 1]);
+        plotterTop2.region(2).plot(tTPlots[-5 + 23][2 + 5 - 1]);
+        plotterTop3.region(2).plot(tTPlots[-5 + 23][2 + 5 - 1]);
+        plotterTop4.region(2).plot(aTPlots[-5 + 23][2 + 5 - 1]);
+
+
+
+
+
+
+
+
+        plotterFrameBot = new AIDAFrame();
+        plotterFrameBot.setTitle("HPS Bottom Trig ECal Crystal Filter Plots");
+
+        xComboBot = new JComboBox(xListBot);
+        xComboBot.addActionListener(this);
+        xLabelBot = new JLabel("x");
+        xLabelBot.setLabelFor(xComboBot);
+        plotterFrameBot.getControlsPanel().add(xLabelBot);
+        plotterFrameBot.getControlsPanel().add(xComboBot);
+        yComboBot = new JComboBox(yListBot);
+        yComboBot.addActionListener(this);
+        yLabelBot = new JLabel("y");
+        yLabelBot.setLabelFor(yComboBot);
+        plotterFrameBot.getControlsPanel().add(yLabelBot);
+        plotterFrameBot.getControlsPanel().add(yComboBot);
+        blankButtonBot = new JButton("Hide histogram");
+        plotterFrameBot.getControlsPanel().add(blankButtonBot);
+        blankButtonBot.addActionListener(this);
+
+        // Setup the plotterBot.
+        plotterBot = aida.analysisFactory().createPlotterFactory().create();
+        plotterBot.setTitle("HPS ECal Amplitude");
+        plotterFrameBot.addPlotter(plotterBot);
+        plotterBot.createRegions(1, 3);
+        plotterBot.setStyle(plotter.style());
+        style = plotterBot.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterBot.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterBot.region(0).plot(aBSigmaPlot);
+        plotterBot.region(1).plot(aBMeanPlot);
+        plotterBot.style().dataStyle().fillStyle().setColor("yellow");
+
+        // Setup the plotterBot.
+        plotterBot2 = aida.analysisFactory().createPlotterFactory().create();
+        plotterBot2.setTitle("HPS ECal Hit Time ");
+        plotterFrameBot.addPlotter(plotterBot2);
+        plotterBot2.createRegions(1, 3);
+
+        plotterBot2.setStyle(plotter2.style());
+        style = plotterBot2.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterBot2.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterBot2.region(0).plot(tBSigmaPlot);
+        plotterBot2.region(1).plot(tBMeanPlot);
+
+
+        // Setup the plotter.
+        plotterBot3 = aida.analysisFactory().createPlotterFactory().create();
+        plotterBot3.setTitle("HPS ECal for Time Outliers ");
+        plotterFrameBot.addPlotter(plotterBot3);
+        plotterBot3.createRegions(1, 3);
+
+        plotterBot3.setStyle(plotter3.style());
+        style = plotterBot3.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterBot3.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterBot3.region(0).plot(tBTOutSigmaPlot);
+        plotterBot3.region(1).plot(tBTOutMeanPlot);
+
+        // Setup the plotter.
+        plotterBot4 = aida.analysisFactory().createPlotterFactory().create();
+        plotterBot4.setTitle("HPS ECal Amplitude for Time Outliers ");
+        plotterFrameBot.addPlotter(plotterBot4);
+        plotterBot4.createRegions(1, 3);
+
+        plotterBot4.setStyle(plotter4.style());
+        style = plotterBot4.region(0).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        style = plotterBot4.region(1).style();
+        style.setParameter("hist2DStyle", "colorMap");
+        style.dataStyle().fillStyle().setParameter("colorMapScheme", "rainbow");
+        plotterBot4.region(0).plot(aBTOutSigmaPlot);
+        plotterBot4.region(1).plot(aBTOutMeanPlot);
+
+        plotterBot.region(2).plot(aBPlots[-5 + 23][2 + 5 - 1]);
+        plotterBot2.region(2).plot(tBPlots[-5 + 23][2 + 5 - 1]);
+        plotterBot3.region(2).plot(tBPlots[-5 + 23][2 + 5 - 1]);
+        plotterBot4.region(2).plot(aBPlots[-5 + 23][2 + 5 - 1]);
+        //xCombo.setSelectedIndex(-5 + 23);
+        //yCombo.setSelectedIndex(2 + 5 - 1);
+
+
+
+
+
+        plotterFrame.pack();
+        if (!hide) {
+            plotterFrame.setVisible(true);
+        }
+
+
+
+        plotterFrameTop.pack();
+        if (!hide) {
+            plotterFrameTop.setVisible(true);
+        }
+
+
+
+
+        plotterFrameBot.pack();
+        if (!hide) {
+            plotterFrameBot.setVisible(true);
+        }
+
+
+
+        xComboBot.setSelectedIndex(-5 + 23);
+        yComboBot.setSelectedIndex(2 + 5 - 1);
+
+
+
+        xComboTop.setSelectedIndex(-5 + 23);
+        yComboTop.setSelectedIndex(2 + 5 - 1);
+
+
+
+
+        xCombo.setSelectedIndex(-5 + 23);
+        yCombo.setSelectedIndex(2 + 5 - 1);
+
+
+
+
+    }
+
+    public void printOutliers() {
+
+        //Outliers in time -- threshold is nr of sigma/rms from the mean
+        System.out.printf("Crystals with time RMS more than %.1f times the RMS(<RMS>)=%.1f from <RMS>=%.1f for all crystals\n", tTOutNSigmaThr, tSigmaPlot.rms(), tSigmaPlot.mean());
+
+        for (int iside = 0; iside < 3; ++iside) {
+            System.out.println("-- Side " + iside);
+            pWriter.printf("# Side %d\n", iside);
+            for (int x = -23; x <= 23; x++) { // slot
+                for (int y = -5; y <= 5; y++) { // crate   
+
+                    boolean ok = false;
+                    if (iside == 0) {
+                        if (tTOutSigmaPlot.binEntries(tTOutSigmaPlot.coordToIndexX(x), tTOutSigmaPlot.coordToIndexY(y)) > 0) {
+                            ok = true;
+                        }
+                    } else if (iside == 1) {
+                        if (tTTOutSigmaPlot.binEntries(tTTOutSigmaPlot.coordToIndexX(x), tTTOutSigmaPlot.coordToIndexY(y)) > 0) {
+                            ok = true;
+                        }
+
+                    } else if (iside == 2) {
+                        if (tBTOutSigmaPlot.binEntries(tBTOutSigmaPlot.coordToIndexX(x), tBTOutSigmaPlot.coordToIndexY(y)) > 0) {
+                            ok = true;
+                        }
+                    }
+                    if (ok) {
+                        IIdentifierHelper helper = EcalConditions.getHelper();
+                        IExpandedIdentifier expId = new ExpandedIdentifier(helper.getIdentifierDictionary().getNumberOfFields());
+                        //expId.setValue(helper.getFieldIndex("system"), ecal.getSystemID());
+                        expId.setValue(helper.getFieldIndex("ix"), x);
+                        expId.setValue(helper.getFieldIndex("iy"), y);
+                        Long id = helper.pack(expId).getValue();
+
+
+                        System.out.printf("[%d,%d]\t%d\t%d\t%d\tTime:%f +- %f\tAmp:%f +- %f\n", x, y, EcalConditions.getCrate(id), EcalConditions.getSlot(id), EcalConditions.getChannel(id), tPlots[x + 23][y + 5].mean(), tPlots[x + 23][y + 5].rms(), aPlots[x + 23][y + 5].mean(), aPlots[x + 23][y + 5].rms());
+
+                        pWriter.printf("%d %d\n", x, y);
+                    }
+
+                }
+            }
+        }
+    }
+
+    @Override
+    public void endOfData() {
+
+        //Redraw one final time and use those values to print out the outlying crystals
+        redraw();
+        printOutliers();
+        //plotterFrame.dispose();
+        if (calWindow > 0) {
+            for (int crate = 1; crate < 3; crate++) {
+                for (short slot = 0; slot < 20; slot++) {
+                    for (short ch = 0; ch < 16; ch++) {
+                        Long id = EcalConditions.daqToPhysicalID(crate, slot, ch);
+                        IIdentifierHelper helper = EcalConditions.getHelper();
+                        if (id == null) {
+                            continue;
+                        }
+                        IIdentifier compactId = new Identifier(id);
+                        int x = helper.getValue(compactId, "ix");
+                        int y = helper.getValue(compactId, "iy");
+                        System.out.printf("%d\t%d\t%d\t%f\t%f\n", crate, slot, ch, aPlots[x + 23][y + 5].mean(), aPlots[x + 23][y + 5].rms());
+                    }
+                }
+            }
+        }
+        try {
+            closeFile();
+        } catch (IOException ex) {
+            Logger.getLogger(EcalCrystalFilter.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+
+        if (!"".equals(outputPlotFileName)) {
+            try {
+                aida.saveAs(outputPlotFileName);
+            } catch (IOException ex) {
+                Logger.getLogger(EcalCrystalFilter.class.getName()).log(Level.SEVERE, "Couldn't save aida plots to file " + outputPlotFileName, ex);
+            }
+        }
+        //displayFastTrackingPlots();
+
+    }
+
+//    @Override
+    public void reset() {
+        if (plotter != null) {
+            plotter.hide();
+            plotter.destroyRegions();
+            for (int x = -23; x <= 23; x++) { // slot
+                for (int y = -5; y <= 5; y++) { // crate                
+                    aPlots[x + 23][y + 5].reset();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void process(EventHeader event) {
+
+        getTrigger(event);
+
+
+        boolean isTop, isBot, isAll;
+        for (int iside = 0; iside < 3; ++iside) {
+
+            isAll = false;
+            isTop = false;
+            isBot = false;
+
+            //System.out.println("Side " + iside + " Trigger " + _trigger[0] + "," + _trigger[1]);
+
+
+            if (iside == 0) {
+                isAll = true;
+            } else if (iside == 1) {
+                if (_trigger[0] > 0) {
+                    isTop = true;
+                } else {
+                    continue;
+                }
+            } else if (iside == 2) {
+                if (_trigger[1] > 0) {
+                    isBot = true;
+                } else {
+                    continue;
+                }
+            }
+
+            //System.out.println("isTop " + isTop + " isBot " + isBot);
+
+
+            if (event.hasCollection(RawTrackerHit.class, inputCollection)) {
+                List<RawTrackerHit> hits = event.get(RawTrackerHit.class, inputCollection);
+                for (RawTrackerHit hit : hits) {
+
+
+                    int y = hit.getIdentifierFieldValue("iy");
+                    int x = hit.getIdentifierFieldValue("ix");
+
+
+                    //System.out.println("RawTrackerHit: iside " + iside + " trigger " + _trigger[0] + ", " + _trigger[1] + " isBot " + isBot + " isTop " + isTop + " iy " + y);
+
+                    //Only look at the opposite half of what triggered
+                    if (isAll) {
+                    }
+                    if (isTop) {
+                        if (y > 0) {
+                            continue;
+                        }
+                    }
+                    if (isBot) {
+                        if (y < 0) {
+                            continue;
+                        }
+                    }
+
+
+
+                    //System.out.println("RawTrackerHit: ===> fill");
+
+
+                    if (calWindow > 0) {
+                        for (int i = 0; i < calWindow; i++) {
+
+                            if (isAll) {
+                                aPlots[x + 23][y + 5].fill(hit.getADCValues()[i]);
+                            }
+                            if (isTop) {
+                                aTPlots[x + 23][y + 5].fill(hit.getADCValues()[i]);
+                            }
+                            if (isBot) {
+                                aBPlots[x + 23][y + 5].fill(hit.getADCValues()[i]);
+                            }
+
+                        }
+                    } else {
+                        for (int i = 0; i < hit.getADCValues().length; i++) {
+                            if (isAll) {
+                                aPlots[x + 23][y + 5].fill(hit.getADCValues()[i]);
+                            }
+                            if (isTop) {
+                                aTPlots[x + 23][y + 5].fill(hit.getADCValues()[i]);
+                            }
+                            if (isBot) {
+                                aBPlots[x + 23][y + 5].fill(hit.getADCValues()[i]);
+                            }
+
+                        }
+                    }
+                }
+                if (eventRefreshRate > 0 && ++eventn % eventRefreshRate == 0) {
+                    redraw();
+                }
+            }
+
+            if (event.hasCollection(BaseRawCalorimeterHit.class, inputCollection)) {
+                List<BaseRawCalorimeterHit> hits = event.get(BaseRawCalorimeterHit.class, inputCollection);
+                for (BaseRawCalorimeterHit hit : hits) {
+                    int x = hit.getIdentifierFieldValue("ix");
+                    int y = hit.getIdentifierFieldValue("iy");
+
+
+                    //System.out.println("BaseRawCalorimeterHit: iside " + iside + " trigger " + _trigger[0] + ", " + _trigger[1] + " isBot " + isBot + " isTop " + isTop + " iy " + y );
+
+                    //Only look at the opposite half of what triggered
+                    if (isAll) {
+                    }
+                    if (isTop) {
+                        if (y > 0) {
+                            continue;
+                        }
+                    }
+                    if (isBot) {
+                        if (y < 0) {
+                            continue;
+                        }
+                    }
+
+                    //System.out.println("BaseRawCalorimeterHit: ===> fill");
+
+
+                    if (isAll) {
+                        aPlots[x + 23][y + 5].fill(hit.getAmplitude());
+                        tPlots[x + 23][y + 5].fill(hit.getTimeStamp() / 64);
+                    }
+                    if (isTop) {
+                        aTPlots[x + 23][y + 5].fill(hit.getAmplitude());
+                        tTPlots[x + 23][y + 5].fill(hit.getTimeStamp() / 64);
+                    }
+                    if (isBot) {
+                        aBPlots[x + 23][y + 5].fill(hit.getAmplitude());
+                        tBPlots[x + 23][y + 5].fill(hit.getTimeStamp() / 64);
+                    }
+
+                }
+                if (eventRefreshRate > 0 && ++eventn % eventRefreshRate == 0) {
+                    redraw();
+                }
+            }
+
+            if (event.hasCollection(CalorimeterHit.class, inputCollection)) {
+                List<CalorimeterHit> hits = event.get(CalorimeterHit.class, inputCollection);
+                for (CalorimeterHit hit : hits) {
+                    int x = hit.getIdentifierFieldValue("ix");
+                    int y = hit.getIdentifierFieldValue("iy");
+
+                    //System.out.println("CalorimeterHit: iside " + iside + " trigger " + _trigger[0] + ", " + _trigger[1] + " isBot " + isBot + " isTop " + isTop + " iy " + y );
+
+
+
+                    //Only look at the opposite half of what triggered
+                    if (isAll) {
+                    }
+                    if (isTop) {
+                        if (y > 0) {
+                            continue;
+                        }
+                    }
+                    if (isBot) {
+                        if (y < 0) {
+                            continue;
+                        }
+                    }
+
+                    //System.out.println("CalorimeterHit: ===> fill");
+                    if (isAll) {
+                        aPlots[x + 23][y + 5].fill(hit.getRawEnergy());
+                        tPlots[x + 23][y + 5].fill(hit.getTime() / 4.0);
+                    }
+                    if (isTop) {
+                        aTPlots[x + 23][y + 5].fill(hit.getRawEnergy());
+                        tTPlots[x + 23][y + 5].fill(hit.getTime() / 4.0);
+                    }
+                    if (isBot) {
+                        aBPlots[x + 23][y + 5].fill(hit.getRawEnergy());
+                        tBPlots[x + 23][y + 5].fill(hit.getTime() / 4.0);
+                    }
+                }
+                if (eventRefreshRate > 0 && ++eventn % eventRefreshRate == 0) {
+                    redraw();
+                }
+            }
+        }
+    }
+
+    private void clearTrigger() {
+        _trigger[0] = 0;
+        _trigger[1] = 0;
+    }
+
+    private void getTrigger(EventHeader event) {
+
+        clearTrigger();
+        if (event.hasCollection(TriggerData.class, "TriggerBank")) {
+            List<TriggerData> triggerDataList = event.get(TriggerData.class, "TriggerBank");
+            if (triggerDataList.isEmpty() == false) {
+                TriggerData triggerData = triggerDataList.get(0);
+                int topTrig = triggerData.getTopTrig();
+                int botTrig = triggerData.getBotTrig();
+                _trigger[0] = topTrig > 0 ? 1 : 0;
+                _trigger[1] = botTrig > 0 ? 1 : 0;
+            } else {
+                System.out.println("Event has EMPTY trigger list!!");
+                _trigger[0] = 0;
+                _trigger[1] = 0;
+            }
+        } else {
+            System.out.println("Event has NO trigger bank!!");
+            _trigger[0] = 0;
+            _trigger[1] = 0;
+        }
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent ae) {
+        if (ae.getSource() == blankButton) {
+            plotter.region(2).clear();
+            plotter2.region(2).clear();
+            /*
+            plotter3.region(2).clear();
+            plotter4.region(2).clear();
+            
+            
+             */
+        } else if (ae.getSource() == blankButtonTop) {
+            plotterTop.region(2).clear();
+            plotterTop2.region(2).clear();
+
+        } else if (ae.getSource() == blankButtonBot) {
+            plotterBot.region(2).clear();
+            plotterBot2.region(2).clear();
+
+        } else {
+            Integer x, y;
+            x = (Integer) xCombo.getSelectedItem();
+            y = (Integer) yCombo.getSelectedItem();
+            plotter.region(2).clear();
+            plotter.region(2).plot(aPlots[x + 23][y + 5]);
+            plotter2.region(2).clear();
+            plotter2.region(2).plot(tPlots[x + 23][y + 5]);
+//            ((PlotterRegion) plotter.region(2)).getPlot().setAllowUserInteraction(false);
+//            ((PlotterRegion) plotter.region(2)).getPlot().setAllowPopupMenus(false);
+            plotter3.region(2).clear();
+            plotter3.region(2).plot(tPlots[x + 23][y + 5]);
+            plotter4.region(2).clear();
+            plotter4.region(2).plot(aPlots[x + 23][y + 5]);
+
+
+            //Integer x, y;
+            x = (Integer) xComboTop.getSelectedItem();
+            y = (Integer) yComboTop.getSelectedItem();
+
+            plotterTop.region(2).clear();
+            plotterTop.region(2).plot(aTPlots[x + 23][y + 5]);
+            plotterTop2.region(2).clear();
+            plotterTop2.region(2).plot(tTPlots[x + 23][y + 5]);
+//            ((PlotterRegion) plotter.region(2)).getPlot().setAllowUserInteraction(false);
+//            ((PlotterRegion) plotter.region(2)).getPlot().setAllowPopupMenus(false);
+            plotterTop3.region(2).clear();
+            plotterTop3.region(2).plot(tTPlots[x + 23][y + 5]);
+            plotterTop4.region(2).clear();
+            plotterTop4.region(2).plot(aTPlots[x + 23][y + 5]);
+
+            //Integer x, y;
+            x = (Integer) xComboBot.getSelectedItem();
+            y = (Integer) yComboBot.getSelectedItem();
+
+            plotterBot.region(2).clear();
+            plotterBot.region(2).plot(aBPlots[x + 23][y + 5]);
+            plotterBot2.region(2).clear();
+            plotterBot2.region(2).plot(tBPlots[x + 23][y + 5]);
+//            ((PlotterRegion) plotter.region(2)).getPlot().setAllowUserInteraction(false);
+//            ((PlotterRegion) plotter.region(2)).getPlot().setAllowPopupMenus(false);
+            plotterBot3.region(2).clear();
+            plotterBot3.region(2).plot(tBPlots[x + 23][y + 5]);
+            plotterBot4.region(2).clear();
+            plotterBot4.region(2).plot(aBPlots[x + 23][y + 5]);
+
+        }
+    }
+
+    public void redraw() {
+//        aSigmaPlot.reset();
+//        aMeanPlot.reset();
+//        tSigmaPlot.reset();
+//        tMeanPlot.reset();
+        aTOutSigmaPlot.reset();
+        aTOutMeanPlot.reset();
+        tTOutSigmaPlot.reset();
+        tTOutMeanPlot.reset();
+        aTTOutSigmaPlot.reset();
+        aTTOutMeanPlot.reset();
+        tTTOutSigmaPlot.reset();
+        tTTOutMeanPlot.reset();
+        aBTOutSigmaPlot.reset();
+        aBTOutMeanPlot.reset();
+        tBTOutSigmaPlot.reset();
+        tBTOutMeanPlot.reset();
+        for (int x = -23; x <= 23; x++) { // slot
+            for (int y = -5; y <= 5; y++) { // crate   
+                //System.out.println("redraw x,y " + x + "," + y + " tT " + tTPlots[x + 23][y + 5].entries() + " tB " + tBPlots[x + 23][y + 5].entries());
+                if (aPlots[x + 23][y + 5].entries() > 10) {
+                    aSigmaPlot.fill(aPlots[x + 23][y + 5].rms());
+                    aMeanPlot.fill(aPlots[x + 23][y + 5].mean());
+
+                }
+                if (aTPlots[x + 23][y + 5].entries() > 10) {
+
+                    aTSigmaPlot.fill(aTPlots[x + 23][y + 5].rms());
+                    aTMeanPlot.fill(aTPlots[x + 23][y + 5].mean());
+
+                }
+                if (aBPlots[x + 23][y + 5].entries() > 10) {
+
+
+                    aBSigmaPlot.fill(aBPlots[x + 23][y + 5].rms());
+                    aBMeanPlot.fill(aBPlots[x + 23][y + 5].mean());
+                }
+
+                if (tPlots[x + 23][y + 5].entries() > 10) {
[truncated at 1000 lines; 51 more skipped]

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalEdepToTriggerConverterDriver.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalEdepToTriggerConverterDriver.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalEdepToTriggerConverterDriver.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,208 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.geometry.Detector;
+import org.lcsim.hps.util.RandomGaussian;
+import org.lcsim.util.Driver;
+
+/**
+ *
+ * @version $Id: HPSEcalRawConverterDriver.java,v 1.2 2012/05/03 00:17:54
+ * phansson Exp $
+ */
+public class EcalEdepToTriggerConverterDriver extends Driver {
+
+    private String ecalReadoutName = "EcalHits";
+    private String inputCollection = "EcalHits";
+    private String readoutCollection = "EcalCalHits";
+    private String triggerCollection = "EcalTriggerHits";
+    private boolean applyBadCrystalMap = true;
+    private double tp = 14.0;
+    private double readoutPeriod = 4.0;
+    private int readoutThreshold = 50;
+    private int triggerThreshold = 80;
+    private int truncateScale = 128;
+    private double pulseIntegral = tp * Math.E / readoutPeriod;
+    private double gainScale = 1.0; //gain miscalibration factor
+    private double _gain = -1.0; //constant gain, activated if >0
+    private boolean addNoise = false;
+    private double pePerMeV = 2.0; //photoelectrons per MeV, used to calculate noise
+
+    public EcalEdepToTriggerConverterDriver() {
+    }
+
+    public void setTp(double tp) {
+        this.tp = tp;
+    }
+
+    public void setAddNoise(boolean addNoise) {
+        this.addNoise = addNoise;
+    }
+
+    public void setReadoutCollection(String readoutCollection) {
+        this.readoutCollection = readoutCollection;
+    }
+
+    public void setTriggerCollection(String triggerCollection) {
+        this.triggerCollection = triggerCollection;
+    }
+
+    public void setInputCollection(String inputCollection) {
+        this.inputCollection = inputCollection;
+    }
+
+    public void setApplyBadCrystalMap(boolean apply) {
+        this.applyBadCrystalMap = apply;
+    }
+
+    public void setTruncateScale(int truncateScale) {
+        this.truncateScale = truncateScale;
+    }
+
+    public void setConstantGain(double gain) {
+        this._gain = gain;
+    }
+
+    @Override
+    public void startOfData() {
+        if (readoutCollection == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+    }
+
+    public boolean isBadCrystal(CalorimeterHit hit) {
+        return EcalConditions.badChannelsLoaded() ? EcalConditions.isBadChannel(hit.getCellID()) : false;
+    }
+
+    @Override
+    public void process(EventHeader event) {
+        ArrayList<CalorimeterHit> triggerHits = new ArrayList<CalorimeterHit>();
+        ArrayList<CalorimeterHit> readoutHits = new ArrayList<CalorimeterHit>();
+
+        if (event.hasCollection(CalorimeterHit.class, inputCollection)) {
+            List<CalorimeterHit> hits = event.get(CalorimeterHit.class, inputCollection);
+
+            for (CalorimeterHit hit : hits) {
+                if (applyBadCrystalMap && isBadCrystal(hit)) {
+                    continue;
+                }
+                double amplitude = hitAmplitude(hit);
+                CalorimeterHit triggerHit = makeTriggerHit(hit, amplitude);
+                if (triggerHit != null) {
+                    triggerHits.add(triggerHit);
+//                    System.out.format("trigger hit: %f %f\n", amplitude, triggerHit.getRawEnergy());
+                }
+                CalorimeterHit readoutHit = makeReadoutHit(hit, amplitude);
+                if (readoutHit != null) {
+                    readoutHits.add(readoutHit);
+//                    System.out.format("readout hit: %f %f\n", amplitude, readoutHit.getRawEnergy());
+                }
+            }
+        }
+        int flags = 0;
+        event.put(triggerCollection, triggerHits, CalorimeterHit.class, flags, ecalReadoutName);
+        event.put(readoutCollection, readoutHits, CalorimeterHit.class, flags, ecalReadoutName);
+    }
+
+    public CalorimeterHit makeTriggerHit(CalorimeterHit hit, double amplitude) {
+
+//        double time = readoutPeriod * (Math.random() - 1);
+        double time = 0 - hit.getTime();
+        double triggerIntegral = 0;
+        boolean overThreshold = false;
+        while (true) {
+            double currentValue = amplitude * pulseAmplitude(time);
+            if (!overThreshold && currentValue > triggerThreshold) {
+                overThreshold = true;
+            }
+            if (overThreshold) {
+                triggerIntegral += amplitude * pulseAmplitude(time);
+                if (currentValue < triggerThreshold) {
+                    break;
+                }
+            }
+            time += readoutPeriod;
+
+            if (time > 200.0) {
+                break;
+            }
+        }
+
+//        System.out.format("trigger: %f %f\n", amplitude, triggerIntegral);
+
+        int truncatedIntegral = (int) Math.floor(triggerIntegral / truncateScale);
+        if (truncatedIntegral > 0) {
+            return new HPSCalorimeterHit(truncatedIntegral, hit.getTime(), hit.getCellID(), 0);
+        }
+        return null;
+    }
+
+    public CalorimeterHit makeReadoutHit(CalorimeterHit hit, double amplitude) {
+        if (amplitude < readoutThreshold) {
+            return null;
+        }
+
+//        double integral = hit.getRawEnergy()/ECalUtils.GeV * gainScale;
+        double gain = _gain > 0 ? _gain : EcalConditions.physicalToGain(hit.getCellID());
+        double integral = amplitude * gain * pulseIntegral * gainScale * ECalUtils.MeV / ECalUtils.GeV;
+
+//        double thresholdCrossingTime = 0 - hit.getTime();
+//        while (true) {
+//            double currentValue = amplitude * pulseAmplitude(thresholdCrossingTime);
+//            if (currentValue > readoutThreshold) {
+//                break;
+//            }
+//            thresholdCrossingTime += readoutPeriod;
+//
+//            if (thresholdCrossingTime > 200.0) {
+//                break;
+//            }
+//        }
+//
+//        double readoutIntegral = 0;
+//        for (int i = 0; i < 35; i++) {
+//            readoutIntegral += amplitude * pulseAmplitude(thresholdCrossingTime + (i - 5) * readoutPeriod);
+//        }
+////        double integral = readoutIntegral * HPSEcalConditions.physicalToGain(id);
+//        System.out.format("dumb: %f, full: %f\n",hit.getRawEnergy() * 1000.0,readoutIntegral * HPSEcalConditions.physicalToGain(id));
+
+//        System.out.format("readout: %f %f\n", amplitude, integral);
+        CalorimeterHit h = new HPSCalorimeterHit(integral, hit.getTime(), hit.getCellID(), 0);
+        return h;
+    }
+
+    private double hitAmplitude(CalorimeterHit hit) {
+        double energyAmplitude = hit.getRawEnergy();
+        if (addNoise) {
+            //add preamp noise and photoelectron Poisson noise in quadrature
+            double noise = Math.sqrt(Math.pow(EcalConditions.physicalToNoise(hit.getCellID()) * EcalConditions.physicalToGain(hit.getCellID()) * ECalUtils.MeV, 2) + hit.getRawEnergy() * ECalUtils.MeV / pePerMeV);
+            energyAmplitude += RandomGaussian.getGaussian(0, noise);
+        }
+
+        double gain = _gain > 0 ? _gain : EcalConditions.physicalToGain(hit.getCellID());
+//        System.out.format("amplitude: %f %f %f %f\n", hit.getRawEnergy(), energyAmplitude, gain, (energyAmplitude / ECalUtils.MeV) / (gain * pulseIntegral));
+        return (energyAmplitude / ECalUtils.MeV) / (gain * pulseIntegral);
+    }
+
+    private double pulseAmplitude(double time) {
+        if (time <= 0.0) {
+            return 0.0;
+        }
+        if (tp > 0.0) {
+            return (time / tp) * Math.exp(1.0 - time / tp);
+        } else {
+            if (time < -tp) {
+                return 1.0;
+            } else {
+                return 0.0;
+            }
+        }
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalRawConverter.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,117 @@
+package org.hps.recon.ecal;
+
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.RawCalorimeterHit;
+import org.lcsim.event.RawTrackerHit;
+import org.lcsim.event.base.BaseRawCalorimeterHit;
+
+/**
+ *
+ * @version $Id: HPSEcalRawConverterDriver.java,v 1.2 2012/05/03 00:17:54
+ * phansson Exp $
+ */
+public class EcalRawConverter {
+
+    private boolean debug = false;
+    private boolean constantGain = false;
+    private double gain;
+    private boolean use2014Gain = false;
+
+    public EcalRawConverter() {
+    }
+
+    public void setGain(double gain) {
+        constantGain = true;
+        this.gain = gain;
+    }
+
+    public void setUse2014Gain(boolean use2014Gain) {
+        this.use2014Gain = use2014Gain;
+    }
+
+    private short sumADC(RawTrackerHit hit) {
+        //Sum all pedestal subtracted ADC values 
+        //return scale * (amplitude + 0.5) + pedestal;
+        if (debug) {
+            System.out.println("Summing ADC for hit: " + hit.toString());
+        }
+        double pedestal = EcalConditions.physicalToPedestal(hit.getCellID());
+        short sum = 0;
+        short samples[] = hit.getADCValues();
+        for (int isample = 0; isample < samples.length; ++isample) {
+            sum += (samples[isample] - pedestal);
+            if (debug) {
+                System.out.println("Sample " + isample + " " + samples[isample] + " pedestal " + pedestal + " (" + sum + ")");
+            }
+        }
+        return sum;
+    }
+
+    public CalorimeterHit HitDtoA(RawTrackerHit hit) {
+        double time = hit.getTime();
+        long id = hit.getCellID();
+        double rawEnergy = adcToEnergy(sumADC(hit), id);      
+//        double[] pos = hit.getDetectorElement().getGeometry().getPosition().v();
+        CalorimeterHit h = new HPSCalorimeterHit(rawEnergy + 0.0000001, time, id, 0);
+        //+0.0000001 is a horrible hack to ensure rawEnergy!=BaseCalorimeterHit.UNSET_CORRECTED_ENERGY
+        return h;
+    }
+
+    public CalorimeterHit HitDtoA(RawCalorimeterHit hit, int window) {
+        if (hit.getTimeStamp() % 64 != 0) {
+            System.out.println("unexpected timestamp " + hit.getTimeStamp());
+        }
+        double time = hit.getTimeStamp() / 16.0;
+        long id = hit.getCellID();
+        double adcSum = hit.getAmplitude() - window * EcalConditions.physicalToPedestal(id);
+        double rawEnergy = adcToEnergy(adcSum, id);  
+        CalorimeterHit h = new HPSCalorimeterHit(rawEnergy + 0.0000001, time, id, 0);
+        //+0.0000001 is a horrible hack to ensure rawEnergy!=BaseCalorimeterHit.UNSET_CORRECTED_ENERGY
+        return h;
+    }
+
+    public RawCalorimeterHit HitAtoD(CalorimeterHit hit, int window) {
+        int time = (int) (Math.round(hit.getTime() / 4.0) * 64.0);
+        long id = hit.getCellID();
+        int amplitude;
+        if (constantGain) {
+            amplitude = (int) Math.round((hit.getRawEnergy() / ECalUtils.MeV) / gain + window * EcalConditions.physicalToPedestal(id));
+        } else {
+            amplitude = (int) Math.round((hit.getRawEnergy() / ECalUtils.MeV) / EcalConditions.physicalToGain(id) + window * EcalConditions.physicalToPedestal(id));
+        }
+        RawCalorimeterHit h = new BaseRawCalorimeterHit(id, amplitude, time);
+        return h;
+    }
+
+    /*
+     * return energy (units of GeV) corresponding to the ADC sum and crystal ID
+     */
+    private double adcToEnergy(double adcSum, long cellID) {
+        if (use2014Gain) {
+            if (constantGain) {
+                return adcSum * ECalUtils.gainFactor * ECalUtils.ecalReadoutPeriod;
+            } else {
+                return EcalConditions.physicalToGain(cellID) * adcSum * ECalUtils.gainFactor * ECalUtils.ecalReadoutPeriod; // should not be used for the moment (2014/02)
+            }
+        } else {
+            if (constantGain) {
+                return gain * adcSum * ECalUtils.MeV;
+            } else {
+                return EcalConditions.physicalToGain(cellID) * adcSum * ECalUtils.MeV; //gain is defined as MeV/integrated ADC
+            }
+        }
+    }
+    /*
+     public static CalorimeterHit HitDtoA(RawCalorimeterHit hit, int window, double g) {
+     if (hit.getTimeStamp() % 64 != 0) {
+     System.out.println("unexpected timestamp " + hit.getTimeStamp());
+     }
+     double time = hit.getTimeStamp() / 16.0;
+     long id = hit.getCellID();
+     double rawEnergy = g * (hit.getAmplitude() - window * EcalConditions.physicalToPedestal(id)) * ECalUtils.MeV;
+     CalorimeterHit h = new HPSCalorimeterHit(rawEnergy + 0.0000001, time, id, 0);
+     //+0.0000001 is a horrible hack to ensure rawEnergy!=BaseCalorimeterHit.UNSET_CORRECTED_ENERGY
+     return h;
+     }
+     */
+}
\ No newline at end of file

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalRawConverterDriver.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverterDriver.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverterDriver.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,165 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.RawCalorimeterHit;
+import org.lcsim.event.RawTrackerHit;
+import org.lcsim.geometry.Detector;
+import org.lcsim.util.Driver;
+import org.lcsim.lcio.LCIOConstants;
+
+/**
+ *
+ * @version $Id: HPSEcalRawConverterDriver.java,v 1.2 2012/05/03 00:17:54
+ * phansson Exp $
+ */
+public class EcalRawConverterDriver extends Driver {
+
+    EcalRawConverter converter = null;
+    String rawCollectionName = "EcalReadoutHits";
+    String ecalReadoutName = "EcalHits";
+    String ecalCollectionName = "EcalCalHits";
+    int integralWindow = 35;
+    boolean debug = false;
+    double threshold = Double.NEGATIVE_INFINITY;
+    boolean applyBadCrystalMap = true;
+    boolean dropBadFADC = false;
+    private boolean runBackwards = false;
+
+    public EcalRawConverterDriver() {
+        converter = new EcalRawConverter();
+    }
+
+    public void setUse2014Gain(boolean use2014Gain) {
+        converter.setUse2014Gain(use2014Gain);
+    }
+
+    public void setRunBackwards(boolean runBackwards) {
+        this.runBackwards = runBackwards;
+    }
+
+    public void setDropBadFADC(boolean dropBadFADC) {
+        this.dropBadFADC = dropBadFADC;
+    }
+
+    public void setThreshold(double threshold) {
+        this.threshold = threshold;
+    }
+
+    public void setGain(double gain) {
+        converter.setGain(gain);
+    }
+
+    public void setIntegralWindow(int integralWindow) {
+        this.integralWindow = integralWindow;
+    }
+
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    public void setRawCollectionName(String rawCollectionName) {
+        this.rawCollectionName = rawCollectionName;
+    }
+
+    public void setApplyBadCrystalMap(boolean apply) {
+        this.applyBadCrystalMap = apply;
+    }
+
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+    }
+
+    @Override
+    public void startOfData() {
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+    }
+
+    public static boolean isBadCrystal(CalorimeterHit hit) {
+        return EcalConditions.badChannelsLoaded() ? EcalConditions.isBadChannel(hit.getCellID()) : false;
+    }
+
+    public static boolean isBadFADC(CalorimeterHit hit) {
+        long daqID = EcalConditions.physicalToDaqID(hit.getCellID());
+        return (EcalConditions.getCrate(daqID) == 1 && EcalConditions.getSlot(daqID) == 3);
+    }
+
+    @Override
+    public void process(EventHeader event) {
+        int flags = 0;
+        flags += 1 << LCIOConstants.RCHBIT_TIME; //store cell ID
+
+        if (!runBackwards) {
+            ArrayList<CalorimeterHit> newHits = new ArrayList<CalorimeterHit>();
+
+            // Get the list of ECal hits.
+            if (event.hasCollection(RawTrackerHit.class, rawCollectionName)) {
+                List<RawTrackerHit> hits = event.get(RawTrackerHit.class, rawCollectionName);
+
+                for (RawTrackerHit hit : hits) {
+                    CalorimeterHit newHit = converter.HitDtoA(hit);
+                    if (applyBadCrystalMap && isBadCrystal(newHit)) {
+                        continue;
+                    }
+                    if (dropBadFADC && isBadFADC(newHit)) {
+                        continue;
+                    }
+                    if (newHit.getRawEnergy() > threshold) {
+                        newHits.add(newHit);
+                    }
+                }
+                event.put(ecalCollectionName, newHits, CalorimeterHit.class, flags, ecalReadoutName);
+            }
+            if (event.hasCollection(RawCalorimeterHit.class, rawCollectionName)) {
+                List<RawCalorimeterHit> hits = event.get(RawCalorimeterHit.class, rawCollectionName);
+
+                for (RawCalorimeterHit hit : hits) {
+                    if (debug) {
+                        System.out.format("old hit energy %d\n", hit.getAmplitude());
+                    }
+                    CalorimeterHit newHit = converter.HitDtoA(hit, integralWindow);
+                    if (newHit.getRawEnergy() > threshold) {
+                        if (applyBadCrystalMap && isBadCrystal(newHit)) {
+                            continue;
+                        }
+                        if (dropBadFADC && isBadFADC(newHit)) {
+                            continue;
+                        }
+                        if (debug) {
+                            System.out.format("new hit energy %f\n", newHit.getRawEnergy());
+                        }
+                        newHits.add(newHit);
+                    }
+                }
+                event.put(ecalCollectionName, newHits, CalorimeterHit.class, flags, ecalReadoutName);
+            }
+        } else {
+            ArrayList<RawCalorimeterHit> newHits = new ArrayList<RawCalorimeterHit>();
+            if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) {
+                List<CalorimeterHit> hits = event.get(CalorimeterHit.class, ecalCollectionName);
+
+                for (CalorimeterHit hit : hits) {
+                    if (debug) {
+                        System.out.format("old hit energy %f\n", hit.getRawEnergy());
+                    }
+                    RawCalorimeterHit newHit = converter.HitAtoD(hit, integralWindow);
+                    if (newHit.getAmplitude() > 0) {
+                        if (debug) {
+                            System.out.format("new hit energy %d\n", newHit.getAmplitude());
+                        }
+                        newHits.add(newHit);
+                    }
+                }
+                event.put(rawCollectionName, newHits, RawCalorimeterHit.class, flags, ecalReadoutName);
+            }
+        }
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalReadoutToTriggerConverterDriver.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalReadoutToTriggerConverterDriver.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalReadoutToTriggerConverterDriver.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,189 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.base.BaseRawCalorimeterHit;
+import org.lcsim.geometry.Detector;
+import org.lcsim.util.Driver;
+
+/**
+ *
+ * @version $Id: HPSEcalRawConverterDriver.java,v 1.2 2012/05/03 00:17:54
+ * phansson Exp $
+ */
+public class EcalReadoutToTriggerConverterDriver extends Driver {
+
+    String rawCollectionName = "EcalReadoutHits";
+    String ecalReadoutName = "EcalHits";
+    String ecalCollectionName = "EcalCalHits";
+    int integralWindow = 30;
+    boolean debug = false;
+    double threshold = Double.NEGATIVE_INFINITY;
+    boolean applyBadCrystalMap = true;
+    boolean dropBadFADC = false;
+    double tp = 14.0;
+    double readoutPeriod = 4.0;
+    private int readoutThreshold = 50;
+    private int triggerThreshold = 80;
+    private double timeShift = 0;
+    private int truncateScale = 128;
+
+    public EcalReadoutToTriggerConverterDriver() {
+    }
+
+    public void setTp(double tp) {
+        this.tp = tp;
+    }
+
+    public void setDropBadFADC(boolean dropBadFADC) {
+        this.dropBadFADC = dropBadFADC;
+    }
+
+    public void setThreshold(double threshold) {
+        this.threshold = threshold;
+    }
+
+    public void setIntegralWindow(int integralWindow) {
+        this.integralWindow = integralWindow;
+    }
+
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    public void setRawCollectionName(String rawCollectionName) {
+        this.rawCollectionName = rawCollectionName;
+    }
+
+    public void setApplyBadCrystalMap(boolean apply) {
+        this.applyBadCrystalMap = apply;
+    }
+
+    public void setTruncateScale(int truncateScale) {
+        this.truncateScale = truncateScale;
+    }
+
+    @Override
+    public void startOfData() {
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+    }
+
+    public boolean isBadCrystal(CalorimeterHit hit) {
+        return EcalConditions.badChannelsLoaded() ? EcalConditions.isBadChannel(hit.getCellID()) : false;
+    }
+
+    public boolean isBadFADC(CalorimeterHit hit) {
+        long daqID = EcalConditions.physicalToDaqID(hit.getCellID());
+        return (EcalConditions.getCrate(daqID) == 1 && EcalConditions.getSlot(daqID) == 3);
+    }
+
+    @Override
+    public void process(EventHeader event) {
+        ArrayList<CalorimeterHit> newHits = new ArrayList<CalorimeterHit>();
+
+        if (event.hasCollection(BaseRawCalorimeterHit.class, rawCollectionName)) {
+            List<BaseRawCalorimeterHit> hits = event.get(BaseRawCalorimeterHit.class, rawCollectionName);
+
+            for (BaseRawCalorimeterHit hit : hits) {
+                CalorimeterHit newHit = HitDtoA(hit, integralWindow);
+                if (newHit != null && newHit.getRawEnergy() > threshold) {
+                    if (applyBadCrystalMap && isBadCrystal(newHit)) {
+                        continue;
+                    }
+                    if (dropBadFADC && isBadFADC(newHit)) {
+                        continue;
+                    }
+                    newHits.add(newHit);
+                }
+            }
+        }
+        int flags = 0;
+        event.put(ecalCollectionName, newHits, CalorimeterHit.class, flags, ecalReadoutName);
+    }
+
+    public CalorimeterHit HitDtoA(BaseRawCalorimeterHit hit, int window) {
+        double integral = tp * Math.E / readoutPeriod;
+        double readoutIntegral = (hit.getAmplitude() - window * EcalConditions.physicalToPedestal(hit.getCellID()));
+        double amplitude = readoutIntegral / integral;
+
+//        double time = readoutPeriod * (Math.random() - 1);
+        double time = 0 - timeShift;
+        timeShift += 0.01;
+        if (timeShift > readoutPeriod) {
+            timeShift = 0;
+        }
+        double triggerIntegral = 0;
+        boolean overThreshold = false;
+//        double readoutTime = -1;
+//        double triggerTime = -1;
+        while (true) {
+            double currentValue = amplitude * pulseAmplitude(time);
+//            if (readoutTime < 0 && currentValue > readoutThreshold) {
+//                readoutTime = time;
+//            }
+            if (!overThreshold && currentValue > triggerThreshold) {
+                overThreshold = true;
+//                triggerTime = time;
+            }
+            if (overThreshold) {
+                triggerIntegral += amplitude * pulseAmplitude(time);
+                if (currentValue < triggerThreshold) {
+                    break;
+                }
+            }
+            time += readoutPeriod;
+
+            if (time > 200.0) {
+                break;
+            }
+        }
+
+//        System.out.format("%f %f %f\n", readoutIntegral, amplitude, triggerIntegral);
+
+        if (hit.getTimeStamp() % 64 != 0) {
+            System.out.println("unexpected timestamp " + hit.getTimeStamp());
+        }
+        int truncatedIntegral = (int) Math.floor(triggerIntegral / truncateScale) * truncateScale;
+        double hitTime = hit.getTimeStamp() / 16.0;
+//        if (readoutTime >= 0 && triggerTime >= 0) {
+//            hitTime += triggerTime - readoutTime;
+//        }
+        long id = hit.getCellID();
+//                Hep3Vector pvec = hit.getDetectorElement().getGeometry().getPosition();
+//                double [] pos = new double[3];
+//                pos[0] = pvec.x();
+//                pos[1] = pvec.y();
+//                pos[2] = pvec.z();
+//        if (truncatedIntegral<=0) return null;
+        if (truncatedIntegral <= 0) {
+            truncatedIntegral = 0;
+        }
+        CalorimeterHit h = new HPSCalorimeterHit(truncatedIntegral, hitTime, id, 0);
+//        CalorimeterHit h = new HPSRawCalorimeterHit(triggerIntegral + 0.0000001, hit.getPosition(), hitTime, id, 0);
+        //+0.0000001 is a horrible hack to ensure rawEnergy!=BaseCalorimeterHit.UNSET_CORRECTED_ENERGY
+        return h;
+    }
+
+    private double pulseAmplitude(double time) {
+        if (time <= 0.0) {
+            return 0.0;
+        }
+        if (tp > 0.0) {
+            return (time / tp) * Math.exp(1.0 - time / tp);
+        } else {
+            if (time < -tp) {
+                return 1.0;
+            } else {
+                return 0.0;
+            }
+        }
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
EcalTriggerFilterDriver.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalTriggerFilterDriver.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/EcalTriggerFilterDriver.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,108 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.geometry.Detector;
+import org.lcsim.util.Driver;
+
+/**
+ * Changes ECal hit IDs to match what the test run trigger sees.
+ * @version $Id: HPSEcalRawConverterDriver.java,v 1.2 2012/05/03 00:17:54
+ * phansson Exp $
+ */
+public class EcalTriggerFilterDriver extends Driver {
+
+    private String ecalReadoutName = "EcalHits";
+    private String inputCollection = "EcalReadoutHits";
+    private String outputCollection = "EcalCalHits";
+    private int topDelay = 0;
+    private int bottomDelay = 5;
+    private Queue<List<CalorimeterHit>> topHitsQueue = null;
+    private Queue<List<CalorimeterHit>> bottomHitsQueue = null;
+
+    public EcalTriggerFilterDriver() {
+    }
+
+    public void setOutputCollection(String outputCollection) {
+        this.outputCollection = outputCollection;
+    }
+
+    public void setInputCollection(String inputCollection) {
+        this.inputCollection = inputCollection;
+    }
+
+    @Override
+    public void startOfData() {
+        if (outputCollection == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+
+        topHitsQueue = new ArrayBlockingQueue<List<CalorimeterHit>>(topDelay + 1);
+        for (int i = 0; i < topDelay; i++) {
+            topHitsQueue.add(new ArrayList<CalorimeterHit>());
+        }
+        bottomHitsQueue = new ArrayBlockingQueue<List<CalorimeterHit>>(bottomDelay + 1);
+        for (int i = 0; i < bottomDelay; i++) {
+            bottomHitsQueue.add(new ArrayList<CalorimeterHit>());
+        }
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+    }
+
+    @Override
+    public void process(EventHeader event) {
+        if (event.hasCollection(CalorimeterHit.class, inputCollection)) {
+            ArrayList<CalorimeterHit> newHits = new ArrayList<CalorimeterHit>();
+
+            ArrayList<CalorimeterHit> topHits = new ArrayList<CalorimeterHit>();
+            ArrayList<CalorimeterHit> bottomHits = new ArrayList<CalorimeterHit>();
+
+            List<CalorimeterHit> hits = event.get(CalorimeterHit.class, inputCollection);
+            for (CalorimeterHit hit : hits) {
+                CalorimeterHit newHit = filterHit(hit);
+                if (newHit != null) {
+                    if (hit.getIdentifierFieldValue("iy") > 0) { //should really be checking newHit, but it doesn't have metadata yet
+                        topHits.add(newHit);
+                    } else {
+                        bottomHits.add(newHit);
+                    }
+                }
+            }
+            topHitsQueue.add(topHits);
+            bottomHitsQueue.add(bottomHits);
+            newHits.addAll(topHitsQueue.poll());
+            newHits.addAll(bottomHitsQueue.poll());
+            int flags = 0;
+            event.put(outputCollection, newHits, CalorimeterHit.class, flags, ecalReadoutName);
+        }
+    }
+
+    private CalorimeterHit filterHit(CalorimeterHit hit) {
+        int ix = hit.getIdentifierFieldValue("ix");
+        int iy = hit.getIdentifierFieldValue("iy");
+        long daqID = EcalConditions.physicalToDaqID(hit.getCellID());
+        int crate = EcalConditions.getCrate(daqID);
+        short slot = EcalConditions.getSlot(daqID);
+        short channel = EcalConditions.getChannel(daqID);
+
+        int delay = iy>0?topDelay:bottomDelay;
+        // no triggers from crate 1, slot 3 
+        if (crate == 1 && slot == 3) {
+            return null;
+        }
+
+        // flip quadrant
+        if (ix > 0 && iy > 0) {
+            ix = 24 - ix;
+        }
+        long newID = EcalConditions.makePhysicalID(ix, iy);
+        //make new hit; set position to null so it gets recalculated
+        return new HPSCalorimeterHit(hit.getRawEnergy(), hit.getTime()+delay*4, newID, hit.getType());
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
FADCConverterDriver.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/FADCConverterDriver.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/FADCConverterDriver.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,101 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.RawTrackerHit;
+import org.lcsim.event.base.BaseRawCalorimeterHit;
+import org.lcsim.geometry.Detector;
+import org.lcsim.util.Driver;
+
+/**
+ *
+ * @version $Id: FADCConverterDriver.java,v 1.4 2013/02/25 22:39:24 meeg Exp $
+ */
+public class FADCConverterDriver extends Driver {
+
+    String rawCollectionName = "EcalReadoutHits";
+    String ecalReadoutName = "EcalHits";
+    String ecalCollectionName = "EcalIntegralHits";
+    boolean debug = false;
+    int numSamplesBefore = 5;
+    int numSamplesAfter = 30;
+    int threshold = 50;
+
+    public FADCConverterDriver() {
+    }
+
+    public void setThreshold(int threshold) {
+        this.threshold = threshold;
+    }
+
+    public void setEcalReadoutName(String ecalReadoutName) {
+        this.ecalReadoutName = ecalReadoutName;
+    }
+
+    public void setNumSamplesAfter(int numSamplesAfter) {
+        this.numSamplesAfter = numSamplesAfter;
+    }
+
+    public void setNumSamplesBefore(int numSamplesBefore) {
+        this.numSamplesBefore = numSamplesBefore;
+    }
+
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    public void setRawCollectionName(String rawCollectionName) {
+        this.rawCollectionName = rawCollectionName;
+    }
+
+    @Override
+    public void startOfData() {
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+    }
+
+    @Override
+    public void process(EventHeader event) {
+        ArrayList<BaseRawCalorimeterHit> readoutHits = new ArrayList<BaseRawCalorimeterHit>();
+        ArrayList<BaseRawCalorimeterHit> triggerHits = new ArrayList<BaseRawCalorimeterHit>();
+
+        // Get the list of ECal hits.
+        if (event.hasCollection(RawTrackerHit.class, rawCollectionName)) {
+            List<RawTrackerHit> hits = event.get(RawTrackerHit.class, rawCollectionName);
+
+            for (RawTrackerHit hit : hits) {
+                short[] window = hit.getADCValues();
+                long id = hit.getCellID();
+                //do DAQ readout
+                double crystalThreshold = EcalConditions.physicalToPedestal(id) + threshold;
+                int adcSum = 0;
+                int pointerOffset = 0;
+                int numSamplesToRead = 0;
+                int thresholdCrossing = 0;
+                for (int i = 0; i < window.length; i++) {
+                    if (numSamplesToRead != 0) {
+                        adcSum += window[i - pointerOffset];
+                        numSamplesToRead--;
+                        if (numSamplesToRead == 0) {
+                            readoutHits.add(new BaseRawCalorimeterHit(id, adcSum, 64*thresholdCrossing));
+                        }
+                    } else if ((i == 0 || window[i - 1] <= crystalThreshold) && window[i] > crystalThreshold) {
+                        thresholdCrossing = i;
+                        pointerOffset = Math.min(numSamplesBefore, i);
+                        numSamplesToRead = pointerOffset + Math.min(numSamplesAfter, window.length - i - pointerOffset - 1);
+                        adcSum = 0;
+                    }
+                }
+                //do trigger readout
+            }
+        }
+        int flags = 0;
+        event.put(ecalCollectionName, readoutHits, BaseRawCalorimeterHit.class, flags, ecalReadoutName);
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
GTPEcalClusterer.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/GTPEcalClusterer.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/GTPEcalClusterer.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,365 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.geometry.Detector;
+import org.lcsim.geometry.subdetector.HPSEcal3;
+import org.lcsim.geometry.subdetector.HPSEcal3.NeighborMap;
+import org.lcsim.lcio.LCIOConstants;
+import org.lcsim.util.Driver;
+
+/**
+ * Class
+ * <code>GTPCalorimeterClusterer</code> processes events and converts hits into
+ * clusters, where appropriate. It uses the modified 2014 clustering algorithm.
+ *
+ * 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.
+ *
+ * A hit is added to a cluster as a component if it has a non-zero energy and
+ * within the aforementioned tunable time buffer used for clustering and is
+ * either at the same location as the seed hit or is a neighbor to the seed hit.
+ *
+ * @author Kyle McCarty
+ * @author Sho Uemura
+ */
+public class GTPEcalClusterer extends Driver {
+
+    /**
+     * <b>calorimeter</b><br/><br/>
+     * <code>private HPSEcal3 <b>calorimeter</b></code><br/><br/>
+     * The sub-detector representing the calorimeter.
+     */
+    private HPSEcal3 calorimeter;
+    /**
+     * <b>ecalName</b><br/><br/>
+     * <code>private String <b>ecalName</b></code><br/><br/>
+     * The name of the calorimeter sub-detector stored in the
+     * <code>
+     * Detector</code> object for this run.
+     */
+    String ecalName;
+    /**
+     * <b>clusterCollectionName</b><br/><br/>
+     * <code>private String <b>clusterCollectionName</b></code><br/><br/>
+     * The name of the LCIO collection name in which the clusters will be
+     * stored.
+     */
+    String clusterCollectionName = "EcalClusters";
+    /**
+     * <b>clusterWindow</b><br/><br/>
+     * <code>private int <b>clusterWindow</b></code><br/><br/>
+     * Indicates the number of FADC clock cycles (each cycle is 4 ns) before and
+     * after a given cycle that should be considered when checking if a cluster
+     * is a local maximum in space-time.
+     */
+    int clusterWindow = 2;
+    /**
+     * <b>hitBuffer</b><br/><br/>
+     * <code>private LinkedList<List<CalorimeterHit>> <b>hitBuffer</b></code><br/><br/>
+     * Stores a set of all the hits occurring in each clock cycle for the number
+     * of clock cycles that should be considered for clustering.
+     */
+    private LinkedList<Map<Long, CalorimeterHit>> hitBuffer;
+    /**
+     * <b>ecalCollectionName</b><br/><br/>
+     * <code>private String <b>ecalCollectionName</b></code><br/><br/>
+     * The name of LCIO collection containing the calorimeter hits that are to
+     * be used for clustering.
+     */
+    String ecalCollectionName;
+    /**
+     * <b>neighborMap</b><br/><br/>
+     * <code>private NeighborMap <b>neighborMap</b></code><br/><br/>
+     * Maps the
+     * <code>long</code> crystal ID to the set of crystal IDs of the crystals
+     * which are adjacent to the key.
+     */
+    private NeighborMap neighborMap = null;
+    /**
+     * <b>seedEnergyThreshold</b><br/><br/>
+     * <code>private double <b>seedEnergyThreshold</b></code><br/><br/>
+     * The minimum energy required for a hit to be considered as a cluster
+     * center. Hits with energy less than this value will be ignored.
+     */
+    double seedEnergyThreshold = 0.05;
+
+    /**
+     * <b>detectorChanged</b><br/><br/>
+     * <code>public void <b>detectorChanged</b>(Detector detector)</code><br/><br/>
+     * Initializes detector-dependent parameters for clustering. Method is
+     * responsible for determining which crystals are valid cluster centers
+     * (stored in
+     * <code>validCrystals</code>), defining the detector object (stored in
+     * <code>calorimeter</code>), defining the detector ID decoder (stored in
+     * <code>decoder</code>), and defining the mapping of crystal IDs to the
+     * crystal's neighbors (defined in
+     * <code>neighborMap</code>).
+     *
+     * @param detector - The new detector to use.
+     */
+    @Override
+    public void detectorChanged(Detector detector) {
+        // Get the calorimeter object.
+        calorimeter = (HPSEcal3) detector.getSubdetector(ecalName);
+
+        // Get a map to associate crystals with their neighbors.
+        neighborMap = calorimeter.getNeighborMap();
+    }
+
+    /**
+     * <b>getClusters</b><br/><br/>
+     * <code>public List<HPSEcalCluster> <b>getClusters</b>()</code><br/><br/>
+     * Generates a list of clusters from the current hit buffer. The "present"
+     * event is taken to be the list of hits occurring at index
+     * <code>clusterWindow</code>, which is the middle of the buffer.
+     *
+     * @return Returns a <code>List</code> of <code>HPSEcalCluster
+     * </code> objects generated from the current event.
+     */
+    public List<HPSEcalCluster> getClusters() {
+        // Generate a list for storing clusters.
+        List<HPSEcalCluster> clusters = new ArrayList<HPSEcalCluster>();
+
+        // Get the list of hits at the current time in the event buffer.
+        Map<Long, CalorimeterHit> currentHits = hitBuffer.get(clusterWindow);
+
+        // For a hit to be a cluster center, it must be a local maximum
+        // both with respect to its neighbors and itself both in the
+        // present time and at all times within the event buffer.
+        seedLoop:
+        for (Long currentID : currentHits.keySet()) {
+            // Get the actual hit object.
+            CalorimeterHit currentHit = currentHits.get(currentID);
+
+            // Store the energy of the current hit.
+            double currentEnergy = currentHit.getRawEnergy();
+
+            // If the hit energy is lower than the minimum threshold,
+            // then we immediately reject this hit as a possible cluster.
+            if (currentEnergy < seedEnergyThreshold) {
+                continue seedLoop;
+            }
+
+            // Store the crystals that are part of this potential cluster, 
+            // starting with the cluster seed candidate.
+            HPSEcalCluster cluster = new HPSEcalCluster(currentHit);
+            cluster.addHit(currentHit);
+
+            // Get the set of neighbors for this hit.
+            Set<Long> neighbors = neighborMap.get(currentHit.getCellID());
+
+            // Sort through each event stored in the buffer.
+            addLoop:
+            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 {
+                        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 {
+                            cluster.addHit(neighborHit);
+                        }
+                    }
+                }
+            }
+
+            // Add the cluster to the list of clusters.
+            clusters.add(cluster);
+        }
+
+        // Return the generated list of clusters.
+        return clusters;
+    }
+
+    /**
+     * <b>process</b><br/><br/>
+     * <code>public void <b>process</b>(EventHeader event)</code><br/><br/>
+     * Places hits from the current event into the event hit buffer and
+     * processes the buffer to extract clusters. Clusters are then stored in the
+     * event object.
+     *
+     * @param event - The event to process.
+     */
+    @Override
+    public void process(EventHeader event) {
+        // Only run the clusterer if this event has the calorimeter hit collection. 
+        // This ensures this driver makes clusters in sync with the FADC readout cycle.
+        if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) {
+            // Get the list of calorimeter hits from the event.
+            List<CalorimeterHit> hitList = event.get(CalorimeterHit.class, ecalCollectionName);
+
+            // Store each hit in a set by its cell ID so that it may be
+            // easily acquired later.
+            HashMap<Long, CalorimeterHit> hitMap = new HashMap<Long, CalorimeterHit>();
+            for (CalorimeterHit hit : hitList) {
+                hitMap.put(hit.getCellID(), hit);
+            }
+
+            // Remove the last event from the hit buffer and add the new one.
+            hitBuffer.removeLast();
+            hitBuffer.addFirst(hitMap);
+
+            // Run the clustering algorithm on the buffer.
+            List<HPSEcalCluster> clusterList = getClusters();
+
+            // Store the cluster list in the LCIO collection.
+            int flag = 1 << LCIOConstants.CLBIT_HITS;
+            event.put(clusterCollectionName, clusterList, HPSEcalCluster.class, flag);
+        }
+    }
+
+    /**
+     * <b>setEcalCollectionName</b><br/><br/>
+     * <code>public void <b>setEcalCollectionName</b>(String ecalCollectionName)</code><br/><br/>
+     * Sets the name of the LCIO collection containing the calorimeter hits
+     * which should be used for clustering.
+     *
+     * @param ecalCollectionName - The appropriate collection name.
+     */
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    /**
+     * <b>setClusterCollectionName</b><br/><br/>
+     * <code>public void <b>setClusterCollectionName</b>(String clusterCollectionName)</code><br/><br/>
+     * Sets the name of the LCIO collection in which the clusters should be
+     * stored.
+     *
+     * @param clusterCollectionName - The desired collection name.
+     */
+    public void setClusterCollectionName(String clusterCollectionName) {
+        this.clusterCollectionName = clusterCollectionName;
+    }
+
+    /**
+     * <b>setEcalName</b><br/><br/>
+     * <code>public void <b>setEcalName</b>(String ecalName)</code><br/><br/>
+     * Sets the name of the calorimeter sub-detector stored in the
+     * <code>Detector</code> object that is to be used for this run.
+     *
+     * @param ecalName - The calorimeter's name.
+     */
+    public void setEcalName(String ecalName) {
+        this.ecalName = ecalName;
+    }
+
+    /**
+     * <b>setClusterWindow</b><br/><br/>
+     * <code>public void <b>setClusterWindow</b>(int clusterWindow)</code><br/><br/>
+     * Sets the number of clock cycles before and after a given cycle that will
+     * be used when checking whether a given hit is a local maximum in both time
+     * and space. Note that a value of
+     * <code>N
+     * </code> indicates that
+     * <code>N</code> clock cycles before and
+     * <code>N</code> clock cycles after will be considered. Thusly, a total of
+     * <code>2N + 1</code> clock cycles will be used total.
+     *
+     * @param clusterWindow - The number of additional clock cycles to include
+     * in the clustering checks. A negative value will be treated as zero.
+     */
+    public void setClusterWindow(int clusterWindow) {
+        // The cluster window of must always be at least zero.
+        if (clusterWindow < 0) {
+            this.clusterWindow = 0;
+        } // If the cluster window is non-zero, then store it.
+        else {
+            this.clusterWindow = clusterWindow;
+        }
+    }
+
+    /**
+     * <b>setSeedEnergyThreshold</b><br/><br/>
+     * <code>public void <b>setSeedEnergyThreshold</b>(double seedEnergyThreshold)</code><br/><br/>
+     * Sets the minimum energy threshold below which hits will not be considered
+     * as cluster centers.
+     *
+     * @param seedEnergyThreshold - The minimum energy for a cluster center.
+     */
+    public void setSeedEnergyThreshold(double seedEnergyThreshold) {
+        // A negative energy threshold is non-physical. All thresholds
+        // be at least zero.
+        if (seedEnergyThreshold < 0.0) {
+            this.seedEnergyThreshold = 0.0;
+        } // If the energy threshold is valid, then use it.
+        else {
+            this.seedEnergyThreshold = seedEnergyThreshold;
+        }
+    }
+
+    /**
+     * <b>startOfData</b><br/><br/>
+     * <code>public void <b>startOfData</b>()</code><br/><br/>
+     * Initializes the clusterer. This ensures that the collection name for the
+     * calorimeter hits from which clusters are to be generated, along with the
+     * calorimeter name, have been defined. Method also initializes the event
+     * hit buffer.
+     */
+    @Override
+    public void startOfData() {
+        // Make sure that there is a cluster collection name into
+        // which clusters may be placed.
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+
+        // Make sure that there is a calorimeter detector.
+        if (ecalName == null) {
+            throw new RuntimeException("The parameter ecalName was not set!");
+        }
+
+        // Initiate the hit buffer.
+        hitBuffer = new LinkedList<Map<Long, CalorimeterHit>>();
+
+        // Populate the event buffer with (2 * clusterWindow + 1)
+        // empty events. These empty events represent the fact that
+        // the first few events will not have any events in the past
+        // portion of the buffer.
+        int bufferSize = (2 * clusterWindow) + 1;
+        for (int i = 0; i < bufferSize; i++) {
+            hitBuffer.add(new HashMap<Long, CalorimeterHit>(0));
+        }
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
HPSCalorimeterHit.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/HPSCalorimeterHit.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/HPSCalorimeterHit.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,72 @@
+package org.hps.recon.ecal;
+
+import hep.physics.vec.Hep3Vector;
+import java.util.Comparator;
+import org.lcsim.detector.IDetectorElement;
+import org.lcsim.detector.IDetectorElementContainer;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.base.BaseCalorimeterHit;
+
+/**
+ * An implementation of CalorimeterHit, with a constructor that sets rawEnergy
+ * for use in ECalReadout
+ *
+ * @author Sho Uemura
+ * @version $Id: HPSCalorimeterHit.java,v 1.1 2013/02/25 22:39:24 meeg Exp $
+ */
+public class HPSCalorimeterHit extends BaseCalorimeterHit {
+
+    /**
+     * Fully qualified constructor that sets rawEnergy
+     *
+     * @param energy   Raw energy for this cell
+     * @param position Global Cartesian coordinate for this cell
+     * @param time     Time of energy deposition
+     * @param id       Cell ID
+     * @param type     Type
+     */
+    public HPSCalorimeterHit(double energy, double time, long id, int type) {
+        this.rawEnergy = energy;
+//        if (position != null) {
+//            this.positionVec = new BasicHep3Vector(position);
+//        } else {
+//            positionVec = null;
+//        }
+        this.time = time;
+        this.id = id;
+        this.type = type;
+    }
+
+    @Override
+    public IDetectorElement getDetectorElement() {
+        if (de == null) {
+//            findDetectorElementByPosition();
+            IDetectorElementContainer detectorElements = EcalConditions.getSubdetector().getDetectorElement().findDetectorElement(getIdentifier());
+            if (detectorElements.size() != 1) {
+                throw new RuntimeException("Expected exactly one DetectorElement matching ID " + getIdentifier() + ", got " + detectorElements.size());
+            } else {
+                de = detectorElements.get(0);
+            }
+        }
+        // setupDetectorElement();
+        return de;
+    }
+
+    @Override
+    public double[] getPosition() {
+        return getPositionVec().v();
+    }
+
+    @Override
+    public Hep3Vector getPositionVec() {
+        return this.getDetectorElement().getGeometry().getPosition();
+    }
+
+    static class TimeComparator implements Comparator<CalorimeterHit> {
+
+        @Override
+        public int compare(CalorimeterHit o1, CalorimeterHit o2) {
+            return Double.compare(o1.getTime(), o2.getTime());
+        }
+    }
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
HPSEcalCluster.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/HPSEcalCluster.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/HPSEcalCluster.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,422 @@
+package org.hps.recon.ecal;
+
+import hep.physics.vec.BasicHep3Vector;
+import hep.physics.vec.Hep3Vector;
+import hep.physics.vec.VecOp;
+import java.util.List;
+import org.lcsim.detector.IGeometryInfo;
+import org.lcsim.detector.solids.Trd;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.base.BaseCluster;
+
+/**
+ * Cluster with position defined by seed hit (for 1-bit trigger)
+ *
+ * @author Sho Uemura <[log in to unmask]>
+ * @version $Id: HPSEcalCluster.java,v 1.11 2013/02/25 22:39:24 meeg Exp $
+ */
+public class HPSEcalCluster extends BaseCluster {
+
+    private CalorimeterHit seedHit = null;
+    private long cellID;
+    
+    static final double eCriticalW = 800.0*ECalUtils.MeV/(74+1);
+    static final double radLenW = 8.8; //mm
+    double[] electronPosAtDepth = new double[3];
+    private boolean needsElectronPosCalculation = true;
+    double[] photonPosAtDepth = new double[3];
+    private boolean needsPhotonPosCalculation = true;
+    
+    public HPSEcalCluster(Long cellID) {
+        this.cellID = cellID;
+    }
+
+    public HPSEcalCluster(CalorimeterHit seedHit) {
+        this.seedHit = seedHit;
+        this.cellID = seedHit.getCellID();
+    }
+
+    public CalorimeterHit getSeedHit() {
+        if (seedHit == null) {
+            CalorimeterHit hit = hits.get(0);
+            if (hit == null) {
+                throw new RuntimeException("HPSEcalCluster has no hits");
+            }
+            seedHit = new HPSCalorimeterHit(0.0, 0.0, cellID, hit.getType());
+            seedHit.setMetaData(hit.getMetaData());
+        }
+        return seedHit;
+    }
+    
+//    public double[] getPosition() {
+//        return getSeedHit().getPosition();
+//    }
+//    
+    @Override
+    public double[] getPosition() {
+        //Electron by default!?
+        return this.getPositionAtShowerMax(true);
+    }
+        
+    public double[] getPositionAtShowerMax(boolean isElectron) {
+        if( isElectron) {
+            if(needsElectronPosCalculation) {
+                this.calcPositionAtShowerMax(true);
+            }
+            return this.electronPosAtDepth;
+        }
+        else {
+            if(needsPhotonPosCalculation) {
+                this.calcPositionAtShowerMax(false);
+            }
+            return this.photonPosAtDepth;
+        }  
+    }
+        
+    public void calcPositionAtShowerMax(boolean isElectron) {
+        double E = this.getEnergy();
+        double y = E/eCriticalW;
+        double Cj = isElectron ? -0.5 : 0.5;
+        double tmax = Math.log(y) + Cj; //Maximum of dE/dt profile in units of rad. len. 
+        double dmax = tmax*radLenW; //mm
+        if(isElectron) {
+            electronPosAtDepth =  calculatePositionAtDepth(dmax);
+        } else {
+            photonPosAtDepth =  calculatePositionAtDepth(dmax);
+        }
+            
+    }
+    
+    
+    
+    public double[] calculatePositionAtDepth(double dmax) 
+    {
+        return this.calculatePositionAtDepth(this.getCalorimeterHits(), dmax);
+    }    
+    
+    public double[] calculatePositionAtDepth(List<CalorimeterHit> hits, double dmax)
+    {
+        //copy from package org.lcsim.recon.cluster.util.TensorClusterPropertyCalculator;
+
+        double positionErrorLocal[] = new double[6];
+        double directionErrorLocal[] = new double[6];
+        double shapeParametersLocal[] = new double[6];
+        double positionLocal[] = new double[3];
+        double ithetaLocal;
+        double iphiLocal;
+        
+        
+        double[] mm_NE = new double[3];
+        double[] mm_CE = new double[3];
+        double[][] mm_PA = new double[3][3];
+        for(int i=0;i<3;++i)
+        {
+            mm_NE[i] = 0.;
+            mm_CE[i] = 0.;
+            for(int j=0;j<3;++j)
+            {mm_PA[i][j]= 0.;}
+        }
+        double Etot = 0.0;
+        double Exx = 0.0;
+        double Eyy = 0.0;
+        double Ezz = 0.0;
+        double Exy = 0.0;
+        double Eyz = 0.0;
+        double Exz = 0.0;
+        double CEx = 0.0;
+        double CEy = 0.0;
+        double CEz = 0.0;
+        double CEr = 0.0;
+        double E1 = 0.0;
+        double E2 = 0.0;
+        double E3 = 0.0;
+        double NE1 = 0.0;
+        double NE2 = 0.0;
+        double NE3 = 0.0;
+        double Tr = 0.0;
+        double M = 0.0;
+        double Det = 0.0;
+        int nhits = hits.size();
+        for(int i=0;i<hits.size();i++)
+        {
+            CalorimeterHit hit = hits.get(i);
+            //	CalorimeterIDDecoder decoder = hit.getDecoder();
+            //	decoder.setID(hit.getCellID());
+            //	double[] pos = new double[3];
+            //	pos[0] = decoder.getX();
+            //	pos[1] = decoder.getY();
+            //	pos[2] = decoder.getZ();
+            //double[] pos = hit.getPosition();
+            //Find position at shower max
+            IGeometryInfo geom = hit.getDetectorElement().getGeometry();
+            double[] pos = geom.transformLocalToGlobal(VecOp.add(geom.transformGlobalToLocal(geom.getPosition()),(Hep3Vector)new BasicHep3Vector(0,0,dmax-1*((Trd)geom.getLogicalVolume().getSolid()).getZHalfLength()))).v();
+                        
+//            System.out.println("global pos " + global_pos.toString());
+//            System.out.println("local pos " + local_pos.toString());
+//            System.out.println("local pos tmax " + local_pos_tmax.toString());
+//            System.out.println("global pos tmax " + global_pos_tmax.toString());
+//            
+            //pos = global_pos_tmax.v();
+            
+            double E = hit.getCorrectedEnergy();
+            Etot += E;
+            CEx += E*pos[0];
+            CEy += E*pos[1];
+            CEz += E*pos[2];
+            Exx += E*(pos[1]*pos[1] + pos[2]*pos[2]);
+            Eyy += E*(pos[0]*pos[0] + pos[2]*pos[2]);
+            Ezz += E*(pos[1]*pos[1] + pos[0]*pos[0]);
+            Exy -= E*pos[0]*pos[1];
+            Eyz -= E*pos[1]*pos[2];
+            Exz -= E*pos[0]*pos[2];
+        }
+        CEx = CEx/Etot;
+        CEy = CEy/Etot;
+        CEz = CEz/Etot;
+        double CErSq = CEx*CEx + CEy*CEy + CEz*CEz;
+        CEr = Math.sqrt(CErSq);
+        // now go to center of energy coords.
+        if (nhits > 3 )
+        {
+            Exx = Exx - Etot*(CErSq - CEx*CEx);
+            Eyy = Eyy - Etot*(CErSq - CEy*CEy);
+            Ezz = Ezz - Etot*(CErSq - CEz*CEz);
+            Exy = Exy + Etot*CEx*CEy;
+            Eyz = Eyz + Etot*CEy*CEz;
+            Exz = Exz + Etot*CEz*CEx;
+
+            //
+            Tr = Exx + Eyy + Ezz;
+            double Dxx = Eyy*Ezz - Eyz*Eyz;
+            double Dyy = Ezz*Exx - Exz*Exz;
+            double Dzz = Exx*Eyy - Exy*Exy;
+            M = Dxx + Dyy + Dzz;
+            double Dxy = Exy*Ezz - Exz*Eyz;
+            double Dxz = Exy*Eyz - Exz*Eyy;
+            Det = Exx*Dxx - Exy*Dxy + Exz*Dxz;
+            double xt = Tr*Tr - 3*M;
+            double sqrtxt = Math.sqrt(xt);
+            // eqn to solve for eigenvalues is x**3 - x**2*Tr + x*M - Det = 0
+            // crosses y axis at -Det and inflection points are
+
+            double mE1 = 0.;
+            double mE2 = 0.;
+            double mE3 = 0.;
+            double a = (3*M - Tr*Tr)/3.;
+            double b = (-2*Tr*Tr*Tr + 9*Tr*M -27*Det)/27.;
+            double test = b*b/4. + a*a*a/27.;
+            if(test >= 0.01)
+            {
+                //System.out.println("AbstractCluster: Only 1 real root!!!");
+                //System.out.println("  nhits = " + nhits + "\n");
+                //System.out.println(" a,b,test = " + a + " " + b + " " + test + "\n");
+            }
+            else
+            {
+                double temp;
+                if(test >= 0.)temp = 1.;
+                else temp = Math.sqrt(b*b*27./(-a*a*a*4.));
+                if(b > 0.)temp = -temp;
+                double phi = Math.acos(temp);
+                double temp1 = 2.*Math.sqrt(-a/3.);
+                mE1 = Tr/3. + 2.*Math.sqrt(-a/3.)*Math.cos(phi/3.);
+                mE2 = Tr/3. + 2.*Math.sqrt(-a/3.)*Math.cos(phi/3. + 2.*Math.PI/3.);
+                mE3 = Tr/3. + 2.*Math.sqrt(-a/3.)*Math.cos(phi/3. + 4.*Math.PI/3.);
+            }
+            if(mE1 < mE2)
+            {
+                if(mE1 < mE3)
+                {
+                    E1 = mE1;
+                    if(mE2 < mE3)
+                    {
+                        E2 = mE2;
+                        E3 = mE3;
+                    }
+                    else
+                    {
+                        E2 = mE3;
+                        E3 = mE2;
+                    }
+                }
+                else
+                {
+                    E1 = mE3;
+                    E2 = mE1;
+                    E3 = mE2;
+                }
+            }
+            else
+            {
+                if(mE2 < mE3)
+                {
+                    E1 = mE2;
+                    if(mE1 < mE3)
+                    {
+                        E2 = mE1;
+                        E3 = mE3;
+                    }
+                    else
+                    {
+                        E2 = mE3;
+                        E3 = mE1;
+                    }
+                }
+                else
+                {
+                    E1 = mE3;
+                    E2 = mE2;
+                    E3 = mE1;
+                }
+            }
+
+            NE1 = E1/Etot;
+            NE2 = E2/Etot;
+            NE3 = E3/Etot;
+            double[] EV = new double[3];
+            EV[0] = E1;
+            EV[1] = E2;
+            EV[2] = E3;
+            // Now calculate principal axes
+	    // For eigenvalue EV, the axis is (nx, ny, nz) where:
+	    //    (Exx - EV)nx + (Exy)ny + (Exz)nz = 0
+	    //    (Eyx)nx + (Eyy - EV)ny + (Eyz)nz = 0
+	    //    (Ezx)nx + (Ezy)ny + (Ezz - EV)nz = 0
+	    // Setting nx = 1, we have:
+	    //    (Exx - EV) + (Exy)ny + (Exz)nz = 0
+	    //    (Eyx) + (Eyy - EV)ny + (Eyz)nz = 0
+	    //    (Ezx) + (Ezy)ny + (Ezz - EV)nz = 0
+	    // and so
+	    //    (Exy)ny = EV - Exx - (Exz)nz  =>  ny = (EV - Exx - Exz*nz)/Exy
+	    // What if Exy = 0? Then provided Eyz is non-zero we can write:
+	    //    (Ezx) + (Ezy)ny + (Ezz - EV)nz = 0
+	    //    ny = (Exz - (Ezz-EV)*nz)/Eyz
+	    // What if Exy = 0 and Eyz = 0 but (Eyy - EV) is non-zero?
+	    //    (Eyy - EV)ny + (Eyz)nz = 0
+	    //    ny = -(Eyz*nz)/(Eyy-EV)
+
+	    // In the pathological case where Exz = Eyz = Ezz = 0:
+	    //    (Exx - EV)nx + (Exy)ny = 0  =>  ny/nx = -(Exx-EV)/Exy
+	    //    (Eyx)nx + (Eyy - EV)ny = 0  =>  ny/nx = -Eyx/(Eyy-EV)
+	    //    (EV)nz = 0
+	    // so
+	    //     -ny/nx = (EV-Exx)/Exy = Eyx/(EV-Eyy)
+	    // But watch out for order! Recalculate eigenvalues for this pathological case.
+	    //     (EV-Exx)(EV-Eyy) = Eyx*Exy
+	    //     EV^2 - EV(Exx+Eyy) + Exx*Eyy - Eyx*Exy = 0
+	    // 
+	    // In another pathological case, Exz = Exy = 0:
+	    //    (Exx - EV)nx = 0
+	    //    (Eyy - EV)ny + (Eyz)nz = 0 => ny/nz = -(Eyz)/(Eyy-EV)
+	    //    (Ezy)ny + (Ezz - EV)nz = 0 => ny/nz = -(Ezz-EV)/(Ezy)
+	    // so we cannot set nx = 1. Instead, write:
+	    //    -ny/nz = (Eyz)/(Eyy-EV) = (Ezz-EV)/(Ezy)
+	    // Then
+	    //    (Eyz)(Ezy) = (Eyy-EV)(Ezz-EV)
+	    //    (Eyz)^2 = (Eyy)(Ezz) - (Eyy)(EV) - (Ezz)(EV) + (EV)^2
+	    //    EV^2 - EV(Eyy+Ezz) + Eyy*Ezz - Eyz*Eyz = 0
+
+	    // Handle pathological case
+	    if (Exz == 0.0 && Eyz == 0.0) {
+		// Recompute eigenvectors.
+		EV[0] = 0.5*(Exx+Eyy) + 0.5*Math.sqrt((Exx+Eyy)*(Exx+Eyy) + 4.0*Exy*Exy);
+		EV[1] = 0.5*(Exx+Eyy) - 0.5*Math.sqrt((Exx+Eyy)*(Exx+Eyy) + 4.0*Exy*Exy);
+		EV[2] = 0.0;
+		for( int i = 0 ; i < 2 ; i++ ) {
+		    double nx_over_ny = Exy / (Exx-EV[i]);
+		    double nx_unnormalized = nx_over_ny;
+		    double ny_unnormalized = 1.0;
+		    double norm = Math.sqrt(nx_unnormalized*nx_unnormalized + ny_unnormalized*ny_unnormalized);
+		    mm_PA[i][0] = ny_unnormalized/norm;
+		    mm_PA[i][1] = nx_unnormalized/norm;
+		    mm_PA[i][2] = 0.0;
+		}
+		// ... and now set third eigenvector to the z direction:
+		mm_PA[2][0] = 0.0;
+		mm_PA[2][1] = 0.0;
+		mm_PA[2][2] = 1.0;
+	    } else if (Exz == 0.0 && Exy == 0.0) {
+		// Another pathological case
+		EV[0] = 0.5*(Eyy+Ezz) + 0.5*Math.sqrt((Eyy+Ezz)*(Eyy+Ezz) + 4.0*Eyz*Eyz);
+		EV[1] = 0.5*(Eyy+Ezz) - 0.5*Math.sqrt((Eyy+Ezz)*(Eyy+Ezz) + 4.0*Eyz*Eyz);
+		EV[2] = 0.0;
+		for( int i = 0 ; i < 2 ; i++ ) {
+		    double ny_over_nz = Eyz / (Eyy-EV[i]);
+		    double ny_unnormalized = ny_over_nz;
+		    double nz_unnormalized = 1.0;
+		    double norm = Math.sqrt(ny_unnormalized*ny_unnormalized + nz_unnormalized*nz_unnormalized);
+		    mm_PA[i][0] = nz_unnormalized/norm;
+		    mm_PA[i][1] = ny_unnormalized/norm;
+		    mm_PA[i][2] = 0.0;
+		}
+		mm_PA[2][0] = 0.0;
+		mm_PA[2][1] = 0.0;
+		mm_PA[2][2] = 1.0;
+	    } else {
+		for( int i = 0 ; i < 3 ; i++ )
+		    {
+			double[] C = new double[3];
+			C[0] = 1.0;
+			C[2] = (Exy*Exy + (Eyy - EV[i])*(EV[i] - Exx))/
+			    ((Eyy - EV[i])*Exz - Eyz*Exy);
+			C[1] = (EV[i] - Exx - Exz*C[2])/Exy;
+			if (Exy == 0.0) {
+			    // Recompute
+			    if (Eyz != 0.0) {
+				// ny = (Exz - (Ezz-EV)*nz)/Eyz
+				C[1] = (Exz - (Ezz-EV[i])*C[2])/Eyz;
+			    } else {
+				// ny = -(Eyz*nz)/(Eyy-EV)
+				C[1] = -(Eyz*C[2])/(Eyy-EV[i]);
+			    }
+			}
+			double norm = Math.sqrt(C[0]*C[0] + C[1]*C[1] + C[2]*C[2]);
+			mm_PA[i][0] = C[0]/norm;
+			mm_PA[i][1] = C[1]/norm;
+			mm_PA[i][2] = C[2]/norm;
+		    }
+	    }
+        }
+        mm_NE[0] = NE1;
+        mm_NE[1] = NE2;
+        mm_NE[2] = NE3;
+        mm_CE[0] = CEx;
+        mm_CE[1] = CEy;
+        mm_CE[2] = CEz;
+        for(int i=0;i<6;i++)
+        {
+            positionErrorLocal[i] = 0.;
+            directionErrorLocal[i] = 0.;
+            shapeParametersLocal[i] = 0.;
+        }
+        for(int i=0;i<3;i++)
+        {
+            positionLocal[i] = mm_CE[i];
+            shapeParametersLocal[i] = mm_NE[i];
+        }
+        if(nhits > 3)
+        {
+            double dr = Math.sqrt(  (position[0]+mm_PA[0][0])*(position[0]+mm_PA[0][0]) +
+                    (position[1]+mm_PA[0][1])*(position[1]+mm_PA[0][1]) +
+                    (position[2]+mm_PA[0][2])*(position[2]+mm_PA[0][2]) ) -
+                    Math.sqrt(	(position[0])*(position[0]) +
+                    (position[1])*(position[1]) +
+                    (position[2])*(position[2]) ) ;
+            double sign = 1.;
+            if(dr < 0.)sign = -1.;
+            itheta = Math.acos(sign*mm_PA[0][2]);
+            iphi = Math.atan2(sign*mm_PA[0][1],sign*mm_PA[0][0]);
+        }
+        else
+        {
+            itheta = 999.;
+            iphi = 999.;
+        }
+    
+        return positionLocal;
+    }
+    
+    
+    
+    
+}

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
HPSRawCalorimeterHit.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/HPSRawCalorimeterHit.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/HPSRawCalorimeterHit.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,60 @@
+package org.hps.recon.ecal;
+
+import java.util.Comparator;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.RawCalorimeterHit;
+
+/**
+ *
+ * @author Sho Uemura <[log in to unmask]>
+ * @version $Id: HPSRawCalorimeterHit.java,v 1.9 2013/02/25 22:39:24 meeg Exp $
+ */
+public class HPSRawCalorimeterHit implements RawCalorimeterHit {
+
+	long cellID;
+	int amplitude;
+	int timeStamp;
+	int windowSize;
+	CalorimeterHit analogHit = null;
+
+	public HPSRawCalorimeterHit(long cellID, int amplitude, int timeStamp, int windowSize) {
+		this.cellID = cellID;
+		this.amplitude = amplitude;
+		this.timeStamp = timeStamp;
+		this.windowSize = windowSize;
+	}
+
+    @Override
+	public long getCellID() {
+		return cellID;
+	}
+
+    @Override
+	public int getAmplitude() {
+		return amplitude;
+	}
+
+    @Override
+	public int getTimeStamp() {
+		return timeStamp;
+	}
+
+	public int getWindowSize() {
+		return windowSize;
+	}
+
+	public CalorimeterHit getAnalogHit() {
+		return analogHit;
+	}
+
+	public void setAnalogHit(CalorimeterHit analogHit) {
+		this.analogHit = analogHit;
+	}
+
+	public static class TimeComparator implements Comparator<RawCalorimeterHit> {
+
+		public int compare(RawCalorimeterHit o1, RawCalorimeterHit o2) {
+			return o1.getTimeStamp() - o2.getTimeStamp();
+		}
+	}
+}
\ No newline at end of file

java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal
TriggerData.java added at 346
--- java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/TriggerData.java	                        (rev 0)
+++ java/sandbox/ecal-recon/src/main/java/org/hps/recon/ecal/TriggerData.java	2014-03-25 23:35:20 UTC (rev 346)
@@ -0,0 +1,111 @@
+package org.hps.recon.ecal;
+
+import org.lcsim.event.GenericObject;
+
+/**
+ *
+ * @author Sho Uemura <[log in to unmask]>
+ * @version $Id: TriggerData.java,v 1.3 2012/08/03 23:14:39 meeg Exp $
+ */
+public class TriggerData implements GenericObject {
+
+    public static final int OR_TRIG = 3;
+    public static final int TOP_TRIG = 4;
+    public static final int BOT_TRIG = 5;
+    public static final int AND_TRIG = 6;
+    public static final int TIME = 7;
+    public static final int TRIG_BANK_SIZE = 8;
+    public static final String TRIG_COLLECTION = "TriggerBank";
+    private int[] bank;
+
+    public TriggerData(int[] bank) {
+        this.bank = bank;
+    }
+
+    public int getTime() {
+        return getIntVal(TIME);
+    }
+
+    public int getOrTrig() {
+        return getIntVal(OR_TRIG);
+    }
+
+    public int getTopTrig() {
+        return getIntVal(TOP_TRIG);
+    }
+
+    public int getBotTrig() {
+        return getIntVal(BOT_TRIG);
+    }
+
+    public int getAndTrig() {
+        return getIntVal(AND_TRIG);
+    }
+
+    public int[] getBank() {
+        return bank;
+    }
+
+    public static int getTime(GenericObject object) {
+        return object.getIntVal(TIME);
+    }
+
+    public static int getOrTrig(GenericObject object) {
+        return object.getIntVal(OR_TRIG);
+    }
+
+    public static int getTopTrig(GenericObject object) {
+        return object.getIntVal(TOP_TRIG);
+    }
+
+    public static int getBotTrig(GenericObject object) {
+        return object.getIntVal(BOT_TRIG);
+    }
+
+    public static int getAndTrig(GenericObject object) {
+        return object.getIntVal(AND_TRIG);
+    }
+
+    public static int[] getBank(GenericObject object) {
+        int[] bank = new int[8];
+        for (int i = 0; i < 8; i++) {
+            bank[i] = object.getIntVal(i);
+        }
+        return bank;
+    }
+
+    @Override
+    public int getNInt() {
+        return TRIG_BANK_SIZE;
+    }
+
+    @Override
+    public int getNFloat() {
+        return 0;
+    }
+
+    @Override
+    public int getNDouble() {
+        return 0;
+    }
+
+    @Override
+    public int getIntVal(int index) {
+        return bank[index];
+    }
+
+    @Override
+    public float getFloatVal(int index) {
+        throw new UnsupportedOperationException("No float values in " + this.getClass().getSimpleName());
+    }
+
+    @Override
+    public double getDoubleVal(int index) {
+        throw new UnsupportedOperationException("No double values in " + this.getClass().getSimpleName());
+    }
+
+    @Override
+    public boolean isFixedSize() {
+        return true;
+    }
+}
SVNspam 0.1