mt-metadata 0.3.9__py2.py3-none-any.whl → 0.4.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mt-metadata might be problematic. Click here for more details.

Files changed (95) hide show
  1. mt_metadata/__init__.py +1 -1
  2. mt_metadata/base/helpers.py +84 -9
  3. mt_metadata/base/metadata.py +137 -65
  4. mt_metadata/features/__init__.py +14 -0
  5. mt_metadata/features/coherence.py +303 -0
  6. mt_metadata/features/cross_powers.py +29 -0
  7. mt_metadata/features/fc_coherence.py +81 -0
  8. mt_metadata/features/feature.py +72 -0
  9. mt_metadata/features/feature_decimation_channel.py +26 -0
  10. mt_metadata/features/feature_fc.py +24 -0
  11. mt_metadata/{transfer_functions/processing/aurora/decimation.py → features/feature_fc_run.py} +9 -4
  12. mt_metadata/features/feature_ts.py +24 -0
  13. mt_metadata/{transfer_functions/processing/aurora/window.py → features/feature_ts_run.py} +11 -18
  14. mt_metadata/features/standards/__init__.py +6 -0
  15. mt_metadata/features/standards/base_feature.json +46 -0
  16. mt_metadata/features/standards/coherence.json +57 -0
  17. mt_metadata/features/standards/fc_coherence.json +57 -0
  18. mt_metadata/features/standards/feature_decimation_channel.json +68 -0
  19. mt_metadata/features/standards/feature_fc_run.json +35 -0
  20. mt_metadata/features/standards/feature_ts_run.json +35 -0
  21. mt_metadata/features/standards/feature_weighting_window.json +46 -0
  22. mt_metadata/features/standards/weight_kernel.json +46 -0
  23. mt_metadata/features/standards/weights.json +101 -0
  24. mt_metadata/features/test_helpers/channel_weight_specs_example.json +156 -0
  25. mt_metadata/features/weights/__init__.py +0 -0
  26. mt_metadata/features/weights/base.py +44 -0
  27. mt_metadata/features/weights/channel_weight_spec.py +209 -0
  28. mt_metadata/features/weights/feature_weight_spec.py +194 -0
  29. mt_metadata/features/weights/monotonic_weight_kernel.py +275 -0
  30. mt_metadata/features/weights/standards/__init__.py +6 -0
  31. mt_metadata/features/weights/standards/activation_monotonic_weight_kernel.json +38 -0
  32. mt_metadata/features/weights/standards/base.json +36 -0
  33. mt_metadata/features/weights/standards/channel_weight_spec.json +35 -0
  34. mt_metadata/features/weights/standards/composite.json +36 -0
  35. mt_metadata/features/weights/standards/feature_weight_spec.json +13 -0
  36. mt_metadata/features/weights/standards/monotonic_weight_kernel.json +49 -0
  37. mt_metadata/features/weights/standards/taper_monotonic_weight_kernel.json +16 -0
  38. mt_metadata/features/weights/taper_weight_kernel.py +60 -0
  39. mt_metadata/helper_functions.py +69 -0
  40. mt_metadata/timeseries/filters/channel_response.py +77 -37
  41. mt_metadata/timeseries/filters/coefficient_filter.py +6 -5
  42. mt_metadata/timeseries/filters/filter_base.py +11 -15
  43. mt_metadata/timeseries/filters/fir_filter.py +8 -1
  44. mt_metadata/timeseries/filters/frequency_response_table_filter.py +26 -11
  45. mt_metadata/timeseries/filters/helper_functions.py +0 -2
  46. mt_metadata/timeseries/filters/obspy_stages.py +4 -1
  47. mt_metadata/timeseries/filters/pole_zero_filter.py +9 -5
  48. mt_metadata/timeseries/filters/time_delay_filter.py +8 -1
  49. mt_metadata/timeseries/location.py +20 -5
  50. mt_metadata/timeseries/person.py +14 -7
  51. mt_metadata/timeseries/standards/person.json +1 -1
  52. mt_metadata/timeseries/standards/run.json +2 -2
  53. mt_metadata/timeseries/station.py +4 -2
  54. mt_metadata/timeseries/stationxml/__init__.py +5 -0
  55. mt_metadata/timeseries/stationxml/xml_channel_mt_channel.py +25 -27
  56. mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py +16 -47
  57. mt_metadata/timeseries/stationxml/xml_station_mt_station.py +25 -24
  58. mt_metadata/transfer_functions/__init__.py +3 -0
  59. mt_metadata/transfer_functions/core.py +8 -11
  60. mt_metadata/transfer_functions/io/emtfxml/metadata/location.py +5 -0
  61. mt_metadata/transfer_functions/io/emtfxml/metadata/provenance.py +14 -3
  62. mt_metadata/transfer_functions/io/tools.py +2 -0
  63. mt_metadata/transfer_functions/io/zonge/metadata/header.py +1 -1
  64. mt_metadata/transfer_functions/io/zonge/metadata/standards/header.json +1 -1
  65. mt_metadata/transfer_functions/io/zonge/metadata/standards/job.json +2 -2
  66. mt_metadata/transfer_functions/io/zonge/zonge.py +19 -23
  67. mt_metadata/transfer_functions/processing/__init__.py +2 -1
  68. mt_metadata/transfer_functions/processing/aurora/__init__.py +2 -4
  69. mt_metadata/transfer_functions/processing/aurora/band.py +46 -125
  70. mt_metadata/transfer_functions/processing/aurora/channel_nomenclature.py +27 -20
  71. mt_metadata/transfer_functions/processing/aurora/decimation_level.py +324 -152
  72. mt_metadata/transfer_functions/processing/aurora/frequency_bands.py +230 -0
  73. mt_metadata/transfer_functions/processing/aurora/processing.py +3 -3
  74. mt_metadata/transfer_functions/processing/aurora/run.py +32 -7
  75. mt_metadata/transfer_functions/processing/aurora/standards/decimation_level.json +7 -73
  76. mt_metadata/transfer_functions/processing/aurora/stations.py +33 -4
  77. mt_metadata/transfer_functions/processing/fourier_coefficients/decimation.py +176 -178
  78. mt_metadata/transfer_functions/processing/fourier_coefficients/fc.py +11 -9
  79. mt_metadata/transfer_functions/processing/fourier_coefficients/standards/decimation.json +1 -111
  80. mt_metadata/transfer_functions/processing/short_time_fourier_transform.py +64 -0
  81. mt_metadata/transfer_functions/processing/standards/__init__.py +6 -0
  82. mt_metadata/transfer_functions/processing/standards/short_time_fourier_transform.json +94 -0
  83. mt_metadata/transfer_functions/processing/{aurora/standards/decimation.json → standards/time_series_decimation.json} +17 -6
  84. mt_metadata/transfer_functions/processing/{aurora/standards → standards}/window.json +13 -2
  85. mt_metadata/transfer_functions/processing/time_series_decimation.py +50 -0
  86. mt_metadata/transfer_functions/processing/window.py +118 -0
  87. mt_metadata/transfer_functions/tf/station.py +17 -1
  88. mt_metadata/utils/mttime.py +22 -3
  89. mt_metadata/utils/validators.py +4 -2
  90. {mt_metadata-0.3.9.dist-info → mt_metadata-0.4.0.dist-info}/METADATA +39 -15
  91. {mt_metadata-0.3.9.dist-info → mt_metadata-0.4.0.dist-info}/RECORD +95 -55
  92. {mt_metadata-0.3.9.dist-info → mt_metadata-0.4.0.dist-info}/WHEEL +1 -1
  93. {mt_metadata-0.3.9.dist-info → mt_metadata-0.4.0.dist-info}/AUTHORS.rst +0 -0
  94. {mt_metadata-0.3.9.dist-info → mt_metadata-0.4.0.dist-info}/LICENSE +0 -0
  95. {mt_metadata-0.3.9.dist-info → mt_metadata-0.4.0.dist-info}/top_level.txt +0 -0
mt_metadata/__init__.py CHANGED
@@ -39,7 +39,7 @@ you should only have to changes these dictionaries.
39
39
 
40
40
  __author__ = """Jared Peacock"""
41
41
  __email__ = "jpeacock@usgs.gov"
42
- __version__ = "0.3.9"
42
+ __version__ = "0.4.0"
43
43
 
44
44
  # =============================================================================
45
45
  # Imports
@@ -252,9 +252,7 @@ def write_block(key, attr_dict, c1=45, c2=45, c3=15):
252
252
  f" :widths: {c1} {c2} {c3}",
253
253
  "",
254
254
  hline,
255
- line.format(
256
- f"**{key}**", c1, "**Description**", c2, "**Example**", c3
257
- ),
255
+ line.format(f"**{key}**", c1, "**Description**", c2, "**Example**", c3),
258
256
  mline,
259
257
  ]
260
258
 
@@ -449,14 +447,43 @@ def recursive_split_getattr(base_object, name, sep="."):
449
447
  return value, prop
450
448
 
451
449
 
452
- def recursive_split_setattr(base_object, name, value, sep="."):
450
+ def recursive_split_setattr(
451
+ base_object, name, value, sep=".", skip_validation=False
452
+ ):
453
+ """
454
+ Recursively split a name and set the value of the last key. Recursion splits on the separator present in the name.
455
+
456
+ :param base_object: The object having its attribute set, or a "parent" object in the recursive/nested scenario
457
+ :type base_object: object
458
+ :param name: The name of the attribute to set
459
+ :type name: str
460
+ :param value: The value to set the attribute to
461
+ :type value: any
462
+ :param sep: The separator to split the name on, defaults to "."
463
+ :type sep: str, optional
464
+ :param skip_validation: Whether to skip validation/parse of the attribute, defaults to False
465
+ :type skip_validation: Optional[bool]
466
+
467
+ :return: None
468
+ :rtype: NoneType
469
+
470
+ """
453
471
  key, *other = name.split(sep, 1)
454
472
 
455
- if other:
456
- base_object = getattr(base_object, key)
457
- recursive_split_setattr(base_object, other[0], value)
473
+ if skip_validation:
474
+ if other:
475
+ base_object = getattr(base_object, key)
476
+ recursive_split_setattr(
477
+ base_object, other[0], value, skip_validation=True
478
+ )
479
+ else:
480
+ base_object.setattr_skip_validation(key, value)
458
481
  else:
459
- setattr(base_object, key, value)
482
+ if other:
483
+ base_object = getattr(base_object, key)
484
+ recursive_split_setattr(base_object, other[0], value)
485
+ else:
486
+ setattr(base_object, key, value)
460
487
 
461
488
 
462
489
  def structure_dict(meta_dict, sep="."):
@@ -637,7 +664,6 @@ def element_to_string(element):
637
664
  # Helper function to be sure everything is encoded properly
638
665
  # =============================================================================
639
666
  class NumpyEncoder(json.JSONEncoder):
640
-
641
667
  """
642
668
  Need to encode numpy ints and floats for json to work
643
669
  """
@@ -694,3 +720,52 @@ def validate_name(name, pattern=None):
694
720
  if name is None:
695
721
  return "unknown"
696
722
  return name.replace(" ", "_")
723
+
724
+
725
+ def requires(**requirements):
726
+ """Decorate a function with optional dependencies.
727
+
728
+ Parameters
729
+ ----------
730
+ **requirements : obj
731
+ keywords of package name and the required object for
732
+ a function.
733
+
734
+ Returns
735
+ -------
736
+ decorated_function : function
737
+ Original function if all soft dependencies are met, otherwise
738
+ it returns an empty function which prints why it is not running.
739
+
740
+ Examples
741
+ --------
742
+ ```
743
+ try:
744
+ import obspy
745
+ except ImportError:
746
+ obspy = None
747
+
748
+ @requires(obspy=obspy)
749
+ def obspy_function():
750
+ ...
751
+ # does something using obspy
752
+
753
+ """
754
+ # Check the requirements, add missing package name in the list `missing`.
755
+ missing = []
756
+ for key, item in requirements.items():
757
+ if not item:
758
+ missing.append(key)
759
+
760
+ def decorated_function(function):
761
+ """Wrap function."""
762
+ if not missing:
763
+ return function
764
+ else:
765
+ def passer(*args, **kwargs):
766
+ print(("Missing dependencies: {d}.".format(d=missing)))
767
+ print(("Not running `{}`.".format(function.__name__)))
768
+
769
+ return passer
770
+
771
+ return decorated_function
@@ -16,6 +16,7 @@ from collections import OrderedDict
16
16
  from operator import itemgetter
17
17
  from pathlib import Path
18
18
  from loguru import logger
19
+ from typing import Optional, Union
19
20
 
20
21
  import json
21
22
  import pandas as pd
@@ -31,6 +32,8 @@ from . import helpers
31
32
  from mt_metadata.base.helpers import write_lines
32
33
 
33
34
  attr_dict = {}
35
+
36
+
34
37
  # =============================================================================
35
38
  # Base class that everything else will inherit
36
39
  # =============================================================================
@@ -38,9 +41,9 @@ attr_dict = {}
38
41
 
39
42
  class Base:
40
43
  __doc__ = write_lines(attr_dict)
44
+ _base_attr_loaded = False
41
45
 
42
46
  def __init__(self, attr_dict={}, **kwargs):
43
-
44
47
  self._changed = False
45
48
 
46
49
  self._class_name = validate_attribute(self.__class__.__name__)
@@ -48,12 +51,17 @@ class Base:
48
51
  self.logger = logger
49
52
  self._debug = False
50
53
 
51
- self._set_attr_dict(attr_dict)
54
+ # attr_dict from subclass has already been validated on json load, so
55
+ # we shouldn't need to validate it again re-validation of the attribute
56
+ # dictionary used to contribute to some slowness in instantiation of
57
+ # subclasses
58
+ # self._set_attr_dict(deepcopy(attr_dict), skip_validation=True)
59
+ self._set_attr_dict(attr_dict, skip_validation=True)
52
60
 
53
61
  for name, value in kwargs.items():
54
- self.set_attr_from_name(name, value)
62
+ self.set_attr_from_name(name, value, skip_validation=False)
55
63
 
56
- def _set_attr_dict(self, attr_dict):
64
+ def _set_attr_dict(self, attr_dict, skip_validation=False):
57
65
  """
58
66
  Set attribute dictionary and variables.
59
67
 
@@ -61,13 +69,15 @@ class Base:
61
69
 
62
70
  :param attr_dict: attribute dictionary
63
71
  :type attr_dict: dict
72
+ :param skip_validation: skip validation/parse of the attribute dictionary
73
+ :type skip_validation: bool
64
74
 
65
75
  """
66
76
 
67
77
  self._attr_dict = attr_dict
68
78
 
69
79
  for key, value_dict in attr_dict.items():
70
- self.set_attr_from_name(key, value_dict["default"])
80
+ self.set_attr_from_name(key, value_dict["default"], skip_validation)
71
81
 
72
82
  def __str__(self):
73
83
  """
@@ -85,62 +95,99 @@ class Base:
85
95
  def __repr__(self):
86
96
  return self.to_json()
87
97
 
88
- def __eq__(self, other):
89
- if other in [None]:
98
+ def __eq__(
99
+ self,
100
+ other: Union["Base", dict, str, pd.Series],
101
+ ignore_keys: Optional[Union[list, tuple]] = None
102
+ ) -> bool:
103
+ """
104
+
105
+ Checks for equality between self and input argument `other`.
106
+
107
+ Logic:
108
+ - verify that object is of expected dtype (else return False)
109
+ - form two dicts, one representing self and one representing other
110
+ - compare the two dicts
111
+ - return the outcome of the comparison
112
+
113
+ :param other: Another Base object, or it's representation as dict, str, pd.Series
114
+ :type other: Union["Base", dict, str, pd.Series]
115
+ :param ignore_keys: An iterable of keys to ignore during the comparison.
116
+ :type other: Union["Base", dict, str, pd.Series]
117
+
118
+ TODO: Once python 3.10 and lower are supported, change "Base" to Self in typehints.
119
+
120
+ """
121
+ # validate dtype
122
+ allowed_input_dtypes = (Base, dict, str, pd.Series)
123
+ if not isinstance(other, allowed_input_dtypes):
124
+ msg = f"Unable to evaluate equality of {type(self)} with {type(other)}"
125
+ self.logger.info(msg)
90
126
  return False
91
- elif isinstance(other, (Base, dict, str, pd.Series)):
92
- home_dict = self.to_dict(single=True, required=False)
93
- if isinstance(other, Base):
94
- other_dict = other.to_dict(single=True, required=False)
95
- elif isinstance(other, dict):
96
- other_dict = other
97
- elif isinstance(other, str):
98
- if other.lower() in ["none", "null", "unknown"]:
99
- return False
100
- other_dict = OrderedDict(
101
- sorted(json.loads(other).items(), key=itemgetter(0))
102
- )
103
- elif isinstance(other, pd.Series):
104
- other_dict = OrderedDict(
105
- sorted(other.to_dict().items(), key=itemgetter(0))
106
- )
107
- else:
108
- raise ValueError(
109
- f"Cannot compare {self._class_name} with {type(other)}"
110
- )
111
- fail = False
112
- for key, value in home_dict.items():
113
- try:
114
- other_value = other_dict[key]
115
- if isinstance(value, np.ndarray):
116
- if value.size != other_value.size:
117
- msg = f"Array sizes for {key} differ: {value.size} != {other_value.size}"
118
- self.logger.info(msg)
119
- fail=True
120
- continue
121
- if not (value == other_value).all():
122
- msg = f"{key}: {value} != {other_value}"
123
- self.logger.info(msg)
124
- fail = True
125
- elif isinstance(value, (float, int, complex)):
126
- if not np.isclose(value, other_value):
127
- msg = f"{key}: {value} != {other_value}"
128
- self.logger.info(msg)
129
- fail = True
130
- else:
131
- if value != other_value:
132
- msg = f"{key}: {value} != {other_value}"
133
- self.logger.info(msg)
134
- fail = True
135
- except KeyError:
136
- msg = "Cannot find {0} in other".format(key)
137
- self.logger.info(msg)
138
- if fail:
127
+
128
+ # form two dicts, one for self and one for other
129
+ home_dict = self.to_dict(single=True, required=False)
130
+ if isinstance(other, Base):
131
+ other_dict = other.to_dict(single=True, required=False)
132
+ elif isinstance(other, dict):
133
+ other_dict = other
134
+ elif isinstance(other, str):
135
+ if other.lower() in ["none", "null", "unknown"]:
139
136
  return False
140
- else:
141
- return True
137
+ other_dict = OrderedDict(
138
+ sorted(json.loads(other).items(), key=itemgetter(0))
139
+ )
140
+ elif isinstance(other, pd.Series):
141
+ other_dict = OrderedDict(
142
+ sorted(other.to_dict().items(), key=itemgetter(0))
143
+ )
142
144
  else:
145
+ raise ValueError(
146
+ f"Cannot compare {self._class_name} with {type(other)}"
147
+ )
148
+
149
+ # set ignore keys to empty iterable if it was none
150
+ if ignore_keys is None:
151
+ ignore_keys = ()
152
+
153
+ # Compare the two dicts
154
+ fail = False
155
+ for key, value in home_dict.items():
156
+ if key in ignore_keys:
157
+ continue
158
+ try:
159
+ other_value = other_dict[key]
160
+ if isinstance(value, np.ndarray):
161
+ if value.size != other_value.size:
162
+ msg = f"Array sizes for {key} differ: {value.size} != {other_value.size}"
163
+ self.logger.info(msg)
164
+ fail=True
165
+ continue
166
+ if not (value == other_value).all():
167
+ msg = f"{key}: {value} != {other_value}"
168
+ self.logger.info(msg)
169
+ fail = True
170
+ # TODO: Add np.allclose here?
171
+ elif isinstance(value, (float, int, complex)):
172
+ if not np.isclose(value, other_value):
173
+ msg = f"{key}: {value} != {other_value}"
174
+ self.logger.info(msg)
175
+ fail = True
176
+ else:
177
+ if value != other_value:
178
+ msg = f"{key}: {value} != {other_value}"
179
+ self.logger.info(msg)
180
+ fail = True
181
+ except KeyError:
182
+ msg = f"Cannot find key {key} in other"
183
+ self.logger.info(msg)
184
+
185
+ # return the outcome of the comparison
186
+ if fail:
143
187
  return False
188
+ else:
189
+ return True
190
+
144
191
 
145
192
  def __ne__(self, other):
146
193
  return not self.__eq__(other)
@@ -329,7 +376,11 @@ class Base:
329
376
  + f" are allowed. Allowing {option_list} to be set to {value}."
330
377
  )
331
378
  return True, other_possible, msg
332
- return False, other_possible, f"Value '{value}' for metadata field '{name}' not found in options list {option_list}"
379
+ return (
380
+ False,
381
+ other_possible,
382
+ f"Value '{value}' for metadata field '{name}' not found in options list {option_list}",
383
+ )
333
384
 
334
385
  def __setattr__(self, name, value):
335
386
  """
@@ -383,9 +434,8 @@ class Base:
383
434
  try:
384
435
  test_property = getattr(self.__class__, name, None)
385
436
  if isinstance(test_property, property):
386
- self.logger.debug(
387
- f"Identified {name} as property, using fset"
388
- )
437
+ msg = f"Identified {name} as property, using fset"
438
+ self.logger.debug(msg)
389
439
  test_property.fset(self, value)
390
440
  return
391
441
  except AttributeError:
@@ -399,7 +449,9 @@ class Base:
399
449
  # check options
400
450
  if v_dict["style"] == "controlled vocabulary":
401
451
  options = v_dict["options"]
402
- accept, other, msg = self._validate_option(name, value, options)
452
+ accept, other, msg = self._validate_option(
453
+ name, value, options
454
+ )
403
455
  if not accept:
404
456
  self.logger.error(msg.format(value, options))
405
457
  raise MTSchemaError(msg.format(value, options))
@@ -473,7 +525,20 @@ class Base:
473
525
  return value
474
526
  return self._validate_type(value, v_type)
475
527
 
476
- def set_attr_from_name(self, name, value):
528
+ def setattr_skip_validation(self, name, value):
529
+ """
530
+ Set attribute without validation
531
+
532
+ :param name: name of attribute
533
+ :type name: string
534
+
535
+ :param value: value of the new attribute
536
+ :type value: described in value_dict
537
+
538
+ """
539
+ self.__dict__[name] = value
540
+
541
+ def set_attr_from_name(self, name, value, skip_validation=False):
477
542
  """
478
543
  Helper function to set attribute from the given name.
479
544
 
@@ -487,6 +552,8 @@ class Base:
487
552
  :type name: string
488
553
  :param value: attribute value
489
554
  :type value: type is defined by the attribute name
555
+ :param skip_validation: skip validation/parse of the key-value pair
556
+ :type skip_validation: bool
490
557
 
491
558
  :Example:
492
559
 
@@ -497,7 +564,9 @@ class Base:
497
564
  """
498
565
  if "." in name:
499
566
  try:
500
- helpers.recursive_split_setattr(self, name, value)
567
+ helpers.recursive_split_setattr(
568
+ self, name, value, skip_validation=skip_validation
569
+ )
501
570
  except AttributeError as error:
502
571
  msg = (
503
572
  "{0} is not in the current standards. "
@@ -508,7 +577,10 @@ class Base:
508
577
  self.logger.error(msg.format(name))
509
578
  raise AttributeError(error)
510
579
  else:
511
- setattr(self, name, value)
580
+ if skip_validation:
581
+ self.setattr_skip_validation(name, value)
582
+ else:
583
+ setattr(self, name, value)
512
584
 
513
585
  def add_base_attribute(self, name, value, value_dict):
514
586
  """
@@ -0,0 +1,14 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .feature_ts_run import FeatureTSRun
4
+ from .feature_fc_run import FeatureFCRun
5
+ from .feature_decimation_channel import FeatureDecimationChannel
6
+ from .feature import Feature
7
+
8
+
9
+ __all__ = [
10
+ "FeatureTSRun",
11
+ "FeatureFCRun",
12
+ "Feature",
13
+ "FeatureDecimationChannel",
14
+ ]