mt-metadata 0.3.4__py2.py3-none-any.whl → 0.3.5__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 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.4"
42
+ __version__ = "0.3.5"
43
43
 
44
44
  # =============================================================================
45
45
  # Imports
@@ -300,7 +300,7 @@ class Base:
300
300
  self.logger.exception(error)
301
301
  raise MTSchemaError(error)
302
302
 
303
- def _validate_option(self, name, option_list):
303
+ def _validate_option(self, name, value, option_list):
304
304
  """
305
305
  validate the given attribute name agains possible options and check
306
306
  for aliases
@@ -315,21 +315,21 @@ class Base:
315
315
  :rtype: TYPE
316
316
 
317
317
  """
318
- if name is None:
318
+ if value is None:
319
319
  return True, False, None
320
320
  options = [ss.lower() for ss in option_list]
321
321
  other_possible = False
322
322
  if "other" in options:
323
323
  other_possible = True
324
- if name.lower() in options:
324
+ if value.lower() in options:
325
325
  return True, other_possible, None
326
- elif name.lower() not in options and other_possible:
326
+ elif value.lower() not in options and other_possible:
327
327
  msg = (
328
- "{0} not found in options list {1}, but other options"
329
- + " are allowed. Allowing {2} to be set to {0}."
328
+ f"Value '{value}' not found for metadata field '{name}' in options list {option_list}, but other options"
329
+ + f" are allowed. Allowing {option_list} to be set to {value}."
330
330
  )
331
331
  return True, other_possible, msg
332
- return False, other_possible, "{0} not found in options list {1}"
332
+ return False, other_possible, f"Value '{value}' for metadata field '{name}' not found in options list {option_list}"
333
333
 
334
334
  def __setattr__(self, name, value):
335
335
  """
@@ -399,7 +399,7 @@ class Base:
399
399
  # check options
400
400
  if v_dict["style"] == "controlled vocabulary":
401
401
  options = v_dict["options"]
402
- accept, other, msg = self._validate_option(value, options)
402
+ accept, other, msg = self._validate_option(name, value, options)
403
403
  if not accept:
404
404
  self.logger.error(msg.format(value, options))
405
405
  raise MTSchemaError(msg.format(value, options))
@@ -170,7 +170,10 @@ class XMLInventoryMTExperiment:
170
170
  xml_network = self.network_translator.mt_to_xml(mt_survey)
171
171
  for mt_station in mt_survey.stations:
172
172
  xml_station = self.station_translator.mt_to_xml(mt_station)
173
- xml_station.site.country = mt_survey.country
173
+ if mt_survey.country is not None:
174
+ xml_station.site.country = ",".join(
175
+ [str(country) for country in mt_survey.country]
176
+ )
174
177
  for mt_run in mt_station.runs:
175
178
  xml_station = self.add_run(
176
179
  xml_station, mt_run, mt_survey.filters
@@ -12,7 +12,14 @@ from pathlib import Path
12
12
  import pandas as pd
13
13
  from xml.etree import cElementTree as et
14
14
 
15
- from mt_metadata.timeseries import Experiment, Survey, Station, Run, Electric, Magnetic
15
+ from mt_metadata.timeseries import (
16
+ Experiment,
17
+ Survey,
18
+ Station,
19
+ Run,
20
+ Electric,
21
+ Magnetic,
22
+ )
16
23
 
17
24
  from mt_metadata.timeseries.filters import (
18
25
  PoleZeroFilter,
@@ -22,6 +29,7 @@ from mt_metadata.timeseries.filters import (
22
29
  )
23
30
  from mt_metadata.timeseries.stationxml import XMLInventoryMTExperiment
24
31
 
32
+
25
33
  # =============================================================================
26
34
  # Useful Class
27
35
  # =============================================================================
@@ -182,7 +190,7 @@ class MT2StationXML(XMLInventoryMTExperiment):
182
190
  channels_list = []
183
191
  for ch in order:
184
192
  for fn in rdf:
185
- if ch in fn.name.lower():
193
+ if ch in fn.name[len(station) :].lower():
186
194
  channels_list.append(fn)
187
195
  break
188
196
 
@@ -196,7 +204,11 @@ class MT2StationXML(XMLInventoryMTExperiment):
196
204
  :rtype: TYPE
197
205
 
198
206
  """
199
- fn_dict = {"survey": self.survey, "filters": self.filters, "stations": []}
207
+ fn_dict = {
208
+ "survey": self.survey,
209
+ "filters": self.filters,
210
+ "stations": [],
211
+ }
200
212
  if stations in [None, []]:
201
213
  station_iterator = self.stations
202
214
  else:
@@ -24,6 +24,7 @@ class Band(Base):
24
24
  def __init__(self, **kwargs):
25
25
 
26
26
  super().__init__(attr_dict=attr_dict, **kwargs)
27
+ self._name = None
27
28
 
28
29
  @property
29
30
  def lower_bound(self):
@@ -41,6 +42,21 @@ class Band(Base):
41
42
  def upper_closed(self):
42
43
  return self.to_interval().closed_right
43
44
 
45
+ @property
46
+ def name(self):
47
+ """
48
+ :return: The name of the frequency band (currently defaults to fstring with 6 decimal places.
49
+ :rtype: str
50
+ """
51
+ if self._name is None:
52
+ self._name = f"{self.center_frequency:.6f}"
53
+ return self._name
54
+
55
+
56
+ @name.setter
57
+ def name(self, value):
58
+ self._name = value
59
+
44
60
  def _indices_from_frequencies(self, frequencies):
45
61
  """
46
62
 
@@ -4,7 +4,7 @@
4
4
  "required": true,
5
5
  "style": "number",
6
6
  "units": null,
7
- "description": "Decimation level, must be a positive integer starting at 0.",
7
+ "description": "Decimation level, must be a non-negative integer starting at 0",
8
8
  "options": [],
9
9
  "alias": [],
10
10
  "example": "1",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mt-metadata
3
- Version: 0.3.4
3
+ Version: 0.3.5
4
4
  Summary: Metadata for magnetotelluric data
5
5
  Home-page: https://github.com/kujaku11/mt_metadata
6
6
  Author: Jared Peacock
@@ -28,7 +28,7 @@ Requires-Dist: matplotlib
28
28
  Requires-Dist: xarray
29
29
  Requires-Dist: loguru
30
30
 
31
- # mt_metadata version 0.3.4
31
+ # mt_metadata version 0.3.5
32
32
  Standard MT metadata
33
33
 
34
34
  [![PyPi version](https://img.shields.io/pypi/v/mt_metadata.svg)](https://pypi.python.org/pypi/mt-metadata)
@@ -58,7 +58,7 @@ MT Metadata is a project led by [IRIS-PASSCAL MT Software working group](https:/
58
58
 
59
59
  Most people will be using the transfer functions, but a lot of that metadata comes from the time series metadata. This module supports both and has tried to make them more or less seamless to reduce complication.
60
60
 
61
- * **Version**: 0.3.4
61
+ * **Version**: 0.3.5
62
62
  * **Free software**: MIT license
63
63
  * **Documentation**: https://mt-metadata.readthedocs.io.
64
64
  * **Examples**: Click the `Binder` badge above and Jupyter Notebook examples are in **mt_metadata/examples/notebooks** and **docs/source/notebooks**
@@ -1,7 +1,7 @@
1
- mt_metadata/__init__.py,sha256=yDD8tnPwN4hjj0ZOfrtCtW41zqGbBWK2pjAbrleQ_yQ,5588
1
+ mt_metadata/__init__.py,sha256=CmllQ7LH3dBndOB3nUPeBxK2sQMDqPyhjhdr2byrOJY,5588
2
2
  mt_metadata/base/__init__.py,sha256=bylCGBoJkeytxeQgMnuqivqFAvbyNE5htvP-3yu1GEY,184
3
3
  mt_metadata/base/helpers.py,sha256=oS4s7OysTQZ5J8VVPUJYYuVkqviSIlOwnNFKZe5FWIc,19721
4
- mt_metadata/base/metadata.py,sha256=xl_GwpCfB7qFXR05VjFsCLz5dE0qAoYKl9GuSmvtGVw,27012
4
+ mt_metadata/base/metadata.py,sha256=obOFdU6FPf2o-SbdxOxZ60LvzbI6Cp-bbRXw8KdkBao,27145
5
5
  mt_metadata/base/schema.py,sha256=MvZBvy2elYia5VLRXqOV9x0GIklEWLM4qpTdqo820P4,12842
6
6
  mt_metadata/data/__init__.py,sha256=PECwullCdCwKGpd1fkQc1jL8CboaSQfy_Cdm-obl-Y8,217
7
7
  mt_metadata/data/mt_xml/__init__.py,sha256=fsW8De_Dhrjp55R12F2RH5M6QHoPO3Qf51yR3AFWztg,26
@@ -75,7 +75,6 @@ mt_metadata/timeseries/time_period.py,sha256=eUFucwMY8Gsoi375pwuPlrg-HCBENU6UJ-s
75
75
  mt_metadata/timeseries/timing_system.py,sha256=3Uvu_Ihk1b1SyPFMKHWCDe1_BUfiVYphpatRP3wwS-s,839
76
76
  mt_metadata/timeseries/filters/__init__.py,sha256=9FaNM3OVQ1dMiAn5JyfSK8QtICqGXH6TaKhPYjbpgj8,474
77
77
  mt_metadata/timeseries/filters/channel_response.py,sha256=yxPaukC2yyEJFayniSyH2IPNGgE4oDIFMmTp4o3jJ1I,17297
78
- mt_metadata/timeseries/filters/channel_response_filter.py,sha256=8qcQF4UUd_W1FVKQcq_4rII1ZR-sImYhwo6ALhtSoWs,15626
79
78
  mt_metadata/timeseries/filters/coefficient_filter.py,sha256=HryPmsFGr-SEkox85DBYc3q8A3M4C_Hmjc-CNvm6e-w,3322
80
79
  mt_metadata/timeseries/filters/filter_base.py,sha256=IiPhOkVvJUbdboFxWWeKyj1iKdW1HE061nvBdw6G8TM,12885
81
80
  mt_metadata/timeseries/filters/filtered.py,sha256=0o3aFYLhtSeBgpgi5ndSsKGbE5tBJhY_wEq5oQblhOg,6979
@@ -126,11 +125,11 @@ mt_metadata/timeseries/stationxml/fdsn_tools.py,sha256=6H1hZCxf5-skNSjPazMS_wKu4
126
125
  mt_metadata/timeseries/stationxml/utils.py,sha256=16617e6snyrsNjletGbw-gLYQ2vt-7VfYPokz6dakts,7257
127
126
  mt_metadata/timeseries/stationxml/xml_channel_mt_channel.py,sha256=Pkyg3plhwOp02Yl_ymbsWU0eydpE-lA8OqAo-Yoo1HM,20289
128
127
  mt_metadata/timeseries/stationxml/xml_equipment_mt_run.py,sha256=yRPk6lhnzkpgARe6lQkU_-vZrTDDmIIeRCTI9Wig9XY,5151
129
- mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py,sha256=XUjXQBJg_94ZP-FDyOHPVJWeNPV2oR8oh20hsaJDZeE,13893
128
+ mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py,sha256=cmDJybDh6-JgzEHQM0CuLeDTIKoNnIyn9PnYrzV1RZs,14036
130
129
  mt_metadata/timeseries/stationxml/xml_network_mt_survey.py,sha256=RciEmnFGb8kMf1mA1lLn9d0R7WiOW2BeoV1bDB-eJuU,7124
131
130
  mt_metadata/timeseries/stationxml/xml_station_mt_station.py,sha256=pelvkiQios4gz8gHebWY1MPSsfBhfTz6uTgC92Yz9-4,11112
132
131
  mt_metadata/timeseries/tools/__init__.py,sha256=loPgjYnajbOX2rQTlLBh79cG2eaUNpI3KaCjp7SB4ik,78
133
- mt_metadata/timeseries/tools/from_many_mt_files.py,sha256=qx_sRPlPe5-VkaqNpC_lHtmgN4FI6Hmd2F1WANPZBC8,14300
132
+ mt_metadata/timeseries/tools/from_many_mt_files.py,sha256=rtx5NAPBUmOgrMXUT-YJxznqfI1qdRkS4B2SWjrU_1c,14405
134
133
  mt_metadata/transfer_functions/__init__.py,sha256=wpGghfoqFj4nuFOoHeR8PFGQGMzkWvTb3T_KfmMWswQ,42
135
134
  mt_metadata/transfer_functions/core.py,sha256=70hxEFszjZzed-OKFcmF-B_Tm_Zy7212dPJPCF0_lMA,79933
136
135
  mt_metadata/transfer_functions/io/__init__.py,sha256=8DKEQZrpF0RK_MTjR0_w0UQfgVf9VwuJrzy7eann1N8,215
@@ -277,7 +276,7 @@ mt_metadata/transfer_functions/io/zonge/metadata/standards/tx.json,sha256=W0ITo4
277
276
  mt_metadata/transfer_functions/io/zonge/metadata/standards/unit.json,sha256=A2KIoTXBANiqp8Mjxg3VDZbjNPgASnS7QVdK7KOJ0y8,953
278
277
  mt_metadata/transfer_functions/processing/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
279
278
  mt_metadata/transfer_functions/processing/aurora/__init__.py,sha256=zczI5zoF7ceLEdu0gkmsLbBYJ2rxuyWsYIjZtxwQP8g,668
280
- mt_metadata/transfer_functions/processing/aurora/band.py,sha256=YaVSXxjjlw0zJgZ-KlMadAFU4mKqYM_6urXU7OHjaPs,8100
279
+ mt_metadata/transfer_functions/processing/aurora/band.py,sha256=pBk8fYXhJ0S_lyWH3JQIbUr3ZeDKkU93pybSUy5jtbE,8509
281
280
  mt_metadata/transfer_functions/processing/aurora/channel.py,sha256=TZHfy-XgrijdNVd4Sjbq3tjORFsJNZIzwa4_J7tDOHM,773
282
281
  mt_metadata/transfer_functions/processing/aurora/channel_nomenclature.py,sha256=GD6teHAtBUKTkGGDWX2clZwk1Fac8ndCOZ3r8TJDGrY,4264
283
282
  mt_metadata/transfer_functions/processing/aurora/decimation.py,sha256=AF7vdU-Q7W4yfnJoPDbhDqgxJTtHiLCLEUDLBEzfmFM,785
@@ -309,7 +308,7 @@ mt_metadata/transfer_functions/processing/fourier_coefficients/decimation.py,sha
309
308
  mt_metadata/transfer_functions/processing/fourier_coefficients/fc.py,sha256=bN3S_0uoMqXDcJcw_7oYN3YQqsWITD2UX0aRcgdY7fw,10450
310
309
  mt_metadata/transfer_functions/processing/fourier_coefficients/fc_channel.py,sha256=HFFo2XoqaKJOae3mtRSMZ80_AZ6wDx0KKq3oBfIbJKg,926
311
310
  mt_metadata/transfer_functions/processing/fourier_coefficients/standards/__init__.py,sha256=Y3rdyXKOgxbSh9FQxQCCplsbqxwWmFIGm6yZG1cj0Uw,135
312
- mt_metadata/transfer_functions/processing/fourier_coefficients/standards/decimation.json,sha256=Ws10Rs8AL77Sm6KWlRDV5htYZtfm_VBCDj9uqV1uKdQ,3769
311
+ mt_metadata/transfer_functions/processing/fourier_coefficients/standards/decimation.json,sha256=aatnDixbOC_lPsBE26iXuGTxCnT6uBZAU_VX6iLosbg,3772
313
312
  mt_metadata/transfer_functions/processing/fourier_coefficients/standards/fc.json,sha256=ZiIvXRkYIU3tOcUKeIhBwZtDIdm4rlnG4zUMdiKzOT0,1523
314
313
  mt_metadata/transfer_functions/processing/fourier_coefficients/standards/fc_channel.json,sha256=pdR6r2BQTmrw8udtjaVz0Z9zmeX_Jd1uezuBQfKd7kY,1873
315
314
  mt_metadata/transfer_functions/tf/__init__.py,sha256=rJN-VGiGjCWBzFpruDOQitM8v-dfON9tKgZzX8zAhsI,3890
@@ -331,9 +330,9 @@ mt_metadata/utils/mttime.py,sha256=6XRIlY6eaSwLqNnRTeaDraQz4sk0PN27Z7z6BMeZkbk,1
331
330
  mt_metadata/utils/summarize.py,sha256=KisJt2PWz1-_FOBH8NQtidgxjdWPAbIDwPzEB197uhs,4109
332
331
  mt_metadata/utils/units.py,sha256=OdALLmytoPvjJ8rYf7QsGq1b8nrNt85A8wUhjqRyTOo,6405
333
332
  mt_metadata/utils/validators.py,sha256=vj55VvH11A0H9SeUcVy9lJCDKNzwCiMTSra-_Ws1Ojk,16264
334
- mt_metadata-0.3.4.dist-info/AUTHORS.rst,sha256=3RKy4std2XZQLNF6xYIiA8S5A0bBPqNO7ypJsuEhiN8,706
335
- mt_metadata-0.3.4.dist-info/LICENSE,sha256=P33RkFPriIBxsgZtVzSn9KxYa2K7Am42OwMV0h_m5e0,1080
336
- mt_metadata-0.3.4.dist-info/METADATA,sha256=CTPVq3wSMWfy548axtCT2ifis4Q1eXkEg3pJ2Hg8KMI,17286
337
- mt_metadata-0.3.4.dist-info/WHEEL,sha256=iYlv5fX357PQyRT2o6tw1bN-YcKFFHKqB_LwHO5wP-g,110
338
- mt_metadata-0.3.4.dist-info/top_level.txt,sha256=fxe_q_GEd9h6iR3050ZnrhSfxUSO5pR8u65souS4RWM,12
339
- mt_metadata-0.3.4.dist-info/RECORD,,
333
+ mt_metadata-0.3.5.dist-info/AUTHORS.rst,sha256=3RKy4std2XZQLNF6xYIiA8S5A0bBPqNO7ypJsuEhiN8,706
334
+ mt_metadata-0.3.5.dist-info/LICENSE,sha256=P33RkFPriIBxsgZtVzSn9KxYa2K7Am42OwMV0h_m5e0,1080
335
+ mt_metadata-0.3.5.dist-info/METADATA,sha256=Vz1gxXzTNbwIH3WLimJ5zd2jyQt5QBO5gZHDmWYOWAk,17286
336
+ mt_metadata-0.3.5.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
337
+ mt_metadata-0.3.5.dist-info/top_level.txt,sha256=fxe_q_GEd9h6iR3050ZnrhSfxUSO5pR8u65souS4RWM,12
338
+ mt_metadata-0.3.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,476 +0,0 @@
1
- """
2
- ==========================
3
- Channel Response Filter
4
- ==========================
5
-
6
- Combines all filters for a given channel into a total response that can be used in
7
- the frequency domain.
8
-
9
- .. note:: Time Delay filters should be applied in the time domain
10
- otherwise bad things can happen.
11
- """
12
- # =============================================================================
13
- # Imports
14
- # =============================================================================
15
- from copy import deepcopy
16
- import numpy as np
17
-
18
- from mt_metadata.base import Base, get_schema
19
- from mt_metadata.timeseries.filters.standards import SCHEMA_FN_PATHS
20
- from mt_metadata.timeseries.filters import (
21
- PoleZeroFilter,
22
- CoefficientFilter,
23
- TimeDelayFilter,
24
- FrequencyResponseTableFilter,
25
- FIRFilter,
26
- )
27
- from mt_metadata.utils.units import get_unit_object
28
- from mt_metadata.timeseries.filters.plotting_helpers import plot_response
29
- from obspy.core import inventory
30
-
31
- # =============================================================================
32
- attr_dict = get_schema("channel_response", SCHEMA_FN_PATHS)
33
- # =============================================================================
34
-
35
-
36
- class ChannelResponseFilter(Base):
37
- """
38
- This class holds a list of all the filters associated with a channel.
39
- It has methods for combining the responses of all the filters into a total
40
- response that we will apply to a data segment.
41
- """
42
-
43
- def __init__(self, **kwargs):
44
- self.filters_list = []
45
- self.frequencies = np.logspace(-4, 4, 100)
46
- self.normalization_frequency = None
47
-
48
- super().__init__(attr_dict=attr_dict)
49
- for k, v in kwargs.items():
50
- setattr(self, k, v)
51
-
52
- def __str__(self):
53
- lines = ["Filters Included:\n", "=" * 25, "\n"]
54
- for f in self.filters_list:
55
- lines.append(f.__str__())
56
- lines.append(f"\n{'-'*20}\n")
57
-
58
- return "".join(lines)
59
-
60
- def __repr__(self):
61
- return self.__str__()
62
-
63
- @property
64
- def filters_list(self):
65
- """filters list"""
66
- return self._filters_list
67
-
68
- @filters_list.setter
69
- def filters_list(self, filters_list):
70
- """set the filters list and validate the list"""
71
- self._filters_list = self._validate_filters_list(filters_list)
72
- self._check_consistency_of_units()
73
-
74
- @property
75
- def frequencies(self):
76
- """frequencies to estimate filters"""
77
- return self._frequencies
78
-
79
- @frequencies.setter
80
- def frequencies(self, value):
81
- """
82
- Set the frequencies, make sure the input is validated
83
-
84
- Linear frequencies
85
- :param value: Linear Frequencies
86
- :type value: iterable
87
-
88
- """
89
- if value is None:
90
- self._frequencies = None
91
-
92
- elif isinstance(value, (list, tuple, np.ndarray)):
93
- self._frequencies = np.array(value, dtype=float)
94
- else:
95
- msg = f"input values must be an list, tuple, or np.ndarray, not {type(value)}"
96
- self.logger.error(msg)
97
- raise TypeError(msg)
98
-
99
- @property
100
- def names(self):
101
- """names of the filters"""
102
- names = []
103
- if self.filters_list:
104
- names = [f.name for f in self.filters_list]
105
- return names
106
-
107
- def _validate_filters_list(self, filters_list):
108
- """
109
- make sure the filters list is valid
110
-
111
- :param filters_list: DESCRIPTION
112
- :type filters_list: TYPE
113
- :return: DESCRIPTION
114
- :rtype: TYPE
115
-
116
- """
117
- ACCEPTABLE_FILTERS = [
118
- PoleZeroFilter,
119
- CoefficientFilter,
120
- TimeDelayFilter,
121
- FrequencyResponseTableFilter,
122
- FIRFilter,
123
- ]
124
-
125
- def is_acceptable_filter(item):
126
- if isinstance(item, tuple(ACCEPTABLE_FILTERS)):
127
- return True
128
- else:
129
- return False
130
-
131
- if filters_list in [[], None]:
132
- return []
133
-
134
- if not isinstance(filters_list, list):
135
- msg = f"Input filters list must be a list not {type(filters_list)}"
136
- self.logger.error(msg)
137
- raise TypeError(msg)
138
-
139
- fails = []
140
- return_list = []
141
- for item in filters_list:
142
- if is_acceptable_filter(item):
143
- return_list.append(item)
144
- else:
145
- fails.append(
146
- f"Item is not an acceptable filter type, {type(item)}"
147
- )
148
-
149
- if fails:
150
- raise TypeError(", ".join(fails))
151
-
152
- return return_list
153
-
154
- @property
155
- def pass_band(self):
156
- """estimate pass band for all filters in frequency"""
157
- if self.frequencies is None:
158
- raise ValueError(
159
- "frequencies are None, must be input to calculate pass band"
160
- )
161
- pb = []
162
- for f in self.filters_list:
163
- if hasattr(f, "pass_band"):
164
- f_pb = f.pass_band(self.frequencies)
165
- if f_pb is None:
166
- continue
167
- pb.append((f_pb.min(), f_pb.max()))
168
-
169
- if pb != []:
170
- pb = np.array(pb)
171
- return np.array([pb[:, 0].max(), pb[:, 1].min()])
172
- return None
173
-
174
- @property
175
- def normalization_frequency(self):
176
- """get normalization frequency from ZPK or FAP filter"""
177
-
178
- if self._normalization_frequency == 0.0:
179
- if self.pass_band is not None:
180
- return np.round(10 ** np.mean(np.log10(self.pass_band)), 3)
181
-
182
- return self._normalization_frequency
183
-
184
- @normalization_frequency.setter
185
- def normalization_frequency(self, value):
186
- """Set normalization frequency if input"""
187
-
188
- self._normalization_frequency = value
189
-
190
- @property
191
- def non_delay_filters(self):
192
- """
193
-
194
- :return: all the non-time_delay filters as a list
195
-
196
- """
197
- non_delay_filters = [
198
- x for x in self.filters_list if x.type != "time delay"
199
- ]
200
- return non_delay_filters
201
-
202
- @property
203
- def delay_filters(self):
204
- """
205
-
206
- :return: all the time delay filters as a list
207
-
208
- """
209
- delay_filters = [x for x in self.filters_list if x.type == "time delay"]
210
- return delay_filters
211
-
212
- @property
213
- def total_delay(self):
214
- """
215
-
216
- :return: the total delay of all filters
217
-
218
- """
219
- delay_filters = self.delay_filters
220
- total_delay = 0.0
221
- for delay_filter in delay_filters:
222
- total_delay += delay_filter.delay
223
- return total_delay
224
-
225
- def complex_response(
226
- self,
227
- frequencies=None,
228
- include_delay=False,
229
- normalize=False,
230
- include_decimation=True,
231
- **kwargs,
232
- ):
233
- """
234
-
235
- :param frequencies: frequencies to compute complex response,
236
- defaults to None
237
- :type frequencies: np.ndarray, optional
238
- :param include_delay: include delay in complex response,
239
- defaults to False
240
- :type include_delay: bool, optional
241
- :param normalize: normalize the response to 1, defaults to False
242
- :type normalize: bool, optional
243
- :param include_decimation: Include decimation in response,
244
- defaults to True
245
- :type include_decimation: bool, optional
246
- :return: complex response along give frequency array
247
- :rtype: np.ndarray
248
-
249
- """
250
-
251
- if frequencies is not None:
252
- self.frequencies = frequencies
253
-
254
- if include_delay:
255
- filters_list = deepcopy(self.filters_list)
256
- else:
257
- filters_list = deepcopy(self.non_delay_filters)
258
-
259
- if not include_decimation:
260
- filters_list = deepcopy(
261
- [x for x in filters_list if not x.decimation_active]
262
- )
263
-
264
- if len(filters_list) == 0:
265
- # warn that there are no filters associated with channel?
266
- return np.ones(len(self.frequencies), dtype=complex)
267
-
268
- result = filters_list[0].complex_response(self.frequencies)
269
-
270
- for ff in filters_list[1:]:
271
- result *= ff.complex_response(self.frequencies)
272
-
273
- if normalize:
274
- result /= np.max(np.abs(result))
275
- return result
276
-
277
- def compute_instrument_sensitivity(
278
- self, normalization_frequency=None, sig_figs=6
279
- ):
280
- """
281
- Compute the StationXML instrument sensitivity for the given normalization frequency
282
-
283
- :param normalization_frequency: DESCRIPTION
284
- :type normalization_frequency: TYPE
285
- :return: DESCRIPTION
286
- :rtype: TYPE
287
-
288
- """
289
- if normalization_frequency is not None:
290
- self.normalization_frequency = normalization_frequency
291
- sensitivity = 1.0
292
- for mt_filter in self.filters_list:
293
- complex_response = mt_filter.complex_response(
294
- self.normalization_frequency
295
- )
296
- sensitivity *= complex_response.astype(complex)
297
- try:
298
- sensitivity = np.abs(sensitivity[0])
299
- except (IndexError, TypeError):
300
- sensitivity = np.abs(sensitivity)
301
-
302
- return round(
303
- sensitivity, sig_figs - int(np.floor(np.log10(abs(sensitivity))))
304
- )
305
-
306
- @property
307
- def units_in(self):
308
- """
309
- :return: the units of the channel
310
- """
311
- if self.filters_list is [] or len(self.filters_list) == 0:
312
- return None
313
-
314
- return self.filters_list[0].units_in
315
-
316
- @property
317
- def units_out(self):
318
- """
319
- :return: the units of the channel
320
- """
321
- if self.filters_list is [] or len(self.filters_list) == 0:
322
- return None
323
-
324
- return self.filters_list[-1].units_out
325
-
326
- def _check_consistency_of_units(self):
327
- """
328
- confirms that the input and output units of each filter state are consistent
329
- """
330
- if len(self._filters_list) > 1:
331
- previous_units = self._filters_list[0].units_out
332
- for mt_filter in self._filters_list[1:]:
333
- if mt_filter.units_in != previous_units:
334
- msg = (
335
- "Unit consistency is incorrect. "
336
- f"The input units for {mt_filter.name} should be "
337
- f"{previous_units} not {mt_filter.units_in}"
338
- )
339
- self.logger.error(msg)
340
- raise ValueError(msg)
341
- previous_units = mt_filter.units_out
342
-
343
- return True
344
-
345
- def to_obspy(self, sample_rate=1):
346
- """
347
- Output :class:`obspy.core.inventory.InstrumentSensitivity` object that
348
- can be used in a stationxml file.
349
-
350
- :param normalization_frequency: DESCRIPTION
351
- :type normalization_frequency: TYPE
352
- :return: DESCRIPTION
353
- :rtype: TYPE
354
-
355
- """
356
- total_sensitivity = self.compute_instrument_sensitivity()
357
-
358
- units_in_obj = get_unit_object(self.units_in)
359
- units_out_obj = get_unit_object(self.units_out)
360
-
361
- total_response = inventory.Response()
362
- total_response.instrument_sensitivity = inventory.InstrumentSensitivity(
363
- total_sensitivity,
364
- self.normalization_frequency,
365
- units_in_obj.abbreviation,
366
- units_out_obj.abbreviation,
367
- input_units_description=units_in_obj.name,
368
- output_units_description=units_out_obj.name,
369
- )
370
-
371
- for ii, f in enumerate(self.filters_list, 1):
372
- if f.type in ["coefficient"]:
373
- if f.units_out not in ["count"]:
374
- self.logger.debug(
375
- f"converting CoefficientFilter {f.name} to PZ"
376
- )
377
- pz = PoleZeroFilter()
378
- pz.gain = f.gain
379
- pz.units_in = f.units_in
380
- pz.units_out = f.units_out
381
- pz.comments = f.comments
382
- pz.name = f.name
383
- else:
384
- pz = f
385
-
386
- total_response.response_stages.append(
387
- pz.to_obspy(
388
- stage_number=ii,
389
- normalization_frequency=self.normalization_frequency,
390
- sample_rate=sample_rate,
391
- )
392
- )
393
- else:
394
- total_response.response_stages.append(
395
- f.to_obspy(
396
- stage_number=ii,
397
- normalization_frequency=self.normalization_frequency,
398
- sample_rate=sample_rate,
399
- )
400
- )
401
-
402
- return total_response
403
-
404
- def plot_response(
405
- self,
406
- frequencies=None,
407
- x_units="period",
408
- unwrap=True,
409
- pb_tol=1e-1,
410
- interpolation_method="slinear",
411
- include_delay=False,
412
- include_decimation=True,
413
- ):
414
- """
415
- Plot the response
416
-
417
- :param frequencies: frequencies to compute response, defaults to None
418
- :type frequencies: np.ndarray, optional
419
- :param x_units: [ period | frequency ], defaults to "period"
420
- :type x_units: string, optional
421
- :param unwrap: Unwrap phase, defaults to True
422
- :type unwrap: bool, optional
423
- :param pb_tol: pass band tolerance, defaults to 1e-1
424
- :type pb_tol: float, optional
425
- :param interpolation_method: Interpolation method see scipy.signal.interpolate
426
- [ slinear | nearest | cubic | quadratic | ], defaults to "slinear"
427
- :type interpolation_method: string, optional
428
- :param include_delay: include delays in response, defaults to False
429
- :type include_delay: bool, optional
430
- :param include_decimation: Include decimation in response,
431
- defaults to True
432
- :type include_decimation: bool, optional
433
-
434
- """
435
-
436
- if frequencies is not None:
437
- self.frequencies = frequencies
438
-
439
- # get only the filters desired
440
- if include_delay:
441
- filters_list = deepcopy(self.filters_list)
442
- else:
443
- filters_list = deepcopy(self.non_delay_filters)
444
-
445
- if not include_decimation:
446
- filters_list = deepcopy(
447
- [x for x in filters_list if not x.decimation_active]
448
- )
449
-
450
- cr_kwargs = {"interpolation_method": interpolation_method}
451
-
452
- # get response of individual filters
453
- cr_list = [
454
- f.complex_response(self.frequencies, **cr_kwargs)
455
- for f in filters_list
456
- ]
457
-
458
- # compute total response
459
- cr_kwargs["include_delay"] = include_delay
460
- cr_kwargs["include_decimation"] = include_decimation
461
- complex_response = self.complex_response(self.frequencies, **cr_kwargs)
462
-
463
- cr_list.append(complex_response)
464
- labels = [f.name for f in filters_list] + ["Total Response"]
465
-
466
- # plot with proper attributes.
467
- kwargs = {
468
- "title": f"Channel Response: [{', '.join([f.name for f in filters_list])}]",
469
- "unwrap": unwrap,
470
- "x_units": x_units,
471
- "pass_band": self.pass_band,
472
- "label": labels,
473
- "normalization_frequency": self.normalization_frequency,
474
- }
475
-
476
- plot_response(self.frequencies, cr_list, **kwargs)