3 Process Garfield field response output files to produce Wire Cell 4 field response input files. 6 Garfield input is provided as a tar file. Internal directory 7 structure does not matter much but the files are assumed to be spelled 12 where <impact> spells the impact position in mm and plane is from the 15 Each .dat file may hold many records. See parse_text_record() for
16 details of the assumptions made when parsing.
18 These quantities must be given explicitly:
20 - speed :: the nominal drift speed
24 from . import response
31 # fixme: move to some util module
32 def fromtarfile(filename):
34 Iterate on tarfile, returning (name,text) pair of each file.
36 tf = tarfile.open(filename, '
r') 37 for name,member
in sorted([(m.name,m)
for m
in tf.getmembers()]):
40 yield (member.name, tf.extractfile(member).
read().
decode(
'utf-8'))
44 If string, assume file, open proper generator, o.w. just return 49 if osp.splitext(source)[1]
in [
".tar",
".gz",
".tgz"]:
51 raise ValueError(
'unknown garfield data source: "%s"' % source)
56 Return a generator that splits text by record separators. 58 for maybe
in text.split(
"\n% "):
59 if maybe.startswith(
"Created"):
64 Iterate on garfield text, returning one record. 66 lines = text.split(
'\n')
71 created = lines[0].
split()
72 ret[
'created'] =
'%s %s' %(created[1], created[3])
74 if 'Direct signal' in lines[0]:
75 ret[
'signal'] =
'direct' 76 if 'Cross-talk' in lines[0]:
77 ret[
'signal'] =
'x-talk' 80 ret[
'group'] =
int(lines[2].
split()[1])
83 wire = lines[3].
split()
84 ret[
'wire_region'] =
int(wire[1])
85 ret[
'label'] = wire[4]
87 pos = map(float, wire[6].
split(
'=')[1][1:-1].
split(
','))
88 ret[
'wire_region_pos'] = tuple([p*units.cm
for p
in pos])
89 ret[
'bias_voltage'] =
float(wire[9])
92 ret[
'nbins'] = nbins =
int(lines[4].
split()[4])
95 xunit, yunit = lines[5].
split(
":")[1].
split(
",")
96 xunit = [x.strip()
for x
in xunit.split(
"in")]
97 yunit = [y.strip()
for y
in yunit.split(
"in")]
100 if "micro second" in xunit[1]:
104 if "micro Ampere" in yunit[1]:
105 yscale = units.microampere
107 ret[
'xlabel'] = xunit[0]
108 ret[
'ylabel'] = yunit[0]
117 for line
in lines[9:9+nbins]:
118 xy = line[4:].
split()
119 xdata.append(
float(xy[0]))
120 ydata.append(
float(xy[1]))
121 if nbins != len(xdata)
or nbins != len(ydata):
122 raise ValueError(
'parse error for "%s"' % wire)
123 ret[
'x'] = numpy.asarray(xdata)*xscale
124 ret[
'y'] = numpy.asarray(ydata)*yscale
130 Try to parse whatever data is encoded into the file name. 132 fname = osp.split(filename)[-1]
133 dist, plane = osp.splitext(fname)[0].
split(
'_')
134 plane = plane.lower()
137 return dict(impact=
float(dist), plane=plane, filename=filename)
139 def load(source, normalization = None, zero_wire_loc = 0.0):
140 '''Load Garfield data source (eg, tarball). 142 Return list of response.ResponseFunction objects. 144 The responses will be normalized according to the value of 147 none or 0: no normalization, take Garfield as absolutely 150 less than 0: normalize such that the average of the integrals 151 along an impact position of the central collection wire is this 154 greater than 0: simply multiply the responses by this number. 156 The `zero_wire_loc` is the transverse location of the wire to be 157 considered the central wire. 162 from collections
import defaultdict
163 uniq = defaultdict(dict)
165 for filename, text
in source:
173 key = tuple([filename] + [dat[k]
for k
in [
'group',
'wire_region',
'label']])
175 old = uniq.get(key,
None)
189 for plane, zwl
in zip(
'uvw', zero_wire_loc):
190 byplane = [one
for one
in uniq.values()
if one[
'plane'] == plane]
191 zeros = [one
for one
in byplane
if one[
'wire_region_pos'][0] == zwl
and one[
'impact'] == 0.0]
193 for one
in sorted(byplane, key=
lambda x: (x[
'wire_region_pos'][0], x[
'impact'])):
194 print (
"region=%s impact=%s" % (one[
'wire_region_pos'], one[
'impact']))
195 raise ValueError(
"%s-plane, failed to find exactly one zero wire (at %f). Found: %s wires" % (plane.upper(), zwl, zeros))
196 zero_wire_region = zeros[0][
'wire_region']
202 ls = (t0, tf, len(times))
204 relative_region = one[
'wire_region'] - zero_wire_region
205 fix_garfield_impact_sign = -1
206 impact_number = fix_garfield_impact_sign * one[
'impact']
208 one[
'wire_region_pos'],
209 ls, numpy.asarray(one[
'y']),
211 this_plane.append(rf)
212 this_plane.sort(key=
lambda x: x.region * 10000 + x.impact)
215 w0 = [r
for r
in ret
if r.region == 0
and r.plane ==
'w']
216 dt = (w0[0].times[1]-w0[0].times[0])
217 itot = sum([sum(w.response)
for w
in w0])/len(w0)
221 if normalization
is None or normalization == 0:
222 print (
"No normalizing. But, %d paths (Qavg=%f fC = %f electrons)" % \
223 (len(w0), qtot/units.femtocoulomb, -qtot/units.eplus))
227 if normalization < 0:
228 norm = normalization*units.eplus/qtot
229 print (
"Normalizing over %d paths is %f (dt=%.2f us, Qavg=%f fC = %f electrons)" % \
230 (len(w0), norm, dt/units.microsecond, qtot/units.femtocoulomb, -qtot/units.eplus))
233 print (
"Normalization by scaling with: %f" % norm)
244 Return field response current waveforms as 3 2D arrays. 246 Return as tuple (u,v,w) where each is a 2D array shape: 247 (#regions, #responses). 250 for byplane
in response.group_by(rflist,
'plane'):
252 byregion = response.group_by(byplane,
'region')
253 if len(byregion) != 1:
254 raise ValueError(
"unexpected number of regions: %d" % len(byregion))
255 for region
in byregion:
256 this_plane.append(region.response)
257 ret.append(numpy.vstack(this_plane))
262 def convert(inputfile, outputfile = "wire-cell-garfield-fine-response.json.bz2", average=False, shaped=False):
264 Convert an input Garfield file pack into an output wire cell field response file. 266 See also wirecell.sigproc.response.persist 268 rflist =
load(inputfile)
270 rflist = [d.shaped()
for d
in rflist]
272 rflist = response.average(rflist)
273 response.write(rflist, outputfile)
def parse_filename(filename)
def convert(inputfile, outputfile="wire-cell-garfield-fine-response.json.bz2", average=False, shaped=False)
def fromtarfile(filename)
def load(source, normalization=None, zero_wire_loc=0.0)
def parse_text_record(text)
int read(int, char *, size_t)
Read bytes from a file descriptor.
auto zip(Iterables &&...iterables)
Range-for loop helper iterating across many collections at the same time.
void decode(std::any const &a, Hep2Vector &result)
void split(std::string const &s, char c, OutIter dest)
def split_text_records(text)