main.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 '''
3 Export wirecell.sigproc functionality to a main Click program.
4 '''
5 
6 import sys
7 import click
8 
9 from wirecell import units
10 
11 @click.group("sigproc")
12 @click.pass_context
13 def cli(ctx):
14  '''
15  Wire Cell Signal Processing Features
16  '''
17 
18 
19 @cli.command("response-info")
20 @click.argument("json-file")
21 @click.pass_context
22 def response_info(ctx, json_file):
23  '''
24  Show some info about a field response file (.json or .json.bz2).
25  '''
26  import response.persist as per
27  fr = per.load(json_file)
28  print ("origin:%.2f cm, period:%.2f us, tstart:%.2f us, speed:%.2f mm/us, axis:(%.2f,%.2f,%.2f)" % \
29  (fr.origin/units.cm, fr.period/units.us, fr.tstart/units.us, fr.speed/(units.mm/units.us), fr.axis[0],fr.axis[1],fr.axis[2]))
30  for pr in fr.planes:
31  print ("\tplane:%d, location:%.4fmm, pitch:%.4fmm" % \
32  (pr.planeid, pr.location/units.mm, pr.pitch/units.mm))
33 
34 @cli.command("convert-garfield")
35 @click.option("-o", "--origin", default="10.0*cm",
36  help="Set drift origin (give units, eg '10*cm').")
37 @click.option("-s", "--speed", default="1.114*mm/us",
38  help="Set nominal drift speed (give untis, eg '1.114*mm/us').")
39 @click.option("-n", "--normalization", default=0.0,
40  help="Set normalization: 0:none, <0:electrons, >0:multiplicative scale. def=0")
41 @click.option("-z", "--zero-wire-locs", default=[0.0,0.0,0.0], nargs=3, type=float,
42  help="Set location of zero wires. def: 0 0 0")
43 @click.argument("garfield-fileset")
44 @click.argument("wirecell-field-response-file")
45 @click.pass_context
46 def convert_garfield(ctx, origin, speed, normalization, zero_wire_locs,
47  garfield_fileset, wirecell_field_response_file):
48  '''
49  Convert an archive of a Garfield fileset (zip, tar, tgz) into a
50  Wire Cell field response file (.json with optional .gz or .bz2
51  compression).
52  '''
53  import garfield as gar
54  import response as res
55  import response.persist as per
56 
57  origin = eval(origin, units.__dict__)
58  speed = eval(speed, units.__dict__)
59  rflist = gar.load(garfield_fileset, normalization, zero_wire_locs)
60  fr = res.rf1dtoschema(rflist, origin, speed)
61  per.dump(wirecell_field_response_file, fr)
62 
63 
64 
65 
66 @cli.command("plot-garfield-exhaustive")
67 @click.option("-n", "--normalization", default=0.0,
68  help="Set normalization: 0:none, <0:electrons, >0:multiplicative scale. def=0")
69 @click.option("-z", "--zero-wire-locs", default=[0.0,0.0,0.0], nargs=3, type=float,
70  help="Set location of zero wires. def: 0 0 0")
71 @click.argument("garfield-fileset")
72 @click.argument("pdffile")
73 @click.pass_context
74 def plot_garfield_exhaustive(ctx, normalization, zero_wire_locs,
75  garfield_fileset, pdffile):
76  '''
77  Plot all the Garfield current responses.
78  '''
79  import wirecell.sigproc.garfield as gar
80  dat = gar.load(garfield_fileset, normalization, zero_wire_locs)
81  import wirecell.sigproc.plots as plots
82  plots.garfield_exhaustive(dat, pdffile)
83 
84 @cli.command("plot-garfield-track-response")
85 @click.option("-g", "--gain", default=-14.0,
86  help="Set gain in mV/fC.")
87 @click.option("-s", "--shaping", default=2.0,
88  help="Set shaping time in us.")
89 @click.option("-t", "--tick", default=0.5,
90  help="Set tick time in us (0.1 is good for no shaping).")
91 @click.option("-p", "--tick-padding", default=0,
92  help="Number of ticks of zero ADC to pre-pad the plots.")
93 @click.option("-e", "--electrons", default=13300,
94  help="Set normalization in units of electron charge.")
95 @click.option("-a", "--adc-gain", default=1.2,
96  help="Set ADC gain (unitless).")
97 @click.option("--adc-voltage", default=2.0,
98  help="Set ADC voltage range in Volt.")
99 @click.option("--adc-resolution", default=12,
100  help="Set ADC resolution in bits.")
101 @click.option("-n", "--normalization", default=-1,
102  help="Set normalization: 0:none, <0:electrons, >0:multiplicative scale. def=-1")
103 @click.option("-z", "--zero-wire-locs", default=[0.0, 0.0, 0.0], nargs=3, type=float,
104  help="Set location of zero wires. def: 0 0 0")
105 @click.option("--ymin", default=-40.0,
106  help="Set Y min")
107 @click.option("--ymax", default=60.0,
108  help="Set Y max")
109 @click.option("--regions", default=0, type=int,
110  help="Set how many wire regions to use, default to all")
111 @click.option("--dump-data", default="", type=str,
112  help="Dump the plotted data in format given by extension (.json, .txt or .npz/.npy)")
113 @click.argument("garfield-fileset")
114 @click.argument("pdffile")
115 @click.pass_context
116 def plot_garfield_track_response(ctx, gain, shaping, tick, tick_padding, electrons,
117  adc_gain, adc_voltage, adc_resolution,
118  normalization, zero_wire_locs,
119  ymin, ymax, regions,
120  dump_data,
121  garfield_fileset, pdffile):
122  '''
123  Plot Garfield response assuming a perpendicular track.
124 
125  Note, defaults are chosen to reproduce the "ADC Waveform with 2D
126  MicroBooNE Wire Plane Model" plot for the MicroBooNE noise paper.
127  '''
128  import wirecell.sigproc.garfield as gar
129  import wirecell.sigproc.response as res
130  import wirecell.sigproc.plots as plots
131 
132  gain *= units.mV/units.fC
133  shaping *= units.us
134  tick *= units.us
135  electrons *= units.eplus
136 
137  adc_gain *= 1.0 # unitless
138  adc_voltage *= units.volt
139  adc_resolution = 1<<adc_resolution
140  adc_per_voltage = adc_gain*adc_resolution/adc_voltage
141 
142  dat = gar.load(garfield_fileset, normalization, zero_wire_locs)
143 
144  if regions:
145  print ("Limiting to %d regions" % regions)
146  dat = [r for r in dat if abs(r.region) in range(regions)]
147 
148  uvw = res.line(dat, electrons)
149 
150  detector = ""
151  if "ub_" in garfield_fileset:
152  detector = "MicroBooNE"
153  if "dune_" in garfield_fileset:
154  detector = "DUNE"
155  print ('Using detector hints: "%s"' % detector)
156 
157  nwires = len(set([abs(r.region) for r in dat])) - 1
158  #msg = "%d electrons, +/- %d wires" % (electrons, nwires)
159  msg=""
160 
161  fig,data = plots.plot_digitized_line(uvw, gain, shaping,
162  adc_per_voltage = adc_per_voltage,
163  detector = detector,
164  ymin=ymin, ymax=ymax, msg=msg,
165  tick_padding=tick_padding)
166  print ("plotting to %s" % pdffile)
167  fig.savefig(pdffile)
168 
169  if dump_data:
170  print ("dumping data to %s" % dump_data)
171 
172  if dump_data.endswith(".npz"):
173  import numpy
174  numpy.savez(dump_data, data);
175  if dump_data.endswith(".npy"):
176  import numpy
177  numpy.save(dump_data, data);
178  if dump_data.endswith(".txt"):
179  with open(dump_data,"wt") as fp:
180  for line in data:
181  line = '\t'.join(map(str, line))
182  fp.write(line+'\n')
183  if dump_data.endswith(".json"):
184  import json
185  open(dump_data,"wt").write(json.dumps(data.tolist(), indent=4))
186 
187 
188 
189 
190 @cli.command("plot-response")
191 @click.argument("responsefile")
192 @click.argument("pdffile")
193 @click.pass_context
194 def plot_response(ctx, responsefile, pdffile):
195  import response.persist as per
196  import response.plots as plots
197 
198  fr = per.load(responsefile)
199  plots.plot_planes(fr, pdffile)
200 
201 
202 @cli.command("plot-electronics-response")
203 @click.option("-g", "--gain", default=14.0,
204  help="Set gain in mV/fC.")
205 @click.option("-s", "--shaping", default=2.0,
206  help="Set shaping time in us.")
207 @click.option("-t", "--tick", default=0.5,
208  help="Set tick time in us (0.1 is good for no shaping).")
209 @click.argument("plotfile")
210 @click.pass_context
211 def plot_electronics_response(ctx, gain, shaping, tick, plotfile):
212  '''
213  Plot the electronics response function.
214  '''
215  gain *= units.mV/units.fC
216  shaping *= units.us
217  tick *= units.us
218  import wirecell.sigproc.plots as plots
219  fig = plots.one_electronics(gain, shaping, tick)
220  fig.savefig(plotfile)
221 
222 
223 @cli.command("convert-noise-spectra")
224 @click.option("-f","--format", default="microboonev1",
225  help="Format of input file")
226 @click.argument("inputfile")
227 @click.argument("outputfile")
228 @click.pass_context
229 def convert_noise_spectra(ctx, format, inputfile, outputfile):
230  '''
231  Convert an file of noise spectra in some external format into WCT format.
232  '''
233  loader = None
234  if format == "microboonev1":
235  from wirecell.sigproc.noise.microboone import load_noise_spectra_v1
236  loader = load_noise_spectra_v1
237  #elif:...
238 
239  if not loader:
240  click.echo('Unknown format: "%s"' % format)
241  sys.exit(1)
242 
243  spectra = loader(inputfile)
244 
245  from wirecell.sigproc.noise import persist
246  persist.dump(outputfile, spectra)
247 
248 @cli.command("plot-noise-spectra")
249 @click.argument("spectrafile")
250 @click.argument("plotfile")
251 @click.pass_context
252 def plot_noise_spectra(ctx, spectrafile, plotfile):
253  '''
254  Plot contents of a WCT noise spectra file such as produced by
255  the convert-noise-spectra subcommand.
256  '''
257  from wirecell.sigproc.noise import persist, plots
258  spectra = persist.load(spectrafile)
259  plots.plot_many(spectra, plotfile)
260 
261 
262 
263 @cli.command("channel-responses")
264 @click.option("-t","--tscale", default="0.5*us", type=str,
265  help="Scale of time axis in the histogram.")
266 @click.option("-s","--scale", default="1e-9*0.5/1.13312", type=str,
267  help="Scale applied to the samples.")
268 @click.option("-n","--name", default="pResponseVsCh",
269  help="Data name (eg, the TH2D name if using 'hist' schema")
270 @click.argument("infile")
271 @click.argument("outfile")
272 @click.pass_context
273 def channel_responses(ctx, tscale, scale, name, infile, outfile):
274  '''Produce the per-channel calibrated response JSON file from a TH2D
275  of the given name in the input ROOT file provided by the analysis.
276 
277  - tscale :: a number to multiply to the time axis of the histogram
278  in order to bring the result into the WCT system of units. It
279  may be expressed as a string of an algebraic expression which
280  includes symbols, eg "0.5*us".
281 
282  - scale :: a number multiplied to all samples in the histogram in
283  order to make the sample value a unitless relative measure. It
284  may be expressed as a string of an algebraic expression which
285  includes symbols, eg "0.5*us". For uBoone's initial
286  20171006_responseWaveforms.root the appropriate scale is
287  1e-9*0.5/1.13312 = 4.41267e-10
288  '''
289  import json
290  import ROOT
291  import numpy
292  from root_numpy import hist2array
293 
294  tscale = eval(tscale, units.__dict__)
295  scale = eval(scale, units.__dict__)
296 
297  tf = ROOT.TFile.Open(str(infile))
298  assert(tf)
299  h = tf.Get(str(name))
300  if not h:
301  click.echo('Failed to get histogram "%s" from %s' % (name, infile))
302  sys.exit(1)
303 
304  arr,edges = hist2array(h, return_edges=True)
305 
306  arr *= scale
307  tedges = edges[1]
308  t0,t1 = tscale*(tedges[0:2])
309  tick = t1-t0
310 
311  nchans, nticks = arr.shape
312  channels = list()
313  for ch in range(nchans):
314  # reduce down to ~32 bit float precision to save file space
315  res = [float("%.6g"%x) for x in arr[ch,:].tolist()]
316  one = [ch, res]
317  channels.append(one)
318 
319  dat = dict(tick=tick, t0=t0, channels=channels)
320 
321  jtext = json.dumps(dat, indent=4)
322  if outfile.endswith(".json.bz2"):
323  import bz2
324  bz2.BZ2File(outfile, 'w').write(jtext)
325  return
326  if outfile.endswith(".json.gz"):
327  import gzip
328  gzip.open(outfile, 'wb').write(jtext) # wb?
329  return
330 
331  open(outfile, 'w').write(jtext)
332  return
333 
334 
335 def main():
336  cli(obj=dict())
337 
338 if '__main__' == __name__:
339  main()
340 
def plot_electronics_response(ctx, gain, shaping, tick, plotfile)
Definition: main.py:211
def plot_noise_spectra(ctx, spectrafile, plotfile)
Definition: main.py:252
int open(const char *, int)
Opens a file descriptor.
def plot_garfield_track_response(ctx, gain, shaping, tick, tick_padding, electrons, adc_gain, adc_voltage, adc_resolution, normalization, zero_wire_locs, ymin, ymax, regions, dump_data, garfield_fileset, pdffile)
Definition: main.py:121
size_t write(int, const char *, size_t)
Writes count bytes from buf to the filedescriptor fd.
T abs(T value)
def channel_responses(ctx, tscale, scale, name, infile, outfile)
Definition: main.py:273
def cli(ctx)
Definition: main.py:13
def convert_garfield(ctx, origin, speed, normalization, zero_wire_locs, garfield_fileset, wirecell_field_response_file)
Definition: main.py:47
def convert_noise_spectra(ctx, format, inputfile, outputfile)
Definition: main.py:229
def plot_response(ctx, responsefile, pdffile)
Definition: main.py:194
def response_info(ctx, json_file)
Definition: main.py:22
def plot_garfield_exhaustive(ctx, normalization, zero_wire_locs, garfield_fileset, pdffile)
Definition: main.py:75
static QCString str