All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Response_tool.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 /// \file Response.cc
3 /// \author T. Usher
4 ////////////////////////////////////////////////////////////////////////
5 
6 #include <cmath>
7 #include "IResponse.h"
8 #include "art/Utilities/ToolMacros.h"
9 #include "art_root_io/TFileService.h"
10 #include "messagefacility/MessageLogger/MessageLogger.h"
11 #include "cetlib_except/exception.h"
17 
18 #include "art/Utilities/make_tool.h"
19 #include "icarus_signal_processing/WaveformTools.h"
20 #include "IFieldResponse.h"
21 #include "IElectronicsResponse.h"
22 #include "IFilter.h"
23 
24 #include "TProfile.h"
25 
26 #include "icarus_signal_processing/Filters/ICARUSFFT.h"
27 
28 #include <fstream>
29 #include <iomanip>
30 
31 namespace icarus_tool
32 {
33 
35 {
36 public:
37  explicit Response(const fhicl::ParameterSet& pset);
38 
39  ~Response() {}
40 
41  void configure(const fhicl::ParameterSet& pset) override;
42  void setResponse(double sampling_rate, double weight) override;
43  void outputHistograms(double sampling_rate, art::TFileDirectory&) const override;
44 
45  size_t getPlane() const override {return fThisPlane;}
46 
47  const IFieldResponse* getFieldResponse() const override {return fFieldResponse.get();}
48  const IElectronicsResponse* getElectronicsResponse() const override {return fElectronicsResponse.get();}
49  const IFilter* getFilter() const override {return fFilter.get();}
50 
51  size_t getNumberTimeSamples() const override {return fNumberTimeSamples;}
52  const icarusutil::TimeVec& getResponse() const override {return fResponse;}
53  const icarusutil::FrequencyVec& getConvKernel() const override {return fConvolutionKernel;}
55  double getTOffset() const override {return fT0Offset;};
56 
57 private:
58  // Calculate the response function
59  void calculateResponse(double sampling_rate,
60  double weight);
61 
62  // Utility routine for converting numbers to strings
63  std::string numberToString(int number);
64 
65  // Keep track of our status
67 
68  // Member variables from the fhicl file
69  size_t fThisPlane;
70  double f3DCorrection;
72 
73  using IFieldResponsePtr = std::unique_ptr<icarus_tool::IFieldResponse>;
74  using IElectronicsResponsePtr = std::unique_ptr<icarus_tool::IElectronicsResponse>;
75  using IFilterPtr = std::unique_ptr<icarus_tool::IFilter>;
76 
77  // Keep track of our base tools
81 
82  // Keep track of overall response functions
87 
88  bool fUseEmpiricalOffsets; ///< Use emperical offsets divined from data
89  double fT0Offset; ///< The overall T0 offset for the response function
90 
91  std::unique_ptr<icarus_signal_processing::ICARUSFFT<double>> fFFT; ///< Object to handle thread safe FFT
92 };
93 
94 //----------------------------------------------------------------------
95 // Constructor.
96 Response::Response(const fhicl::ParameterSet& pset)
97 {
98  configure(pset);
99 }
100 
101 void Response::configure(const fhicl::ParameterSet& pset)
102 {
103  // Start by recovering the parameters
104  fThisPlane = pset.get<size_t>("Plane");
105  f3DCorrection = pset.get<size_t>("Correction3D");
106  fTimeScaleFactor = pset.get<size_t>("TimeScaleFactor");
107  fUseEmpiricalOffsets = pset.get<bool >("UseEmpiricalOffsets");
108 
109  fResponseHasBeenSet = false;
110 
111  // Build out the underlying tools we'll be using
112  fFieldResponse = art::make_tool<icarus_tool::IFieldResponse>(pset.get<fhicl::ParameterSet>("FieldResponse"));
113  fElectronicsResponse = art::make_tool<icarus_tool::IElectronicsResponse>(pset.get<fhicl::ParameterSet>("ElectronicsResponse"));
114  fFilter = art::make_tool<icarus_tool::IFilter>(pset.get<fhicl::ParameterSet>("Filter"));
115 
116  auto const detProp = art::ServiceHandle<detinfo::DetectorPropertiesService const>()->DataForJob();
117  fNumberTimeSamples = detProp.NumberTimeSamples();
118 
119  // Now set up our plans for doing the convolution
120  fFFT = std::make_unique<icarus_signal_processing::ICARUSFFT<double>>(fNumberTimeSamples);
121 
122  return;
123 }
124 
125  void Response::setResponse(double sampling_rate, double weight)
126 {
127  // If we have already done the setup then can return
128  if (fResponseHasBeenSet) return;
129 
130  // Calculate the combined field and electronics shaping response
131  calculateResponse(sampling_rate, weight);
132 
133  // Now we compute the convolution kernel which is a straigtforward operation
134  fFFT->forwardFFT(fResponse, fConvolutionKernel);
135 
136  // Set up the filter for use in the deconvolution
138 
139  // Now compute the deconvolution kernel
140  fDeconvolutionKernel = fFilter->getResponseVec();
141 
142  for(size_t idx = 0; idx < fNumberTimeSamples; idx++)
143  {
144  if (std::abs(fConvolutionKernel[idx]) < 0.0001) fDeconvolutionKernel[idx] = 0.;
145  else fDeconvolutionKernel[idx] /= fConvolutionKernel[idx];
146  }
147 
148  // The following can be uncommented to do some consistency checks if desired
149 // // Do some consistency/cross checks here
150 // // Check area of convolution function
151 // const std::vector<TComplex>& convKernel = fSignalShapingICARUS.ConvKernel();
152 //
153 // double normFactor = std::accumulate(convKernel.begin(),convKernel.end(),0.,[](const auto& val, double sum){return sum + std::abs(val);});
154 //
155 // mf::LogInfo("Response_tool") << "Response for plane: " << fThisPlane << ", convKernel integral: " << normFactor << std::endl;
156 //
157 // const std::vector<TComplex>& deconvKernel = fSignalShapingICARUS.DeconvKernel();
158 // std::vector<TComplex> combKernel(deconvKernel.size());
159 //
160 // std::transform(convKernel.begin(),convKernel.end(),deconvKernel.begin(),combKernel.begin(),std::multiplies<TComplex>());
161 //
162 // const std::vector<TComplex>& filterKernel = fSignalShapingICARUS.Filter();
163 //
164 // int diffCount(0);
165 // double maxRhoDiff(0.);
166 //
167 // for(size_t idx = 0; idx < filterKernel.size(); idx++)
168 // {
169 // double rhoDiff = filterKernel[idx].Rho() - combKernel[idx].Rho();
170 //
171 // if (std::abs(rhoDiff) > 0.001) diffCount++;
172 //
173 // if (std::abs(rhoDiff) > std::abs(maxRhoDiff)) maxRhoDiff = rhoDiff;
174 // }
175 //
176 // mf::LogInfo("Response_tool") << "Checking recovery of the filter, # differences: " << diffCount << ", max diff seen: " << maxRhoDiff << std::endl;
177 
178  fResponseHasBeenSet = true;
179 
180  return;
181 }
182 
184  double weight)
185 {
186  // First of all set the field response
187  fFieldResponse->setResponse(weight, f3DCorrection, fTimeScaleFactor);
188 
189  // What kind of response? (0= first induction, 1= middle induction, 2= collection)
190  size_t responseType = fFieldResponse->getResponseType();
191 
192  // handle the electronics response for this plane
193  fElectronicsResponse->setResponse(fFieldResponse->getResponseVec().size(), fFieldResponse->getBinWidth());
194 
195  // Next we perform the convolution of the field and electronics responses and then invert to get the
196  // interim response function which will be set up in the time binning/range of the field response
197  const icarusutil::FrequencyVec& fieldResponseFFTVec = fFieldResponse->getResponseFFTVec();
198  const icarusutil::FrequencyVec& electronicsResponseFFTVec = fElectronicsResponse->getResponseFFTVec();
199 
200  icarusutil::FrequencyVec curResponseFFTVec(fieldResponseFFTVec.size());
201 
202  std::transform(fieldResponseFFTVec.begin(), fieldResponseFFTVec.begin() + fieldResponseFFTVec.size()/2, electronicsResponseFFTVec.begin(), curResponseFFTVec.begin(), std::multiplies<icarusutil::ComplexVal>());
203 
204  // And now we recover the current response vector which is the convolution of the two above
205  // (and still in the units of the original field response)
206  icarusutil::TimeVec curResponseVec(fieldResponseFFTVec.size());
207 
208  // Note that we need a local version of the FFT because our time samples currently don't match what we will have
209  icarus_signal_processing::ICARUSFFT<double> locFFT(curResponseVec.size());
210 
211  locFFT.inverseFFT(curResponseFFTVec, curResponseVec);
212 
213  // Now set to the task of determing the actual sampling response
214  // We have to remember that the bin size for determining the field response probably
215  // does not match that for the detector readout so we'll need to "convert"
216  // from one to the other.
217  fResponse.resize(fNumberTimeSamples, 0.);
218 
219  // Recover the combined response from above
220 // const std::vector<double>& curResponseVec = fSignalShapingICARUS.Response_save();
221 
222  double respIntegral = std::accumulate(curResponseVec.begin(),curResponseVec.end(),0.);
223 
224  mf::LogInfo("Response_tool") << "***** Response for plane: " << fThisPlane << " ******" << "\n"
225  << " initial response integral: " << respIntegral << std::endl;
226 
227  // Need two factors: 1) the detector sampling rate and 2) the response sampling rate
228  double samplingRate = sampling_rate; // This is input in ns/tick
229  double responseRate = fFieldResponse->getBinWidth(); // This is returned in ns
230  double rateRatio = samplingRate / responseRate; // This gives the ratio of time bins for full readout to response bins
231 
232  // The idea is to step through each bin of the sampling response vector and then to
233  // look up the corresponding bins in the current response vector. Since the two sample
234  // rates are not the same there will be some "stretching" between the two. In addition,
235  // we want to continue to allow for the possibility for further sample stretching
236  double binScaleFactor = rateRatio * f3DCorrection * fTimeScaleFactor;
237 
238  // ok, do the loop
239  for(size_t sampleIdx = 0; sampleIdx < fNumberTimeSamples; sampleIdx++)
240  {
241  // calculate the index for the response
242  size_t responseLowIdx = std::floor(sampleIdx * binScaleFactor);
243 
244  if (responseLowIdx < curResponseVec.size())
245  {
246  // Calculate the index for the next bin
247  size_t responseHiIdx = std::floor((sampleIdx + 1) * binScaleFactor);
248 
249  // This can't happen? But protect against zero divides...
250  if (responseHiIdx == responseLowIdx) responseHiIdx += 1;
251 
252  if (responseHiIdx >= curResponseVec.size()) break;
253 
254  icarusutil::TimeVec::const_iterator curResponseItr = curResponseVec.begin();
255 
256  std::advance(curResponseItr,responseLowIdx);
257 
258  int nBins = responseHiIdx - responseLowIdx;
259 
260  // Obtain the average of these bins
261  double aveResponse = std::accumulate(curResponseItr,curResponseItr+nBins,0.)/double(nBins);
262 
263  // Now interpolate between the two bins to get the sampling response for this bin
264 // double responseSlope = (curResponseVec.at(responseHiIdx) - curResponseVec.at(responseLowIdx)) / (responseHiIdx - responseLowIdx);
265 // double response = curResponseVec.at(responseLowIdx) + 0.5 * responseSlope * (responseHiIdx - responseLowIdx);
266 
267  fResponse[sampleIdx] = aveResponse;
268  }
269  }
270 
271  // We need to scale by the binScaleFactor to preserve normalization
272  std::transform(fResponse.begin(),fResponse.end(),fResponse.begin(),std::bind(std::multiplies<double>(),std::placeholders::_1,binScaleFactor));
273 
274  respIntegral = std::accumulate(fResponse.begin(),fResponse.end(),0.);
275 
276  // Now compute the T0 offset for the response function
277  std::pair<icarusutil::TimeVec::iterator,icarusutil::TimeVec::iterator> minMaxPair = std::minmax_element(fResponse.begin(),fResponse.end());
278 
279  // Calculation of the T0 offset depends on the signal type
280  int timeBin = std::distance(fResponse.begin(),minMaxPair.first);
281 
282  if (responseType == 2) timeBin = std::distance(fResponse.begin(),minMaxPair.second);
283  else
284  {
285  std::cout << "*** plane: " << fThisPlane << ", first idx/val: " << std::distance(fResponse.begin(),minMaxPair.first) << "/" << *minMaxPair.first << ", second idx/val: " << std::distance(fResponse.begin(),minMaxPair.second) << "/" << *minMaxPair.second << std::endl;
286  double weightSum = abs(*minMaxPair.first) + abs(*minMaxPair.second);
287  double sum = std::distance(fResponse.begin(),minMaxPair.first) * abs(*minMaxPair.first) + std::distance(fResponse.begin(),minMaxPair.second) * abs(*minMaxPair.second);
288 
289  timeBin = std::round(sum/weightSum);
290  }
291 
292  std::cout << "*** Response tool is setting timeBin = " << timeBin << std::endl;
293 
294  if (fUseEmpiricalOffsets && fThisPlane == 0) timeBin = 83.;
295  if (fUseEmpiricalOffsets && fThisPlane == 1) timeBin = 83.;
296 
297 // // Do a backwards search to find the first positive bin
298 // while(1)
299 // {
300 // // Did we go too far?
301 // if (timeBin < 0)
302 // throw cet::exception("Response::configure") << "Cannot find zero-point crossover for induction response! ResponseType: " << responseType << ", plane: " << fThisPlane << std::endl;
303 //
304 // double content = fResponse[timeBin];
305 //
306 // if (content >= 0.) break;
307 //
308 // timeBin--;
309 // }
310 //
311 //
312  fT0Offset = -timeBin; // Note that this value being returned is in tick units now
313 
314  std::cout << "** Response tool for plane: " << fThisPlane << ", T0: " << fT0Offset << std::endl;
315 
316  mf::LogInfo("Response_tool") << " final response integral: " << respIntegral << ", T0Offset: " << fT0Offset << std::endl;
317 
318  return;
319 }
320 
322  art::TFileDirectory& histDir) const
323 {
324  // Create a subfolder in which to place the "response" histograms
325  std::string thisResponse = "ResponsesPlane_" + std::to_string(fThisPlane);
326 
327  art::TFileDirectory dir = histDir.mkdir(thisResponse.c_str());
328 
329  // Do the field response histograms
330  fFieldResponse->outputHistograms(dir);
331  fElectronicsResponse->outputHistograms(dir);
332  fFilter->outputHistograms(dir);
333 
334  // Now make hists for the full response
335  std::string dirName = "Response_" + std::to_string(fThisPlane);
336 
337  art::TFileDirectory responesDir = dir.mkdir(dirName.c_str());
338  const icarusutil::TimeVec& responseVec = fResponse;
339  double numBins = responseVec.size();
340  double samplingRate = sampling_rate; // Sampling time in us
341  double maxFreq = 1. / samplingRate; // Max frequency in MHz
342  double minFreq = maxFreq / numBins;
343  std::string histName = "Response_Plane_" + std::to_string(fThisPlane);
344  TProfile* hist = dir.make<TProfile>(histName.c_str(), "Response;Time(us)", numBins, 0., numBins * sampling_rate * 1.e-3);
345 
346  for(int bin = 0; bin < numBins; bin++)
347  {
348  hist->Fill((double(bin) + 0.5) * sampling_rate * 1.e-3, responseVec.at(bin), 1.);
349  }
350 
351  icarusutil::TimeVec powerVec;
352 
353  fFFT->getFFTPower(responseVec, powerVec);
354 
355  std::string freqName = "Response_FFTPlane_" + std::to_string(fThisPlane);
356  TProfile* freqHist = dir.make<TProfile>(freqName.c_str(), "Response;Frequency(kHz)", numBins/2, minFreq, 0.5*maxFreq);
357 
358  for(size_t idx = 0; idx < numBins/2; idx++)
359  {
360  double freq = minFreq * (idx + 0.5);
361 
362  freqHist->Fill(freq, powerVec.at(idx), 1.);
363  }
364 
365  const icarusutil::FrequencyVec& convKernel = fConvolutionKernel;
366 
367  std::string convKernelName = "ConvKernel_" + std::to_string(fThisPlane);
368  TProfile* fullResponseHist = dir.make<TProfile>(convKernelName.c_str(), "Convolution Kernel;Frequency(kHz)", numBins/2, minFreq, 0.5*maxFreq);
369 
370  for(size_t idx = 0; idx < numBins/2; idx++)
371  {
372  double freq = minFreq * (idx + 0.5);
373 
374  fullResponseHist->Fill(freq, std::abs(convKernel[idx]), 1.);
375  }
376 
377  const icarusutil::FrequencyVec& deconKernel = fDeconvolutionKernel;
378 
379  std::string deconName = "DeconKernel_" + std::to_string(fThisPlane);
380  TProfile* deconHist = dir.make<TProfile>(deconName.c_str(), "Deconvolution Kernel;Frequency(kHz)", numBins/2, minFreq, 0.5*maxFreq);
381 
382  for(size_t idx = 0; idx < numBins/2; idx++)
383  {
384  double freq = minFreq * (idx + 0.5);
385 
386  deconHist->Fill(freq, std::abs(deconKernel[idx]), 1.);
387  }
388 
389  return;
390 }
391 
392 std::string Response::numberToString(int number)
393 {
394  std::ostringstream string;
395 
396  string << std::setfill('0') << std::setw(2) << number;
397 
398  return string.str();
399 }
400 
401 
402 DEFINE_ART_CLASS_TOOL(Response)
403 }
IElectronicsResponsePtr fElectronicsResponse
Utilities related to art service access.
static constexpr Sample_t transform(Sample_t sample)
void configure(const fhicl::ParameterSet &pset) override
Setup routines.
const icarusutil::TimeVec & getResponse() const override
void outputHistograms(double sampling_rate, art::TFileDirectory &) const override
IFieldResponsePtr fFieldResponse
const icarusutil::FrequencyVec & getDeconvKernel() const override
std::unique_ptr< icarus_tool::IFilter > IFilterPtr
void setResponse(double sampling_rate, double weight) override
This is the interface class for a tool to handle the field response It is assumed that the field resp...
double fT0Offset
The overall T0 offset for the response function.
std::vector< ComplexVal > FrequencyVec
const IElectronicsResponse * getElectronicsResponse() const override
pure virtual base interface for detector clocks
constexpr details::BinObj< T > bin(T value)
Returns a wrapper to print the specified data in binary format.
void calculateResponse(double sampling_rate, double weight)
size_t getNumberTimeSamples() const override
here recover the combined response elements
T abs(T value)
double getTOffset() const override
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
const icarusutil::FrequencyVec & getConvKernel() const override
std::unique_ptr< icarus_signal_processing::ICARUSFFT< double > > fFFT
Object to handle thread safe FFT.
std::vector< SigProcPrecision > TimeVec
bool fUseEmpiricalOffsets
Use emperical offsets divined from data.
This is the interface class for a tool to handle a filter for the overall response.
icarusutil::FrequencyVec fConvolutionKernel
Definition of data types for geometry description.
std::unique_ptr< icarus_tool::IFieldResponse > IFieldResponsePtr
tuple dir
Definition: dropbox.py:28
std::string to_string(WindowPattern const &pattern)
const IFieldResponse * getFieldResponse() const override
Recover the individual response elements.
do i e
std::string numberToString(int number)
const IFilter * getFilter() const override
This is the interface class for a tool to handle the field response It is assumed that the field resp...
This is the interface class for a tool to handle the electronics response.
size_t getPlane() const override
Return the plane these functions represent.
double sampling_rate(DetectorClocksData const &data)
Returns the period of the TPC readout electronics clock.
std::unique_ptr< icarus_tool::IElectronicsResponse > IElectronicsResponsePtr
art framework interface to geometry description
BEGIN_PROLOG could also be cout
auto const detProp
icarusutil::FrequencyVec fDeconvolutionKernel
icarusutil::TimeVec fResponse
Response(const fhicl::ParameterSet &pset)