LDMX Software
EventReadoutProducer.cxx
2
3#include <cmath>
4
6#include "TrigScint/Event/TrigScintQIEDigis.h"
7#include "TrigScint/SimQIE.h"
8
9namespace trigscint {
10
11EventReadoutProducer::EventReadoutProducer(const std::string& name,
12 framework::Process& process)
13 : Producer(name, process) {}
14
15void EventReadoutProducer::configure(
17 // Configure this instance of the producer
18 input_collection_ = parameters.get<std::string>("input_collection");
19 input_pass_name_ = parameters.get<std::string>("input_pass_name");
20 output_collection_ = parameters.get<std::string>("output_collection");
21 n_ped_samples_ = parameters.get<int>("number_pedestal_samples");
22 time_shift_ = parameters.get<int>("time_shift");
23 fiber_to_shift_ = parameters.get<int>("fiber_to_shift");
24 verbose_ = parameters.get<bool>("verbose");
25
26 ldmx_log(debug) << "In configure, got parameters:" << "\noutput_collection = "
27 << output_collection_
28 << "\ninput_collection = " << input_collection_
29 << "\ninput_pass_name = " << input_pass_name_
30 << "\nnumber_pedestal_samples = " << n_ped_samples_
31 << "\ntime_shift = " << time_shift_
32 << "\nfiber_to_shift = " << fiber_to_shift_
33 << "\nverbose = " << verbose_;
34}
35
36void EventReadoutProducer::produce(framework::Event& event) {
37 // initialize QIE object for linearizing ADCs
38 SimQIE qie;
39
40 const auto digis{event.getCollection<trigscint::TrigScintQIEDigis>(
41 input_collection_, input_pass_name_)};
42
43 std::vector<trigscint::EventReadout> channel_readout_events;
44 for (const auto& digi : digis) {
46 auto adc{digi.getADC()};
47 auto tdc{digi.getTDC()};
48
49 // copy over from qie digi for convenience
50 out_event.setChanID(digi.getChanID());
51 out_event.setElecID(digi.getElecID());
52 out_event.setTimeSinceSpill(digi.getTimeSinceSpill());
53 // elecID increases monotonically with 8 channels per fiber
54 out_event.setFiberNb(digi.getElecID() / 8);
55 if (out_event.getFiberNb() == fiber_to_shift_)
56 out_event.setTimeOffset(time_shift_);
57
58 out_event.setADC(adc);
59 out_event.setTDC(tdc);
60 std::vector<float> charge;
61 std::vector<float> charge_err;
62
63 float avg_q = 0;
64 float tot_pos_q = 0;
65 int i_s = 0;
66 [[maybe_unused]] int n_pos = 0;
67 float early_ped = 0;
68 for (auto& val : adc) {
69 float q = qie.adc2Q(val);
70 charge.push_back(q);
71 charge_err.push_back(qie.qErr(q));
72 avg_q += q; // charge.back();
73 if (q > 0) {
74 tot_pos_q += q;
75 n_pos++;
76 }
77 if (verbose_)
78 ldmx_log(debug) << "got adc value " << val << " and charge "
79 << q; // qie.ADC2Q(val);
80 if (i_s < n_ped_samples_) early_ped += q;
81 i_s++;
82 }
83 out_event.setQ(charge); // set in proper order before sorting
84 out_event.setQError(charge_err); // set in proper order before sorting
85 early_ped /= n_ped_samples_;
86 out_event.setEarlyPedestal(early_ped);
87
88 // oscillation check. for this the pulse charge needs to be in order, so set
89 // this up now the period is 4. ansatz: an oscillation is a repeated shape.
90 // normalize to maxq=1, in every interval of 4 samples if after
91 // normalization the same numbers are repeated, it's an oscillation edge
92 // case: multiple single PE peaks with that repetition. we probably don't
93 // need to keep those anyway
94 std::vector<float> charge_check = {NULL};
95 float min_charge = 10;
96 // no point in looking at oscillations just around the pedestal.
97 // nearest edge is 10.35 fC //ADC=0 corresponds to -16 fC.
98 float ped = 0;
99 int ped_length = (int)charge.size() / 5;
100 // actually pulse can be up to 12 samples = 2/5*30
101 int ped_offset = 2;
102 // but still skip the lowest (and highest) few
103 // for (int i = pedLength; i < 3*pedLength ; i++) {
105 if (charge.size() > 8) {
106 if (verbose_) ldmx_log(debug) << "going into oscillations check ";
107 for (int i = 3; i < charge.size() - 4; i++) {
108 float max_samp = min_charge;
109 for (int i_q = 0; i_q < 4;
110 i_q++) { // find the local max in the 4 samples
111 // ldmx_log(debug) << "got charge " << charge[i+iQ];
112 if (charge[i + i_q] > max_samp) max_samp = charge[i + i_q];
113 }
114 if (verbose_) ldmx_log(debug) << "got max charge " << max_samp;
115 for (int i_q = 0; i_q < 4; i_q++) // store the locally normalized
116 // numbers. even if the period is
117 // 5 this should work for a while
118 charge_check.push_back(charge[i + i_q] / max_samp);
119 i += 3; // to increment by 4, do 3 here and 1 in the loop
120 }
121 }
122 if (ped_length > 4) {
123 // now calculate the pedestal as the average of the middle half of the
124 // sorted vector
125 std::sort(charge.begin(), charge.end());
126 for (int i = ped_offset; i < 2 * ped_length + ped_offset;
127 i++) { // use 1st and 2nd 5th
128 ped += charge[i];
129 }
130 ped /= 2 * ped_length;
131 }
132 // median: technically only true for odd number of elements but good enough
133 float med_q = charge[(int)charge.size() / 2];
134 float min_q = charge[0];
135 float max_q = charge[charge.size() - 1];
136
137 out_event.setTotQ(tot_pos_q);
138 //-nPos*ped); //store (event) ped subtracted
139 // total charge, before dividing by N -->
140 // actually, ped subtraction makes it confusing
141 // outEvent.setTotQ(totPosQ-adc.size()*ped); //store (event) ped subtracted
142 // total charge, before dividing by N
143 // outEvent.setTotQ(avgQ-adc.size()*ped);
145 avg_q /= adc.size();
146 out_event.setPedestal(ped);
147 out_event.setAvgQ(avg_q);
148 out_event.setMedQ(med_q);
149 out_event.setMinQ(min_q);
150 out_event.setMaxQ(max_q);
151
152 // and the noise as the RMSE of that, same interval as pedestal
153 float diff_sq = 0;
154 // for (int i = pedLength; i < 3*pedLength ; i++) {
155 if (charge.size() > 8) {
156 for (int i = ped_offset; i < 2 * ped_length + ped_offset; i++) {
157 diff_sq += (charge[i] - ped) * (charge[i] - ped);
158 }
159 diff_sq /= 2 * ped_length; // adc.size();
160 }
161 out_event.setNoise(sqrt(diff_sq));
162
163 // oscillation check
164 uint flag_oscillation = 0;
165 if (charge.size() > 8) {
166 // no need to run tedious oscillation check for all-neg channels
167 if (max_q > min_charge) {
168 int max_id = 0;
169 // find the first occurence of a local max
170 for (int i = 0; i < charge_check.size() - 4; i++) {
171 if (charge_check[i] ==
172 1) { // an actual local max has been normalised by its own value
173 max_id = i;
174 // ldmx_log(debug) << "storing max index " <<maxID <<"
175 // and size of vector is " << chargeCheck.size()-4;
176 break;
177 }
178 }
179 int last_match_sample = 0;
180 // start from local max
181 bool do_break = false;
182 for (int i = max_id; i < charge_check.size() - 4; i++) {
183 if (verbose_)
184 ldmx_log(debug)
185 << "Checking how many matching groups of four we can "
186 "find, starting at index "
187 << i;
188
189 for (int i_q = 0; i_q < 4; i_q++) { // check if they are consistently
190 // close
191 if (verbose_)
192 ldmx_log(debug)
193 << "Comparing " << charge_check[i + i_q] << " (sample "
194 << i + i_q << ") to " << charge_check[i + 4 + i_q]
195 << " (sample " << i + 4 + i_q << "), ratio is "
196 << charge_check[i + i_q] / charge_check[i + 4 + i_q];
197 // we can be generous in these crietira since we will require an
198 // unbroken suite of 8 matches to call it oscillation
199 if (fabs(charge_check[i + i_q] / charge_check[i + 4 + i_q] - 1) <
200 0.5 || // need this tolerance to be kind of large, most
201 // actual peaks won't pass it by far anyway.
202 (charge_check[i + 4 + i_q] < 0.01 &&
203 fabs(charge_check[i + i_q] / charge_check[i + 4 + i_q]) <
204 5)) // for very small numbers, one ADC difference can be a
205 // factor 3 so add some margin
206 last_match_sample = i + i_q;
207 else {
208 if (verbose_)
209 ldmx_log(debug)
210 << "Oscillation check for channel " << digi.getChanID()
211 << " breaking at time sample " << i + i_q;
212 do_break = true; // break outer loop
213 break; // break this loop
214 }
215 }
216 if (do_break) {
217 break;
218 }
219 if (verbose_)
220 ldmx_log(debug) << "Current lastMatchSample " << last_match_sample;
221 if (last_match_sample - max_id >= 2 * 4) {
222 // we had at least a couple of oscillations (2nd period
223 // was fully matched by third)
224 flag_oscillation = 1; // there is another check possible later too,
225 // commented for now
226 break; // we've seen what we need to see
227 }
228 i += 3;
229 }
230 } // if positive maxQ
231 }
232 // //use the top and bottom ends of the sorted q as another oscillation
233 // catcher: we don't expect that the top values will be high and basically
234 // identical unless they are from an oscillation
235 int n_high = 0;
236 int quart_length = (int)charge.size() / 4;
237 for (int i = 4 * quart_length - 2; i >= 3 * quart_length; i--) {
238 // maxQ is already at last index
239 if (charge[i] / max_q > 0.66) n_high++;
240 }
241
242 /* used to be: >0.9, > 0.25. But this really killed MC pulses which
243 all end up in 0.90-0.94, and somewhere >0.05 (at least >0.15)
244 */
245 uint flag_spike = (max_q / out_event.getTotQ() > 0.95) ||
246 (charge[charge.size() - 2] / max_q < 0.05);
247 // skip "unnaturally" narrow hits (all charge in
248 // one sample or huge drop to second highest)
249 uint flag_plateau = (ped > 15 || n_high >= 5);
250 //( fabs(ped) > 15 ); //threshold // //skip
251 // events that have strange plateaus
252 uint flag_long_pulse = 0;
253 // easier to deal with in hit reconstruction directly. copy channel
254 // flags to hit flags and add this one there
255 uint flag_noise = (out_event.getNoise() > 3.5 || out_event.getNoise() == 0);
256 // =0 is typically from funky events but
257 // could be too harsh maybe
258 /* //let this wait for now
259 //if we have many high counts, a small event pedestal (where they weren't
260 included), and an avgQ ~ maxQ/nHigh, then this is an oscillation if (
261 (quartLength-nHigh<2 && ped<10) || fabs( maxQ/(avgQ*nHigh)-1 ) <0.1 )
262 flagOscillation=1;
263*/
264
265 /* //this seems to not catch what we want it to
266if ( fabs(chan.getPedestal()) < 15 //threshold // //skip events that have
267strange plateaus
268 // && (chan.getAvgQ()/chan.getPedestal()<0.8) //skip
269events that have strange oscillations
270 && 1 < nSampAboveThr && nSampAboveThr < 10 ) // skip one-time sample
271flips and long weird pulses
272 */
273 uint flag = flag_spike + 2 * flag_plateau + 4 * flag_long_pulse +
274 8 * flag_oscillation + 16 * flag_noise;
275 ldmx_log(debug)
276 << "Got quality flag " << flag
277 << " made up of (spike/plateau/long pulse/oscillation/noise) "
278 << flag_spike << "+" << flag_plateau << "+" << flag_long_pulse << "+"
279 << flag_oscillation << "+" << flag_noise;
280 out_event.setQualityFlag(flag);
281
282 // if (ped > 15 )
283 // continue;
284 ldmx_log(debug) << "In event " << event.getEventHeader().getEventNumber()
285 << ", set pedestal = " << out_event.getPedestal()
286 << // ped <<
287 " fC, noise = " << out_event.getNoise() << " fC for channel "
288 << out_event.getChanID();
289
290 channel_readout_events.push_back(out_event);
291 }
292 // Create the container to hold the
293 // digitized trigger scintillator hits.
294
295 event.add(output_collection_, channel_readout_events);
296 ldmx_log(debug) << "\n";
297}
298} // namespace trigscint
299
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Class that builds linearized full event readout.
Class that stores full reconstructed (linearized) readout QIE sample from the TS.
Implements an event buffer system for storing event data.
Definition Event.h:40
Class which represents the process under execution.
Definition Process.h:34
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
Linearizes ADC info to charge, calculates channel pedestal and noise levels (in charge)
This class represents the linearised QIE output from the trigger scintillator, in charge (fC).
void setMedQ(const float medQ)
Set channel (linearized, charge-equiv) median charge.
void setFiberNb(const int fiberNb)
Set channel readout fiber number.
void setTimeOffset(const int timeOffset)
Set channel readout itme offset (in units of samples)
float getPedestal() const
Get the pedestal.
void setMinQ(const float minQ)
Set channel (linearized, charge-equiv) minimum charge.
void setQError(const std::vector< float > qErr)
Store charge quantization errors of all time samples.
void setNoise(const float noise)
Set channel (linearized, charge-equiv) noise.
float getNoise() const
Get the channel noise.
void setTotQ(const float totQ)
Set channel (linearized, charge-equiv) average charge.
float getTotQ() const
Get the channel totQ.
void setQualityFlag(const uint flag)
Set channel data quality flag.
void setPedestal(const float pedestal)
Set channel (linearized.
void setAvgQ(const float avgQ)
Set channel (linearized, charge-equiv) average charge.
void setMaxQ(const float maxQ)
Set channel (linearized, charge-equiv) maximum charge.
int getFiberNb() const
Get the channel fiberNb.
void setQ(const std::vector< float > q)
Store charges of all time samples.
void setEarlyPedestal(const float earlyPed)
Set channel (linearized.
class for simulating QIE chip output
Definition SimQIE.h:14
float adc2Q(int ADC)
Converting ADC back to charge.
Definition SimQIE.cxx:50
float qErr(float Q)
Quantization error.
Definition SimQIE.cxx:69
class for storing QIE output
void setTDC(const std::vector< int > tdc)
Store tdcs of all time samples.
void setChanID(const int chanid)
Store the channel ID.
void setTimeSinceSpill(const uint32_t timeSpill)
Store the event time since spill counter.
int getChanID() const
Get channel ID.
void setElecID(const int elecid)
Store the electronics ID.
void setADC(const std::vector< int > adc)
Store adcs of all time samples.