mt-metadata 0.3.4__py2.py3-none-any.whl → 0.3.6__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/metadata.py +8 -8
- mt_metadata/timeseries/filters/frequency_response_table_filter.py +10 -7
- mt_metadata/timeseries/stationxml/xml_channel_mt_channel.py +53 -1
- mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py +4 -1
- mt_metadata/timeseries/tools/from_many_mt_files.py +15 -3
- mt_metadata/transfer_functions/core.py +96 -71
- mt_metadata/transfer_functions/io/edi/edi.py +7 -7
- mt_metadata/transfer_functions/io/emtfxml/emtfxml.py +4 -4
- mt_metadata/transfer_functions/processing/aurora/__init__.py +0 -1
- mt_metadata/transfer_functions/processing/aurora/band.py +23 -11
- mt_metadata/transfer_functions/processing/aurora/channel_nomenclature.py +4 -0
- mt_metadata/transfer_functions/processing/fourier_coefficients/standards/decimation.json +1 -1
- {mt_metadata-0.3.4.dist-info → mt_metadata-0.3.6.dist-info}/METADATA +3 -3
- {mt_metadata-0.3.4.dist-info → mt_metadata-0.3.6.dist-info}/RECORD +19 -20
- {mt_metadata-0.3.4.dist-info → mt_metadata-0.3.6.dist-info}/WHEEL +1 -1
- mt_metadata/timeseries/filters/channel_response_filter.py +0 -476
- {mt_metadata-0.3.4.dist-info → mt_metadata-0.3.6.dist-info}/AUTHORS.rst +0 -0
- {mt_metadata-0.3.4.dist-info → mt_metadata-0.3.6.dist-info}/LICENSE +0 -0
- {mt_metadata-0.3.4.dist-info → mt_metadata-0.3.6.dist-info}/top_level.txt +0 -0
|
@@ -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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|