Print

Print


Author: [log in to unmask]
Date: Sun May 17 10:40:20 2015
New Revision: 2993

Log:
code for testing changes to mode 7 timing

Added:
    java/trunk/users/src/main/java/org/hps/users/holly/EcalRawConverter.java

Added: java/trunk/users/src/main/java/org/hps/users/holly/EcalRawConverter.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/holly/EcalRawConverter.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/holly/EcalRawConverter.java	Sun May 17 10:40:20 2015
@@ -0,0 +1,548 @@
+package org.hps.users.holly;
+
+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.recon.ecal.daqconfig.ConfigurationManager;
+import org.hps.recon.ecal.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;
+import org.hps.recon.ecal.EcalUtils;
+import org.hps.recon.ecal.EcalTimeWalk;
+import org.hps.recon.ecal.CalorimeterHitUtilities;
+
+/**
+ * This class is used to convert {@link org.lcsim.event.RawCalorimeterHit}
+ * and {@link org.lcsim.event.RawTrackerHit} to {@link org.lcsim.event.CalorimeterHit}
+ * objects with energy information.
+ *
+ * @author Sho Uemura <[log in to unmask]>
+ * @author Andrea Celentano <[log in to unmask]>
+ * @author Nathan Baltzell <[log in to unmask]>
+ *
+ * baltzell:  New in 2015:  (default behavior is still unchanged)
+ *
+ * Implemented conversion of Mode-1 to Mode-3.
+ * 
+ * Now using NSA/NSB for pedestal subtraction instead of integralWindow, to allow
+ * treating all FADC Modes uniformly.  (New) NSA+NSB == (Old) integralWindow*4(ns) 
+ * 
+ * Pedestal subtracting clipped pulses more correctly for all Modes.
+ * 
+ * Implemented finding multiple peaks for Mode-1.
+ * 
+ * Implemented conversion of Mode-1 to Mode-7 with high-resolution timing.
+ * Only some of the special cases in the firmware for when this algorithm fails due
+ * to bad pulses (e.g. clipping) are already implemented.  Not yet writing Mode-7's
+ * min/max to data stream. 
+ */
+public class EcalRawConverter {
+
+    private boolean useTimeWalkCorrection = false;
+    private boolean useRunningPedestal = false;
+    private boolean constantGain = false;
+    private double gain;
+    private boolean use2014Gain = true;
+    private boolean useDAQConfig = false;
+    private FADCConfig config = null;
+
+    /*
+     * 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.
+     */
+    private int nPeak = 3;
+   
+    /*
+     * Convert Mode-1 into Mode-7, else Mode-3.
+     */
+    private boolean mode7 = false;
+
+
+    private EcalConditions ecalConditions = null;
+
+    public EcalRawConverter() {
+    	// 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();
+				}
+			}
+    	});
+    }
+  
+    public void setLeadingEdgeThreshold(double thresh) {
+        leadingEdgeThreshold=thresh;
+    }
+    
+    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;
+    }
+    
+    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;
+    }
+    
+    public void setWindowSamples(int windowSamples) {
+        this.windowSamples=windowSamples;
+    }
+    
+    public void setNPeak(int nPeak) {
+        if (nPeak<1 || nPeak>3) {
+            throw new RuntimeException("Npeak must be 1, 2, or 3.");
+        }
+        this.nPeak=nPeak;
+    }
+    
+    public void setMode7(boolean mode7)
+    {
+        this.mode7=mode7;
+    }
+
+    public void setGain(double gain) {
+        constantGain = true;
+        this.gain = gain;
+    }
+
+    public void setUse2014Gain(boolean use2014Gain) {
+        this.use2014Gain = use2014Gain;
+    }
+
+    public void setUseRunningPedestal(boolean useRunningPedestal) {
+        this.useRunningPedestal=useRunningPedestal;
+    }
+
+    public void setUseTimeWalkCorrection(boolean useTimeWalkCorrection) {
+        this.useTimeWalkCorrection=useTimeWalkCorrection;
+    }
+    
+    public void setUseDAQConfig(boolean state) {
+    	useDAQConfig = state;
+    }
+
+    /*
+     * This should probably be deprecated.  It just integrates the entire window.
+     */
+    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;
+    }
+
+    /*
+     * 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 rawEnergy = adcToEnergy(sumADC(hit), id);
+        return CalorimeterHitUtilities.create(rawEnergy, time, id);
+    }
+
+    /*
+     * 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")) {
+                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) {
+        short samples[] = hit.getADCValues();
+        
+        // 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); 
+        
+        // mode-7's max pulse height:
+        double maxADC=0;
+        int sampleMaxADC=0;
+        
+        // mode-3/7's pulse integral:
+        short 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.
+                if (samples[jj+1]<samples[jj]) {
+                    sampleMaxADC=jj;
+                    maxADC=samples[jj];
+                }
+            }
+        }
+       
+        // 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:
+                double t0 = thresholdCrossing*nsPerSample;
+                double a0 = samples[thresholdCrossing];
+                double t1 = sampleMaxADC*nsPerSample;
+                double a1 = maxADC;
+                double slope = (a1-a0)/(t1-t0);
+                double halfMax = (maxADC+minADC)/2;
+                // this is not rigorously firmware-correct, need to find halfMax-crossing.
+                double tmpTime = t1 - (a1 - halfMax) / slope;
+                if (slope>0 && tmpTime>0) {
+                    pulseTime = tmpTime;
+                }
+                // else another special firmware case
+            }
+        }
+        
+        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.
+     * 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
+            
+            // do pedestal subtraction:
+            sum -= getPulsePedestal(event, cellID, samples.length, thresholdCrossing);
+          
+            // do gain scaling:
+            double energy = adcToEnergy(sum, cellID);
+            
+            // do time-walk correction, mode-3 only:
+            if (!mode7 && useTimeWalkCorrection) {
+                time = EcalTimeWalk.correctTimeWalk(time,energy);
+            }
+            
+            newHits.add(CalorimeterHitUtilities.create(energy,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, id);
+        if (useTimeWalkCorrection) {
+           time = EcalTimeWalk.correctTimeWalk(time,rawEnergy);
+        }
+        return CalorimeterHitUtilities.create(rawEnergy, 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, id);        
+        return CalorimeterHitUtilities.create(rawEnergy, 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);
+        if (constantGain) {
+            amplitude = (int) Math.round((hit.getRawEnergy() / EcalUtils.MeV) / gain + pedestal);
+        } else {
+            amplitude = (int) Math.round((hit.getRawEnergy() / EcalUtils.MeV) / channelData.getGain().getGain() + pedestal);
+        }
+        RawCalorimeterHit h = new BaseRawCalorimeterHit(id, amplitude, time);
+        return h;
+    }
+
+    /*
+     * return energy (units of GeV) corresponding to the ADC sum and crystal ID
+     */
+    private double adcToEnergy(double adcSum, long cellID) {
+
+        // 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));
+    }
+    
+}