Print

Print


Author: [log in to unmask]
Date: Mon Oct 10 06:41:35 2016
New Revision: 4508

Log:
separated ecal gains calculation from the raw converter driver (new version called EcalRawConverterDriver3), and created new driver EcalGain for the gain corrections.  

Added:
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGain.java   (with props)
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGainDriver.java   (with props)
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3.java   (with props)
    java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3Driver.java   (with props)

Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGain.java
 =============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGain.java	(added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGain.java	Mon Oct 10 06:41:35 2016
@@ -0,0 +1,158 @@
+package org.hps.recon.ecal;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import org.hps.conditions.database.DatabaseConditionsManager;
+import org.hps.conditions.ecal.EcalChannelConstants;
+import org.hps.conditions.ecal.EcalConditions;
+import org.hps.record.daqconfig.ConfigurationManager;
+import org.hps.record.daqconfig.FADCConfig;
+import org.lcsim.geometry.Detector;
+
+public class EcalGain {
+    /**
+     * If true, use a single gain factor for all channels. Else, use 442 gains from the conditions system.
+     */
+    private boolean constantGain = false;
+
+    /**
+     * A single gain factor for all channels (only used if constantGain=true)
+     */
+    private double gain;
+
+    /**
+     * If true, the relationship between ADC and GeV is a convention that includes readoutPeriod and a global scaling
+     * factor. If false, it is the currently used convention: E(GeV) = GAIN * ADC
+     */
+    private boolean use2014Gain = false;
+    
+    /**
+     * Set whether to use DAQ configuration read from EVIO to set EcalRawConverter parameters. This should be removed to
+     * a standalone EcalRawCongverterDriver solely for trigger emulation.
+     */
+    public void setUseDAQConfig(boolean state) {
+        useDAQConfig = state;
+    }
+    
+    /**
+     * If true, use the DAQ configuration from EVIO to set EcalRawConverter parameters. This should be removed to a
+     * standalone EcalRawConverter solely for trigger emulation.
+     */
+    private boolean useDAQConfig = false;
+    
+    /**
+     * Set global gain value and turn on constant gain. The 442 gains from the conditions system will be ignored.
+     */
+    public void setGain(double gain) {
+        constantGain = true;
+        this.gain = gain;
+    }
+
+    /**
+     * Chooses which ADC --> Energy convention is used. If true, the relationship between ADC and GeV is a convention
+     * that includes readoutPeriod and a global scaling factor. If false, it is the currently used convention: E(GeV) =
+     * GAIN * ADC
+     */
+    public void setUse2014Gain(boolean use2014Gain) {
+        this.use2014Gain = use2014Gain;
+    }
+    
+
+    private EcalConditions ecalConditions = null;
+    
+    
+    /**
+     * return energy (units of GeV) corresponding to the ADC sum and crystal ID
+     */
+    public double adcToEnergy(double adcSum, long cellID) {
+
+        // Get the channel data.
+        EcalChannelConstants channelData = findChannel(cellID);
+
+        if (useDAQConfig) {
+            // float gain =
+            // ConfigurationManager.getInstance().getFADCConfig().getGain(ecalConditions.getChannelCollection().findGeometric(cellID));
+            return config.getGain(cellID) * adcSum * EcalUtils.MeV;
+        } else if (use2014Gain) {
+            if (constantGain) {
+                return adcSum * EcalUtils.gainFactor * EcalUtils.ecalReadoutPeriod;
+            } else {
+                return channelData.getGain().getGain() * adcSum * EcalUtils.gainFactor * EcalUtils.ecalReadoutPeriod; // should
+                                                                                                                      // not
+                                                                                                                      // be
+                                                                                                                      // used
+                                                                                                                      // for
+                                                                                                                      // the
+                                                                                                                      // moment
+                                                                                                                      // (2014/02)
+            }
+        } else {
+            if (constantGain) {
+                return gain * adcSum * EcalUtils.MeV;
+            } else {
+                return channelData.getGain().getGain() * adcSum * EcalUtils.MeV; // gain
+                                                                                 // is
+                                                                                 // defined
+                                                                                 // as
+                                                                                 // MeV/integrated
+                                                                                 // ADC
+            }
+        }
+    }
+    
+    
+    
+
+
+    /**
+     * Must be set when an object EcalRawConverter is created.
+     *
+     * @param detector (long)
+     */
+    public void setDetector(Detector detector) {
+        // ECAL combined conditions object.
+        ecalConditions = DatabaseConditionsManager.getInstance().getEcalConditions();
+        
+    }
+
+    /**
+     * Convert physical ID to gain value.
+     *
+     * @param cellID (long)
+     * @return channel constants (EcalChannelConstants)
+     */
+    public EcalChannelConstants findChannel(long cellID) {
+        return ecalConditions.getChannelConstants(ecalConditions.getChannelCollection().findGeometric(cellID));
+    }
+    
+
+    /**
+     * The DAQ configuration from EVIO used to set EcalRawConverter parameters if useDAQConfig=true. This should be
+     * removed to a standalone EcalRawConverter solely for trigger emulation.
+     */
+    private FADCConfig config = null;
+    
+    /**
+     * Currently sets up a listener for DAQ configuration from EVIO. This should be removed to a standalone
+     * ECalRawConverter solely for trigger emulation.
+     */
+    public EcalGain() {
+        // Track changes in the DAQ configuration.
+        ConfigurationManager.addActionListener(new ActionListener() {
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                // If the DAQ configuration should be used, load the
+                // relevant settings into the driver.
+                if (useDAQConfig) {
+                    // Get the FADC configuration.
+                    config = ConfigurationManager.getInstance().getFADCConfig();
+
+                   
+                }
+            }
+        });
+    }
+   
+}

Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGainDriver.java
 =============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGainDriver.java	(added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalGainDriver.java	Mon Oct 10 06:41:35 2016
@@ -0,0 +1,119 @@
+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.util.Driver;
+
+public class EcalGainDriver extends Driver{
+    /**
+     * ecalCollectionName "type" (must match detector-data) 
+     */
+    private final String ecalReadoutName = "EcalHits";
+
+    private String inputHitsCollectionName = "EcalUncalHits";
+    
+    private String outputHitsCollectionName = "EcalCalHits";
+    
+    public void process(EventHeader event) {
+        
+        List<CalorimeterHit> hits = event.get(CalorimeterHit.class, inputHitsCollectionName);
+        
+        List<CalorimeterHit> newHits = new ArrayList<CalorimeterHit>();
+
+        for (CalorimeterHit hit : hits) {
+            double time = hit.getTime();
+            double adcSum = hit.getRawEnergy(); //the "raw energy" is actually the adc sum.  
+            long cellID = hit.getCellID();
+            
+            double energy =converter.adcToEnergy(adcSum, cellID);
+           
+            newHits.add(CalorimeterHitUtilities.create(energy, time, hit.getCellID()));
+        }
+
+        event.put(this.outputHitsCollectionName, newHits, CalorimeterHit.class, event.getMetaData(hits).getFlags(), ecalReadoutName);
+
+    }
+    
+
+    private EcalGain converter = new EcalGain();
+
+    
+    /**
+     * Sets whether the driver should use the DAQ configuration from EvIO file for its parameters. If activated, the
+     * converter will obtain gains, thresholds, pedestals, the window size, and the pulse integration window from the
+     * EvIO file. This will replace and overwrite any manually defined settings.<br/>
+     * <br/>
+     * Note that if this setting is active, the driver will not output any data until a DAQ configuration has been read
+     * from the data stream.
+     * 
+     * @param state - <code>true</code> indicates that the configuration should be read from the DAQ data in an EvIO
+     *            file. Setting this to <code>false</code> will cause the driver to use its regular manually-defined
+     *            settings and pull gains and pedestals from the conditions database.
+     */
+    public void setUseDAQConfig(boolean state) {
+       // useDAQConfig = state;
+        converter.setUseDAQConfig(state);
+    }
+    
+    /**
+     * Set to <code>true</code> to use the "2014" gain formula:<br/>
+     * 
+     * <pre>
+     * channelGain * adcSum * gainFactor * readoutPeriod
+     * </pre>
+     * <p>
+     * Set to <code>false</code> to use the gain formula for the Test Run:
+     * 
+     * <pre>
+     * gain * adcSum * ECalUtils.MeV
+     * </pre>
+     * 
+     * @param use2014Gain True to use 2014 gain formulation.
+     */
+    public void setUse2014Gain(boolean use2014Gain) {
+        converter.setUse2014Gain(use2014Gain);
+    }
+    
+    /**
+     * Set a constant gain factor in the converter for all channels.
+     * 
+     * @param gain The constant gain value.
+     */
+    public void setGain(double gain) {
+        converter.setGain(gain);
+    }
+    
+    @Override
+    public void detectorChanged(Detector detector) {
+
+        // set the detector for the converter
+        // FIXME: This method doesn't even need the detector object and does not use it.
+        converter.setDetector(detector);
+
+        // ECAL combined conditions object.
+       // ecalConditions = DatabaseConditionsManager.getInstance().getEcalConditions();
+    }
+    
+
+    /**
+     * Set the input {@link org.lcsim.event.CalorimeterHit} collection name,
+     * 
+     * @param ecalCollectionName The <code>CalorimeterHit</code> collection name.
+     */
+    public void setInputHitsCollectionName(String inputHitsCollectionName) {
+        this.inputHitsCollectionName = inputHitsCollectionName;
+    }
+    /**
+     * Set the output {@link org.lcsim.event.CalorimeterHit} collection name,
+     * 
+     * @param ecalCollectionName The <code>CalorimeterHit</code> collection name.
+     */
+    public void setOutputHitsCollectionName(String name){
+        this.outputHitsCollectionName = name;
+    }
+
+}

Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3.java
 =============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3.java	(added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3.java	Mon Oct 10 06:41:35 2016
@@ -0,0 +1,629 @@
+package org.hps.recon.ecal;
+
+import hep.aida.IFitResult;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.hps.conditions.database.DatabaseConditionsManager;
+import org.hps.conditions.ecal.EcalChannel;
+import org.hps.conditions.ecal.EcalChannelConstants;
+import org.hps.conditions.ecal.EcalConditions;
+import org.hps.record.daqconfig.ConfigurationManager;
+import org.hps.record.daqconfig.FADCConfig;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.GenericObject;
+import org.lcsim.event.RawCalorimeterHit;
+import org.lcsim.event.RawTrackerHit;
+import org.lcsim.event.base.BaseRawCalorimeterHit;
+import org.lcsim.geometry.Detector;
+
+/**
+ * This class is used to convert between {@link org.lcsim.event.RawCalorimeterHit} or
+ * {@link org.lcsim.event.RawTrackerHit}, objects with ADC/sample information, and
+ * {@link org.lcsim.event.CalorimeterHit}, an object with energy+time information. At minimum this involves pedestal
+ * subtraction/addition and gain scaling. Knows how to deal with Mode-1/3/7 FADC readout formats. Can perform Mode-3/7
+ * firmware algorithms on Mode-1 data. Can alternatively call pulse-fitting on Mode-1 data. All time walk/time offset
+ * corrections are performed to this collection after gains in EcalTimeCorrectionDriver
+ *
+ * @author Sho Uemura <[log in to unmask]>
+ * @author Andrea Celentano <[log in to unmask]>
+ * @author Nathan Baltzell <[log in to unmask]>
+ * @author Holly Szumila <[log in to unmask]>
+ */
+public class EcalRawConverter3 {
+
+
+    /**
+     * If true, running pedestal is used.
+     */
+    private boolean useRunningPedestal = true;
+
+    
+
+    /**
+     * If true, use the DAQ configuration from EVIO to set EcalRawConverter parameters. This should be removed to a
+     * standalone EcalRawConverter solely for trigger emulation.
+     */
+    private boolean useDAQConfig = false;
+
+    /**
+     * The DAQ configuration from EVIO used to set EcalRawConverter parameters if useDAQConfig=true. This should be
+     * removed to a standalone EcalRawConverter solely for trigger emulation.
+     */
+    private FADCConfig config = null;
+
+    /**
+     * Whether to use pulse fitting (EcalPulseFitter) to extract pulse energy time. Only applicable to Mode-1 data.
+     */
+    private boolean useFit = true;
+
+    /**
+     * The pulse fitter class.
+     */
+    private EcalPulseFitter pulseFitter = new EcalPulseFitter();
+
+    /**
+     * The time for one FADC sample (units = ns).
+     */
+    private static final int nsPerSample = 4;
+
+    /**
+     * The leading-edge threshold, relative to pedestal, for pulse-finding and time determination. Units = ADC. Used to
+     * convert mode-1 readout into mode-3/7 used by clustering. The default value of 12 is what we used for most of the
+     * 2014 run.
+     */
+    private double leadingEdgeThreshold = 12;
+
+    /**
+     * Integration range after (NSA) and before (NSB) threshold crossing. Units=ns, same as the DAQ configuration files.
+     * These must be multiples of 4 ns. Used for pulse integration in Mode-1, and pedestal subtraction in all modes. The
+     * default values of 20/100 are what we had during the entire 2014 run.
+     */
+    private int NSB = 20;
+    private int NSA = 100;
+
+    /**
+     * The number of samples in the FADC readout window. Needed in order to properly pedestal-correct clipped pulses for
+     * Mode-3/7. Ignored for mode-1 input, since it already knows its number of samples. A non-positive number disables
+     * pulse-clipped pedestals and reverts to the old behavior which assumed integration range was constant.
+     */
+    private int windowSamples = -1;
+
+    /**
+     * The maximum number of peaks to be searched for. This is applicable only to Mode-1 data.
+     */
+    private int nPeak = 3;
+
+    /**
+     * Perform Mode-7 algorithm, else Mode-3. Only applicable to Mode-1 data.
+     */
+    private boolean mode7 = true;
+
+    private EcalConditions ecalConditions = null;
+
+    /**
+     * Currently sets up a listener for DAQ configuration from EVIO. This should be removed to a standalone
+     * ECalRawConverter solely for trigger emulation.
+     */
+    public EcalRawConverter3() {
+        // Track changes in the DAQ configuration.
+        ConfigurationManager.addActionListener(new ActionListener() {
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                // If the DAQ configuration should be used, load the
+                // relevant settings into the driver.
+                if (useDAQConfig) {
+                    // Get the FADC configuration.
+                    config = ConfigurationManager.getInstance().getFADCConfig();
+
+                    // Load the settings.
+                    NSB = config.getNSB();
+                    NSA = config.getNSA();
+                    windowSamples = config.getWindowWidth() / 4;
+
+                    // Get the number of peaks.
+                    if (config.getMode() == 1) {
+                        nPeak = Integer.MAX_VALUE;
+                    } else {
+                        nPeak = config.getMaxPulses();
+                    }
+
+                    // Print the FADC configuration.
+                    System.out.println();
+                    System.out.println();
+                    System.out.printf("NSA            :: %d ns%n", NSA);
+                    System.out.printf("NSB            :: %d ns%n", NSB);
+                    System.out.printf("Window Samples :: %d clock-cycles%n", windowSamples);
+                    System.out.printf("Max Peaks      :: %d peaks%n", nPeak);
+                    System.out.println("======================================================================");
+                    System.out.println("=== FADC Pulse-Processing Settings ===================================");
+                    System.out.println("======================================================================");
+                    config.printConfig(System.out);
+                }
+            }
+        });
+    }
+
+    public void setUseFit(boolean useFit) {
+        this.useFit = useFit;
+    }
+
+    public void setFixShapeParameter(boolean fix) {
+        pulseFitter.fixShapeParameter = fix;
+    }
+
+    public void setGlobalFixedPulseWidth(double width) {
+        pulseFitter.globalThreePoleWidth = width;
+        pulseFitter.fixShapeParameter = true;
+    }
+
+    /**
+     * Pulses with threshold crossing earlier than this will not be fit.
+     */
+    public void setFitThresholdTimeLo(int sample) {
+        pulseFitter.threshRange[0] = sample;
+    }
+
+    /**
+     * Pulses with threshold crossing time greater than this will not be fit.
+     */
+    public void setFitThresholdTimeHi(int sample) {
+        pulseFitter.threshRange[1] = sample;
+    }
+
+    /**
+     * Tell Minuit to limit pulse time parameter in fit to be greater than this.
+     */
+    public void setFitLimitTimeLo(int sample) {
+        pulseFitter.t0limits[0] = sample;
+    }
+
+    /**
+     * Tell Minuit to limit pulse time parameter in fit to be less than this.
+     */
+    public void setFitLimitTimeHi(int sample) {
+        pulseFitter.t0limits[1] = sample;
+    }
+
+    /**
+     * Set threshold for pulse finding. Units = ADC
+     */
+    public void setLeadingEdgeThreshold(double thresh) {
+        leadingEdgeThreshold = thresh;
+    }
+
+    /**
+     * Set number of samples after threshold crossing for pulse integration range.
+     */
+    public void setNSA(int nsa) {
+        if (NSA % nsPerSample != 0 || NSA < 0) {
+            throw new RuntimeException("NSA must be multiples of 4ns and non-negative.");
+        }
+        NSA = nsa;
+    }
+
+    /**
+     * Set number of samples before threshold crossing for pulse integration range.
+     */
+    public void setNSB(int nsb) {
+        if (NSB % nsPerSample != 0 || NSB < 0) {
+            throw new RuntimeException("NSB must be multiples of 4ns and non-negative.");
+        }
+        NSB = nsb;
+    }
+
+    /**
+     * Set number of samples in readout window. Used for pedestal subtraction for clipped pulses. This is ignored for
+     * Mode-1 raw data, since Mode-1 knows its number of samples.
+     */
+    public void setWindowSamples(int windowSamples) {
+        this.windowSamples = windowSamples;
+    }
+
+    /**
+     * Set maximum number of pulses to search for in Mode-1 data.
+     */
+    public void setNPeak(int nPeak) {
+        if (nPeak < 1 || nPeak > 3) {
+            throw new RuntimeException("Npeak must be 1, 2, or 3.");
+        }
+        this.nPeak = nPeak;
+    }
+
+    /**
+     * Set Mode-7 emulation on/off. If off, falls back to Mode-3.
+     */
+    public void setMode7(boolean mode7) {
+        this.mode7 = mode7;
+    }
+
+ 
+
+    /**
+     * Enables using running pedestals calculated on the fly from previous events. If false, uses 442 fixed pedestals
+     * from the conditions system. Only applies to FADC Mode-1/7 input data formats.
+     */
+    public void setUseRunningPedestal(boolean useRunningPedestal) {
+        this.useRunningPedestal = useRunningPedestal;
+    }
+
+   
+
+    /**
+     * Set whether to use DAQ configuration read from EVIO to set EcalRawConverter parameters. This should be removed to
+     * a standalone EcalRawCongverterDriver solely for trigger emulation.
+     */
+    public void setUseDAQConfig(boolean state) {
+        useDAQConfig = state;
+    }
+
+
+
+    
+
+    /**
+     * Get pedestal for a single ADC sample. Choose whether to use static pedestal from database or running pedestal
+     * from mode-7.
+     */
+    public double getSingleSamplePedestal(EventHeader event, long cellID) {
+        if (useDAQConfig) {
+            // EcalChannel channel =
+            // ecalConditions.getChannelCollection().findGeometric(cellID);
+            return config.getPedestal(cellID);
+        }
+        if (useRunningPedestal && event != null) {
+            if (event.hasItem("EcalRunningPedestals")) {
+                @SuppressWarnings("unchecked")
+                Map<EcalChannel, Double> runningPedMap = (Map<EcalChannel, Double>) event.get("EcalRunningPedestals");
+                EcalChannel chan = ecalConditions.getChannelCollection().findGeometric(cellID);
+                if (!runningPedMap.containsKey(chan)) {
+                    System.err.println("************** Missing Pedestal");
+                } else {
+                    return runningPedMap.get(chan);
+                }
+            } else {
+                System.err.println("*****************************************************************");
+                System.err.println("**  You Requested a Running Pedestal, but it is NOT available. **");
+                System.err.println("**     Reverting to the database. Only printing this ONCE.     **");
+                System.err.println("*****************************************************************");
+                useRunningPedestal = false;
+            }
+        }
+        return findChannel(cellID).getCalibration().getPedestal();
+    }
+
+    /**
+     * Get pedestal for entire pulse integral. Account for clipping if windowSamples is greater than zero.
+     */
+    public double getPulsePedestal(EventHeader event, long cellID, int windowSamples, int thresholdCrossing) {
+        int firstSample, lastSample;
+        if (windowSamples > 0 && (NSA + NSB) / nsPerSample >= windowSamples) {
+            // special case where firmware always integrates entire window
+            firstSample = 0;
+            lastSample = windowSamples - 1;
+        } else {
+            firstSample = thresholdCrossing - NSB / nsPerSample;
+            lastSample = thresholdCrossing + NSA / nsPerSample - 1;
+            if (windowSamples > 0) {
+                // properly pedestal subtract pulses clipped by edge(s) of
+                // readout window:
+                if (firstSample < 0)
+                    firstSample = 0;
+                if (lastSample >= windowSamples)
+                    lastSample = windowSamples - 1;
+            }
+        }
+        return (lastSample - firstSample + 1) * getSingleSamplePedestal(event, cellID);
+    }
+
+    /**
+     * Emulate the FADC250 firmware in conversion of Mode-1 waveform to a Mode-3/7 pulse, given a time for threshold
+     * crossing.
+     */
+    public double[] convertWaveformToPulse(RawTrackerHit hit, int thresholdCrossing, boolean mode7) {
+
+        double fitQuality = -1;
+
+        short samples[] = hit.getADCValues();
+        // System.out.println("NewEvent");
+        // choose integration range:
+        int firstSample, lastSample;
+        if ((NSA + NSB) / nsPerSample >= samples.length) {
+            // firmware treats this case specially:
+            firstSample = 0;
+            lastSample = samples.length - 1;
+        } else {
+            firstSample = thresholdCrossing - NSB / nsPerSample;
+            lastSample = thresholdCrossing + NSA / nsPerSample - 1;
+        }
+
+        // mode-7's minimum/pedestal (average of first 4 samples):
+        double minADC = 0;
+        for (int jj = 0; jj < 4; jj++)
+            minADC += samples[jj];
+        // does the firmware's conversion of min to int occur before or after
+        // time calculation? undocumented.
+        // minADC=(int)(minADC/4);
+        minADC = (minADC / 4);
+
+        // System.out.println("Avg pedestal:\t"+minADC);
+
+        // mode-7's max pulse height:
+        double maxADC = 0;
+        // int sampleMaxADC=0;
+
+        // mode-3/7's pulse integral:
+        double sumADC = 0;
+
+        for (int jj = firstSample; jj <= lastSample; jj++) {
+
+            if (jj < 0)
+                continue;
+            if (jj >= samples.length)
+                break;
+
+            // integrate pulse:
+            sumADC += samples[jj];
+        }
+
+        // find pulse maximum:
+        // if (jj>firstSample && jj<samples.length-5) { // The "5" here is a
+        // firmware constant.
+        for (int jj = thresholdCrossing; jj < samples.length - 5; jj++) { // The
+                                                                          // "5"
+                                                                          // here
+                                                                          // is
+                                                                          // a
+                                                                          // firmware
+                                                                          // constant.
+            if (samples[jj + 1] < samples[jj]) {
+                // sampleMaxADC=jj;
+                maxADC = samples[jj];
+                break;
+            }
+        }
+
+        // pulse time with 4ns resolution:
+        double pulseTime = thresholdCrossing * nsPerSample;
+
+        // calculate Mode-7 high-resolution time:
+        if (mode7) {
+            if (thresholdCrossing < 4) {
+                // special case where firmware sets max to zero and time to 4ns
+                // time.
+                maxADC = 0;
+            } else if (maxADC > 0) {
+                // linear interpolation between threshold crossing and
+                // pulse maximum to find time at pulse half-height:
+
+                final double halfMax = (maxADC + minADC) / 2;
+                int t0 = -1;
+                for (int ii = thresholdCrossing - 1; ii < lastSample; ii++) {
+                    if (ii >= samples.length - 1)
+                        break;
+                    if (samples[ii] <= halfMax && samples[ii + 1] > halfMax) {
+                        t0 = ii;
+                        break;
+                    }
+                }
+                if (t0 > 0) {
+                    final int t1 = t0 + 1;
+                    final int a0 = samples[t0];
+                    final int a1 = samples[t1];
+                    // final double slope = (a1 - a0); // units = ADC/sample
+                    // final double yint = a1 - slope * t1; // units = ADC
+                    pulseTime = ((halfMax - a0) / (a1 - a0) + t0) * nsPerSample;
+                }
+            }
+        }
+
+        if (useFit) {
+            IFitResult fitResult = pulseFitter.fitPulse(hit, thresholdCrossing, maxADC);
+            if (fitResult != null) {
+                fitQuality = fitResult.quality();
+                if (fitQuality > 0) {
+                    pulseTime = fitResult.fittedParameter("time0") * nsPerSample;
+                    sumADC = fitResult.fittedParameter("integral");
+                    minADC = fitResult.fittedParameter("pedestal");
+                    maxADC = ((Ecal3PoleFunction) fitResult.fittedFunction()).maximum();
+                }
+            }
+        }
+
+        return new double[] {pulseTime, sumADC, minADC, maxADC, fitQuality};
+    }
+
+    /**
+     * This HitDtoA is for emulating the conversion of Mode-1 readout (RawTrackerHit) into what EcalRawConverter would
+     * have created from a Mode-3 or Mode-7 readout. Clustering classes will read the resulting CalorimeterHits same as
+     * if they were directly readout from the FADCs in Mode-3/7. For Mode-3, hit time is just the time of threshold
+     * crossing, with an optional time-walk correction. For Mode-7, it is a "high-resolution" one calculated by linear
+     * interpolation between threshold crossing and pulse maximum. TODO: Generate GenericObject (and corresponding
+     * LCRelation) to store min and max to fully emulate mode-7. This is less important for now.
+     */
+    public ArrayList<CalorimeterHit> HitDtoA(EventHeader event, RawTrackerHit hit) {
+        final long cellID = hit.getCellID();
+        final short samples[] = hit.getADCValues();
+        if (samples.length == 0)
+            return null;
+
+        // threshold is pedestal plus threshold configuration parameter:
+        final int absoluteThreshold;
+        if (useDAQConfig) {
+            // EcalChannel channel =
+            // ecalConditions.getChannelCollection().findGeometric(hit.getCellID());
+            // int leadingEdgeThreshold =
+            // ConfigurationManager.getInstance().getFADCConfig().getThreshold(channel.getChannelId());
+            int leadingEdgeThreshold = config.getThreshold(cellID);
+            absoluteThreshold = (int) (getSingleSamplePedestal(event, cellID) + leadingEdgeThreshold);
+        } else {
+            absoluteThreshold = (int) (getSingleSamplePedestal(event, cellID) + leadingEdgeThreshold);
+        }
+
+        ArrayList<Integer> thresholdCrossings = new ArrayList<Integer>();
+
+        // special case, first sample is above threshold:
+        if (samples[0] > absoluteThreshold) {
+            thresholdCrossings.add(0);
+        }
+
+        // search for threshold crossings:
+        for (int ii = 1; ii < samples.length; ++ii) {
+            if (samples[ii] > absoluteThreshold && samples[ii - 1] <= absoluteThreshold) {
+
+                // found one:
+                thresholdCrossings.add(ii);
+
+                // search for next threshold crossing begins at end of this
+                // pulse:
+                if (useDAQConfig && ConfigurationManager.getInstance().getFADCConfig().getMode() == 1) {
+                    // special case, emulating SSP:
+                    ii += 8;
+                } else {
+                    // "normal" case, emulating FADC250:
+                    ii += NSA / nsPerSample - 1;
+                }
+
+                // firmware limit on # of peaks:
+                if (thresholdCrossings.size() >= nPeak)
+                    break;
+            }
+        }
+
+        // make hits
+        ArrayList<CalorimeterHit> newHits = new ArrayList<CalorimeterHit>();
+        for (int thresholdCrossing : thresholdCrossings) {
+            // do pulse integral:
+            final double[] data = convertWaveformToPulse(hit, thresholdCrossing, mode7);
+            double time = data[0];
+            double sum = data[1];
+            // final double min = data[2]; // TODO: stick min and max in a
+            // GenericObject with an
+            // final double max = data[3]; // LCRelation to finish mode-7
+            // emulation
+            final double fitQuality = data[4];
+
+            if (!useFit || fitQuality <= 0) {
+                // do pedestal subtraction:
+                sum -= getPulsePedestal(event, cellID, samples.length, thresholdCrossing);
+            }
+
+            // do gain scaling using a dummy gain.  
+            
+
+            newHits.add(CalorimeterHitUtilities.create(sum, time, cellID));
+        }
+
+        return newHits;
+    }
+
+   
+
+    /**
+     * This HitDtoA is for Mode-3 data. A time-walk correction can be applied.
+     */
+    public CalorimeterHit HitDtoA(EventHeader event, RawCalorimeterHit hit, double timeOffset) {
+        if (hit.getTimeStamp() % 64 != 0) {
+            System.out.println("unexpected timestamp " + hit.getTimeStamp());
+        }
+        double time = hit.getTimeStamp() / 16.0;
+        long id = hit.getCellID();
+        double pedestal = getPulsePedestal(event, id, windowSamples, (int) time / nsPerSample);
+        double adcSum = hit.getAmplitude() - pedestal;
+        //double rawEnergy = adcToEnergy(adcSum);
+        
+        return CalorimeterHitUtilities.create(adcSum, time + timeOffset, id);
+    }
+
+    /**
+     * This HitDtoA is exclusively for Mode-7 data, hence the GenericObject parameter.
+     */
+    public CalorimeterHit HitDtoA(EventHeader event, RawCalorimeterHit hit, GenericObject mode7Data, double timeOffset) {
+        double time = hit.getTimeStamp() / 16.0; // timestamps use the full 62.5
+                                                 // ps resolution
+        long id = hit.getCellID();
+        double pedestal = getPulsePedestal(event, id, windowSamples, (int) time / nsPerSample);
+        double adcSum = hit.getAmplitude() - pedestal;
+        //double rawEnergy = adcToEnergy(adcSum);
+        return CalorimeterHitUtilities.create(adcSum, time + timeOffset, id);
+    }
+
+    /**
+     * This converts a corrected pulse integral (pedestal-subtracted and gain-scaled) back into raw pulse integral with
+     * units ADC.
+     */
+    public RawCalorimeterHit HitAtoD(CalorimeterHit hit) {
+        int time = (int) (Math.round(hit.getTime() / 4.0) * 64.0);
+        long id = hit.getCellID();
+        // Get the channel data.
+        //EcalChannelConstants channelData = findChannel(id);
+        int amplitude;
+        double pedestal = getPulsePedestal(null, id, windowSamples, (int) hit.getTime() / nsPerSample);
+        amplitude = (int) Math.round((hit.getRawEnergy() / EcalUtils.MeV) / (EcalUtils.gainFactor * EcalUtils.ecalReadoutPeriod) + pedestal);
+       
+        // time += findChannel(id).getTimeShift().getTimeShift();
+        RawCalorimeterHit h = new BaseRawCalorimeterHit(id, amplitude, time);
+        return h;
+    }
+
+    /**
+     * This should probably be deprecated. HitDtoA(EventHeader,RawTrackerHit) has the same functionality if NSA+NSB >
+     * windowSamples, with the exception that that one also finds pulse time instead of this one's always reporting
+     * zero.
+     */
+    public CalorimeterHit HitDtoA(RawTrackerHit hit) {
+        double time = hit.getTime();
+        long id = hit.getCellID();
+        double adcSum = sumADC(hit);
+        return CalorimeterHitUtilities.create(adcSum, time, id);
+    }
+    
+    /**
+     * Integrate the entire window. Return pedestal-subtracted integral.
+     */
+    public int sumADC(RawTrackerHit hit) {
+        EcalChannelConstants channelData = findChannel(hit.getCellID());
+        double pedestal;
+        if (useDAQConfig) {
+            // EcalChannel channel =
+            // ecalConditions.getChannelCollection().findGeometric(hit.getCellID());
+            pedestal = config.getPedestal(hit.getCellID());
+        } else {
+            pedestal = channelData.getCalibration().getPedestal();
+        }
+
+        int sum = 0;
+        short samples[] = hit.getADCValues();
+        for (int isample = 0; isample < samples.length; ++isample) {
+            sum += (samples[isample] - pedestal);
+        }
+        return sum;
+    }
+
+    /**
+     * Must be set when an object EcalRawConverter is created.
+     *
+     * @param detector (long)
+     */
+    public void setDetector(Detector detector) {
+        // ECAL combined conditions object.
+        ecalConditions = DatabaseConditionsManager.getInstance().getEcalConditions();
+        pulseFitter.setDetector(detector);
+    }
+
+    /**
+     * Convert physical ID to gain value.
+     *
+     * @param cellID (long)
+     * @return channel constants (EcalChannelConstants)
+     */
+    public EcalChannelConstants findChannel(long cellID) {
+        return ecalConditions.getChannelConstants(ecalConditions.getChannelCollection().findGeometric(cellID));
+    }
+     
+}

Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3Driver.java
 =============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3Driver.java	(added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/EcalRawConverter3Driver.java	Mon Oct 10 06:41:35 2016
@@ -0,0 +1,586 @@
+package org.hps.recon.ecal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hps.conditions.database.DatabaseConditionsManager;
+import org.hps.conditions.ecal.EcalChannelConstants;
+import org.hps.conditions.ecal.EcalConditions;
+import org.hps.record.daqconfig.ConfigurationManager;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.GenericObject;
+import org.lcsim.event.LCRelation;
+import org.lcsim.event.RawCalorimeterHit;
+import org.lcsim.event.RawTrackerHit;
+import org.lcsim.geometry.Detector;
+import org.lcsim.lcio.LCIOConstants;
+import org.lcsim.util.Driver;
+
+/**
+ * This <code>Driver</code> converts raw ECal data collections to {@link org.lcsim.event.CalorimeterHit} collections
+ * with energy and time information. The {@link EcalRawConverter} does most of the low-level work.
+ * <p>
+ * The following input collections are used:
+ * <ul>
+ * <li>EcalReadoutHits
+ * <li>
+ * <li>EcalReadoutExtraDataRelations</li>
+ * <li>EcalRunningPedestals</li>
+ * </ul>
+ * <p>
+ * The results are by default written to the <b>EcalCalHits</b> output collection.
+ */
+public class EcalRawConverter3Driver extends Driver {
+
+    // To import database conditions
+    private EcalConditions ecalConditions = null;
+
+    private EcalRawConverter3 converter = null;
+    /**
+     * Input collection name (unless runBackwards=true, then it's output). Can be a
+     * {@link org.lcsim.event.RawTrackerHit} or {@link org.lcsim.event.RawCalorimeterHit} These have ADC and sample time
+     * information.
+     */
+    private String rawCollectionName = "EcalReadoutHits";
+
+    /**
+     * Output collection name (unless runBackwards=true, then it's input). Always a
+     * {@link org.lcsim.event.CalorimeterHit} This has energy (GeV) and ns time information.
+     */
+    private String ecalCollectionName = "EcalUncalHits";
+
+    /**
+     * ecalCollectionName "type" (must match detector-data)
+     */
+    private final String ecalReadoutName = "EcalHits";
+
+    /*
+     * Output relation between ecalCollectionName and Mode-7 pedestals
+     */
+    private static final String extraDataRelationsName = "EcalReadoutExtraDataRelations";
+
+    private boolean debug = false;
+
+    /**
+     * Hit threshold in GeV. Anything less will not be put into LCIO.
+     */
+    private double threshold = Double.NEGATIVE_INFINITY;
+
+    /**
+     * Whether to reject bad crystals.
+     */
+    private boolean applyBadCrystalMap = true;
+
+    /**
+     * Whether to reject bad FADC channels.
+     */
+    private boolean dropBadFADC = false;
+
+    /**
+     * If true, convert ecalCollectionName to rawCollectionName (GeV to ADC). Else, convert rawCollectionName to
+     * ecalCollectionName (ADC to GeV).
+     */
+    private boolean runBackwards = false;
+
+    /**
+     * 
+     */
+    private boolean useTimestamps = false;
+
+    /**
+     * 
+     */
+    private boolean useTruthTime = false;
+
+    /**
+     * Whether to use DAQ config read from EVIO for EcalRawConverter parameters. Should be completely removed to a
+     * standalone class soilely for trigger emulation.
+     */
+    private boolean useDAQConfig = false;
+
+    /**
+     * Whether to perform "firmware algorithm" on Mode-1 data. If so, this includes finding a threshold crossing,
+     * extracting a pulse time, and integrating over some configurable sample range inside the window to extract pulse
+     * integral. If not, it simply integrates the entire window and makes no attempt at extracting pulse time. This is
+     * poorly named.
+     */
+    private boolean emulateFirmware = true;
+
+    public EcalRawConverter3Driver() {
+        converter = new EcalRawConverter3();
+    }
+
+    /**
+     * Set to <code>true</code> to use pulse fitting instead of arithmetic integration:<br/>
+     */
+    public void setUseFit(boolean useFit) {
+        converter.setUseFit(useFit);
+    }
+
+    /**
+     * Fix 3-pole function width to be the same for all 442 ECal channels. Units=samples.
+     */
+    public void setGlobalFixedPulseWidth(double width) {
+        converter.setGlobalFixedPulseWidth(width);
+    }
+
+    /**
+     * Set to <code>true</code> to fix fitted pulse widths to their channel's mean value:<br/>
+     */
+    public void setFixShapeParameter(boolean fix) {
+        converter.setFixShapeParameter(fix);
+    }
+
+    /**
+     * Limit threshold crossing range that is candidate for pulse-fitting. Units=samples.
+     */
+    public void setFitThresholdTimeLo(int sample) {
+        converter.setFitThresholdTimeLo(sample);
+    }
+
+    public void setFitThresholdTimeHi(int sample) {
+        converter.setFitThresholdTimeHi(sample);
+    }
+
+    /**
+     * Constrain pulse fit time0 parameter. Units=samples.
+     */
+    public void setFitLimitTimeLo(int sample) {
+        converter.setFitLimitTimeLo(sample);
+    }
+
+    public void setFitLimitTimeHi(int sample) {
+        converter.setFitLimitTimeHi(sample);
+    }
+
+    /**
+     * Set to <code>true</code> to use a running pedestal calibration from mode 7 data.
+     * <p>
+     * The running pedestal values are retrieved from the event collection "EcalRunningPedestals" which is a
+     * <code>Map</code> between {@link org.hps.conditions.ecal.EcalChannel} objects are their average pedestal.
+     * 
+     * @param useRunningPedestal True to use a running pedestal value.
+     */
+    public void setUseRunningPedestal(boolean useRunningPedestal) {
+        converter.setUseRunningPedestal(useRunningPedestal);
+    }
+
+    /**
+     * Set to <code>true</code> to generate a {@link org.lcsim.event.CalorimeterHit} collection which is a conversion
+     * from energy to raw signals.
+     * 
+     * @param runBackwards True to run the procedure backwards.
+     */
+    public void setRunBackwards(boolean runBackwards) {
+        this.runBackwards = runBackwards;
+    }
+
+    /**
+     * Set to <code>true</code> to drop hits that are mapped to a hard-coded bad FADC configuration from the Test Run.
+     * 
+     * @param dropBadFADC True to drop hits mapped to a bad FADC.
+     */
+    public void setDropBadFADC(boolean dropBadFADC) {
+        this.dropBadFADC = dropBadFADC;
+    }
+
+    /**
+     * Set a minimum energy threshold in GeV for created {@link org.lcsim.event.CalorimeterHit} objects to be written
+     * into the output collection.
+     * 
+     * @param threshold The minimum energy threshold in GeV.
+     */
+    public void setThreshold(double threshold) {
+        this.threshold = threshold;
+    }
+
+    /**
+     * Set to <code>true</code> to use Mode-7 emulation in calculations. False is Mode-3.
+     * 
+     * @param mode7 True to use Mode-7 emulation in calculations.
+     */
+    public void setEmulateMode7(boolean mode7) {
+        converter.setMode7(mode7);
+    }
+
+    /**
+     * Set to <code>true</code> to emulate firmware conversion of Mode-1 to Mode-3/7 data.
+     * 
+     * @param emulateFirmware True to use firmware emulation.
+     */
+    public void setEmulateFirmware(boolean emulateFirmware) {
+        this.emulateFirmware = emulateFirmware;
+    }
+
+    /**
+     * Set the leading-edge threshold in ADC counts, relative to pedestal, for pulse-finding and time determination.
+     * <p>
+     * Used to convert Mode-1 readout into Mode-3 or Mode-7 data that is usable by clustering.
+     * 
+     * @param threshold The leading edge threshold in ADC counts.
+     */
+    public void setLeadingEdgeThreshold(double threshold) {
+        converter.setLeadingEdgeThreshold(threshold);
+    }
+
+    /**
+     * Set the number of samples in the FADC readout window.
+     * <p>
+     * This is needed in order to properly pedestal-correct clipped pulses for mode-3 and mode-7. It is ignored for
+     * mode-1 input, since this data already includes the number of samples.
+     * <p>
+     * A non-positive number disables pulse-clipped pedestals and reverts to the old behavior which assumed that the
+     * integration range was constant.
+     * 
+     * @param windowSamples The number of samples in the FADC readout window.
+     */
+    public void setWindowSamples(int windowSamples) {
+        converter.setWindowSamples(windowSamples);
+    }
+
+    /**
+     * Set the integration range in nanoseconds after the threshold crossing.
+     * <p>
+     * These numbers must be multiples of 4 nanoseconds.
+     * <p>
+     * This value is used for pulse integration in Mode-1, and pedestal subtraction in all modes.
+     * 
+     * @param nsa The number of nanoseconds after the threshold crossing.
+     * @see #setNsb(int)
+     */
+    public void setNsa(int nsa) {
+        converter.setNSA(nsa);
+    }
+
+    /**
+     * Set the integration range in nanoseconds before the threshold crossing.
+     * <p>
+     * These numbers must be multiples of 4 nanoseconds.
+     * <p>
+     * This value is used for pulse integration in Mode-1, and pedestal subtraction in all modes.
+     * 
+     * @param nsb The number of nanoseconds after the threshold crossing.
+     * @see #setNsa(int)
+     */
+    public void setNsb(int nsb) {
+        converter.setNSB(nsb);
+    }
+
+    /**
+     * Set the maximum number of peaks to search for in the signal, which must be between 1 and 3, inclusive.
+     * 
+     * @param nPeak The maximum number of peaks to search for in the signal.
+     */
+    public void setNPeak(int nPeak) {
+        converter.setNPeak(nPeak);
+    }
+
+    /**
+     * Set the {@link org.lcsim.event.CalorimeterHit} collection name, which is used as input in "normal" mode and
+     * output when running "backwards".
+     * 
+     * @param ecalCollectionName The <code>CalorimeterHit</code> collection name.
+     * @see #runBackwards
+     */
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+
+    /**
+     * Set the raw collection name which is used as output in "normal" mode and input when running "backwards".
+     * <p>
+     * Depending on the Driver configuration, this could be a collection of {@link org.lcsim.event.RawTrackerHit}
+     * objects for Mode-1 or {@link org.lcsim.event.RawCalorimeterHit} objects for Mode-3 or Mode-7.
+     * 
+     * @param rawCollectionName The raw collection name.
+     */
+    public void setRawCollectionName(String rawCollectionName) {
+        this.rawCollectionName = rawCollectionName;
+    }
+
+    /**
+     * Set to <code>true</code> to ignore data from channels that are flagged as "bad" in the conditions system.
+     * 
+     * @param apply True to ignore bad channels.
+     */
+    public void setApplyBadCrystalMap(boolean apply) {
+        this.applyBadCrystalMap = apply;
+    }
+
+    /**
+     * Set to <code>true</code> to turn on debug output.
+     * 
+     * @param debug True to turn on debug output.
+     */
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+    }
+
+    /**
+     * Set to <code>true</code> to use timestamp information from the ECal or trigger.
+     * 
+     * @param useTimestamps True to use timestamp information.
+     */
+    // FIXME: What does this actually do? What calculations does it affect?
+    public void setUseTimestamps(boolean useTimestamps) {
+        this.useTimestamps = useTimestamps;
+    }
+
+    /**
+     * Set to <code>true</code> to use MC truth information.
+     * 
+     * @param useTruthTime True to use MC truth information.
+     */
+    // FIXME: What does this actually do? What calculations does it affect?
+    public void setUseTruthTime(boolean useTruthTime) {
+        this.useTruthTime = useTruthTime;
+    }
+
+    /**
+     * Sets whether the driver should use the DAQ configuration from EvIO file for its parameters. If activated, the
+     * converter will obtain gains, thresholds, pedestals, the window size, and the pulse integration window from the
+     * EvIO file. This will replace and overwrite any manually defined settings.<br/>
+     * <br/>
+     * Note that if this setting is active, the driver will not output any data until a DAQ configuration has been read
+     * from the data stream.
+     * 
+     * @param state - <code>true</code> indicates that the configuration should be read from the DAQ data in an EvIO
+     *            file. Setting this to <code>false</code> will cause the driver to use its regular manually-defined
+     *            settings and pull gains and pedestals from the conditions database.
+     */
+    public void setUseDAQConfig(boolean state) {
+        useDAQConfig = state;
+        converter.setUseDAQConfig(state);
+    }
+
+    @Override
+    public void startOfData() {
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+    }
+
+    @Override
+    public void detectorChanged(Detector detector) {
+
+        // set the detector for the converter
+        // FIXME: This method doesn't even need the detector object and does not use it.
+        converter.setDetector(detector);
+
+        // ECAL combined conditions object.
+        ecalConditions = DatabaseConditionsManager.getInstance().getEcalConditions();
+    }
+
+    /**
+     * @return false if the channel is a good one, true if it is a bad one
+     * @param hit the <code>CalorimeterHit</code> pointing to the channel
+     */
+    public boolean isBadCrystal(CalorimeterHit hit) {
+        // Get the channel data.
+        EcalChannelConstants channelData = findChannel(hit.getCellID());
+        return channelData.isBadChannel();
+    }
+
+    /**
+     * @return false if the ADC is a good one, true if it is a bad one
+     * @param hit the <code>CalorimeterHit</code> pointing to the FADC
+     */
+    public boolean isBadFADC(CalorimeterHit hit) {
+        return (getCrate(hit.getCellID()) == 1 && getSlot(hit.getCellID()) == 3);
+    }
+
+    private static double getTimestamp(int system, EventHeader event) { // FIXME: copied from
+                                                                        // org.hps.readout.ecal.ReadoutTimestamp
+        if (event.hasCollection(GenericObject.class, "ReadoutTimestamps")) {
+            List<GenericObject> timestamps = event.get(GenericObject.class, "ReadoutTimestamps");
+            for (GenericObject timestamp : timestamps) {
+                if (timestamp.getIntVal(0) == system) {
+                    return timestamp.getDoubleVal(0);
+                }
+            }
+            return 0;
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public void process(EventHeader event) {
+        // Do not process the event if the DAQ configuration should be
+        // used for value, but is not initialized.
+        if (useDAQConfig && !ConfigurationManager.isInitialized()) {
+            return;
+        }
+
+        final int SYSTEM_TRIGGER = 0;
+        // final int SYSTEM_TRACKER = 1;
+        final int SYSTEM_ECAL = 2;
+
+        double timeOffset = 0.0;
+        if (useTimestamps) {
+            double t0ECal = getTimestamp(SYSTEM_ECAL, event);
+            double t0Trig = getTimestamp(SYSTEM_TRIGGER, event);
+            timeOffset += (t0ECal - t0Trig) + 200.0;
+        }
+        if (useTruthTime) {
+            double t0ECal = getTimestamp(SYSTEM_ECAL, event);
+            timeOffset += ((t0ECal + 250.0) % 500.0) - 250.0;
+        }
+
+        int flags = 0;
+        flags += 1 << LCIOConstants.RCHBIT_TIME; // store hit time
+        flags += 1 << LCIOConstants.RCHBIT_LONG; // store hit position; this flag has no effect for RawCalorimeterHits
+
+        if (!runBackwards) {
+            ArrayList<CalorimeterHit> newHits = new ArrayList<CalorimeterHit>();
+
+            /*
+             * This is for FADC Mode-1 data:
+             */
+            if (event.hasCollection(RawTrackerHit.class, rawCollectionName)) {
+                List<RawTrackerHit> hits = event.get(RawTrackerHit.class, rawCollectionName);
+
+                for (RawTrackerHit hit : hits) {
+
+                    ArrayList<CalorimeterHit> newHits2 = new ArrayList<CalorimeterHit>();
+                    if (emulateFirmware) {
+                        newHits2.addAll(converter.HitDtoA(event, hit));
+                    } else {
+                        newHits2.add(converter.HitDtoA(hit));
+                    }
+
+                    for (CalorimeterHit newHit : newHits2) {
+
+                        // Get the channel data.
+                        EcalChannelConstants channelData = findChannel(newHit.getCellID());
+
+                        if (applyBadCrystalMap && channelData.isBadChannel()) {
+                            continue;
+                        }
+                        if (dropBadFADC && isBadFADC(newHit)) {
+                            continue;
+                        }
+                        if (newHit.getRawEnergy() > threshold) {
+                            newHits.add(newHit);
+                        }
+                    }
+                }
+                event.put(ecalCollectionName, newHits, CalorimeterHit.class, flags, ecalReadoutName);
+            }
+
+            /*
+             * This is for FADC pulse mode data (Mode-3 or Mode-7):
+             */
+            if (event.hasCollection(RawCalorimeterHit.class, rawCollectionName)) {
+
+                /*
+                 * This is for FADC Mode-7 data:
+                 */
+                if (event.hasCollection(LCRelation.class, extraDataRelationsName)) { // extra information available from
+                                                                                     // mode 7 readout
+                    List<LCRelation> extraDataRelations = event.get(LCRelation.class, extraDataRelationsName);
+                    for (LCRelation rel : extraDataRelations) {
+                        RawCalorimeterHit hit = (RawCalorimeterHit) rel.getFrom();
+                        if (debug) {
+                            System.out.format("old hit energy %d\n", hit.getAmplitude());
+                        }
+                        GenericObject extraData = (GenericObject) rel.getTo();
+                        CalorimeterHit newHit;
+                        newHit = converter.HitDtoA(event, hit, extraData, timeOffset);
+                        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);
+                        }
+
+                    }
+                } else {
+                    /*
+                     * This is for FADC Mode-3 data:
+                     */
+                    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;
+                        newHit = converter.HitDtoA(event, hit, timeOffset);
+                        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);
+                    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);
+            }
+        }
+
+    }
+
+    /**
+     * Convert physical ID to gain value.
+     *
+     * @param cellID (long)
+     * @return channel constants (EcalChannelConstants)
+     */
+    private EcalChannelConstants findChannel(long cellID) {
+        return ecalConditions.getChannelConstants(ecalConditions.getChannelCollection().findGeometric(cellID));
+    }
+
+    /**
+     * Return crate number from cellID
+     *
+     * @param cellID (long)
+     * @return Crate number (int)
+     */
+    private int getCrate(long cellID) {
+        // Find the ECAL channel and return the crate number.
+        return ecalConditions.getChannelCollection().findGeometric(cellID).getCrate();
+    }
+
+    /**
+     * Return slot number from cellID
+     *
+     * @param cellID (long)
+     * @return Slot number (int)
+     */
+    private int getSlot(long cellID) {
+        // Find the ECAL channel and return the slot number.
+        return ecalConditions.getChannelCollection().findGeometric(cellID).getSlot();
+    }
+}