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