LDMX Software
HcalRecProducer.cxx
Go to the documentation of this file.
1
9
10#include "Hcal/Event/HcalHit.h"
11#include "Hcal/HcalReconConditions.h"
14
15namespace hcal {
16
17HcalRecProducer::HcalRecProducer(const std::string& name,
18 framework::Process& process)
19 : Producer(name, process) {}
20
22 // collection names
23 digiCollName_ = ps.getParameter<std::string>("digiCollName");
24 digiPassName_ = ps.getParameter<std::string>("digiPassName");
25 simHitCollName_ = ps.getParameter<std::string>("simHitCollName");
26 simHitPassName_ = ps.getParameter<std::string>("simHitPassName");
27 recHitCollName_ = ps.getParameter<std::string>("recHitCollName");
28
29 // parameters
30 mip_energy_ = ps.getParameter<double>("mip_energy");
31 pe_per_mip_ = ps.getParameter<double>("pe_per_mip");
32 clock_cycle_ = ps.getParameter<double>("clock_cycle");
33 voltage_per_mip_ = ps.getParameter<double>("voltage_per_mip");
34 attlength_ = ps.getParameter<double>("attenuationLength");
35 nADCs_ = ps.getParameter<int>("nADCs");
36
37 // configuring corrections graphs derived on the fly
38 // TODO: maybe we should save these as a graph instead?
39 rateUpSlope_ = ps.getParameter<double>("rateUpSlope");
40 timeUpSlope_ = ps.getParameter<double>("timeUpSlope");
41 rateDnSlope_ = ps.getParameter<double>("rateDnSlope");
42 timeDnSlope_ = ps.getParameter<double>("timeDnSlope");
43 timePeak_ = ps.getParameter<double>("timePeak");
45 TF1("pulseFunc",
46 "[0]*((1.0+exp([1]*(-[2]+[3])))*(1.0+exp([5]*(-[6]+[3]))))/"
47 "((1.0+exp([1]*(x-[2]+[3]-[4])))*(1.0+exp([5]*(x-[6]+[3]-[4]))))",
48 (double)nADCs_ * clock_cycle_ * -1, (double)nADCs_ * clock_cycle_);
49 pulseFunc_.FixParameter(1, rateUpSlope_);
50 pulseFunc_.FixParameter(2, timeUpSlope_);
51 pulseFunc_.FixParameter(3, timePeak_);
52 pulseFunc_.FixParameter(5, rateDnSlope_);
53 pulseFunc_.FixParameter(6, timeDnSlope_);
54 pulseFunc_.FixParameter(4, 0);
55 pulseFunc_.FixParameter(0, 1);
56
57 // build amplitude correction (Ampl[t-1]/Ampl[t]) with pulse-shape
58 int n = 0;
59 for (double t = -clock_cycle_; t < clock_cycle_; t += 0.01) {
60 double ampl_t = pulseFunc_.Eval(t);
61 double ampl_tm1 = pulseFunc_.Eval(t - clock_cycle_);
62 if (ampl_tm1 > ampl_t) continue;
63 correctionAmpl_.SetPoint(n, ampl_tm1 / ampl_t, ampl_t);
64 if (n == 0) minAmplFraction_ = ampl_tm1 / ampl_t;
65 n++;
66 }
67
68 // build TOA timewalk correction with pulse-shape
69 double toaThreshold = ps.getParameter<double>("avgToaThreshold");
70 double gain = ps.getParameter<double>("avgGain");
71 double pedestal = ps.getParameter<double>("avgPedestal");
72 n = 0;
73 for (double ampl = toaThreshold + 0.1; ampl < 10000; ampl += 0.01) {
74 pulseFunc_.FixParameter(0, ampl);
75 double ampl_t = gain * pedestal + pulseFunc_.Eval(0);
76 double toa =
77 fabs(pulseFunc_.GetX(toaThreshold, (double)nADCs_ * clock_cycle_ * -1,
78 (double)nADCs_ * clock_cycle_));
79 correctionTOA_.SetPoint(n, ampl_t, toa);
80 if (n == 0) minAmpl_ = ampl_t;
81 n++;
82 }
83 correctionTOA_.SetBit(TGraph::kIsSortedX);
84}
85
87 const ldmx::HgcrocDigiCollection::HgcrocDigi digi, double pedestal,
88 unsigned int iSOI) const {
89 // get toa relative to the startBX
90 double toaRelStartBX(0.), maxMeas{0.};
91 int toaSample(0), maxSample(0), iADC(0);
92 for (int i_sample{0}; i_sample < digi.size(); i_sample++) {
93 auto sample{digi.at(i_sample)};
94 if (sample.toa() > 0) {
95 toaRelStartBX = sample.toa() * (clock_cycle_ / 1024); // ns
96 // find in which ADC sample the TOA was taken
97 toaSample = iADC;
98 }
99 if ((sample.adc_t() - pedestal) > maxMeas) {
100 maxMeas = (sample.adc_t() - pedestal);
101 maxSample = iADC;
102 }
103 iADC++;
104 }
105
106 // time w.r.t to the peak
107 double toa = (maxSample - toaSample) * clock_cycle_ - toaRelStartBX;
108
109 // time w.r.t to the SOI
110 toa += ((int)iSOI - maxSample) * clock_cycle_;
111
112 return toa;
113}
114
116 // get the Hcal Geometry
117 const auto& hcalGeometry = getCondition<ldmx::HcalGeometry>(
119
120 // get the reconstruction parameters
121 const auto& the_conditions{
122 getCondition<HcalReconConditions>(HcalReconConditions::CONDITIONS_NAME)};
123
124 std::vector<ldmx::HcalHit> hcalRecHits;
125 auto hcalDigis =
127 int numDigiHits = hcalDigis.getNumDigis();
128
129 // get sample of interest index
130 unsigned int iSOI = hcalDigis.getSampleOfInterestIndex();
131
132 // loop through digis
133 int iDigi = 0;
134 while (iDigi < numDigiHits) {
135 auto digi_posend = hcalDigis.getDigi(iDigi);
136
137 // ID from first digi sample (which should be in positive end)
138 ldmx::HcalDigiID id_posend(digi_posend.id());
139 ldmx::HcalID id(id_posend.section(), id_posend.layer(), id_posend.strip());
140
141 // position from ID
142 auto position = hcalGeometry.getStripCenterPosition(id);
143 double half_total_width =
144 hcalGeometry.getHalfTotalWidth(id.section(), id.layer());
145 double ecal_dx = hcalGeometry.getEcalDx();
146 double ecal_dy = hcalGeometry.getEcalDy();
147
148 // compute distance to the end of the bar
149 // for back Hcal, we take the half of the bar
150 // for side Hcal, we take the length of the bar (2*half-width)-Ecal_dxy as
151 // an approximation
152 float distance_posend, distance_negend, distance_ecal;
153 if (id.section() == ldmx::HcalID::HcalSection::BACK) {
154 distance_posend = half_total_width;
155 distance_negend = half_total_width;
156 } else {
157 if ((id.section() == ldmx::HcalID::HcalSection::TOP) ||
158 (id.section() == ldmx::HcalID::HcalSection::BOTTOM))
159 distance_ecal = ecal_dx;
160 else
161 distance_ecal = ecal_dy;
162 distance_posend = 2 * half_total_width - distance_ecal / 2.;
163 distance_negend = distance_ecal / 2.;
164 }
165
166 // get the estimated voltage and time from digi samples
167 double voltage(0.);
168 double voltage_min(0.);
169 double hitTime(0.);
170
171 double amplT(0.);
172 double amplT_posend(0.), amplTm1_posend(0.);
173 double amplT_negend(0.), amplTm1_negend(0.);
174
175 // double readout
176 if (id.section() == ldmx::HcalID::HcalSection::BACK) {
177 auto digi_negend = hcalDigis.getDigi(iDigi + 1);
178 ldmx::HcalDigiID id_negend(digi_negend.id());
179
180 double voltage_posend, voltage_negend;
181 if (digi_posend.isTOT()) {
182 voltage_posend =
183 (digi_posend.tot() - the_conditions.totCalib(id_posend, 0)) *
184 the_conditions.totCalib(id_posend, 1);
185 voltage_negend =
186 (digi_negend.tot() - the_conditions.totCalib(id_negend, 0)) *
187 the_conditions.totCalib(id_negend, 1);
188 } else {
189 amplT_posend =
190 digi_posend.soi().adc_t() - the_conditions.adcPedestal(id_posend);
191 amplTm1_posend =
192 digi_posend.soi().adc_tm1() - the_conditions.adcPedestal(id_posend);
193 amplT_negend =
194 digi_negend.soi().adc_t() - the_conditions.adcPedestal(id_negend);
195 amplTm1_negend =
196 digi_negend.soi().adc_tm1() - the_conditions.adcPedestal(id_negend);
197
198 // correct amplitude (amplitude fractions from both ends need to be
199 // above the boundary of the correction)
200 if (amplTm1_posend / amplT_posend > minAmplFraction_ &&
201 amplTm1_negend / amplT_negend > minAmplFraction_) {
202 amplT_posend *= correctionAmpl_.Eval(amplTm1_posend / amplT_posend);
203 amplT_negend *= correctionAmpl_.Eval(amplTm1_negend / amplT_negend);
204 }
205
206 // set voltage
207 voltage_posend = amplT_posend * the_conditions.adcGain(id_posend, 0);
208 voltage_negend = amplT_negend * the_conditions.adcGain(id_negend, 0);
209 }
210
211 // get TOA
212 double TOA_posend =
213 getTOA(digi_posend, the_conditions.adcPedestal(id_posend), iSOI);
214 double TOA_negend =
215 getTOA(digi_negend, the_conditions.adcPedestal(id_negend), iSOI);
216
217 // get sign of position along the bar
218 int position_bar_sign = (TOA_posend - TOA_negend) > 0 ? 1 : -1;
219
220 // correct TOA
221 // amplitudes from both ends need to be above the boundary of the
222 // correction otherwise, one TOA gets corrected and the other does not,
223 // which results in a large TOA difference and an out-of-bounds position
224 if (amplT_posend > minAmpl_ && amplT_negend > minAmpl_) {
225 TOA_posend = correctionTOA_.Eval(amplT_posend) - TOA_posend;
226 TOA_negend = correctionTOA_.Eval(amplT_negend) - TOA_negend;
227 }
228
229 // get x(y) coordinate from TOA measurement = (dt*v/2)
230 // if time_posend < time_negend: position is positive
231 double v =
232 299.792 / 1.6; // velocity of light in polystyrene, n = 1.6 = c/v
233 double position_bar =
234 position_bar_sign * fabs(TOA_posend - TOA_negend) * v / 2;
235
236 // reverse voltage attenuation
237 // if position along the bar is positive, then the positive end will have
238 // less attenuation than the negative end
239 // NOTE: For now, reverse attenuation is not applied to the energy
240 // deposited since both ends of the bar are summed.
241 double att_posend =
242 exp(-1. * ((distance_posend - position_bar) / 1000.) / attlength_);
243 double att_negend =
244 exp(-1. * ((distance_negend + position_bar) / 1000.) / attlength_);
245
246 // set voltage as the sum of both bars
247 voltage = (voltage_posend + voltage_negend);
248 voltage_min = std::min(voltage_posend, voltage_negend);
249
250 // set amplitude as the average of both bars (reverse attenuated)
251 amplT = (amplT_posend / att_posend + amplT_negend / att_negend) / 2;
252
253 const auto orientation{hcalGeometry.getScintillatorOrientation(id)};
254 // set position along the bar
255 if (orientation ==
256 ldmx::HcalGeometry::ScintillatorOrientation::horizontal) {
257 position.SetX(position_bar);
258 } else {
259 position.SetY(position_bar);
260 }
261
262 // set hit time
263 // TODO: does this need to revert shift because of propagation of light in
264 // polysterene?
265 hitTime = fabs(TOA_posend + TOA_negend) / 2; // ns
266
267 iDigi += 2;
268 } // end double readout loop
269 else { // single readout
270
271 double voltage_i;
272 if (digi_posend.isTOT()) {
273 // TOT - number of clock ticks that pulse was over threshold
274 // this is related to the amplitude of the pulse approximately through a
275 // linear drain rate the amplitude of the pulse is related to the energy
276 // deposited
277
278 // convert the time over threshold into a total energy deposited in the
279 // bar (time over threshold [ns] - pedestal) * gain
280
281 voltage_i = (digi_posend.tot() - the_conditions.totCalib(id_posend)) *
282 the_conditions.totCalib(id_posend);
283
284 } else {
285 // ADC mode of readout
286 // ADC - voltage measurement at a specific time of the pulse
287 amplT_posend =
288 digi_posend.soi().adc_t() - the_conditions.adcPedestal(id_posend);
289 amplTm1_posend =
290 digi_posend.soi().adc_tm1() - the_conditions.adcPedestal(id_posend);
291 voltage_i = amplT_posend * the_conditions.adcGain(id_posend);
292 }
293
294 // reverse voltage attenuation
295 // for now, assume that position along the bar is the half_total_width
296 double distance_end =
297 id_posend.isNegativeEnd() ? distance_negend : distance_posend;
298 double att = exp(-1. * ((distance_end - fabs(half_total_width)) / 1000.) /
299 attlength_);
300
301 // set voltage
302 voltage = voltage_i;
303 voltage_min = voltage_i;
304
305 // set amplitude (reverse attenuated)
306 amplT = amplT_posend / att;
307
308 // get TOA
309 double TOA =
310 getTOA(digi_posend, the_conditions.adcPedestal(id_posend), iSOI);
311
312 // correct TOA
313 TOA = correctionTOA_.Eval(amplT) - TOA;
314
315 // set hit time
316 hitTime = TOA; // ns
317
318 iDigi++;
319 } // end single readout loop
320
321 double num_mips_equivalent = voltage / voltage_per_mip_;
322 double energy_deposited = num_mips_equivalent * mip_energy_;
323
324 // reconstructed energy in the layer (approximate)
325 // TODO: need to incorporate corrections if necessary
342 double reconstructed_energy = energy_deposited;
343
344 int PEs = num_mips_equivalent * pe_per_mip_;
345 int minPEs = (voltage_min / voltage_per_mip_) * pe_per_mip_;
346
347 // copy over information to rec hit structure in new collection
348 ldmx::HcalHit recHit;
349 recHit.setID(id.raw());
350 recHit.setXPos(position.X());
351 recHit.setYPos(position.Y());
352 recHit.setZPos(position.Z());
353 recHit.setSection(id.section());
354 recHit.setStrip(id.strip());
355 recHit.setLayer(id.layer());
356 recHit.setPE(PEs);
357 recHit.setMinPE(minPEs);
358 recHit.setAmplitude((amplT / voltage_per_mip_) * mip_energy_);
359 recHit.setEnergy(reconstructed_energy);
360 recHit.setTime(hitTime);
361 hcalRecHits.push_back(recHit);
362 }
363
365 // hcal sim hits exist ==> label which hits are real and which are pure
366 // noise
367 auto hcalSimHits{event.getCollection<ldmx::SimCalorimeterHit>(
369 std::set<int> real_hits;
370 for (auto const& sim_hit : hcalSimHits) real_hits.insert(sim_hit.getID());
371 for (auto& hit : hcalRecHits)
372 hit.setNoise(real_hits.find(hit.getID()) == real_hits.end());
373 }
374
375 // add collection to event bus
376 event.add(recHitCollName_, hcalRecHits);
377}
378
379} // namespace hcal
380
381DECLARE_PRODUCER_NS(hcal, HcalRecProducer);
#define DECLARE_PRODUCER_NS(NS, CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Class that stores Stores reconstructed hit information from the HCAL.
Class that performs basic HCal digitization.
Class that represents a digitized hit in a calorimeter cell readout by an HGCROC.
Class which stores simulated calorimeter hit information.
Implements an event buffer system for storing event data.
Definition Event.h:41
bool exists(const std::string &name, const std::string &passName="", bool unique=true) const
Check for the existence of an object or collection with the given name and pass name in the event.
Definition Event.cxx:92
Class which represents the process under execution.
Definition Process.h:36
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:27
T getParameter(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:89
TGraph correctionTOA_
Correction to the measured TOA relative to the peak.
double minAmplFraction_
Minimum amplitude fraction to apply amplitude correction.
double clock_cycle_
Length of clock cycle [ns].
double voltage_per_mip_
Voltage by average MIP.
void configure(framework::config::Parameters &) override
Grabs configure parameters from the python config file.
TGraph correctionAmpl_
Correction to the pulse's measured amplitude at the peak.
double timeUpSlope_
Time of Up Slope relative to Pulse Shape Fit [ns].
std::string recHitCollName_
output hit collection name
HcalRecProducer(const std::string &name, framework::Process &process)
Constructor.
std::string digiPassName_
Digi Pass Name to use as input.
double timeDnSlope_
Time of Down Slope relative to Pulse Shape Fit [ns].
double rateUpSlope_
Rate of Up Slope in Pulse Shape [1/ns].
std::string simHitCollName_
simhit collection name
int nADCs_
Depth of ADC buffer.
double minAmpl_
Minimum amplitude to apply TOA correction.
double timePeak_
Time of Peak relative to pulse shape fit [ns].
double mip_energy_
Energy [MeV] deposited by a MIP.
double attlength_
Strip attenuation length [m].
void produce(framework::Event &event) override
Produce HcalHits and put them into the event bus using the HcalDigis as input.
std::string simHitPassName_
simhit pass name
TF1 pulseFunc_
Pulse function.
double pe_per_mip_
PEs per MIP.
double rateDnSlope_
Rate of Down Slope in Pulse Shape [1/ns].
std::string digiCollName_
Digi Collection Name to use as input.
double getTOA(const ldmx::HgcrocDigiCollection::HgcrocDigi digi, double pedestal, unsigned int iSOI) const
Gets Time of Arrival with respect to the SOI.
static const std::string CONDITIONS_NAME
the name of the HcalReconConditions table (must match python registration name)
void setYPos(float ypos)
Set the Y position of the hit [mm].
void setID(int id)
Set the detector ID.
void setZPos(float zpos)
Set the Z position of the hit [mm].
void setXPos(float xpos)
Set the X position of the hit [mm].
void setTime(float time)
Set the time of the hit [ns].
void setAmplitude(float amplitude)
Set the amplitude of the hit, which is proportional to the signal in the calorimeter cell without sam...
void setEnergy(float energy)
Set the calorimetric energy of the hit, corrected for sampling factors [MeV].
Extension of HcalAbstractID providing access to HCal digi information.
Definition HcalDigiID.h:13
int strip() const
Get the value of the 'strip' field from the ID.
Definition HcalDigiID.h:99
bool isNegativeEnd() const
Get whether the 'end' field from the ID is negative.
Definition HcalDigiID.h:111
int section() const
Get the value of the 'section' field from the ID.
Definition HcalDigiID.h:75
int layer() const
Get the value of the layer field from the ID.
Definition HcalDigiID.h:81
static constexpr const char * CONDITIONS_OBJECT_NAME
Conditions object: The name of the python configuration calling this class (Hcal/python/HcalGeometry....
Stores reconstructed hit information from the HCAL.
Definition HcalHit.h:23
void setSection(int section)
Set the section for this hit.
Definition HcalHit.h:132
void setMinPE(float minpe)
Set the minimum number of photoelectrons estimated for this hit.
Definition HcalHit.h:126
void setStrip(int strip)
Set the strip for this hit.
Definition HcalHit.h:144
void setLayer(int layer)
Set the layer for this hit.
Definition HcalHit.h:138
void setPE(float pe)
Set the number of photoelectrons estimated for this hit.
Definition HcalHit.h:119
Implements detector ids for HCal subdetector.
Definition HcalID.h:19
One DIGI signal coming from the HGC ROC.
HgcrocDigiCollection::Sample at(unsigned int i_sample) const
get the sample at a specific index in the digi
int toa() const
Get the Time Of Arrival of this sample which is always the third position in all readout modes.
Represents a collection of the digi hits readout by an HGCROC.
unsigned int getNumDigis() const
Get total number of digis.
Stores simulated calorimeter hit information.