mt-metadata 0.3.5__py2.py3-none-any.whl → 0.3.7__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.
- mt_metadata/__init__.py +1 -1
- mt_metadata/base/helpers.py +9 -2
- mt_metadata/timeseries/filters/filtered.py +133 -75
- mt_metadata/timeseries/filters/frequency_response_table_filter.py +10 -7
- mt_metadata/timeseries/station.py +31 -0
- mt_metadata/timeseries/stationxml/xml_channel_mt_channel.py +53 -1
- mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py +1 -0
- mt_metadata/transfer_functions/__init__.py +38 -0
- mt_metadata/transfer_functions/core.py +96 -71
- mt_metadata/transfer_functions/io/edi/edi.py +29 -19
- mt_metadata/transfer_functions/io/edi/metadata/define_measurement.py +1 -0
- mt_metadata/transfer_functions/io/edi/metadata/emeasurement.py +4 -2
- mt_metadata/transfer_functions/io/edi/metadata/header.py +3 -1
- mt_metadata/transfer_functions/io/edi/metadata/information.py +13 -6
- mt_metadata/transfer_functions/io/emtfxml/emtfxml.py +12 -6
- mt_metadata/transfer_functions/io/emtfxml/metadata/data.py +1 -1
- mt_metadata/transfer_functions/io/emtfxml/metadata/estimate.py +1 -1
- mt_metadata/transfer_functions/io/emtfxml/metadata/period_range.py +6 -1
- mt_metadata/transfer_functions/io/emtfxml/metadata/provenance.py +6 -2
- mt_metadata/transfer_functions/io/emtfxml/metadata/standards/copyright.json +2 -1
- mt_metadata/transfer_functions/processing/aurora/__init__.py +0 -1
- mt_metadata/transfer_functions/processing/aurora/band.py +7 -11
- mt_metadata/transfer_functions/processing/aurora/channel_nomenclature.py +6 -44
- mt_metadata/transfer_functions/processing/aurora/standards/regression.json +46 -1
- mt_metadata/transfer_functions/processing/aurora/station.py +17 -11
- mt_metadata/transfer_functions/processing/aurora/stations.py +4 -4
- mt_metadata/utils/mttime.py +1 -1
- mt_metadata/utils/validators.py +11 -2
- {mt_metadata-0.3.5.dist-info → mt_metadata-0.3.7.dist-info}/METADATA +52 -3
- {mt_metadata-0.3.5.dist-info → mt_metadata-0.3.7.dist-info}/RECORD +34 -34
- {mt_metadata-0.3.5.dist-info → mt_metadata-0.3.7.dist-info}/AUTHORS.rst +0 -0
- {mt_metadata-0.3.5.dist-info → mt_metadata-0.3.7.dist-info}/LICENSE +0 -0
- {mt_metadata-0.3.5.dist-info → mt_metadata-0.3.7.dist-info}/WHEEL +0 -0
- {mt_metadata-0.3.5.dist-info → mt_metadata-0.3.7.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.
|
|
42
|
+
__version__ = "0.3.7"
|
|
43
43
|
|
|
44
44
|
# =============================================================================
|
|
45
45
|
# Imports
|
mt_metadata/base/helpers.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Created on Wed Dec 23 20:37:52 2020
|
|
4
4
|
|
|
5
|
-
:copyright:
|
|
5
|
+
:copyright:
|
|
6
6
|
Jared Peacock (jpeacock@usgs.gov)
|
|
7
7
|
|
|
8
8
|
:license: MIT
|
|
@@ -637,11 +637,18 @@ def element_to_string(element):
|
|
|
637
637
|
# Helper function to be sure everything is encoded properly
|
|
638
638
|
# =============================================================================
|
|
639
639
|
class NumpyEncoder(json.JSONEncoder):
|
|
640
|
+
|
|
640
641
|
"""
|
|
641
642
|
Need to encode numpy ints and floats for json to work
|
|
642
643
|
"""
|
|
643
644
|
|
|
644
645
|
def default(self, obj):
|
|
646
|
+
"""
|
|
647
|
+
|
|
648
|
+
:param obj:
|
|
649
|
+
:type obj:
|
|
650
|
+
:return:
|
|
651
|
+
"""
|
|
645
652
|
if isinstance(
|
|
646
653
|
obj,
|
|
647
654
|
(
|
|
@@ -659,7 +666,7 @@ class NumpyEncoder(json.JSONEncoder):
|
|
|
659
666
|
),
|
|
660
667
|
):
|
|
661
668
|
return int(obj)
|
|
662
|
-
elif isinstance(obj, (np.
|
|
669
|
+
elif isinstance(obj, (np.float16, np.float32, np.float64)):
|
|
663
670
|
return float(obj)
|
|
664
671
|
elif isinstance(obj, (np.ndarray)):
|
|
665
672
|
if obj.dtype == complex:
|
|
@@ -17,6 +17,7 @@ from mt_metadata.base.helpers import write_lines
|
|
|
17
17
|
from mt_metadata.base import get_schema, Base
|
|
18
18
|
from mt_metadata.timeseries.standards import SCHEMA_FN_PATHS
|
|
19
19
|
from mt_metadata.utils.exceptions import MTSchemaError
|
|
20
|
+
from typing import Optional, Union
|
|
20
21
|
|
|
21
22
|
# =============================================================================
|
|
22
23
|
attr_dict = get_schema("filtered", SCHEMA_FN_PATHS)
|
|
@@ -31,6 +32,14 @@ class Filtered(Base):
|
|
|
31
32
|
__doc__ = write_lines(attr_dict)
|
|
32
33
|
|
|
33
34
|
def __init__(self, **kwargs):
|
|
35
|
+
"""
|
|
36
|
+
Constructor
|
|
37
|
+
|
|
38
|
+
:param kwargs:
|
|
39
|
+
|
|
40
|
+
TODO: Consider not setting self.applied = None, as this has the effect of self._applied = [True,]
|
|
41
|
+
"""
|
|
42
|
+
self._applied_values_map = _applied_values_map()
|
|
34
43
|
self._name = []
|
|
35
44
|
self._applied = []
|
|
36
45
|
self.name = None
|
|
@@ -53,7 +62,7 @@ class Filtered(Base):
|
|
|
53
62
|
elif isinstance(names, list):
|
|
54
63
|
self._name = [ss.strip().lower() for ss in names]
|
|
55
64
|
elif isinstance(names, np.ndarray):
|
|
56
|
-
names = names.astype(np.
|
|
65
|
+
names = names.astype(np.str_)
|
|
57
66
|
self._name = [ss.strip().lower() for ss in names]
|
|
58
67
|
else:
|
|
59
68
|
msg = "names must be a string or list of strings not {0}, type {1}"
|
|
@@ -68,25 +77,52 @@ class Filtered(Base):
|
|
|
68
77
|
self.logger.warning(msg)
|
|
69
78
|
|
|
70
79
|
@property
|
|
71
|
-
def applied(self):
|
|
80
|
+
def applied(self) -> list:
|
|
72
81
|
return self._applied
|
|
73
82
|
|
|
74
83
|
@applied.setter
|
|
75
|
-
def applied(
|
|
84
|
+
def applied(
|
|
85
|
+
self,
|
|
86
|
+
applied: Union[list, str, None, int, tuple, np.ndarray, bool],
|
|
87
|
+
) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Sets the value of the booleans for whether each filter has been applied or not
|
|
90
|
+
|
|
91
|
+
:type applied: Union[list, str, None, int, tuple]
|
|
92
|
+
:param applied: The value to set self._applied.
|
|
93
|
+
|
|
94
|
+
Notes:
|
|
95
|
+
self._applied is a list, but we allow this to be assigned by single values as well,
|
|
96
|
+
such as None, True, 0. Supporting these other values makes the logic a little bit involved.
|
|
97
|
+
If a null value is received, the filters are assumed to be applied.
|
|
98
|
+
If a simple value, such as True, None, 0, etc. is not received, the input argument
|
|
99
|
+
applied (which is iterable) is first converted to `applied_list`.
|
|
100
|
+
The values in `applied_list` are then mapped to booleans.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
# Handle cases where we did not pass an iterable
|
|
76
105
|
if not hasattr(applied, "__iter__"):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
self._applied = [self._applied_values_map[applied], ]
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# the returned type from a hdf5 dataset is a numpy array.
|
|
110
|
+
if isinstance(applied, np.ndarray):
|
|
111
|
+
applied = applied.tolist()
|
|
83
112
|
|
|
84
113
|
#sets an empty list to one default value
|
|
85
114
|
if isinstance(applied, list) and len(applied) == 0:
|
|
86
|
-
self.
|
|
115
|
+
self._applied = [True]
|
|
87
116
|
return
|
|
88
117
|
|
|
118
|
+
# Handle string case
|
|
89
119
|
if isinstance(applied, str):
|
|
120
|
+
# Handle simple strings
|
|
121
|
+
if applied in self._applied_values_map.keys():
|
|
122
|
+
self._applied = [self._applied_values_map[applied], ]
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
# Handle string-lists (e.g. from json)
|
|
90
126
|
if applied.find("[") >= 0:
|
|
91
127
|
applied = applied.replace("[", "").replace("]", "")
|
|
92
128
|
if applied.count(",") > 0:
|
|
@@ -97,44 +133,20 @@ class Filtered(Base):
|
|
|
97
133
|
applied_list = [ss.lower() for ss in applied.split()]
|
|
98
134
|
elif isinstance(applied, list):
|
|
99
135
|
applied_list = applied
|
|
100
|
-
|
|
101
|
-
for i, elt in enumerate(applied_list):
|
|
102
|
-
if elt in ["0", "1",]:
|
|
103
|
-
applied_list[i] = int(applied_list[i])
|
|
104
|
-
# set integers to bools [0,1]--> [False, True]
|
|
105
|
-
for i, elt in enumerate(applied_list):
|
|
106
|
-
if elt in [0, 1,]:
|
|
107
|
-
applied_list[i] = bool(applied_list[i])
|
|
108
|
-
elif isinstance(applied, bool):
|
|
109
|
-
applied_list = [applied]
|
|
110
|
-
# the returned type from a hdf5 dataset is a numpy array.
|
|
111
|
-
elif isinstance(applied, np.ndarray):
|
|
136
|
+
elif isinstance(applied, tuple):
|
|
112
137
|
applied_list = list(applied)
|
|
113
|
-
if applied_list == []:
|
|
114
|
-
applied_list = [True]
|
|
115
138
|
else:
|
|
116
|
-
msg = "applied
|
|
117
|
-
self.logger.error(msg
|
|
118
|
-
raise MTSchemaError(msg
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
elif app_bool.lower() in ["true", "1"]:
|
|
128
|
-
bool_list.append(True)
|
|
129
|
-
else:
|
|
130
|
-
msg = "Filter.applied must be [ True | False ], not {0}"
|
|
131
|
-
self.logger.error(msg.format(app_bool))
|
|
132
|
-
raise MTSchemaError(msg.format(app_bool))
|
|
133
|
-
elif isinstance(app_bool, (bool, np.bool_)):
|
|
134
|
-
bool_list.append(bool(app_bool))
|
|
135
|
-
else:
|
|
136
|
-
msg = "Filter.applied must be [True | False], not {0}"
|
|
137
|
-
self.logger.error(msg.format(app_bool))
|
|
139
|
+
msg = f"Input applied cannot be of type {type(applied)}"
|
|
140
|
+
self.logger.error(msg)
|
|
141
|
+
raise MTSchemaError(msg)
|
|
142
|
+
|
|
143
|
+
# Now we have a simple list -- map to bools
|
|
144
|
+
try:
|
|
145
|
+
bool_list = [self._applied_values_map[x] for x in applied_list]
|
|
146
|
+
except KeyError:
|
|
147
|
+
msg = f"A key in {applied_list} is not mapped to a boolean"
|
|
148
|
+
msg += "\n fix this by adding to _applied_values_map"
|
|
149
|
+
self.logger.error(msg)
|
|
138
150
|
self._applied = bool_list
|
|
139
151
|
|
|
140
152
|
# check for consistency
|
|
@@ -146,36 +158,82 @@ class Filtered(Base):
|
|
|
146
158
|
self.logger.warning(msg)
|
|
147
159
|
|
|
148
160
|
|
|
149
|
-
def _check_consistency(self):
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return True
|
|
165
|
-
elif len(self._applied) > 1:
|
|
166
|
-
if len(self._applied) != len(self._name):
|
|
167
|
-
self.logger.warning(
|
|
168
|
-
"Applied and filter names "
|
|
169
|
-
+ "should be the same length. "
|
|
170
|
-
+ "Appied={0}, names={1}".format(
|
|
171
|
-
len(self._applied), len(self._name)
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
return False
|
|
175
|
-
else:
|
|
176
|
-
return True
|
|
177
|
-
elif self._name == [] and len(self._applied) > 0:
|
|
161
|
+
def _check_consistency(self) -> bool:
|
|
162
|
+
"""
|
|
163
|
+
Logic to look for inconstencies in the configuration of the filter names and applied values.
|
|
164
|
+
|
|
165
|
+
In general, list of filter names should be same length as list of applied booleans.
|
|
166
|
+
|
|
167
|
+
Cases:
|
|
168
|
+
The filter has no name -- this could happen on intialization.
|
|
169
|
+
|
|
170
|
+
:return: bool
|
|
171
|
+
True if OK, False if not.
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
# This inconsistency is ok -- the filter may not have been assigned a name yet
|
|
175
|
+
if self._name == [] and len(self._applied) > 0:
|
|
178
176
|
self.logger.debug("Name probably not yet initialized -- skipping consitency check")
|
|
179
177
|
return True
|
|
178
|
+
|
|
179
|
+
# Otherwise self._name != []
|
|
180
|
+
|
|
181
|
+
# Applied not assigned - this is not OK
|
|
182
|
+
if self._applied is None:
|
|
183
|
+
self.logger.warning("Need to input filter.applied")
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
# Name and applied have same length, 1. This is OK
|
|
187
|
+
if len(self._name) == 1:
|
|
188
|
+
if len(self._applied) == 1:
|
|
189
|
+
return True
|
|
190
|
+
|
|
191
|
+
# Multiple filter names (name not of length 0 or 1)
|
|
192
|
+
if len(self._name) > 1:
|
|
193
|
+
# If only one applied boolean, we allow it.
|
|
194
|
+
# TODO: consider being less tolerant here
|
|
195
|
+
if len(self._applied) == 1:
|
|
196
|
+
msg = f"Assuming all filters have been applied as {self._applied[0]}"
|
|
197
|
+
self.logger.debug(msg)
|
|
198
|
+
self._applied = len(self.name) * [self._applied[0],]
|
|
199
|
+
msg = f"Explicitly set filter applied state to {self._applied[0]}"
|
|
200
|
+
self.logger.debug(msg)
|
|
201
|
+
return True
|
|
202
|
+
elif len(self._applied) > 1:
|
|
203
|
+
# need to check the lists are really the same length
|
|
204
|
+
if len(self._applied) != len(self._name):
|
|
205
|
+
msg = "Applied and filter names should be the same length. "
|
|
206
|
+
msg += f"Appied={len(self._applied)}, names={len(self._name)}"
|
|
207
|
+
self.logger.warning(msg)
|
|
208
|
+
return False
|
|
209
|
+
else:
|
|
210
|
+
return True
|
|
180
211
|
else:
|
|
212
|
+
# Some unknown configuration we have not yet encountered
|
|
213
|
+
msg = "Filter consistency check failed for an unknown reason"
|
|
214
|
+
self.logger.warning(msg)
|
|
181
215
|
return False
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _applied_values_map(
|
|
219
|
+
treat_null_values_as: Optional[bool] = True
|
|
220
|
+
) -> dict:
|
|
221
|
+
"""
|
|
222
|
+
helper function to simplify logic in applied setter.
|
|
223
|
+
|
|
224
|
+
Notes:
|
|
225
|
+
The logic in the setter was getting quite complicated handling many types.
|
|
226
|
+
A reasonable solution seemed to be to map each of the allowed values to a bool
|
|
227
|
+
via dict and then use this dict when setting applied values.
|
|
228
|
+
|
|
229
|
+
:return: dict
|
|
230
|
+
Mapping of all tolerated single-values for setting applied booleans
|
|
231
|
+
"""
|
|
232
|
+
null_values = [None, "none", "None", "NONE", "null"]
|
|
233
|
+
null_values_map = {x: treat_null_values_as for x in null_values}
|
|
234
|
+
true_values = [True, 1, "1", "True", "true"]
|
|
235
|
+
true_values_map = {x:True for x in true_values}
|
|
236
|
+
false_values = [False, 0, "0", "False", "false"]
|
|
237
|
+
false_values_map = {x:False for x in false_values}
|
|
238
|
+
values_map = {**null_values_map, **true_values_map, **false_values_map}
|
|
239
|
+
return values_map
|
|
@@ -30,7 +30,6 @@ attr_dict.add_dict(
|
|
|
30
30
|
# =============================================================================
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
|
|
34
33
|
class FrequencyResponseTableFilter(FilterBase):
|
|
35
34
|
"""
|
|
36
35
|
Phases should be in radians.
|
|
@@ -130,7 +129,6 @@ class FrequencyResponseTableFilter(FilterBase):
|
|
|
130
129
|
self._empirical_phases = np.array(value, dtype=float)
|
|
131
130
|
|
|
132
131
|
if self._empirical_phases.size > 0:
|
|
133
|
-
|
|
134
132
|
if self._empirical_phases.mean() > 1000 * np.pi / 2:
|
|
135
133
|
self.logger.warning(
|
|
136
134
|
"Phases appear to be in milli radians attempting to convert to radians"
|
|
@@ -216,12 +214,17 @@ class FrequencyResponseTableFilter(FilterBase):
|
|
|
216
214
|
:rtype: np.ndarray
|
|
217
215
|
|
|
218
216
|
"""
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
if np.min(frequencies) < self.min_frequency:
|
|
218
|
+
# if there is a dc component skip it.
|
|
219
|
+
if np.min(frequencies) != 0:
|
|
220
|
+
self.logger.warning(
|
|
221
|
+
f"Extrapolating frequencies smaller ({np.min(frequencies)} Hz) "
|
|
222
|
+
f"than table frequencies ({self.min_frequency} Hz)."
|
|
223
|
+
)
|
|
224
|
+
if np.max(frequencies) > self.max_frequency:
|
|
223
225
|
self.logger.warning(
|
|
224
|
-
"Extrapolating
|
|
226
|
+
f"Extrapolating frequencies larger ({np.max(frequencies)} Hz) "
|
|
227
|
+
f"than table frequencies ({self.max_frequency} Hz)."
|
|
225
228
|
)
|
|
226
229
|
|
|
227
230
|
phase_response = interp1d(
|
|
@@ -59,6 +59,8 @@ attr_dict.add_dict(get_schema("copyright", SCHEMA_FN_PATHS), None)
|
|
|
59
59
|
attr_dict["release_license"]["required"] = False
|
|
60
60
|
attr_dict.add_dict(get_schema("citation", SCHEMA_FN_PATHS), None, keys=["doi"])
|
|
61
61
|
attr_dict["doi"]["required"] = False
|
|
62
|
+
|
|
63
|
+
|
|
62
64
|
# =============================================================================
|
|
63
65
|
class Station(Base):
|
|
64
66
|
__doc__ = write_lines(attr_dict)
|
|
@@ -316,3 +318,32 @@ class Station(Base):
|
|
|
316
318
|
else:
|
|
317
319
|
if self.time_period.end < max(end):
|
|
318
320
|
self.time_period.end = max(end)
|
|
321
|
+
|
|
322
|
+
def sort_runs_by_time(self, inplace=True, ascending=True):
|
|
323
|
+
"""
|
|
324
|
+
return a list of runs sorted by start time in the order of ascending or
|
|
325
|
+
descending.
|
|
326
|
+
|
|
327
|
+
:param ascending: DESCRIPTION, defaults to True
|
|
328
|
+
:type ascending: TYPE, optional
|
|
329
|
+
:return: DESCRIPTION
|
|
330
|
+
:rtype: TYPE
|
|
331
|
+
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
run_ids = []
|
|
335
|
+
run_starts = []
|
|
336
|
+
for run_key, run_obj in self.runs.items():
|
|
337
|
+
run_ids.append(run_key)
|
|
338
|
+
run_starts.append(run_obj.time_period.start.split("+")[0])
|
|
339
|
+
|
|
340
|
+
index = np.argsort(np.array(run_starts, dtype=np.datetime64))
|
|
341
|
+
|
|
342
|
+
new_runs = ListDict()
|
|
343
|
+
for ii in index:
|
|
344
|
+
new_runs[run_ids[ii]] = self.runs[run_ids[ii]]
|
|
345
|
+
|
|
346
|
+
if inplace:
|
|
347
|
+
self.runs = new_runs
|
|
348
|
+
else:
|
|
349
|
+
return new_runs
|
|
@@ -8,6 +8,8 @@ Created on Fri Feb 19 16:14:41 2021
|
|
|
8
8
|
:license: MIT
|
|
9
9
|
|
|
10
10
|
"""
|
|
11
|
+
import copy
|
|
12
|
+
|
|
11
13
|
# =============================================================================
|
|
12
14
|
# Imports
|
|
13
15
|
# =============================================================================
|
|
@@ -24,6 +26,7 @@ from mt_metadata.timeseries.stationxml.utils import BaseTranslator
|
|
|
24
26
|
from mt_metadata.utils.units import get_unit_object
|
|
25
27
|
|
|
26
28
|
from obspy.core import inventory
|
|
29
|
+
from obspy import UTCDateTime
|
|
27
30
|
|
|
28
31
|
# =============================================================================
|
|
29
32
|
|
|
@@ -33,6 +36,15 @@ class XMLChannelMTChannel(BaseTranslator):
|
|
|
33
36
|
translate back and forth between StationXML Channel and MT Channel
|
|
34
37
|
"""
|
|
35
38
|
|
|
39
|
+
understood_sensor_types = [
|
|
40
|
+
"logger",
|
|
41
|
+
"magnetometer",
|
|
42
|
+
"induction coil",
|
|
43
|
+
"coil",
|
|
44
|
+
"dipole",
|
|
45
|
+
"electrode"
|
|
46
|
+
]
|
|
47
|
+
|
|
36
48
|
def __init__(self):
|
|
37
49
|
super().__init__()
|
|
38
50
|
|
|
@@ -109,7 +121,8 @@ class XMLChannelMTChannel(BaseTranslator):
|
|
|
109
121
|
# fill channel filters
|
|
110
122
|
mt_channel.filter.name = list(mt_filters.keys())
|
|
111
123
|
mt_channel.filter.applied = [True] * len(list(mt_filters.keys()))
|
|
112
|
-
|
|
124
|
+
if UTCDateTime(mt_channel.time_period.end) < UTCDateTime(mt_channel.time_period.start):
|
|
125
|
+
mt_channel.time_period.end = '2200-01-01T00:00:00+00:00'
|
|
113
126
|
return mt_channel, mt_filters
|
|
114
127
|
|
|
115
128
|
def mt_to_xml(self, mt_channel, filters_dict, hard_code=True):
|
|
@@ -217,6 +230,8 @@ class XMLChannelMTChannel(BaseTranslator):
|
|
|
217
230
|
:rtype: TYPE
|
|
218
231
|
|
|
219
232
|
"""
|
|
233
|
+
sensor.type = self._deduce_sensor_type(sensor)
|
|
234
|
+
|
|
220
235
|
if not sensor.type:
|
|
221
236
|
return mt_channel
|
|
222
237
|
|
|
@@ -566,3 +581,40 @@ class XMLChannelMTChannel(BaseTranslator):
|
|
|
566
581
|
xml_channel.calibration_units_description = unit_obj.name
|
|
567
582
|
|
|
568
583
|
return xml_channel
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _deduce_sensor_type(self, sensor):
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
:param sensor: Information about a sensor, usually extractes from FDSN XML
|
|
590
|
+
:type sensor: obspy.core.inventory.util.Equipment
|
|
591
|
+
|
|
592
|
+
:return:
|
|
593
|
+
"""
|
|
594
|
+
original_sensor_type = sensor.type
|
|
595
|
+
# set sensor_type to be a string if it is None
|
|
596
|
+
if original_sensor_type is None:
|
|
597
|
+
sensor_type = "" # make a string
|
|
598
|
+
msg = f"Sensor {sensor} does not have field type attr"
|
|
599
|
+
self.logger.debug(msg)
|
|
600
|
+
else:
|
|
601
|
+
sensor_type = copy.deepcopy(original_sensor_type)
|
|
602
|
+
|
|
603
|
+
if sensor_type.lower() in self.understood_sensor_types:
|
|
604
|
+
return sensor_type
|
|
605
|
+
else:
|
|
606
|
+
self.logger.warning(f" sensor {sensor} type {sensor.type} not in {self.understood_sensor_types}")
|
|
607
|
+
|
|
608
|
+
# Try handling Bartington FGM at Earthscope ... this is a place holder for handling non-standard cases
|
|
609
|
+
if sensor.description == "Bartington 3-Axis Fluxgate Sensor":
|
|
610
|
+
sensor_type = "magnetometer"
|
|
611
|
+
elif sensor_type.lower() == "bartington":
|
|
612
|
+
sensor_type = "magnetometer"
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# reset sensor_type to None it it was not handled
|
|
616
|
+
if not sensor_type:
|
|
617
|
+
sensor_type = original_sensor_type
|
|
618
|
+
self.logger.error("sensor type could not be resolved")
|
|
619
|
+
|
|
620
|
+
return sensor_type
|
|
@@ -174,6 +174,7 @@ class XMLInventoryMTExperiment:
|
|
|
174
174
|
xml_station.site.country = ",".join(
|
|
175
175
|
[str(country) for country in mt_survey.country]
|
|
176
176
|
)
|
|
177
|
+
# need to sort the runs by time
|
|
177
178
|
for mt_run in mt_station.runs:
|
|
178
179
|
xml_station = self.add_run(
|
|
179
180
|
xml_station, mt_run, mt_survey.filters
|
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
# Define allowed sets of channel labellings
|
|
2
|
+
STANDARD_INPUT_CHANNELS = [
|
|
3
|
+
"hx",
|
|
4
|
+
"hy",
|
|
5
|
+
]
|
|
6
|
+
STANDARD_OUTPUT_CHANNELS = [
|
|
7
|
+
"ex",
|
|
8
|
+
"ey",
|
|
9
|
+
"hz",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
CHANNEL_MAPS = {
|
|
13
|
+
"default": {"hx": "hx", "hy": "hy", "hz": "hz", "ex": "ex", "ey": "ey"},
|
|
14
|
+
"lemi12": {"hx": "bx", "hy": "by", "hz": "bz", "ex": "e1", "ey": "e2"},
|
|
15
|
+
"lemi34": {"hx": "bx", "hy": "by", "hz": "bz", "ex": "e3", "ey": "e4"},
|
|
16
|
+
"phoenix123": {"hx": "h1", "hy": "h2", "hz": "h3", "ex": "e1", "ey": "e2"},
|
|
17
|
+
"musgraves": {"hx": "bx", "hy": "by", "hz": "bz", "ex": "ex", "ey": "ey"},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_allowed_channel_names(standard_names):
|
|
22
|
+
"""
|
|
23
|
+
:param standard_names: one of STANDARD_INPUT_NAMES, or STANDARD_OUTPUT_NAMES
|
|
24
|
+
:type standard_names: list
|
|
25
|
+
:return: allowed_names: list of channel names that are supported
|
|
26
|
+
:rtype: list
|
|
27
|
+
"""
|
|
28
|
+
allowed_names = []
|
|
29
|
+
for ch in standard_names:
|
|
30
|
+
for _, channel_map in CHANNEL_MAPS.items():
|
|
31
|
+
allowed_names.append(channel_map[ch])
|
|
32
|
+
allowed_names = list(set(allowed_names))
|
|
33
|
+
return allowed_names
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
ALLOWED_INPUT_CHANNELS = get_allowed_channel_names(STANDARD_INPUT_CHANNELS)
|
|
37
|
+
ALLOWED_OUTPUT_CHANNELS = get_allowed_channel_names(STANDARD_OUTPUT_CHANNELS)
|
|
38
|
+
|
|
1
39
|
from .core import TF
|
|
2
40
|
|
|
3
41
|
__all__ = ["TF"]
|