LDMX Software
SiStripWaveformFitProcessor.cxx
1#include "Tracking/Reco/SiStripWaveformFitProcessor.h"
2
3#include "Framework/Exception/Exception.h"
4#include "Tracking/Digitization/SiStripConstants.h"
5#include "Tracking/Digitization/StripPulseFitter.h"
6#include "Tracking/Event/FittedSiStripHit.h"
7#include "Tracking/Event/SiStripWaveform.h"
8#include "Tracking/Reco/SiStripChannelMap.h"
9#include "Tracking/Reco/TrackerPedestals.h"
10
11namespace tracking::reco {
12
14 input_collection_ =
15 ps.get<std::string>("input_collection", input_collection_);
16 input_pass_name_ = ps.get<std::string>("input_pass_name", input_pass_name_);
17 output_collection_ =
18 ps.get<std::string>("output_collection", output_collection_);
19 daq_map_file_ = ps.get<std::string>("daq_map_file", daq_map_file_);
20
21 t_scan_min_ns_ = ps.get<double>("t_scan_min_ns", t_scan_min_ns_);
22 t_scan_max_ns_ = ps.get<double>("t_scan_max_ns", t_scan_max_ns_);
23 t_scan_step_ns_ = ps.get<double>("t_scan_step_ns", t_scan_step_ns_);
24 max_chi2_ndf_ = ps.get<double>("max_chi2_ndf", max_chi2_ndf_);
25}
26
28 using namespace tracking::digitization;
29
30 if (daq_map_file_.empty()) {
31 EXCEPTION_RAISE("BadConfig",
32 "SiStripWaveformFitProcessor requires a daq_map_file.");
33 }
34 // Loads eagerly so a missing/malformed map fails here, at start-up, with the
35 // path in the message -- never as a silently empty event stream.
36 daq_map_ = TrackerDaqMap::fromJsonFile(daq_map_file_);
37
38 pulse_shape_ = PulseShape::make(std::string(PULSE_SHAPE_NAME),
39 PEAKING_TIME_NS, SECOND_TIME_CONST_NS);
40
41 ldmx_log(info) << "SiStripWaveformFitProcessor configured:"
42 << " daq_map='" << daq_map_file_ << "' (" << daq_map_.size()
43 << " sensors)" << " shape=" << PULSE_SHAPE_NAME
44 << " tp=" << PEAKING_TIME_NS << " ns" << " T scan ["
45 << t_scan_min_ns_ << ", "
46 << (t_scan_max_ns_ > 0.0 ? std::to_string(t_scan_max_ns_)
47 : std::string("auto"))
48 << "] ns" << " step=" << t_scan_step_ns_ << " ns";
49}
50
52 using namespace tracking::digitization;
53
54 const auto& peds =
56
57 const auto& waveforms = event.getCollection<ldmx::SiStripWaveform>(
58 input_collection_, input_pass_name_);
59
60 std::vector<ldmx::FittedSiStripHit> hits;
61 hits.reserve(waveforms.size());
62
63 for (const auto& wf : waveforms) {
65
66 // -----------------------------------------------------------------------
67 // Address first: an unmapped hybrid or an unbonded channel is dropped
68 // before paying for the fit.
69 // -----------------------------------------------------------------------
70 const uint8_t feb = wf.getFebId();
71 const uint8_t hybrid = wf.getHybridId();
72
73 if (!daq_map_.has(feb, hybrid)) {
75 const uint16_t k =
76 static_cast<uint16_t>((static_cast<uint16_t>(feb) << 8) | hybrid);
77 if (unmapped_sensors_[k]++ == 0) {
78 ldmx_log(warn) << "No DAQ-map entry for feb=" << static_cast<int>(feb)
79 << " hybrid=" << static_cast<int>(hybrid)
80 << " -- dropping its waveforms (reported once)";
81 }
82 continue;
83 }
84
85 const auto& sensor = daq_map_.at(feb, hybrid);
86 const int16_t pchannel = wf.getPchannel();
87 const int strip_id = channelmap::stripId(
88 pchannel, sensor.n_strips_, sensor.first_strip_, sensor.reversed_);
89
90 // Reject channels that fall outside the bonded strip range (e.g. a read-out
91 // but unbonded APV). Counted so an unexpected layout shows up loudly.
92 if (strip_id < sensor.first_strip_ ||
93 strip_id >= sensor.first_strip_ + sensor.n_strips_) {
95 continue;
96 }
97
98 // -----------------------------------------------------------------------
99 // Fit. The noise enters the chi2, so the fitter is per channel; it is a
100 // cheap value type over a shared pulse shape.
101 // -----------------------------------------------------------------------
102 uint8_t apv_id, channel;
103 channelmap::apvChannelFromPchannel(pchannel, apv_id, channel);
104 const float noise = peds.noise(feb, hybrid, apv_id, channel);
105
106 const auto& samples = wf.getSamples();
107 const int n_samples = static_cast<int>(samples.size());
108 // Samples are pedestal-subtracted and lie on a uniform grid measured from
109 // sample 0, so T may peak anywhere from before sample 0 to the last sample.
110 const double t_scan_max = (t_scan_max_ns_ > 0.0)
111 ? t_scan_max_ns_
112 : n_samples * SAMPLING_INTERVAL_NS;
113
115 /*t0_offset_ns=*/0.0, SAMPLING_INTERVAL_NS,
116 /*pedestal_adc=*/0.0,
117 /*noise_sigma_adc=*/noise, t_scan_min_ns_,
118 t_scan_max, t_scan_step_ns_);
119 const auto fit = fitter.fit(samples);
121
122 ldmx_log(trace) << "fit feb=" << static_cast<int>(feb)
123 << " hyb=" << static_cast<int>(hybrid)
124 << " pch=" << pchannel << " nsamp=" << n_samples
125 << " noise=" << noise
126 << " -> converged=" << (fit.converged ? "yes" : "no")
127 << " amp=" << fit.amplitude << " t0=" << fit.t0 << "ns"
128 << " chi2/ndf=" << fit.chi2 << "/" << fit.ndf << " ("
129 << (fit.ndf > 0 ? fit.chi2 / fit.ndf : 0.0) << ")";
130
131 if (!fit.converged) {
133 continue;
134 }
135
136 if (max_chi2_ndf_ > 0.0 && fit.ndf > 0) {
137 if (fit.chi2 / fit.ndf > max_chi2_ndf_) {
138 ++n_bad_chi2_;
139 continue;
140 }
141 }
142
143 hits.emplace_back(
144 sensor.layer_id_, strip_id, static_cast<float>(fit.amplitude),
145 static_cast<float>(fit.t0), static_cast<float>(fit.chi2), fit.ndf,
146 /*track_id=*/-1, /*pdg_id=*/0, /*sim_hit_id=*/-1,
147 /*edep=*/0.f, noise);
148 ++n_hits_;
149 }
150
151 ldmx_log(debug) << "Produced " << hits.size() << " FittedSiStripHits from "
152 << waveforms.size() << " waveforms";
153
154 event.add(output_collection_, hits);
155}
156
158 const long n_converged = n_fit_attempted_ - n_unconverged_;
159 const double fail_pct =
161 ? 100.0 * static_cast<double>(n_unconverged_) / n_fit_attempted_
162 : 0.0;
163 ldmx_log(info) << "Fit summary: " << n_fit_attempted_ << " attempted, "
164 << n_converged << " converged, " << n_unconverged_
165 << " failed (" << fail_pct << "%)";
166 ldmx_log(info) << "SiStripWaveformFitProcessor summary: " << n_waveforms_
167 << " waveforms -> " << n_hits_ << " hits (" << n_unmapped_
168 << " unmapped, " << n_out_of_range_ << " out-of-range, "
169 << n_unconverged_ << " unconverged, " << n_bad_chi2_
170 << " bad chi2/ndf)";
171 for (const auto& [k, n] : unmapped_sensors_) {
172 ldmx_log(warn) << " unmapped feb=" << (k >> 8) << " hybrid=" << (k & 0xFF)
173 << ": " << n << " waveforms dropped";
174 }
175}
176
177} // namespace tracking::reco
178
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
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
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
Full multi-trigger waveform for one silicon strip channel.
Fits a pulse shape to the ADC samples of a single silicon-strip readout channel to extract hit amplit...
FitResult fit(const std::vector< short > &samples) const
Fit the pulse to the given ADC sample vector.
Fit a pulse shape to each SiStripWaveform and produce FittedSiStripHits.
void produce(framework::Event &event) override
Process the event and put new data products into it.
std::map< uint16_t, long > unmapped_sensors_
(feb, hybrid) pairs already warned about, so each is reported only once.
std::unique_ptr< tracking::digitization::PulseShape > pulse_shape_
Pulse shape shared by every per-channel fitter (built in onProcessStart).
long n_unmapped_
skipped: (feb, hybrid) absent from the DAQ map
long n_fit_attempted_
waveforms passed to the fitter
void onProcessEnd() override
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
void onProcessStart() override
Callback for the EventProcessor to take any necessary action when the processing of events starts,...
void configure(framework::config::Parameters &ps) override
Callback for the EventProcessor to configure itself from the given set of parameters.
long n_out_of_range_
skipped: strip_id outside the sensor
long n_unconverged_
skipped because the pulse fit did not converge
const SensorInfo & at(uint8_t feb, uint8_t hybrid) const
Look up the sensor read by (feb, hybrid).
std::size_t size() const
Number of sensors in the map.
bool has(uint8_t feb, uint8_t hybrid) const
True if the map has an entry for this electronics sensor.
static TrackerDaqMap fromJsonFile(const std::string &path)
Load a DAQ map from a JSON file.
static const std::string CONDITIONS_NAME
Name of the conditions object (must match the python registration).
constexpr int stripId(int16_t pchannel, int n_strips, int first_strip, bool reversed)
Map a physical strip number (pchannel) onto a sensor strip index using a DAQ-map transform: an option...
constexpr void apvChannelFromPchannel(int16_t pchannel, uint8_t &apv_id, uint8_t &channel)
Inverse of pchannel(): recover the (APV id, APV channel) pair from a physical strip number.