Print

Print


Author: [log in to unmask]
Date: Sat May 23 13:16:58 2015
New Revision: 3016

Log:
improving ecal pulse fitting

Added:
    java/trunk/steering-files/src/main/resources/org/hps/steering/users/baltzell/EngineeringRun2014EcalRecon_FitPulses.lcsim
    java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverterDriver.java
Modified:
    java/trunk/users/src/main/java/org/hps/users/baltzell/Ecal3PoleFunction.java
    java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverter.java

Added: java/trunk/steering-files/src/main/resources/org/hps/steering/users/baltzell/EngineeringRun2014EcalRecon_FitPulses.lcsim
 =============================================================================
--- java/trunk/steering-files/src/main/resources/org/hps/steering/users/baltzell/EngineeringRun2014EcalRecon_FitPulses.lcsim	(added)
+++ java/trunk/steering-files/src/main/resources/org/hps/steering/users/baltzell/EngineeringRun2014EcalRecon_FitPulses.lcsim	Sat May 23 13:16:58 2015
@@ -0,0 +1,77 @@
+<!-- 
+    Offline reconstruction for 2014 engineering run (ECal only) data.
+
+    Changes made by JM:
+            
+    -Replaced clustering Drivers with new recon.ecal.cluster classes.    
+    -Commented out the legacy clusterer. 
+    -Configured ReconClusterDriver to not write the rejected hit collection.
+    -Changed output cluster collection names.
+     
+     NAB: (Feb 11, 2015) Added EcalRunningPedestalDriver
+     
+    @author Matt Graham <[log in to unmask]> 
+    @author Jeremy McCormick<[log in to unmask]>
+-->
+<lcsim xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
+    xs:noNamespaceSchemaLocation="http://www.lcsim.org/schemas/lcsim/1.0/lcsim.xsd">
+    <execute>
+        <!--<driver name="EventMarkerDriver" />-->
+        <driver name="EcalRunningPedestal"/>
+        <driver name="EcalRawConverter" />
+        <driver name="ReconClusterer" />
+        <driver name="GTPOnlineClusterer" />
+        <driver name="LCIOWriter" />
+        <!--<driver name="AidaSaveDriver" />-->
+        <driver name="CleanupDriver" />
+    </execute>
+    <drivers>
+        <driver name="EventMarkerDriver" type="org.lcsim.job.EventMarkerDriver">
+            <eventInterval>1</eventInterval>
+        </driver>
+        <driver name="EcalRunningPedestal" type="org.hps.recon.ecal.EcalRunningPedestalDriver">
+            <minLookbackEvents>10</minLookbackEvents>
+            <maxLookbackEvents>50</maxLookbackEvents>
+        </driver>
+        <!--<driver name="EcalRawConverter" type="org.hps.recon.ecal.EcalRawConverterDriver">-->
+        <driver name="EcalRawConverter" type="org.hps.users.baltzell.EcalRawConverterDriver">
+            <ecalCollectionName>EcalCalHits</ecalCollectionName>
+            <use2014Gain>false</use2014Gain>
+            <useTimestamps>false</useTimestamps>
+            <useTruthTime>false</useTruthTime>
+            <useRunningPedestal>true</useRunningPedestal>
+            <useTimeWalkCorrection>false</useTimeWalkCorrection>
+            <emulateFirmware>true</emulateFirmware>
+            <emulateMode7>true</emulateMode7>
+            <useFit>true</useFit>
+            <fixShapeParameter>true</fixShapeParameter>
+            <debug>0</debug>
+            <fitFileName>ecalPulseFits-SHAPEFIX.txt-Minus10</fitFileName>
+        </driver>       
+        <driver name="ReconClusterer" type="org.hps.recon.ecal.cluster.ReconClusterDriver">
+            <logLevel>WARNING</logLevel>
+            <outputClusterCollectionName>EcalClusters</outputClusterCollectionName>
+            <hitEnergyThreshold>0.01</hitEnergyThreshold>
+            <seedEnergyThreshold>0.100</seedEnergyThreshold> 
+            <clusterEnergyThreshold>0.200</clusterEnergyThreshold>
+            <minTime>0.0</minTime>
+            <timeWindow>25.0</timeWindow>
+            <useTimeCut>true</useTimeCut>
+            <writeRejectedHitCollection>false</writeRejectedHitCollection>
+        </driver>
+        <driver name="GTPOnlineClusterer" type="org.hps.recon.ecal.cluster.ClusterDriver">
+            <logLevel>WARNING</logLevel>
+            <clustererName>GTPOnlineClusterer</clustererName>
+            <outputClusterCollectionName>EcalClustersGTP</outputClusterCollectionName>
+            <!-- seedMinEnergy -->
+            <cuts>0.100</cuts>
+        </driver>        
+        <driver name="LCIOWriter" type="org.lcsim.util.loop.LCIODriver">
+            <outputFilePath>${outputFile}.slcio</outputFilePath>
+        </driver>
+        <driver name="AidaSaveDriver" type="org.lcsim.job.AidaSaveDriver">
+          <outputFileName>ecalPulseFits.root</outputFileName>
+        </driver>
+        <driver name="CleanupDriver" type="org.lcsim.recon.tracking.digitization.sisim.config.ReadoutCleanupDriver" />
+    </drivers>
+</lcsim>

Modified: java/trunk/users/src/main/java/org/hps/users/baltzell/Ecal3PoleFunction.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/baltzell/Ecal3PoleFunction.java	(original)
+++ java/trunk/users/src/main/java/org/hps/users/baltzell/Ecal3PoleFunction.java	Sat May 23 13:16:58 2015
@@ -7,8 +7,9 @@
  
     protected double pedestal=0;
     protected double time0=0;
-    protected double amplitude=0;
-    protected double shape=0;
+    protected double integral=0;
+    protected double width=0;
+    public boolean debug=false;
    
     public Ecal3PoleFunction() {
         this("");
@@ -16,19 +17,20 @@
     public Ecal3PoleFunction(String title) {
         super();
         this.variableNames = new String[] { "time" };
-        this.parameterNames = new String[] { "pedestal","time0","amplitude","shape" };
+        this.parameterNames = new String[] { "pedestal","time0","integral","width" };
         init(title);
     }
+   
+    void setDebug(boolean debug) { this.debug=debug; }
     
     @Override
     public double value(double[] v) {
         final double time = v[0];
         if (time <= time0) return pedestal;
         return pedestal 
-             + amplitude 
+             + integral / Math.pow(width,3) / 2 
              * Math.pow(time-time0,2)
-             / (2*shape)
-             * Math.exp(-(time-time0)/shape);
+             * Math.exp(-(time-time0)/width);
     }
    
     @Override
@@ -36,9 +38,9 @@
         super.setParameters(pars);
         pedestal=pars[0];
         time0=pars[1];
-        amplitude=pars[2];
-        shape=pars[3];
-        System.err.println(String.format("%8.2f %8.2f %8.2f %8.2f",pars[0],pars[1],pars[2],pars[3]));
+        integral=pars[2];
+        width=pars[3];
+        if (debug) System.err.println(String.format("%8.2f %8.2f %8.2f %8.2f",pars[0],pars[1],pars[2],pars[3]));
     }
 
     @Override
@@ -46,7 +48,8 @@
         super.setParameter(key,value);
         if      (key.equals("pedestal"))  pedestal=value;
         else if (key.equals("time0"))     time0=value;
-        else if (key.equals("amplitude")) amplitude=value;
-        else if (key.equals("shape"))     shape=value;
+        else if (key.equals("integral"))  integral=value;
+        else if (key.equals("width"))     width=value;
     }
+    
 }

Modified: java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverter.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverter.java	(original)
+++ java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverter.java	Sat May 23 13:16:58 2015
@@ -1,4 +1,6 @@
 package org.hps.users.baltzell;
+
+import org.hps.users.baltzell.Ecal3PoleFunction;
 
 import hep.aida.IAnalysisFactory;
 import hep.aida.IDataPointSet;
@@ -11,6 +13,8 @@
 
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.logging.Level;
@@ -73,7 +77,8 @@
     IFunction fitFunction=new Ecal3PoleFunction();
     IDataPointSet data=aida.analysisFactory().createDataPointSetFactory(null).create("ADC DataPointSet", 2);
    
-    IHistogram1D hShape = aida.histogram1D("shape",500,0,5);
+    IHistogram1D hWidth = aida.histogram1D("hShape",500,0,5);
+    IHistogram1D hQuality = aida.histogram1D("hChiSquare",500,0,5);
     
     private boolean useTimeWalkCorrection = false;
     private boolean useRunningPedestal = false;
@@ -83,6 +88,12 @@
     private boolean useDAQConfig = false;
     private FADCConfig config = null;
 
+    public int debug = 0;
+    public boolean useFit = false;
+    public String fitFileName = null;
+    public FileWriter fitFileWriter = null;
+    public boolean fixShapeParameter = false;
+
     /*
      * The time for one FADC sample (units = ns).
      */
@@ -132,10 +143,8 @@
     private EcalConditions ecalConditions = null;
 
     public EcalRawConverter() {
-        
-        Logger minuitLogger = Logger.getLogger("org.freehep.math.minuit");
-        minuitLogger.setLevel(Level.OFF);
-        
+        if (debug<1) Logger.getLogger("org.freehep.math.minuit").setLevel(Level.OFF);
+
     	// Track changes in the DAQ configuration.
     	ConfigurationManager.addActionListener(new ActionListener() {
 			@Override
@@ -316,8 +325,8 @@
     public IFitResult fitPulse(short samples[],int thresholdCrossing,double maxADC,double noise) {
        
         if (thresholdCrossing < 9 || thresholdCrossing > 15) return null;
-        
-        System.err.println("FITTING.....................................................");
+    
+        if (debug>0) System.err.println("FITTING.....................................................");
        
         data.clear();
         int nped=0;
@@ -330,39 +339,70 @@
         ped /= nped;
         if (nped==0) return null;
         int npts=0;
-        for (int ii=thresholdCrossing-5; ii<thresholdCrossing+20; ii++) {
-            if (ii<0 || ii>=samples.length) break;
-            System.err.print(ii+":"+samples[ii]+" ");
+        int sumADC=0;
+        for (int ii=thresholdCrossing-10; ii<thresholdCrossing+15; ii++) {
+            if (ii<0) continue;
+            if (ii>=samples.length) break;
+            if (debug>0) System.err.print(ii+":"+samples[ii]+" ");
             data.addPoint();
             data.point(npts).coordinate(0).setValue(ii);
             data.point(npts).coordinate(1).setValue(samples[ii]);
             data.point(npts).coordinate(1).setErrorMinus(noise);
             data.point(npts).coordinate(1).setErrorPlus(noise);
+            sumADC += samples[ii];
             npts++;
         }
-        System.err.print("\n");
+        if (debug>0) System.err.print("\n");
         if (npts>=10) {
-//            System.err.println("------- "+ped+" "+thresholdCrossing+" "+maxADC);
+            if (debug>0) System.err.println("------- "+ped+" "+thresholdCrossing+" "+maxADC);
             if (maxADC-ped < 0) return null;
+            final double pulseIntegral = sumADC-ped*npts;
             fitFunction.setParameter("pedestal",ped);
             fitFunction.setParameter("time0",(double)thresholdCrossing-2);
-            fitFunction.setParameter("amplitude",(maxADC-ped)*37.2/24);
-            fitFunction.setParameter("shape",2.5);
-
-            System.err.println(String.format("A= %8.2f",(maxADC-ped)*37.2/24));
-            System.err.println(String.format("T= %8.2f",4*((double)thresholdCrossing-2)));
-            System.err.println(String.format("P= %8.2f",ped));
-            System.err.println(String.format("S= %8.2f",2.5));
-
-            fitter.fitParameterSettings("amplitude").setBounds(0,999999);
+            fitFunction.setParameter("integral",pulseIntegral>0?pulseIntegral:10);
+            fitFunction.setParameter("width",2.478);
+            ((Ecal3PoleFunction)fitFunction).setDebug(debug>1);
+
+            if (debug>0) {
+                System.err.println(String.format("A= %8.2f",fitFunction.parameter("integral")));
+                System.err.println(String.format("T= %8.2f",fitFunction.parameter("time0")*4));
+                System.err.println(String.format("P= %8.2f",fitFunction.parameter("pedestal")));
+                System.err.println(String.format("S= %8.2f",fitFunction.parameter("width")));
+            }
+
+            fitter.fitParameterSettings("integral").setBounds(0,999999);
             fitter.fitParameterSettings("time0").setBounds(5,40);
-            fitter.fitParameterSettings("shape").setBounds(0.1,5);
-            
-            Logger.getLogger("org.freehep.math.minuit").setLevel(Level.OFF);
-            return fitter.fit(data,fitFunction);
+            fitter.fitParameterSettings("width").setBounds(0.1,5);
+            if (fixShapeParameter) fitter.fitParameterSettings("width").setFixed(true);
+           
+            if (debug<1) Logger.getLogger("org.freehep.math.minuit").setLevel(Level.OFF);
+            IFitResult fitResult = fitter.fit(data,fitFunction);
+            writeFit(samples,fitResult);
+            return fitResult;
         }
         return null;
     }
+    
+    public void writeFit(short samples[],IFitResult fit) {
+        if (fitFileName == null) return;
+        if (fitFileWriter == null) {
+            try { fitFileWriter=new FileWriter(fitFileName); }
+            catch (IOException ee) { throw new RuntimeException("Error opening file "+fitFileName,ee); }
+        }
+        
+        try {
+            for (final short ss : samples) fitFileWriter.write(String.format("%6d ",ss));
+            fitFileWriter.write(String.format("%8.3f",fit.fittedParameter("integral")));
+            fitFileWriter.write(String.format("%8.3f",fit.fittedParameter("time0")));
+            fitFileWriter.write(String.format("%8.3f",fit.fittedParameter("pedestal")));
+            fitFileWriter.write(String.format("%8.3f",fit.fittedParameter("width")));
+            fitFileWriter.write(String.format("%8.3f",fit.quality()));
+            fitFileWriter.write("\n");
+        } catch (IOException ee) {
+            throw new RuntimeException("Error writing file "+fitFileName,ee);
+        } 
+    }
+    
         
     
     /*
@@ -457,36 +497,48 @@
 
            }
         }
-        
-        double noise=findChannel(hit.getCellID()).getCalibration().getNoise();
-        IFitResult fitResult=fitPulse(samples,thresholdCrossing,maxADC,noise);
-        if (fitResult != null) {
-            double A=fitResult.fittedParameter("amplitude");
-            double T=fitResult.fittedParameter("time0");
-            double P=fitResult.fittedParameter("pedestal");
-            double S=fitResult.fittedParameter("shape");
-
-            // analytic integral from T to T+NSAMP
-            final int NSAMP=30;
-            double I = (A/2/S) * S*S*S* ( 2 - Math.exp(-NSAMP/S) * (Math.pow((S+NSAMP)/S,2) + 1));
-          /* 
-            System.err.println(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
-            System.err.println(String.format("A= %8.2f %8.2f",I,sumADC-P*30));
-            System.err.println(String.format("T= %8.2f %8.2f",T*4,pulseTime));
-            System.err.println(String.format("P= %8.2f %8.2f",P,minADC));
-            System.err.println(String.format("S= %8.2f",S));
-            System.err.println(String.format("M= %8.2f %8.2f",A,(maxADC-P)));
-            */
+       
+        if (useFit)
+        {
+          final double noise=findChannel(hit.getCellID()).getCalibration().getNoise();
+          IFitResult fitResult=fitPulse(samples,thresholdCrossing,maxADC,noise);
+          
+          if (fitResult != null) {
+
+            final double P=fitResult.fittedParameter("pedestal");
+            final double A=fitResult.fittedParameter("integral");
+            final double T=fitResult.fittedParameter("time0");
+            final double S=fitResult.fittedParameter("width");
+            final double Q=fitResult.quality();
+
+            // finite integral from T to T+NSAMP:
+            //final int NSAMP=30;
+            //final double I = (A/2/S) * S*S*S* ( 2 - Math.exp(-NSAMP/S) * (Math.pow((S+NSAMP)/S,2) + 1));
+
+            // infinite integral:
+            //final double I = A*S*S;
+            
+            // after normalizing the function:
+            final double I = A;
+            
+            if (debug>0) {
+               System.err.println(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
+               System.err.println(String.format("A= %8.2f %8.2f",I,sumADC-P*30));
+               System.err.println(String.format("T= %8.2f %8.2f",T*4,pulseTime));
+               System.err.println(String.format("P= %8.2f %8.2f",P,minADC));
+               System.err.println(String.format("S= %8.2f",S));
+               System.err.println(String.format("M= %8.2f %8.2f",A,(maxADC-P)));
+            }
+
+            hWidth.fill(S);
+            hQuality.fill(Q);
             pulseTime = T*4;
             sumADC = I;
-            maxADC = -1;
-            
-            hShape.fill(S);
+          }
         }
         return new double []{pulseTime,sumADC,minADC,maxADC};
     }
-   
-    
+  
     /*
      * 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.
@@ -556,7 +608,7 @@
             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
            
-            if (max > 0) {
+            if (!useFit) {
               // do pedestal subtraction:
               sum -= getPulsePedestal(event, cellID, samples.length, thresholdCrossing);
             }

Added: java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverterDriver.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverterDriver.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/baltzell/EcalRawConverterDriver.java	Sat May 23 13:16:58 2015
@@ -0,0 +1,534 @@
+package org.hps.users.baltzell;
+
+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.recon.ecal.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;
+
+/**
+ * 
+ * 
+ * baltzell New in 2015:  (default behavior is unchanged)
+ * Added firmware emulation for converting from Mode-1 readout (RawTrackerHit)
+ * to Mode-3 pulse (CalorimeterHit).  Turn it on with "emulateFirmware", else
+ * defaults to previous behavior.  
+ *  
+ * Removed integralWindow in favor of NSA/NSB to allow treating all Modes uniformly.
+ * (New) NSA+NSB == (Old) integralWindow*4(ns) 
+ * 
+ * Implemented finding multiple peaks for Mode-1.
+ */
+public class EcalRawConverterDriver extends Driver {
+
+    // To import database conditions
+    private EcalConditions ecalConditions = null;
+
+    private EcalRawConverter converter = null;
+    private String rawCollectionName = "EcalReadoutHits";
+    private final String ecalReadoutName = "EcalHits";
+    private String ecalCollectionName = "EcalCalHits";
+
+    private static final String extraDataRelationsName = "EcalReadoutExtraDataRelations";
+
+    private int debug = 0;
+    private double threshold = Double.NEGATIVE_INFINITY;
+    private boolean applyBadCrystalMap = true;
+    private boolean dropBadFADC = false;
+    private boolean runBackwards = false;
+    private boolean useTimestamps = false;
+    private boolean useTruthTime = false;
+    private boolean useDAQConfig = false;
+
+    private boolean emulateFirmware = false;
+    
+    public EcalRawConverterDriver() {
+        converter = new EcalRawConverter();
+    }
+
+    public void setUseFit(boolean useFit) { converter.useFit=useFit; }
+    public void setFitFileName(String name) { converter.fitFileName=name; }
+    public void setFixShapeParameter(boolean fix) { converter.fixShapeParameter=fix; }
+    
+    
+    /**
+     * 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 to <code>true</code> to apply time walk correction from {@link EcalTimeWalk#correctTimeWalk(double, double)}.
+     * <p>
+     * This is only applicable to Mode-3 data.
+     * 
+     * @param useTimeWalkCorrection True to apply time walk correction.
+     */
+    public void setUseTimeWalkCorrection(boolean useTimeWalkCorrection) {
+        converter.setUseTimeWalkCorrection(useTimeWalkCorrection);
+    }
+    
+    /**
+     * 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 a constant gain factor in the converter for all channels.
+     * @param gain The constant gain value.
+     */
+    public void setGain(double gain) {
+        converter.setGain(gain);
+    }
+
+    /**
+     * 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(int debug) {
+        this.debug = debug;
+        converter.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 CalorimeterHit
+     */
+    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 CalorimeterHit
+     */
+    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>0) {
+                            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>0) {
+                                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>0) {
+                            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>0) {
+                                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>0) {
+                        System.out.format("old hit energy %f\n", hit.getRawEnergy());
+                    }
+                    RawCalorimeterHit newHit = converter.HitAtoD(hit);
+                    if (newHit.getAmplitude() > 0) {
+                        if (debug>0) {
+                            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();
+    }
+}