mt-metadata 0.3.8__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 (97) 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/data/transfer_functions/test.edi +28 -28
  5. mt_metadata/features/__init__.py +14 -0
  6. mt_metadata/features/coherence.py +303 -0
  7. mt_metadata/features/cross_powers.py +29 -0
  8. mt_metadata/features/fc_coherence.py +81 -0
  9. mt_metadata/features/feature.py +72 -0
  10. mt_metadata/features/feature_decimation_channel.py +26 -0
  11. mt_metadata/features/feature_fc.py +24 -0
  12. mt_metadata/{transfer_functions/processing/aurora/decimation.py → features/feature_fc_run.py} +9 -4
  13. mt_metadata/features/feature_ts.py +24 -0
  14. mt_metadata/{transfer_functions/processing/aurora/window.py → features/feature_ts_run.py} +11 -18
  15. mt_metadata/features/standards/__init__.py +6 -0
  16. mt_metadata/features/standards/base_feature.json +46 -0
  17. mt_metadata/features/standards/coherence.json +57 -0
  18. mt_metadata/features/standards/fc_coherence.json +57 -0
  19. mt_metadata/features/standards/feature_decimation_channel.json +68 -0
  20. mt_metadata/features/standards/feature_fc_run.json +35 -0
  21. mt_metadata/features/standards/feature_ts_run.json +35 -0
  22. mt_metadata/features/standards/feature_weighting_window.json +46 -0
  23. mt_metadata/features/standards/weight_kernel.json +46 -0
  24. mt_metadata/features/standards/weights.json +101 -0
  25. mt_metadata/features/test_helpers/channel_weight_specs_example.json +156 -0
  26. mt_metadata/features/weights/__init__.py +0 -0
  27. mt_metadata/features/weights/base.py +44 -0
  28. mt_metadata/features/weights/channel_weight_spec.py +209 -0
  29. mt_metadata/features/weights/feature_weight_spec.py +194 -0
  30. mt_metadata/features/weights/monotonic_weight_kernel.py +275 -0
  31. mt_metadata/features/weights/standards/__init__.py +6 -0
  32. mt_metadata/features/weights/standards/activation_monotonic_weight_kernel.json +38 -0
  33. mt_metadata/features/weights/standards/base.json +36 -0
  34. mt_metadata/features/weights/standards/channel_weight_spec.json +35 -0
  35. mt_metadata/features/weights/standards/composite.json +36 -0
  36. mt_metadata/features/weights/standards/feature_weight_spec.json +13 -0
  37. mt_metadata/features/weights/standards/monotonic_weight_kernel.json +49 -0
  38. mt_metadata/features/weights/standards/taper_monotonic_weight_kernel.json +16 -0
  39. mt_metadata/features/weights/taper_weight_kernel.py +60 -0
  40. mt_metadata/helper_functions.py +69 -0
  41. mt_metadata/timeseries/filters/channel_response.py +77 -37
  42. mt_metadata/timeseries/filters/coefficient_filter.py +6 -5
  43. mt_metadata/timeseries/filters/filter_base.py +11 -15
  44. mt_metadata/timeseries/filters/fir_filter.py +8 -1
  45. mt_metadata/timeseries/filters/frequency_response_table_filter.py +26 -11
  46. mt_metadata/timeseries/filters/helper_functions.py +0 -2
  47. mt_metadata/timeseries/filters/obspy_stages.py +4 -1
  48. mt_metadata/timeseries/filters/pole_zero_filter.py +9 -5
  49. mt_metadata/timeseries/filters/time_delay_filter.py +8 -1
  50. mt_metadata/timeseries/location.py +20 -5
  51. mt_metadata/timeseries/person.py +14 -7
  52. mt_metadata/timeseries/standards/person.json +1 -1
  53. mt_metadata/timeseries/standards/run.json +2 -2
  54. mt_metadata/timeseries/station.py +4 -2
  55. mt_metadata/timeseries/stationxml/__init__.py +5 -0
  56. mt_metadata/timeseries/stationxml/xml_channel_mt_channel.py +38 -27
  57. mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py +16 -47
  58. mt_metadata/timeseries/stationxml/xml_station_mt_station.py +25 -24
  59. mt_metadata/transfer_functions/__init__.py +3 -0
  60. mt_metadata/transfer_functions/core.py +16 -11
  61. mt_metadata/transfer_functions/io/emtfxml/metadata/location.py +5 -0
  62. mt_metadata/transfer_functions/io/emtfxml/metadata/provenance.py +14 -3
  63. mt_metadata/transfer_functions/io/tools.py +2 -0
  64. mt_metadata/transfer_functions/io/zonge/metadata/header.py +1 -1
  65. mt_metadata/transfer_functions/io/zonge/metadata/standards/header.json +1 -1
  66. mt_metadata/transfer_functions/io/zonge/metadata/standards/job.json +2 -2
  67. mt_metadata/transfer_functions/io/zonge/zonge.py +19 -23
  68. mt_metadata/transfer_functions/processing/__init__.py +2 -1
  69. mt_metadata/transfer_functions/processing/aurora/__init__.py +2 -4
  70. mt_metadata/transfer_functions/processing/aurora/band.py +46 -125
  71. mt_metadata/transfer_functions/processing/aurora/channel_nomenclature.py +27 -20
  72. mt_metadata/transfer_functions/processing/aurora/decimation_level.py +324 -152
  73. mt_metadata/transfer_functions/processing/aurora/frequency_bands.py +230 -0
  74. mt_metadata/transfer_functions/processing/aurora/processing.py +3 -3
  75. mt_metadata/transfer_functions/processing/aurora/run.py +32 -7
  76. mt_metadata/transfer_functions/processing/aurora/standards/decimation_level.json +7 -73
  77. mt_metadata/transfer_functions/processing/aurora/stations.py +33 -4
  78. mt_metadata/transfer_functions/processing/fourier_coefficients/decimation.py +176 -177
  79. mt_metadata/transfer_functions/processing/fourier_coefficients/fc.py +11 -9
  80. mt_metadata/transfer_functions/processing/fourier_coefficients/standards/decimation.json +1 -111
  81. mt_metadata/transfer_functions/processing/short_time_fourier_transform.py +64 -0
  82. mt_metadata/transfer_functions/processing/standards/__init__.py +6 -0
  83. mt_metadata/transfer_functions/processing/standards/short_time_fourier_transform.json +94 -0
  84. mt_metadata/transfer_functions/processing/{aurora/standards/decimation.json → standards/time_series_decimation.json} +17 -6
  85. mt_metadata/transfer_functions/processing/{aurora/standards → standards}/window.json +13 -2
  86. mt_metadata/transfer_functions/processing/time_series_decimation.py +50 -0
  87. mt_metadata/transfer_functions/processing/window.py +118 -0
  88. mt_metadata/transfer_functions/tf/standards/transfer_function.json +1 -1
  89. mt_metadata/transfer_functions/tf/station.py +17 -1
  90. mt_metadata/utils/mttime.py +22 -3
  91. mt_metadata/utils/validators.py +4 -2
  92. {mt_metadata-0.3.8.dist-info → mt_metadata-0.4.0.dist-info}/METADATA +39 -15
  93. {mt_metadata-0.3.8.dist-info → mt_metadata-0.4.0.dist-info}/RECORD +97 -57
  94. {mt_metadata-0.3.8.dist-info → mt_metadata-0.4.0.dist-info}/WHEEL +1 -1
  95. {mt_metadata-0.3.8.dist-info → mt_metadata-0.4.0.dist-info}/AUTHORS.rst +0 -0
  96. {mt_metadata-0.3.8.dist-info → mt_metadata-0.4.0.dist-info}/LICENSE +0 -0
  97. {mt_metadata-0.3.8.dist-info → mt_metadata-0.4.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,14 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
+ This module contains the DecimationLevel class.
4
+ TODO: Factor or rename. The decimation level class here has information about the entire processing.
5
+
3
6
  Created on Thu Feb 17 14:15:20 2022
4
7
 
5
8
  @author: jpeacock
9
+
10
+
11
+
6
12
  """
7
13
  # =============================================================================
8
14
  # Imports
@@ -12,18 +18,26 @@ import pandas as pd
12
18
 
13
19
  from mt_metadata.base.helpers import write_lines
14
20
  from mt_metadata.base import get_schema, Base
21
+ from mt_metadata.transfer_functions.processing.fourier_coefficients import (
22
+ Decimation as FCDecimation,
23
+ )
24
+ from typing import List, Union
15
25
 
16
26
  from .band import Band
17
- from .decimation import Decimation
27
+ from ..time_series_decimation import TimeSeriesDecimation as Decimation
28
+ from ..short_time_fourier_transform import ShortTimeFourierTransform as STFT
18
29
  from .estimator import Estimator
30
+ from .frequency_bands import FrequencyBands
19
31
  from .regression import Regression
20
32
  from .standards import SCHEMA_FN_PATHS
21
- from .window import Window
33
+ from mt_metadata.features.weights.channel_weight_spec import ChannelWeightSpec
34
+ from mt_metadata.helper_functions import cast_to_class_if_dict
35
+ from mt_metadata.helper_functions import validate_setter_input
22
36
 
23
37
  # =============================================================================
24
38
  attr_dict = get_schema("decimation_level", SCHEMA_FN_PATHS)
25
- attr_dict.add_dict(get_schema("decimation", SCHEMA_FN_PATHS), "decimation")
26
- attr_dict.add_dict(get_schema("window", SCHEMA_FN_PATHS), "window")
39
+ attr_dict.add_dict(Decimation()._attr_dict, "decimation")
40
+ attr_dict.add_dict(STFT()._attr_dict, "stft")
27
41
  attr_dict.add_dict(get_schema("regression", SCHEMA_FN_PATHS), "regression")
28
42
  attr_dict.add_dict(get_schema("estimator", SCHEMA_FN_PATHS), "estimator")
29
43
 
@@ -31,141 +45,65 @@ attr_dict.add_dict(get_schema("estimator", SCHEMA_FN_PATHS), "estimator")
31
45
  # =============================================================================
32
46
 
33
47
 
34
- def df_from_bands(band_list: list) -> pd.DataFrame:
35
- """
36
- Utility function that transforms a list of bands into a dataframe
37
-
38
- Note: The decimation_level here is +1 to agree with EMTF convention.
39
- Not clear this is really necessary
40
-
41
- Parameters
42
- ----------
43
- band_list: list
44
- obtained from mt_metadata.transfer_functions.processing.aurora.decimation_level.DecimationLevel.bands
45
-
46
- Returns
47
- -------
48
- out_df: pd.Dataframe
49
- Same format as that generated by EMTFBandSetupFile.get_decimation_level()
50
- """
51
- df_columns = [
52
- "decimation_level",
53
- "lower_bound_index",
54
- "upper_bound_index",
55
- "frequency_min",
56
- "frequency_max",
57
- ]
58
- n_rows = len(band_list)
59
- df_columns_dict = {}
60
- for col in df_columns:
61
- df_columns_dict[col] = n_rows * [None]
62
- for i_band, band in enumerate(band_list):
63
- df_columns_dict["decimation_level"][i_band] = band.decimation_level + 1
64
- df_columns_dict["lower_bound_index"][i_band] = band.index_min
65
- df_columns_dict["upper_bound_index"][i_band] = band.index_max
66
- df_columns_dict["frequency_min"][i_band] = band.frequency_min
67
- df_columns_dict["frequency_max"][i_band] = band.frequency_max
68
- out_df = pd.DataFrame(data=df_columns_dict)
69
- out_df.sort_values(by="lower_bound_index", inplace=True)
70
- out_df.reset_index(inplace=True, drop=True)
71
- return out_df
72
-
73
- def get_fft_harmonics(samples_per_window: int, sample_rate: float) -> np.ndarray:
74
- """
75
- Works for odd and even number of points.
76
-
77
- Development notes:
78
- Could be modified with kwargs to support one_sided, two_sided, ignore_dc
79
- ignore_nyquist, and etc. Consider taking FrequencyBands as an argument.
80
-
81
- Parameters
82
- ----------
83
- samples_per_window: integer
84
- Number of samples in a window that will be Fourier transformed.
85
- sample_rate: float
86
- Inverse of time step between samples,
87
- Samples per second
88
-
89
- Returns
90
- -------
91
- harmonic_frequencies: numpy array
92
- The frequencies that the fft will be computed.
93
- These are one-sided (positive frequencies only)
94
- Does not return Nyquist
95
- Does return DC component
96
- """
97
- n_fft_harmonics = int(samples_per_window / 2) # no bin at Nyquist,
98
- delta_t = 1.0 / sample_rate
99
- harmonic_frequencies = np.fft.fftfreq(samples_per_window, d=delta_t)
100
- harmonic_frequencies = harmonic_frequencies[0:n_fft_harmonics]
101
- return harmonic_frequencies
102
-
103
-
104
48
  class DecimationLevel(Base):
105
49
  __doc__ = write_lines(attr_dict)
106
50
 
107
51
  def __init__(self, **kwargs):
108
52
 
109
- self.window = Window()
110
53
  self.decimation = Decimation()
111
54
  self.regression = Regression()
112
55
  self.estimator = Estimator()
56
+ self.stft = STFT()
113
57
 
114
58
  self._bands = []
59
+ self._channel_weight_specs = []
115
60
 
116
61
  super().__init__(attr_dict=attr_dict, **kwargs)
117
62
 
118
- # if self.decimation.level == 0:
119
- # self.anti_alias_filter = None
120
-
121
63
  @property
122
- def bands(self):
64
+ def bands(self) -> List[Band]:
123
65
  """
124
- get bands, something weird is going on with appending.
66
+ Return bands.
125
67
 
126
68
  """
127
- return_list = []
128
- for band in self._bands:
129
- if isinstance(band, dict):
130
- b = Band()
131
- b.from_dict(band)
132
- elif isinstance(band, Band):
133
- b = band
134
- return_list.append(b)
135
- return return_list
69
+ return self._bands
136
70
 
137
71
  @bands.setter
138
72
  def bands(self, value):
139
73
  """
140
- Set bands make sure they are a band object
74
+ Set bands. If any are in dict form, cast them to Band objects before setting.
141
75
 
142
76
  :param value: list of bands
143
77
  :type value: list, Band
144
78
 
145
79
  """
80
+ values = validate_setter_input(value, Band)
81
+ bands_list = [cast_to_class_if_dict(obj, Band) for obj in values]
82
+ self._bands = bands_list
146
83
 
147
- if isinstance(value, Band):
148
- self._bands = [value]
84
+ @property
85
+ def channel_weight_specs(self) -> List[ChannelWeightSpec]:
86
+ """
87
+ Return the channel weight spec objects.
149
88
 
150
- elif isinstance(value, list):
151
- self._bands = []
152
- for obj in value:
153
- if not isinstance(obj, (Band, dict)):
154
- raise TypeError(
155
- f"List entry must be a Band object not {type(obj)}"
156
- )
157
- if isinstance(obj, dict):
158
- band = Band()
159
- band.from_dict(obj)
89
+ """
90
+ return self._channel_weight_specs
160
91
 
161
- else:
162
- band = obj
92
+ @channel_weight_specs.setter
93
+ def channel_weight_specs(self, value: List[Union[dict, ChannelWeightSpec]]) -> None:
94
+ """
95
+ Set channel_weight_specs.
96
+ If any are in dict form, cast to ChannelWeightSpec before assigning.
163
97
 
164
- self._bands.append(band)
165
- else:
166
- raise TypeError(f"Not sure what to do with {type(value)}")
98
+ :param value: list of ChannelWeightSpec objects
99
+ :type value: list, ChannelWeightSpec
100
+
101
+ """
102
+ values = validate_setter_input(value, ChannelWeightSpec)
103
+ cws_list = [cast_to_class_if_dict(obj, ChannelWeightSpec) for obj in values]
104
+ self._channel_weight_specs = cws_list
167
105
 
168
- def add_band(self, band):
106
+ def add_band(self, band: Union[Band, dict]) -> None:
169
107
  """
170
108
  add a band
171
109
  """
@@ -177,14 +115,13 @@ class DecimationLevel(Base):
177
115
  if isinstance(band, dict):
178
116
  obj = Band()
179
117
  obj.from_dict(band)
180
-
181
118
  else:
182
119
  obj = band
183
120
 
184
121
  self._bands.append(obj)
185
122
 
186
123
  @property
187
- def lower_bounds(self):
124
+ def lower_bounds(self) -> np.ndarray:
188
125
  """
189
126
  get lower bounds index values into an array.
190
127
  """
@@ -192,7 +129,7 @@ class DecimationLevel(Base):
192
129
  return np.array(sorted([band.index_min for band in self.bands]))
193
130
 
194
131
  @property
195
- def upper_bounds(self):
132
+ def upper_bounds(self) -> np.ndarray:
196
133
  """
197
134
  get upper bounds index values into an array.
198
135
  """
@@ -200,65 +137,77 @@ class DecimationLevel(Base):
200
137
  return np.array(sorted([band.index_max for band in self.bands]))
201
138
 
202
139
  @property
203
- def bands_dataframe(self):
140
+ def bands_dataframe(self) -> pd.DataFrame:
204
141
  """
205
- This is just a utility function that transforms a list of bands into a dataframe
206
-
207
- Note: The decimation_level here is +1 to agree with EMTF convention.
208
- Not clear this is really necessary
142
+ Utility function that transforms a list of bands into a dataframe
209
143
 
210
- ToDo: Consider adding columns lower_edge, upper_edge to df
144
+ See notes in `_df_from_bands`.
211
145
 
212
146
  Returns
213
147
  -------
214
148
  bands_df: pd.Dataframe
215
149
  Same format as that generated by EMTFBandSetupFile.get_decimation_level()
216
150
  """
217
- bands_df = df_from_bands(self.bands)
151
+ bands_df = _df_from_bands(self.bands)
218
152
  return bands_df
219
153
 
220
154
  @property
221
- def frequency_sample_interval(self):
222
- return self.sample_rate_decimation/self.window.num_samples
155
+ def frequency_sample_interval(self) -> float:
156
+ """
157
+ Returns the delta_f in frequency domain df = 1 / (N * dt)
158
+ Here dt is the sample interval after decimation
159
+ """
160
+ return self.decimation.sample_rate / self.stft.window.num_samples
223
161
 
224
162
  @property
225
- def band_edges(self):
163
+ def band_edges(self) -> np.ndarray:
164
+ """
165
+ Returns the band edges as a numpy array
166
+ :return band_edges: 2D numpy array, one row per frequency band and two columns
167
+ :rtype band_edges: np.ndarray
168
+ """
226
169
  bands_df = self.bands_dataframe
227
170
  band_edges = np.vstack(
228
171
  (bands_df.frequency_min.values, bands_df.frequency_max.values)
229
172
  ).T
230
173
  return band_edges
231
174
 
232
- def frequency_bands_obj(self):
175
+ def frequency_bands_obj(self) -> FrequencyBands:
233
176
  """
234
177
  Gets a FrequencyBands object that is used as input to processing.
235
- This used to be needed because I only had
236
178
 
237
- ToDO: consider adding .to_frequnecy_bands() method directly to self.bands
179
+ Used by Aurora.
180
+
181
+ TODO: consider adding .to_frequency_bands() method directly to self.bands
182
+
238
183
  Returns
239
184
  -------
185
+ frequency_bands: FrequencyBands
186
+ A FrequencyBands object that can be used as an iterator for processing.
240
187
 
241
188
  """
242
- from mt_metadata.transfer_functions.processing.aurora.band import (
243
- FrequencyBands,
244
- )
245
-
246
189
  frequency_bands = FrequencyBands(band_edges=self.band_edges)
247
190
  return frequency_bands
248
191
 
249
192
  @property
250
- def fft_frequencies(self):
251
- freqs = get_fft_harmonics(
252
- self.window.num_samples, self.decimation.sample_rate
253
- )
254
- return freqs
193
+ def fft_frequencies(self) -> np.ndarray:
194
+ """
195
+ Gets the harmonics of the STFT.
255
196
 
256
- @property
257
- def sample_rate_decimation(self):
258
- return self.decimation.sample_rate
197
+ :return freqs: The frequencies at which the stft will be available.
198
+ :rtype freqs: np.ndarray
199
+ """
200
+ freqs = self.stft.window.fft_harmonics(self.decimation.sample_rate)
201
+ return freqs
259
202
 
260
203
  @property
261
- def harmonic_indices(self):
204
+ def harmonic_indices(self) -> List[int]:
205
+ """
206
+ Loops over all bands and returns a list of the harminic indices.
207
+ TODO: Distinguish the bands which are a processing construction vs harmonic indices which are FFT info.
208
+ :return: list of fc indices (integers)
209
+ :rtype: List[int]
210
+ """
262
211
  return_list = []
263
212
  for band in self.bands:
264
213
  fc_indices = band.harmonic_indices
@@ -270,14 +219,196 @@ class DecimationLevel(Base):
270
219
  def local_channels(self):
271
220
  return self.input_channels + self.output_channels
272
221
 
273
- def to_fc_decimation(self, remote=False, ignore_harmonic_indices=True):
222
+ def is_consistent_with_archived_fc_parameters(
223
+ self,
224
+ fc_decimation: FCDecimation,
225
+ remote: bool
226
+ ):
227
+ """
228
+ Usage: For an already existing spectrogram stored in an MTH5 archive, this compares the metadata
229
+ within the archive (fc_decimation) with an aurora decimation level (self), and tells whether the
230
+ parameters are in agreement. If True, this allows aurora to skip the calculation of FCs and instead
231
+ read them from the archive.
232
+
233
+ TODO: Merge all checks of TimeSeriesDecimation parameters into a single check.
234
+ - e.g. Compress all decimation checks to: assert fc_decimation.decimation == self.decimation
235
+
236
+ Parameters
237
+ ----------
238
+ decimation_level: FCDecimation
239
+ metadata describing the parameters used to compute an archived spectrogram
240
+ remote: bool
241
+ If True, we are looking for reference channels, not local channels in the FCGroup.
242
+
243
+ Iterates over FCDecimation attributes:
244
+ "channels_estimated": to ensure all expected channels are in the group
245
+ "decimation.anti_alias_filter": check that the expected AAF was applied
246
+ "decimation.sample_rate,
247
+ "decimation.method",
248
+ "stft.prewhitening_type",
249
+ "stft.recoloring",
250
+ "stft.pre_fft_detrend_type",
251
+ "stft.min_num_stft_windows",
252
+ "stft.window",
253
+ "stft.harmonic_indices",
254
+ Returns
255
+ -------
256
+
257
+ :return:
258
+ """
259
+ # channels_estimated: Checks that the archived spectrogram has the required channels
260
+ if remote:
261
+ required_channels = self.reference_channels
262
+ else:
263
+ required_channels = self.local_channels
264
+ try:
265
+ assert set(required_channels).issubset(fc_decimation.channels_estimated)
266
+ except AssertionError:
267
+ msg = (
268
+ f"required_channels for processing {required_channels} not available"
269
+ f"-- fc channels estimated are {fc_decimation.channels_estimated}"
270
+ )
271
+ self.logger.info(msg)
272
+ return False
273
+
274
+ # anti_alias_filter: Check that the data were filtered the same way
275
+ try:
276
+ assert fc_decimation.time_series_decimation.anti_alias_filter == self.decimation.anti_alias_filter
277
+ except AssertionError:
278
+ cond1 = self.time_series_decimation.anti_alias_filter == "default"
279
+ cond2 = fc_decimation.time_series_decimation.anti_alias_filter is None
280
+ if cond1 & cond2:
281
+ pass
282
+ else:
283
+ msg = (
284
+ "Antialias Filters Not Compatible -- need to add handling for "
285
+ f"{msg} FCdec {fc_decimation.time_series_decimation.anti_alias_filter} and "
286
+ f"{msg} processing config:{self.decimation.anti_alias_filter}"
287
+ )
288
+ raise NotImplementedError(msg)
289
+
290
+ # sample_rate
291
+ try:
292
+ assert (
293
+ fc_decimation.time_series_decimation.sample_rate
294
+ == self.decimation.sample_rate
295
+ )
296
+ except AssertionError:
297
+ msg = (
298
+ f"Sample rates do not agree: fc {fc_decimation.time_series_decimation.sample_rate} differs from "
299
+ f"processing config {self.decimation.sample_rate}"
300
+ )
301
+ self.logger.info(msg)
302
+ return False
303
+
304
+ # transform method (fft, wavelet, etc.)
305
+ try:
306
+ assert fc_decimation.short_time_fourier_transform.method == self.stft.method # FFT, Wavelet, etc.
307
+ except AssertionError:
308
+ msg = (
309
+ "Transform methods do not agree: "
310
+ f"fc {fc_decimation.short_time_fourier_transform.method} != processing config {self.stft.method}"
311
+ )
312
+ self.logger.info(msg)
313
+ return False
314
+
315
+ # prewhitening_type
316
+ try:
317
+ assert fc_decimation.stft.prewhitening_type == self.stft.prewhitening_type
318
+ except AssertionError:
319
+ msg = (
320
+ "prewhitening_type does not agree "
321
+ f"fc {fc_decimation.stft.prewhitening_type} != processing config {self.stft.prewhitening_type}"
322
+ )
323
+ self.logger.info(msg)
324
+ return False
325
+
326
+ # recoloring
327
+ try:
328
+ assert fc_decimation.stft.recoloring == self.stft.recoloring
329
+ except AssertionError:
330
+ msg = (
331
+ "recoloring does not agree "
332
+ f"fc {fc_decimation.stft.recoloring} != processing config {self.stft.recoloring}"
333
+ )
334
+ self.logger.info(msg)
335
+ return False
336
+
337
+ # pre_fft_detrend_type
338
+ try:
339
+ assert (
340
+ fc_decimation.stft.pre_fft_detrend_type
341
+ == self.stft.pre_fft_detrend_type
342
+ )
343
+ except AssertionError:
344
+ msg = (
345
+ "pre_fft_detrend_type does not agree "
346
+ f"fc {fc_decimation.stft.pre_fft_detrend_type} != processing config {self.stft.pre_fft_detrend_type}"
347
+ )
348
+ self.logger.info(msg)
349
+ return False
350
+
351
+ # min_num_stft_windows
352
+ try:
353
+ assert (
354
+ fc_decimation.stft.min_num_stft_windows
355
+ == self.stft.min_num_stft_windows
356
+ )
357
+ except AssertionError:
358
+ msg = (
359
+ "min_num_stft_windows do not agree "
360
+ f"fc {fc_decimation.stft.min_num_stft_windows} != processing config {self.stft.min_num_stft_windows}"
361
+ )
362
+ self.logger.info(msg)
363
+ return False
364
+
365
+ # window
366
+ try:
367
+ assert fc_decimation.stft.window == self.stft.window
368
+ except AssertionError:
369
+ msg = "window does not agree: "
370
+ msg = f"{msg} FC Group: {fc_decimation.stft.window} "
371
+ msg = f"{msg} Processing Config {self.stft.window}"
372
+ self.logger.info(msg)
373
+ return False
374
+
375
+ if -1 in fc_decimation.stft.harmonic_indices:
376
+ # if harmonic_indices is -1, it means the archive kept all so we can skip this check.
377
+ pass
378
+ else:
379
+ msg = "WIP: harmonic indices in AuroraDecimationlevel are derived from processing bands -- Not robustly tested to compare with FCDecimation"
380
+ self.logger.debug(msg)
381
+ harmonic_indices_requested = self.harmonic_indices
382
+ fcdec_group_set = set(fc_decimation.stft.harmonic_indices)
383
+ processing_set = set(harmonic_indices_requested)
384
+ if processing_set.issubset(fcdec_group_set):
385
+ pass
386
+ else:
387
+ msg = (
388
+ f"Processing FC indices {processing_set} is not contained "
389
+ f"in FC indices {fcdec_group_set}"
390
+ )
391
+ self.logger.info(msg)
392
+ return False
393
+
394
+ # Getting here means no checks were failed. The FCDecimation supports the processing config
395
+ return True
396
+
397
+ def to_fc_decimation(
398
+ self,
399
+ remote: bool = False,
400
+ ignore_harmonic_indices: bool = True,
401
+ ) -> FCDecimation:
274
402
  """
275
403
  Generates a FC Decimation() object for use with FC Layer in mth5.
276
404
 
405
+ TODO: this is being tested only in aurora -- move a test to mt_metadata or move the method.
277
406
  Ignoring for now these properties
278
407
  "time_period.end": "1980-01-01T00:00:00+00:00",
279
408
  "time_period.start": "1980-01-01T00:00:00+00:00",
280
409
 
410
+ TODO: FIXME: Assignment of TSDecimation can be done in one shot once #235 is addressed.
411
+
281
412
  Parameters
282
413
  ----------
283
414
  remote: bool
@@ -293,27 +424,68 @@ class DecimationLevel(Base):
293
424
  A decimation object configured for STFT processing
294
425
 
295
426
  """
296
- from mt_metadata.transfer_functions.processing.fourier_coefficients import (
297
- Decimation as FourierCoefficientDecimation,
298
- )
299
- fc_dec_obj = FourierCoefficientDecimation()
300
- fc_dec_obj.anti_alias_filter = self.anti_alias_filter
427
+
428
+ fc_dec_obj = FCDecimation()
429
+ fc_dec_obj.time_series_decimation.anti_alias_filter = self.decimation.anti_alias_filter
301
430
  if remote:
302
431
  fc_dec_obj.channels_estimated = self.reference_channels
303
432
  else:
304
433
  fc_dec_obj.channels_estimated = self.local_channels
305
- fc_dec_obj.decimation_factor = self.decimation.factor
306
- fc_dec_obj.decimation_level = self.decimation.level
434
+ fc_dec_obj.time_series_decimation.factor = self.decimation.factor
435
+ fc_dec_obj.time_series_decimation.level = self.decimation.level
307
436
  if ignore_harmonic_indices:
308
437
  pass
309
438
  else:
310
- fc_dec_obj.harmonic_indices = self.harmonic_indices()
439
+ fc_dec_obj.stft.harmonic_indices = self.harmonic_indices()
311
440
  fc_dec_obj.id = f"{self.decimation.level}"
312
- fc_dec_obj.method = self.method
313
- fc_dec_obj.pre_fft_detrend_type = self.pre_fft_detrend_type
314
- fc_dec_obj.prewhitening_type = self.prewhitening_type
315
- fc_dec_obj.recoloring = self.recoloring
316
- fc_dec_obj.sample_rate_decimation = self.sample_rate_decimation
317
- fc_dec_obj.window = self.window
441
+ fc_dec_obj.stft.method = self.stft.method
442
+ fc_dec_obj.stft.pre_fft_detrend_type = self.stft.pre_fft_detrend_type
443
+ fc_dec_obj.stft.prewhitening_type = self.stft.prewhitening_type
444
+ fc_dec_obj.stft.recoloring = self.stft.recoloring
445
+ fc_dec_obj.time_series_decimation.sample_rate = self.decimation.sample_rate
446
+ fc_dec_obj.stft.window = self.stft.window
318
447
 
319
448
  return fc_dec_obj
449
+
450
+
451
+ def _df_from_bands(band_list: List[Union[Band, dict, None]]) -> pd.DataFrame:
452
+ """
453
+ Utility function that transforms a list of bands into a dataframe
454
+
455
+ Note: The decimation_level here is +1 to agree with EMTF convention.
456
+ Not clear this is really necessary
457
+ TODO: Consider making this a method of FrequencyBands() class.
458
+ TODO: Check typehint -- should None be allowed value in the band_list?
459
+ TODO: Consider adding columns lower_closed, upper_closed to df
460
+
461
+ Parameters
462
+ ----------
463
+ band_list: list
464
+ obtained from mt_metadata.transfer_functions.processing.aurora.decimation_level.DecimationLevel.bands
465
+
466
+ Returns
467
+ -------
468
+ out_df: pd.Dataframe
469
+ Same format as that generated by EMTFBandSetupFile.get_decimation_level()
470
+ """
471
+ df_columns = [
472
+ "decimation_level",
473
+ "lower_bound_index",
474
+ "upper_bound_index",
475
+ "frequency_min",
476
+ "frequency_max",
477
+ ]
478
+ n_rows = len(band_list)
479
+ df_columns_dict = {}
480
+ for col in df_columns:
481
+ df_columns_dict[col] = n_rows * [None]
482
+ for i_band, band in enumerate(band_list):
483
+ df_columns_dict["decimation_level"][i_band] = band.decimation_level + 1
484
+ df_columns_dict["lower_bound_index"][i_band] = band.index_min
485
+ df_columns_dict["upper_bound_index"][i_band] = band.index_max
486
+ df_columns_dict["frequency_min"][i_band] = band.frequency_min
487
+ df_columns_dict["frequency_max"][i_band] = band.frequency_max
488
+ out_df = pd.DataFrame(data=df_columns_dict)
489
+ out_df.sort_values(by="lower_bound_index", inplace=True)
490
+ out_df.reset_index(inplace=True, drop=True)
491
+ return out_df