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 digi_coll_name_ = ps.get<std::string>("digiCollName");
24 digi_pass_name_ = ps.get<std::string>("digiPassName");
25 sim_hit_coll_name_ = ps.get<std::string>("simHitCollName");
26 sim_hit_pass_name_ = ps.get<std::string>("simHitPassName");
27 rec_hit_coll_name_ = ps.get<std::string>("recHitCollName");
28
29 // parameters
30 mip_energy_ = ps.get<double>("mip_energy");
31 pe_per_mip_ = ps.get<double>("pe_per_mip");
32 clock_cycle_ = ps.get<double>("clock_cycle");
33 voltage_per_mip_ = ps.get<double>("voltage_per_mip");
34 attlength_ = ps.get<double>("attenuationLength");
35 n_ad_cs_ = ps.get<int>("nADCs");
36
37 // configuring corrections graphs derived on the fly
38 // TODO: maybe we should save these as a graph instead?
39 rate_up_slope_ = ps.get<double>("rateUpSlope");
40 time_up_slope_ = ps.get<double>("timeUpSlope");
41 rate_dn_slope_ = ps.get<double>("rateDnSlope");
42 time_dn_slope_ = ps.get<double>("timeDnSlope");
43 time_peak_ = ps.get<double>("timePeak");
44 pulse_func_ = TF1(
45 "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)n_ad_cs_ * clock_cycle_ * -1, (double)n_ad_cs_ * clock_cycle_);
49 pulse_func_.FixParameter(1, rate_up_slope_);
50 pulse_func_.FixParameter(2, time_up_slope_);
51 pulse_func_.FixParameter(3, time_peak_);
52 pulse_func_.FixParameter(5, rate_dn_slope_);
53 pulse_func_.FixParameter(6, time_dn_slope_);
54 pulse_func_.FixParameter(4, 0);
55 pulse_func_.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 = pulse_func_.Eval(t);
61 double ampl_tm1 = pulse_func_.Eval(t - clock_cycle_);
62 if (ampl_tm1 > ampl_t) continue;
63 correction_ampl_.SetPoint(n, ampl_tm1 / ampl_t, ampl_t);
64 if (n == 0) min_ampl_fraction_ = ampl_tm1 / ampl_t;
65 n++;
66 }
67
68 // build TOA timewalk correction with pulse-shape
69 double toa_threshold = ps.get<double>("avgToaThreshold");
70 double gain = ps.get<double>("avgGain");
71 double pedestal = ps.get<double>("avgPedestal");
72 n = 0;
73 for (double ampl = toa_threshold + 0.1; ampl < 10000; ampl += 0.01) {
74 pulse_func_.FixParameter(0, ampl);
75 double ampl_t = gain * pedestal + pulse_func_.Eval(0);
76 double toa = fabs(pulse_func_.GetX(toa_threshold,
77 (double)n_ad_cs_ * clock_cycle_ * -1,
78 (double)n_ad_cs_ * clock_cycle_));
79 correction_toa_.SetPoint(n, ampl_t, toa);
80 if (n == 0) min_ampl_ = ampl_t;
81 n++;
82 }
83 correction_toa_.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 toa_rel_start_bx(0.), max_meas{0.};
91 int toa_sample(0), max_sample(0), i_adc(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 toa_rel_start_bx = sample.toa() * (clock_cycle_ / 1024); // ns
96 // find in which ADC sample the TOA was taken
97 toa_sample = i_adc;
98 }
99 if ((sample.adcT() - pedestal) > max_meas) {
100 max_meas = (sample.adcT() - pedestal);
101 max_sample = i_adc;
102 }
103 i_adc++;
104 }
105
106 // time w.r.t to the peak
107 double toa = (max_sample - toa_sample) * clock_cycle_ - toa_rel_start_bx;
108
109 // time w.r.t to the SOI
110 toa += ((int)iSOI - max_sample) * clock_cycle_;
111
112 return toa;
113}
114
116 // get the Hcal Geometry
117 const auto& hcal_geometry = getCondition<ldmx::HcalGeometry>(
119
120 // get the reconstruction parameters
121 const auto& the_conditions{
123
124 std::vector<ldmx::HcalHit> hcal_rec_hits;
125 auto hcal_digis = event.getObject<ldmx::HgcrocDigiCollection>(
127 int num_digi_hits = hcal_digis.getNumDigis();
128
129 // get sample of interest index
130 unsigned int i_soi = hcal_digis.getSampleOfInterestIndex();
131
132 // loop through digis
133 int i_digi = 0;
134 while (i_digi < num_digi_hits) {
135 auto digi_posend = hcal_digis.getDigi(i_digi);
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 = hcal_geometry.getStripCenterPosition(id);
143 double half_total_width =
144 hcal_geometry.getHalfTotalWidth(id.section(), id.layer());
145 double ecal_dx = hcal_geometry.getEcalDx();
146 double ecal_dy = hcal_geometry.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 hit_time(0.);
170
171 double ampl_t(0.);
172 double ampl_t_posend(0.), ampl_tm1_posend(0.);
173 double ampl_t_negend(0.), ampl_tm1_negend(0.);
174
175 // Check if the bar is oriented in X or Y
176 const auto orientation{hcal_geometry.getScintillatorOrientation(id)};
177 int orientation_int = static_cast<int>(orientation);
178
179 // double readout
180 if (id.section() == ldmx::HcalID::HcalSection::BACK) {
181 auto digi_negend = hcal_digis.getDigi(i_digi + 1);
182 ldmx::HcalDigiID id_negend(digi_negend.id());
183
184 double voltage_posend, voltage_negend;
185 if (digi_posend.isTOT()) {
186 voltage_posend =
187 (digi_posend.tot() - the_conditions.totCalib(id_posend, 0)) *
188 the_conditions.totCalib(id_posend, 1);
189 voltage_negend =
190 (digi_negend.tot() - the_conditions.totCalib(id_negend, 0)) *
191 the_conditions.totCalib(id_negend, 1);
192 } else {
193 ampl_t_posend =
194 digi_posend.soi().adcT() - the_conditions.adcPedestal(id_posend);
195 ampl_tm1_posend =
196 digi_posend.soi().adcTm1() - the_conditions.adcPedestal(id_posend);
197 ampl_t_negend =
198 digi_negend.soi().adcT() - the_conditions.adcPedestal(id_negend);
199 ampl_tm1_negend =
200 digi_negend.soi().adcTm1() - the_conditions.adcPedestal(id_negend);
201
202 // correct amplitude (amplitude fractions from both ends need to be
203 // above the boundary of the correction)
204 if (ampl_tm1_posend / ampl_t_posend > min_ampl_fraction_ &&
205 ampl_tm1_negend / ampl_t_negend > min_ampl_fraction_) {
206 ampl_t_posend *=
207 correction_ampl_.Eval(ampl_tm1_posend / ampl_t_posend);
208 ampl_t_negend *=
209 correction_ampl_.Eval(ampl_tm1_negend / ampl_t_negend);
210 }
211
212 // set voltage
213 voltage_posend = ampl_t_posend * the_conditions.adcGain(id_posend, 0);
214 voltage_negend = ampl_t_negend * the_conditions.adcGain(id_negend, 0);
215 }
216
217 // get TOA
218 double toa_posend =
219 getTOA(digi_posend, the_conditions.adcPedestal(id_posend), i_soi);
220 double toa_negend =
221 getTOA(digi_negend, the_conditions.adcPedestal(id_negend), i_soi);
222
223 // get sign of position along the bar
224 int position_bar_sign = (toa_posend - toa_negend) > 0 ? 1 : -1;
225
226 // correct TOA
227 // amplitudes from both ends need to be above the boundary of the
228 // correction otherwise, one TOA gets corrected and the other does not,
229 // which results in a large TOA difference and an out-of-bounds position
230 if (ampl_t_posend > min_ampl_ && ampl_t_negend > min_ampl_) {
231 toa_posend = correction_toa_.Eval(ampl_t_posend) - toa_posend;
232 toa_negend = correction_toa_.Eval(ampl_t_negend) - toa_negend;
233 }
234
235 // get x(y) coordinate from TOA measurement = (dt*v/2)
236 // if time_posend < time_negend: position is positive
237 // velocity of light in polystyrene, n = 1.6 = c/v
238 double v = 299.792 / 1.6;
239 double position_bar =
240 position_bar_sign * fabs(toa_posend - toa_negend) * v / 2;
241
242 // reverse voltage attenuation
243 // if position along the bar is positive, then the positive end will have
244 // less attenuation than the negative end
245 // NOTE: For now, reverse attenuation is not applied to the energy
246 // deposited since both ends of the bar are summed.
247 double att_posend =
248 exp(-1. * ((distance_posend - position_bar) / 1000.) / attlength_);
249 double att_negend =
250 exp(-1. * ((distance_negend + position_bar) / 1000.) / attlength_);
251
252 // set voltage as the sum of both bars
253 voltage = (voltage_posend + voltage_negend);
254 voltage_min = std::min(voltage_posend, voltage_negend);
255
256 // set amplitude as the average of both bars (reverse attenuated)
257 ampl_t = (ampl_t_posend / att_posend + ampl_t_negend / att_negend) / 2;
258
259 // set position along the bar
260 if (orientation ==
261 ldmx::HcalGeometry::ScintillatorOrientation::horizontal) {
262 position.SetX(position_bar);
263 } else {
264 position.SetY(position_bar);
265 }
266
267 // set hit time
268 // TODO: does this need to revert shift because of propagation of light in
269 // polysterene?
270 hit_time = fabs(toa_posend + toa_negend) / 2; // ns
271
272 i_digi += 2;
273 } // end double readout loop
274 else { // single readout
275
276 double voltage_i;
277 if (digi_posend.isTOT()) {
278 // TOT - number of clock ticks that pulse was over threshold
279 // this is related to the amplitude of the pulse approximately through a
280 // linear drain rate the amplitude of the pulse is related to the energy
281 // deposited
282
283 // convert the time over threshold into a total energy deposited in the
284 // bar (time over threshold [ns] - pedestal) * gain
285
286 voltage_i = (digi_posend.tot() - the_conditions.totCalib(id_posend)) *
287 the_conditions.totCalib(id_posend);
288
289 } else {
290 // ADC mode of readout
291 // ADC - voltage measurement at a specific time of the pulse
292 ampl_t_posend =
293 digi_posend.soi().adcT() - the_conditions.adcPedestal(id_posend);
294 ampl_tm1_posend =
295 digi_posend.soi().adcTm1() - the_conditions.adcPedestal(id_posend);
296 voltage_i = ampl_t_posend * the_conditions.adcGain(id_posend);
297 }
298
299 // reverse voltage attenuation
300 // for now, assume that position along the bar is the half_total_width
301 double distance_end =
302 id_posend.isNegativeEnd() ? distance_negend : distance_posend;
303 double att = exp(-1. * ((distance_end - fabs(half_total_width)) / 1000.) /
304 attlength_);
305
306 // set voltage
307 voltage = voltage_i;
308 voltage_min = voltage_i;
309
310 // set amplitude (reverse attenuated)
311 ampl_t = ampl_t_posend / att;
312
313 // get TOA
314 double toa =
315 getTOA(digi_posend, the_conditions.adcPedestal(id_posend), i_soi);
316
317 // correct TOA
318 toa = correction_toa_.Eval(ampl_t) - toa;
319
320 // set hit time
321 hit_time = toa; // ns
322
323 i_digi++;
324 } // end single readout loop
325
326 double num_mips_equivalent = voltage / voltage_per_mip_;
327 double energy_deposited = num_mips_equivalent * mip_energy_;
328
329 // reconstructed energy in the layer_ (approximate)
330 // TODO: need to incorporate corrections if necessary
347 double reconstructed_energy = energy_deposited;
348
349 int p_es = num_mips_equivalent * pe_per_mip_;
350 int min_p_es = (voltage_min / voltage_per_mip_) * pe_per_mip_;
351
352 // copy over information to rec hit structure in new collection
353 ldmx::HcalHit rec_hit;
354 rec_hit.setID(id.raw());
355 rec_hit.setXPos(position.X());
356 rec_hit.setYPos(position.Y());
357 rec_hit.setZPos(position.Z());
358 rec_hit.setSection(id.section());
359 rec_hit.setStrip(id.strip());
360 rec_hit.setLayer(id.layer());
361 rec_hit.setPE(p_es);
362 rec_hit.setMinPE(min_p_es);
363 rec_hit.setAmplitude((ampl_t / voltage_per_mip_) * mip_energy_);
364 rec_hit.setEnergy(reconstructed_energy);
365 rec_hit.setTime(hit_time);
366 rec_hit.setOrientation(orientation_int);
367 hcal_rec_hits.push_back(rec_hit);
368 }
369
371 // hcal sim hits_ exist ==> label which hits_ are real and which are pure
372 // noise
373 auto hcal_sim_hits{event.getCollection<ldmx::SimCalorimeterHit>(
375 std::set<int> real_hits;
376 for (auto const& sim_hit : hcal_sim_hits) real_hits.insert(sim_hit.getID());
377 for (auto& hit : hcal_rec_hits)
378 hit.setNoise(real_hits.find(hit.getID()) == real_hits.end());
379 }
380
381 // add collection to event bus
382 event.add(rec_hit_coll_name_, hcal_rec_hits);
383}
384
385} // namespace hcal
386
#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:36
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].
int n_ad_cs_
Depth of ADC buffer.
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.
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.
std::string digi_pass_name_
Digi Pass Name to use as input.
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 digi_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 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.