1import logging
2import math
3import re
4import numpy as num
5import os.path as op
6from string import Template
7from pyrocko.guts import Object, String, Float, Unicode, StringPattern, Bool
8from pyrocko import util, gf
10guts_prefix = 'grond'
13try:
14 newstr = unicode
15except NameError:
16 newstr = str
19logger = logging.getLogger('grond.meta')
20km = 1e3
22classes_with_have_get_plot_classes = []
25def has_get_plot_classes(cls):
26 classes_with_have_get_plot_classes.append(cls)
27 return cls
30class StringID(StringPattern):
31 pattern = r'^[A-Za-z][A-Za-z0-9._-]{0,64}$'
34StringID.regex = re.compile(StringID.pattern)
37def xjoin(basepath, path):
38 if path is None and basepath is not None:
39 return basepath
40 elif op.isabs(path) or basepath is None:
41 return path
42 else:
43 return op.join(basepath, path)
46def xrelpath(path, start):
47 if op.isabs(path):
48 return path
49 else:
50 return op.relpath(path, start)
53def ordersort(x):
54 isort = num.argsort(x)
55 iorder = num.empty(isort.size, dtype=int)
56 iorder[isort] = num.arange(isort.size)
57 return iorder
60def nextpow2(i):
61 return 2**int(math.ceil(math.log(i) / math.log(2.)))
64def gather(seq, key, sort=None, filter=None):
65 d = {}
66 for x in seq:
67 if filter is not None and not filter(x):
68 continue
70 k = key(x)
71 if k not in d:
72 d[k] = []
74 d[k].append(x)
76 if sort is not None:
77 for v in d.values():
78 v.sort(key=sort)
80 return d
83def str_dist(dist):
84 if dist < 10.0:
85 return '%g m' % dist
86 elif 10. <= dist < 1. * km:
87 return '%.0f m' % dist
88 elif 1. * km <= dist < 10. * km:
89 return '%.1f km' % (dist / km)
90 else:
91 return '%.0f km' % (dist / km)
94def str_duration(t):
95 s = ''
96 if t < 0.:
97 s = '-'
99 t = abs(t)
101 if t < 10.0:
102 return s + '%.2g s' % t
103 elif 10.0 <= t < 3600.:
104 return s + util.time_to_str(t, format='%M:%S min')
105 elif 3600. <= t < 24 * 3600.:
106 return s + util.time_to_str(t, format='%H:%M h')
107 else:
108 return s + '%.1f d' % (t / (24. * 3600.))
111try:
112 nanmedian = num.nanmedian
113except AttributeError:
114 def nanmedian(a, axis=None):
115 if axis is None:
116 return num.median(a[num.isfinite(a)])
117 else:
118 shape_out = list(a.shape)
119 shape_out.pop(axis)
120 out = num.empty(shape_out, dtype=a.dtype)
121 out[...] = num.nan
122 for iout in num.ndindex(tuple(shape_out)):
123 iin = list(iout)
124 iin[axis:axis] = [slice(0, a.shape[axis])]
125 b = a[tuple(iin)]
126 out[iout] = num.median(b[num.isfinite(b)])
128 return out
131class Forbidden(Exception):
132 pass
135class GrondError(Exception):
136 pass
139class PhaseMissContext(Object):
140 store_id = gf.StringID.T()
141 timing = gf.Timing.T()
142 source = gf.Source.T()
143 target = gf.Target.T()
146class PhaseMissError(GrondError):
147 def __init__(self, store_id, timing, source, target):
148 GrondError.__init__(self)
149 self._context = PhaseMissContext(
150 store_id=store_id,
151 timing=timing,
152 source=source,
153 target=target)
155 def __str__(self):
156 return 'Phase not available for given geometry. Context:\n%s' \
157 % self._context
160def store_t(store, tdef, source, target):
161 tt = store.t(tdef, source, target)
162 if tt is None:
163 raise PhaseMissError(store.config.id, tdef, source, target)
165 return tt
168def expand_template(template, d):
169 try:
170 return Template(template).substitute(d)
171 except KeyError as e:
172 raise GrondError(
173 'invalid placeholder "%s" in template: "%s"' % (str(e), template))
174 except ValueError:
175 raise GrondError(
176 'malformed placeholder in template: "%s"' % template)
179class ADict(dict):
180 def __getattr__(self, k):
181 return self[k]
183 def __setattr__(self, k, v):
184 self[k] = v
187class Parameter(Object):
188 name__ = String.T()
189 unit = Unicode.T(optional=True)
190 scale_factor = Float.T(default=1., optional=True)
191 scale_unit = Unicode.T(optional=True)
192 label = Unicode.T(optional=True)
193 optional = Bool.T(default=True, optional=True)
195 def __init__(self, *args, **kwargs):
196 if len(args) >= 1:
197 kwargs['name'] = args[0]
198 if len(args) >= 2:
199 kwargs['unit'] = newstr(args[1])
201 self.groups = [None]
202 self._name = None
204 Object.__init__(self, **kwargs)
206 def get_label(self, with_unit=True):
207 lbl = [self.label or self.name]
208 if with_unit:
209 unit = self.get_unit_label()
210 if unit:
211 lbl.append('[%s]' % unit)
213 return ' '.join(lbl)
215 def set_groups(self, groups):
216 if not isinstance(groups, list):
217 raise AttributeError('Groups must be a list of strings.')
218 self.groups = groups
220 def _get_name(self):
221 if None not in self.groups:
222 return '%s.%s' % ('.'.join(self.groups), self._name)
223 return self._name
225 def _set_name(self, value):
226 self._name = value
228 name = property(_get_name, _set_name)
230 @property
231 def name_nogroups(self):
232 return self._name
234 def get_value_label(self, value, format='%(value)g%(unit)s'):
235 value = self.scaled(value)
236 unit = self.get_unit_suffix()
237 return format % dict(value=value, unit=unit)
239 def get_unit_label(self):
240 if self.scale_unit is not None:
241 return self.scale_unit
242 elif self.unit:
243 return self.unit
244 else:
245 return None
247 def get_unit_suffix(self):
248 unit = self.get_unit_label()
249 if not unit:
250 return ''
251 else:
252 return ' %s' % unit
254 def scaled(self, x):
255 if isinstance(x, tuple):
256 return tuple(v/self.scale_factor for v in x)
257 if isinstance(x, list):
258 return list(v/self.scale_factor for v in x)
259 else:
260 return x/self.scale_factor
262 def inv_scaled(self, x):
263 if isinstance(x, tuple):
264 return tuple(v*self.scale_factor for v in x)
265 if isinstance(x, list):
266 return list(v*self.scale_factor for v in x)
267 else:
268 return x*self.scale_factor
271class Path(String):
272 pass
275class HasPaths(Object):
276 path_prefix = Path.T(optional=True)
278 def __init__(self, *args, **kwargs):
279 Object.__init__(self, *args, **kwargs)
280 self._basepath = None
281 self._parent_path_prefix = None
283 def set_basepath(self, basepath, parent_path_prefix=None):
284 self._basepath = basepath
285 self._parent_path_prefix = parent_path_prefix
286 for (prop, val) in self.T.ipropvals(self):
287 if isinstance(val, HasPaths):
288 val.set_basepath(
289 basepath, self.path_prefix or self._parent_path_prefix)
291 def get_basepath(self):
292 assert self._basepath is not None
293 return self._basepath
295 def change_basepath(self, new_basepath, parent_path_prefix=None):
296 assert self._basepath is not None
298 self._parent_path_prefix = parent_path_prefix
299 if self.path_prefix or not self._parent_path_prefix:
301 self.path_prefix = op.normpath(xjoin(xrelpath(
302 self._basepath, new_basepath), self.path_prefix))
304 for val in self.T.ivals(self):
305 if isinstance(val, HasPaths):
306 val.change_basepath(
307 new_basepath, self.path_prefix or self._parent_path_prefix)
309 self._basepath = new_basepath
311 def expand_path(self, path, extra=None):
312 assert self._basepath is not None
314 if extra is None:
315 def extra(path):
316 return path
318 path_prefix = self.path_prefix or self._parent_path_prefix
320 if path is None:
321 return None
322 elif isinstance(path, str):
323 return extra(
324 op.normpath(xjoin(self._basepath, xjoin(path_prefix, path))))
325 else:
326 return [
327 extra(
328 op.normpath(xjoin(self._basepath, xjoin(path_prefix, p))))
329 for p in path]
331 def rel_path(self, path):
332 return xrelpath(path, self.get_basepath())
335def nslc_to_pattern(s):
336 toks = s.split('.')
337 if len(toks) == 1:
338 return '*.%s.*.*' % s
339 elif len(toks) == 2:
340 return '%s.*.*' % s
341 elif len(toks) == 3:
342 return '%s.*' % s
343 elif len(toks) == 4:
344 return s
345 else:
346 raise GrondError('Invalid net.sta.loc.cha pattern: %s' % s)
349def nslcs_to_patterns(seq):
350 return [nslc_to_pattern(s) for s in seq]
353class SelectionError(GrondError):
354 pass
357# --select="magnitude_min:5 tag_contains:a,b "
359g_conditions = {}
361selected_operators_1_1 = {
362 'min': lambda data, key, value: data[key] >= value,
363 'max': lambda data, key, value: data[key] <= value,
364 'is': lambda data, key, value: data[key] == value}
366selected_operators_1_n = {
367 'in': lambda data, key, values:
368 data[key] in values}
370selected_operators_n_1 = {}
372selected_operators_n_n = {
373 'contains': lambda data, key, values:
374 any(d in values for d in data[key]),
375 'notcontains': lambda data, key, values:
376 not any(d in values for d in data[key])}
379selected_operators = set()
381for s in (selected_operators_1_1, selected_operators_1_n,
382 selected_operators_n_1, selected_operators_n_n):
383 for k in s:
384 selected_operators.add(k)
387def _parse_selected_expression(expression):
388 for condition in expression.split():
389 condition = condition.strip()
390 if condition not in g_conditions:
391 try:
392 argument, value = condition.split(':', 1)
393 except ValueError:
394 raise SelectionError(
395 'Invalid condition in selection expression: '
396 '"%s", must be "ARGUMENT:VALUE"' % condition)
398 argument = argument.strip()
400 try:
401 key, operator = argument.rsplit('_', 1)
402 except ValueError:
403 raise SelectionError(
404 'Invalid argument in selection expression: '
405 '"%s", must be "KEY_OPERATOR"' % argument)
407 if operator not in selected_operators:
408 raise SelectionError(
409 'Invalid operator in selection expression: '
410 '"%s", available: %s' % (
411 operator, ', '.join('"%s"' % s for s in sorted(
412 list(selected_operators)))))
414 g_conditions[condition] = key, operator, value
416 yield g_conditions[condition]
419def selected(expression, data, types):
420 results = []
421 for (key, operator, value) in _parse_selected_expression(expression):
422 if key not in data:
423 raise SelectionError(
424 'Invalid key in selection expression: '
425 '"%s", available:\n %s' % (
426 key, '\n '.join(sorted(data.keys()))))
428 typ = types[key]
429 if not isinstance(typ, tuple):
430 if operator in selected_operators_1_1:
431 results.append(
432 selected_operators_1_1[operator](data, key, typ(value)))
433 elif operator in selected_operators_1_n:
434 values = list(typ(v) for v in value.split(','))
435 results.append(
436 selected_operators_1_n[operator](data, key, values))
437 else:
438 raise SelectionError(
439 'Cannot use operator "%s" with argument "%s".' % (
440 operator,
441 key))
443 else:
444 if operator in selected_operators_n_1:
445 results.append(
446 selected_operators_n_1[operator](data, key, typ[1](value)))
447 elif operator in selected_operators_n_n:
448 values = typ[0](typ[1](v) for v in value.split(','))
449 results.append(
450 selected_operators_n_n[operator](data, key, values))
451 else:
452 raise SelectionError(
453 'Cannot use operator "%s" with argument "%s".' % (
454 operator,
455 key))
457 return all(results)
460__all__ = '''
461 Forbidden
462 GrondError
463 PhaseMissContext
464 PhaseMissError
465 store_t
466 Path
467 HasPaths
468 Parameter
469 StringID
470'''.split()