Coverage for /usr/local/lib/python3.11/dist-packages/grond/meta.py: 55%
286 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-26 16:25 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-26 16:25 +0000
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 GrondDeprecationError(GrondError):
140 pass
143class PhaseMissContext(Object):
144 store_id = gf.StringID.T()
145 timing = gf.Timing.T()
146 source = gf.Source.T()
147 target = gf.Target.T()
150class PhaseMissError(GrondError):
151 def __init__(self, store_id, timing, source, target):
152 GrondError.__init__(self)
153 self._context = PhaseMissContext(
154 store_id=store_id,
155 timing=timing,
156 source=source,
157 target=target)
159 def __str__(self):
160 return 'Phase not available for given geometry. Context:\n%s' \
161 % self._context
164def store_t(store, tdef, source, target):
165 tt = store.t(tdef, source, target)
166 if tt is None:
167 raise PhaseMissError(store.config.id, tdef, source, target)
169 return tt
172def expand_template(template, d):
173 try:
174 return Template(template).substitute(d)
175 except KeyError as e:
176 raise GrondError(
177 'invalid placeholder "%s" in template: "%s"' % (str(e), template))
178 except ValueError:
179 raise GrondError(
180 'malformed placeholder in template: "%s"' % template)
183class ADict(dict):
184 def __getattr__(self, k):
185 return self[k]
187 def __setattr__(self, k, v):
188 self[k] = v
191class Parameter(Object):
192 name__ = String.T()
193 unit = Unicode.T(optional=True)
194 scale_factor = Float.T(default=1., optional=True)
195 scale_unit = Unicode.T(optional=True)
196 label = Unicode.T(optional=True)
197 optional = Bool.T(default=True, optional=True)
199 def __init__(self, *args, **kwargs):
200 if len(args) >= 1:
201 kwargs['name'] = args[0]
202 if len(args) >= 2:
203 kwargs['unit'] = newstr(args[1])
205 self.groups = [None]
206 self._name = None
208 Object.__init__(self, **kwargs)
210 def get_label(self, with_unit=True):
211 lbl = [self.label or self.name]
212 if with_unit:
213 unit = self.get_unit_label()
214 if unit:
215 lbl.append('[%s]' % unit)
217 return ' '.join(lbl)
219 def set_groups(self, groups):
220 if not isinstance(groups, list):
221 raise AttributeError('Groups must be a list of strings.')
222 self.groups = groups
224 def _get_name(self):
225 if None not in self.groups:
226 return '%s.%s' % ('.'.join(self.groups), self._name)
227 return self._name
229 def _set_name(self, value):
230 self._name = value
232 name = property(_get_name, _set_name)
234 @property
235 def name_nogroups(self):
236 return self._name
238 def get_value_label(self, value, format='%(value)g%(unit)s'):
239 value = self.scaled(value)
240 unit = self.get_unit_suffix()
241 return format % dict(value=value, unit=unit)
243 def get_unit_label(self):
244 if self.scale_unit is not None:
245 return self.scale_unit
246 elif self.unit:
247 return self.unit
248 else:
249 return None
251 def get_unit_suffix(self):
252 unit = self.get_unit_label()
253 if not unit:
254 return ''
255 else:
256 return ' %s' % unit
258 def scaled(self, x):
259 if isinstance(x, tuple):
260 return tuple(v/self.scale_factor for v in x)
261 if isinstance(x, list):
262 return list(v/self.scale_factor for v in x)
263 else:
264 return x/self.scale_factor
266 def inv_scaled(self, x):
267 if isinstance(x, tuple):
268 return tuple(v*self.scale_factor for v in x)
269 if isinstance(x, list):
270 return list(v*self.scale_factor for v in x)
271 else:
272 return x*self.scale_factor
275class Path(String):
276 pass
279class HasPaths(Object):
280 path_prefix = Path.T(optional=True)
282 def __init__(self, *args, **kwargs):
283 Object.__init__(self, *args, **kwargs)
284 self._basepath = None
285 self._parent_path_prefix = None
287 def set_basepath(self, basepath, parent_path_prefix=None):
288 self._basepath = basepath
289 self._parent_path_prefix = parent_path_prefix
290 for (prop, val) in self.T.ipropvals(self):
291 if isinstance(val, HasPaths):
292 val.set_basepath(
293 basepath, self.path_prefix or self._parent_path_prefix)
295 def get_basepath(self):
296 assert self._basepath is not None
297 return self._basepath
299 def change_basepath(self, new_basepath, parent_path_prefix=None):
300 assert self._basepath is not None
302 self._parent_path_prefix = parent_path_prefix
303 if self.path_prefix or not self._parent_path_prefix:
305 self.path_prefix = op.normpath(xjoin(xrelpath(
306 self._basepath, new_basepath), self.path_prefix))
308 for val in self.T.ivals(self):
309 if isinstance(val, HasPaths):
310 val.change_basepath(
311 new_basepath, self.path_prefix or self._parent_path_prefix)
313 self._basepath = new_basepath
315 def expand_path(self, path, extra=None):
316 assert self._basepath is not None
318 if extra is None:
319 def extra(path):
320 return path
322 path_prefix = self.path_prefix or self._parent_path_prefix
324 if path is None:
325 return None
326 elif isinstance(path, str):
327 return extra(
328 op.normpath(xjoin(self._basepath, xjoin(path_prefix, path))))
329 else:
330 return [
331 extra(
332 op.normpath(xjoin(self._basepath, xjoin(path_prefix, p))))
333 for p in path]
335 def rel_path(self, path):
336 return xrelpath(path, self.get_basepath())
339def nslc_to_pattern(s):
340 toks = s.split('.')
341 if len(toks) == 1:
342 return '*.%s.*.*' % s
343 elif len(toks) == 2:
344 return '%s.*.*' % s
345 elif len(toks) == 3:
346 return '%s.*' % s
347 elif len(toks) == 4:
348 return s
349 else:
350 raise GrondError('Invalid net.sta.loc.cha pattern: %s' % s)
353def nslcs_to_patterns(seq):
354 return [nslc_to_pattern(s) for s in seq]
357class SelectionError(GrondError):
358 pass
361# --select="magnitude_min:5 tag_contains:a,b "
363g_conditions = {}
365selected_operators_1_1 = {
366 'min': lambda data, key, value: data[key] >= value,
367 'max': lambda data, key, value: data[key] <= value,
368 'is': lambda data, key, value: data[key] == value}
370selected_operators_1_n = {
371 'in': lambda data, key, values:
372 data[key] in values}
374selected_operators_n_1 = {}
376selected_operators_n_n = {
377 'contains': lambda data, key, values:
378 any(d in values for d in data[key]),
379 'notcontains': lambda data, key, values:
380 not any(d in values for d in data[key])}
383selected_operators = set()
385for s in (selected_operators_1_1, selected_operators_1_n,
386 selected_operators_n_1, selected_operators_n_n):
387 for k in s:
388 selected_operators.add(k)
391def _parse_selected_expression(expression):
392 for condition in expression.split():
393 condition = condition.strip()
394 if condition not in g_conditions:
395 try:
396 argument, value = condition.split(':', 1)
397 except ValueError:
398 raise SelectionError(
399 'Invalid condition in selection expression: '
400 '"%s", must be "ARGUMENT:VALUE"' % condition)
402 argument = argument.strip()
404 try:
405 key, operator = argument.rsplit('_', 1)
406 except ValueError:
407 raise SelectionError(
408 'Invalid argument in selection expression: '
409 '"%s", must be "KEY_OPERATOR"' % argument)
411 if operator not in selected_operators:
412 raise SelectionError(
413 'Invalid operator in selection expression: '
414 '"%s", available: %s' % (
415 operator, ', '.join('"%s"' % s for s in sorted(
416 list(selected_operators)))))
418 g_conditions[condition] = key, operator, value
420 yield g_conditions[condition]
423def selected(expression, data, types):
424 results = []
425 for (key, operator, value) in _parse_selected_expression(expression):
426 if key not in data:
427 raise SelectionError(
428 'Invalid key in selection expression: '
429 '"%s", available:\n %s' % (
430 key, '\n '.join(sorted(data.keys()))))
432 typ = types[key]
433 if not isinstance(typ, tuple):
434 if operator in selected_operators_1_1:
435 results.append(
436 selected_operators_1_1[operator](data, key, typ(value)))
437 elif operator in selected_operators_1_n:
438 values = list(typ(v) for v in value.split(','))
439 results.append(
440 selected_operators_1_n[operator](data, key, values))
441 else:
442 raise SelectionError(
443 'Cannot use operator "%s" with argument "%s".' % (
444 operator,
445 key))
447 else:
448 if operator in selected_operators_n_1:
449 results.append(
450 selected_operators_n_1[operator](data, key, typ[1](value)))
451 elif operator in selected_operators_n_n:
452 values = typ[0](typ[1](v) for v in value.split(','))
453 results.append(
454 selected_operators_n_n[operator](data, key, values))
455 else:
456 raise SelectionError(
457 'Cannot use operator "%s" with argument "%s".' % (
458 operator,
459 key))
461 return all(results)
464__all__ = '''
465 Forbidden
466 GrondError
467 PhaseMissContext
468 PhaseMissError
469 store_t
470 Path
471 HasPaths
472 Parameter
473 StringID
474'''.split()