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 input_coll_name_ = ps.get<std::string>("input_coll_name");
24 input_pass_name_ = ps.get<std::string>("input_pass_name");
25 sim_hit_coll_name_ = ps.get<std::string>("sim_hit_coll_name");
26 sim_hit_pass_name_ = ps.get<std::string>("sim_hit_pass_name");
27 rec_hit_coll_name_ = ps.get<std::string>("rec_hit_coll_name");
28 // parameters
29 mip_energy_ = ps.get<double>("mip_energy");
30 pe_per_mip_ = ps.get<double>("pe_per_mip");
31 clock_cycle_ = ps.get<double>("clock_cycle");
32 voltage_per_mip_ = ps.get<double>("voltage_per_mip");
33 attlength_ = ps.get<double>("attenuation_length");
34 n_adcs_ = ps.get<int>("n_adcs");
35
36 // configuring corrections graphs derived on the fly
37 // TODO: maybe we should save these as a graph instead?
38 rate_up_slope_ = ps.get<double>("rate_up_slope");
39 time_up_slope_ = ps.get<double>("time_up_slope");
40 rate_dn_slope_ = ps.get<double>("rate_dn_slope");
41 time_dn_slope_ = ps.get<double>("time_dn_slope");
42 time_peak_ = ps.get<double>("time_peak");
44 TF1("pulseFunc",
45 "[0]*((1.0+exp([1]*(-[2]+[3])))*(1.0+exp([5]*(-[6]+[3]))))/"
46 "((1.0+exp([1]*(x-[2]+[3]-[4])))*(1.0+exp([5]*(x-[6]+[3]-[4]))))",
47 (double)n_adcs_ * clock_cycle_ * -1, (double)n_adcs_ * clock_cycle_);
48 pulse_func_.FixParameter(1, rate_up_slope_);
49 pulse_func_.FixParameter(2, time_up_slope_);
50 pulse_func_.FixParameter(3, time_peak_);
51 pulse_func_.FixParameter(5, rate_dn_slope_);
52 pulse_func_.FixParameter(6, time_dn_slope_);
53 pulse_func_.FixParameter(4, 0);
54 pulse_func_.FixParameter(0, 1);
55
56 // build amplitude correction (Ampl[t-1]/Ampl[t]) with pulse-shape
57 int n = 0;
58 for (double t = -clock_cycle_; t < clock_cycle_; t += 0.01) {
59 double ampl_t = pulse_func_.Eval(t);
60 double ampl_tm1 = pulse_func_.Eval(t - clock_cycle_);
61 if (ampl_tm1 > ampl_t) continue;
62 correction_ampl_.SetPoint(n, ampl_tm1 / ampl_t, ampl_t);
63 if (n == 0) min_ampl_fraction_ = ampl_tm1 / ampl_t;
64 n++;
65 }
66
67 // build TOA timewalk correction with pulse-shape
68 double toa_threshold = ps.get<double>("avg_toa_threshold");
69 double gain = ps.get<double>("avg_gain");
70 double pedestal = ps.get<double>("avg_pedestal");
71 n = 0;
72 for (double ampl = toa_threshold + 0.1; ampl < 10000; ampl += 0.01) {
73 pulse_func_.FixParameter(0, ampl);
74 double ampl_t = gain * pedestal + pulse_func_.Eval(0);
75 double toa = fabs(pulse_func_.GetX(toa_threshold,
76 (double)n_adcs_ * clock_cycle_ * -1,
77 (double)n_adcs_ * clock_cycle_));
78 correction_toa_.SetPoint(n, ampl_t, toa);
79 if (n == 0) min_ampl_ = ampl_t;
80 n++;
81 }
82 correction_toa_.SetBit(TGraph::kIsSortedX);
83}
84
86 const ldmx::HgcrocDigiCollection::HgcrocDigi digi, double pedestal,
87 unsigned int iSOI) const {
88 // get toa relative to the startBX
89 double toa_rel_start_bx(0.), max_meas{0.};
90 int toa_sample(0), max_sample(0), i_adc(0);
91 for (int i_sample{0}; i_sample < digi.size(); i_sample++) {
92 auto sample{digi.at(i_sample)};
93 if (sample.toa() > 0) {
94 toa_rel_start_bx = sample.toa() * (clock_cycle_ / 1024); // ns
95 // find in which ADC sample the TOA was taken
96 toa_sample = i_adc;
97 }
98 if ((sample.adcT() - pedestal) > max_meas) {
99 max_meas = (sample.adcT() - pedestal);
100 max_sample = i_adc;
101 }
102 i_adc++;
103 }
104
105 // time w.r.t to the peak
106 double toa = (max_sample - toa_sample) * clock_cycle_ - toa_rel_start_bx;
107
108 // time w.r.t to the SOI
109 toa += (static_cast<int>(iSOI) - max_sample) * clock_cycle_;
110
111 return toa;
112}
113
115 // get the Hcal Geometry
116 const auto& hcal_geometry = getCondition<ldmx::HcalGeometry>(
118
119 // get the reconstruction parameters
120 const auto& the_conditions{
122
123 std::vector<ldmx::HcalHit> hcal_rec_hits;
124 auto hcal_digis = event.getObject<ldmx::HgcrocDigiCollection>(
126 int num_digi_hits = hcal_digis.getNumDigis();
127
128 // get sample of interest index
129 unsigned int i_soi = hcal_digis.getSampleOfInterestIndex();
130
131 // loop through digis
132 int i_digi = 0;
133 while (i_digi < num_digi_hits) {
134 auto digi_posend = hcal_digis.getDigi(i_digi);
135
136 // track readout mode for this hit (1 = ADC, 0 = TOT)
137 bool is_adc_mode = false;
138
139 // ID from first digi sample (which should be in positive end)
140 ldmx::HcalDigiID id_posend(digi_posend.id());
141 ldmx::HcalID id(id_posend.section(), id_posend.layer(), id_posend.strip());
142
143 // position from ID
144 auto position = hcal_geometry.getStripCenterPosition(id);
145 double half_total_width =
146 hcal_geometry.getHalfTotalWidth(id.section(), id.layer());
147 double ecal_dx = hcal_geometry.getEcalDx();
148 double ecal_dy = hcal_geometry.getEcalDy();
149
150 // compute distance to the end of the bar
151 // for back Hcal, we take the half of the bar
152 // for side Hcal, we take the length of the bar (2*half-width)-Ecal_dxy as
153 // an approximation
154 float distance_posend, distance_negend, distance_ecal;
155 if (id.section() == ldmx::HcalID::HcalSection::BACK) {
156 distance_posend = half_total_width;
157 distance_negend = half_total_width;
158 } else {
159 if ((id.section() == ldmx::HcalID::HcalSection::TOP) ||
160 (id.section() == ldmx::HcalID::HcalSection::BOTTOM)) {
161 distance_ecal = ecal_dx;
162 } else {
163 distance_ecal = ecal_dy;
164 }
165 distance_posend = 2 * half_total_width - distance_ecal / 2.;
166 distance_negend = distance_ecal / 2.;
167 }
168
169 // get the estimated voltage and time from digi samples
170 double voltage(0.);
171 double voltage_min(0.);
172 double hit_time(0.);
173
174 double ampl_t(0.);
175 double ampl_t_posend(0.), ampl_tm1_posend(0.);
176 double ampl_t_negend(0.), ampl_tm1_negend(0.);
177
178 // Check if the bar is oriented in X or Y
179 const auto orientation{hcal_geometry.getScintillatorOrientation(id)};
180 int orientation_int = static_cast<int>(orientation);
181
182 // double readout
183 if (id.section() == ldmx::HcalID::HcalSection::BACK) {
184 auto digi_negend = hcal_digis.getDigi(i_digi + 1);
185 ldmx::HcalDigiID id_negend(digi_negend.id());
186
187 double voltage_posend, voltage_negend;
188 // Check if in TOT mode
189 if (digi_posend.isTOT()) {
190 is_adc_mode = false;
191 voltage_posend =
192 (digi_posend.tot() - the_conditions.totCalib(id_posend, 0)) *
193 the_conditions.totCalib(id_posend, 1);
194 voltage_negend =
195 (digi_negend.tot() - the_conditions.totCalib(id_negend, 0)) *
196 the_conditions.totCalib(id_negend, 1);
197 } // end TOT mode, go to ADC mode
198 else {
199 is_adc_mode = true;
200 ampl_t_posend =
201 digi_posend.soi().adcT() - the_conditions.adcPedestal(id_posend);
202 ampl_tm1_posend =
203 digi_posend.soi().adcTm1() - the_conditions.adcPedestal(id_posend);
204 ampl_t_negend =
205 digi_negend.soi().adcT() - the_conditions.adcPedestal(id_negend);
206 ampl_tm1_negend =
207 digi_negend.soi().adcTm1() - the_conditions.adcPedestal(id_negend);
208
209 // correct amplitude (amplitude fractions from both ends need to be
210 // above the boundary of the correction)
211 if (ampl_tm1_posend / ampl_t_posend > min_ampl_fraction_ &&
212 ampl_tm1_negend / ampl_t_negend > min_ampl_fraction_) {
213 ampl_t_posend *=
214 correction_ampl_.Eval(ampl_tm1_posend / ampl_t_posend);
215 ampl_t_negend *=
216 correction_ampl_.Eval(ampl_tm1_negend / ampl_t_negend);
217 }
218
219 // set voltage
220 voltage_posend = ampl_t_posend * the_conditions.adcGain(id_posend, 0);
221 voltage_negend = ampl_t_negend * the_conditions.adcGain(id_negend, 0);
222 } // end ADC mode
223
224 // get TOA
225 double toa_posend =
226 getTOA(digi_posend, the_conditions.adcPedestal(id_posend), i_soi);
227 double toa_negend =
228 getTOA(digi_negend, the_conditions.adcPedestal(id_negend), i_soi);
229
230 // get sign of position along the bar
231 int position_bar_sign = (toa_posend - toa_negend) > 0 ? 1 : -1;
232
233 // correct TOA
234 // amplitudes from both ends need to be above the boundary of the
235 // correction otherwise, one TOA gets corrected and the other does not,
236 // which results in a large TOA difference and an out-of-bounds position
237 if (ampl_t_posend > min_ampl_ && ampl_t_negend > min_ampl_) {
238 toa_posend = correction_toa_.Eval(ampl_t_posend) - toa_posend;
239 toa_negend = correction_toa_.Eval(ampl_t_negend) - toa_negend;
240 }
241
242 // get x(y) coordinate from TOA measurement = (dt*v/2)
243 // if time_posend < time_negend: position is positive
244 // velocity of light in polystyrene, n = 1.6 = c/v
245 double v = 299.792 / 1.6;
246 double position_bar =
247 position_bar_sign * fabs(toa_posend - toa_negend) * v / 2;
248
249 // reverse voltage attenuation
250 // if position along the bar is positive, then the positive end will have
251 // less attenuation than the negative end
252 // NOTE: For now, reverse attenuation is not applied to the energy
253 // deposited since both ends of the bar are summed.
254 double att_posend =
255 exp(-1. * ((distance_posend - position_bar) / 1000.) / attlength_);
256 double att_negend =
257 exp(-1. * ((distance_negend + position_bar) / 1000.) / attlength_);
258
259 // set voltage as the sum of both bars
260 voltage = (voltage_posend + voltage_negend);
261 voltage_min = std::min(voltage_posend, voltage_negend);
262
263 // set amplitude as the average of both bars (reverse attenuated)
264 ampl_t = (ampl_t_posend / att_posend + ampl_t_negend / att_negend) / 2;
265
266 // set position along the bar
267 if (orientation ==
268 ldmx::HcalGeometry::ScintillatorOrientation::horizontal) {
269 position.SetX(position_bar);
270 } else {
271 position.SetY(position_bar);
272 }
273
274 // set hit time
275 // TODO: does this need to revert shift because of propagation of light in
276 // polysterene?
277 hit_time = fabs(toa_posend + toa_negend) / 2; // ns
278
279 i_digi += 2;
280 } // end double readout loop
281 else { // single readout
282 double voltage_i;
283 // Check if in TOT mode (for single-ended readout)
284 if (digi_posend.isTOT()) {
285 is_adc_mode = false;
286 // TOT - number of clock ticks that pulse was over threshold
287 // this is related to the amplitude of the pulse approximately through a
288 // linear drain rate the amplitude of the pulse is related to the energy
289 // deposited
290
291 // convert the time over threshold into a total energy deposited in the
292 // bar (time over threshold [ns] - pedestal) * gain
293
294 voltage_i = (digi_posend.tot() - the_conditions.totCalib(id_posend)) *
295 the_conditions.totCalib(id_posend);
296
297 } // end TOT mode, go to ADC mode (for single-ended readout)
298 else {
299 is_adc_mode = true;
300 // ADC mode of readout
301 // ADC - voltage measurement at a specific time of the pulse
302 ampl_t_posend =
303 digi_posend.soi().adcT() - the_conditions.adcPedestal(id_posend);
304 ampl_tm1_posend =
305 digi_posend.soi().adcTm1() - the_conditions.adcPedestal(id_posend);
306 voltage_i = ampl_t_posend * the_conditions.adcGain(id_posend);
307 }
308
309 // reverse voltage attenuation
310 // for now, assume that position along the bar is the half_total_width
311 double distance_end =
312 id_posend.isNegativeEnd() ? distance_negend : distance_posend;
313 double att = exp(-1. * ((distance_end - fabs(half_total_width)) / 1000.) /
314 attlength_);
315
316 // set voltage
317 voltage = voltage_i;
318 voltage_min = voltage_i;
319
320 // set amplitude (reverse attenuated)
321 ampl_t = ampl_t_posend / att;
322
323 // get TOA
324 double toa =
325 getTOA(digi_posend, the_conditions.adcPedestal(id_posend), i_soi);
326
327 // correct TOA
328 toa = correction_toa_.Eval(ampl_t) - toa;
329
330 // set hit time
331 hit_time = toa; // ns
332
333 i_digi++;
334 } // end single readout loop
335
336 double num_mips_equivalent = voltage / voltage_per_mip_;
337 double energy_deposited = num_mips_equivalent * mip_energy_;
338
339 // reconstructed energy in the layer (approximate)
340 // TODO: need to incorporate corrections if necessary
357 double reconstructed_energy = energy_deposited;
358
359 int pe_s = num_mips_equivalent * pe_per_mip_;
360 int min_pe_s = (voltage_min / voltage_per_mip_) * pe_per_mip_;
361
362 // copy over information to rec hit structure in new collection
363 ldmx::HcalHit rec_hit;
364 rec_hit.setID(id.raw());
365 rec_hit.setXPos(position.X());
366 rec_hit.setYPos(position.Y());
367 rec_hit.setZPos(position.Z());
368 rec_hit.setSection(id.section());
369 rec_hit.setStrip(id.strip());
370 rec_hit.setLayer(id.layer());
371 rec_hit.setPE(pe_s);
372 rec_hit.setMinPE(min_pe_s);
373 rec_hit.setIsADC(is_adc_mode ? 1 : 0);
374 rec_hit.setAmplitude((ampl_t / voltage_per_mip_) * mip_energy_);
375 rec_hit.setEnergy(reconstructed_energy);
376 rec_hit.setTime(hit_time);
377 rec_hit.setOrientation(orientation_int);
378 hcal_rec_hits.push_back(rec_hit);
379 } // end of digi loop
380
381 // mark noise hits if sim hits are available
383 // hcal sim hits_ exist ==> label which hits_ are real and which are pure
384 // noise
385 auto hcal_sim_hits{event.getCollection<ldmx::SimCalorimeterHit>(
387 std::set<int> real_hits;
388 for (auto const& sim_hit : hcal_sim_hits) real_hits.insert(sim_hit.getID());
389 for (auto& hit : hcal_rec_hits)
390 hit.setNoise(real_hits.find(hit.getID()) == real_hits.end());
391 }
392
393 // add collection to event bus
394 event.add(rec_hit_coll_name_, hcal_rec_hits);
395}
396
397} // namespace hcal
398
#define DECLARE_PRODUCER(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.
const T & getCondition(const std::string &condition_name)
Access a conditions object for the current event.
Implements an event buffer system for storing event data.
Definition Event.h:42
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:37
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
Performs basic HCal reconstruction.
double min_ampl_
Minimum amplitude to apply TOA correction.
double rate_dn_slope_
Rate of Down Slope in Pulse Shape [1/ns].
double time_dn_slope_
Time of Down Slope relative to Pulse Shape Fit [ns].
double clock_cycle_
Length of clock cycle [ns].
double voltage_per_mip_
Voltage by average MIP.
std::string rec_hit_coll_name_
output hit collection name
void configure(framework::config::Parameters &) override
Grabs configure parameters from the python config file.
double time_peak_
Time of Peak relative to pulse shape fit [ns].
TGraph correction_toa_
Correction to the measured TOA relative to the peak.
int n_adcs_
Depth of ADC buffer.
std::string input_pass_name_
Digi Pass Name to use as input.
HcalRecProducer(const std::string &name, framework::Process &process)
Constructor.
std::string sim_hit_coll_name_
simhit collection name
double rate_up_slope_
Rate of Up Slope in Pulse Shape [1/ns].
double mip_energy_
Energy [MeV] deposited by a MIP.
double attlength_
Strip attenuation length [m].
TGraph correction_ampl_
Correction to the pulse's measured amplitude at the peak.
void produce(framework::Event &event) override
Produce HcalHits and put them into the event bus using the HcalDigis as input.
std::string input_coll_name_
Digi Collection Name to use as input.
double time_up_slope_
Time of Up Slope relative to Pulse Shape Fit [ns].
TF1 pulse_func_
Pulse function.
double pe_per_mip_
PEs per MIP.
std::string sim_hit_pass_name_
simhit pass name
double min_ampl_fraction_
Minimum amplitude fraction to apply amplitude correction.
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:24
void setSection(int section)
Set the section for this hit.
Definition HcalHit.h:166
void setIsADC(int isADC)
Set if the hit is reconstructed using ADC.
Definition HcalHit.h:190
void setMinPE(float minpe)
Set the minimum number of photoelectrons estimated for this hit.
Definition HcalHit.h:160
void setOrientation(int orientation)
Set if the bar is orientied in X / Y / Z meanig 0 / 1 / 2, respectively.
Definition HcalHit.h:234
void setStrip(int strip)
Set the strip for this hit.
Definition HcalHit.h:178
void setLayer(int layer)
Set the layer for this hit.
Definition HcalHit.h:172
void setPE(float pe)
Set the number of photoelectrons estimated for this hit.
Definition HcalHit.h:153
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
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.