mt-metadata 0.3.3__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.

Files changed (41) hide show
  1. mt_metadata/__init__.py +12 -10
  2. mt_metadata/base/metadata.py +24 -13
  3. mt_metadata/data/transfer_functions/tf_zmm.zmm +1 -1
  4. mt_metadata/timeseries/channel.py +8 -3
  5. mt_metadata/timeseries/filters/__init__.py +2 -2
  6. mt_metadata/timeseries/filters/{channel_response_filter.py → channel_response.py} +62 -29
  7. mt_metadata/timeseries/filters/coefficient_filter.py +1 -3
  8. mt_metadata/timeseries/filters/filter_base.py +70 -37
  9. mt_metadata/timeseries/filters/filtered.py +32 -22
  10. mt_metadata/timeseries/filters/fir_filter.py +10 -11
  11. mt_metadata/timeseries/filters/frequency_response_table_filter.py +9 -8
  12. mt_metadata/timeseries/filters/helper_functions.py +112 -3
  13. mt_metadata/timeseries/filters/pole_zero_filter.py +9 -8
  14. mt_metadata/timeseries/filters/standards/filter_base.json +2 -2
  15. mt_metadata/timeseries/filters/time_delay_filter.py +9 -8
  16. mt_metadata/timeseries/stationxml/fdsn_tools.py +8 -7
  17. mt_metadata/timeseries/stationxml/xml_channel_mt_channel.py +1 -1
  18. mt_metadata/timeseries/stationxml/xml_inventory_mt_experiment.py +4 -1
  19. mt_metadata/timeseries/tools/from_many_mt_files.py +15 -3
  20. mt_metadata/transfer_functions/__init__.py +1 -1
  21. mt_metadata/transfer_functions/core.py +89 -49
  22. mt_metadata/transfer_functions/io/edi/edi.py +9 -5
  23. mt_metadata/transfer_functions/io/edi/metadata/define_measurement.py +7 -3
  24. mt_metadata/transfer_functions/io/emtfxml/emtfxml.py +1 -4
  25. mt_metadata/transfer_functions/io/jfiles/jfile.py +2 -1
  26. mt_metadata/transfer_functions/io/zfiles/zmm.py +108 -62
  27. mt_metadata/transfer_functions/io/zonge/zonge.py +2 -2
  28. mt_metadata/transfer_functions/processing/aurora/band.py +16 -0
  29. mt_metadata/transfer_functions/processing/aurora/channel_nomenclature.py +34 -31
  30. mt_metadata/transfer_functions/processing/aurora/processing.py +12 -5
  31. mt_metadata/transfer_functions/processing/aurora/stations.py +11 -2
  32. mt_metadata/transfer_functions/processing/fourier_coefficients/decimation.py +1 -1
  33. mt_metadata/transfer_functions/processing/fourier_coefficients/standards/decimation.json +1 -1
  34. mt_metadata/transfer_functions/processing/fourier_coefficients/standards/fc_channel.json +23 -1
  35. mt_metadata/transfer_functions/tf/transfer_function.py +93 -1
  36. {mt_metadata-0.3.3.dist-info → mt_metadata-0.3.5.dist-info}/METADATA +379 -379
  37. {mt_metadata-0.3.3.dist-info → mt_metadata-0.3.5.dist-info}/RECORD +41 -41
  38. {mt_metadata-0.3.3.dist-info → mt_metadata-0.3.5.dist-info}/WHEEL +1 -1
  39. {mt_metadata-0.3.3.dist-info → mt_metadata-0.3.5.dist-info}/AUTHORS.rst +0 -0
  40. {mt_metadata-0.3.3.dist-info → mt_metadata-0.3.5.dist-info}/LICENSE +0 -0
  41. {mt_metadata-0.3.3.dist-info → mt_metadata-0.3.5.dist-info}/top_level.txt +0 -0
@@ -25,6 +25,7 @@ from mt_metadata.transfer_functions.tf import (
25
25
  from mt_metadata.transfer_functions.io.tools import get_nm_elev
26
26
  from .metadata import Channel
27
27
  from mt_metadata.utils.list_dict import ListDict
28
+ from mt_metadata import DEFAULT_CHANNEL_NOMENCLATURE
28
29
 
29
30
  # ==============================================================================
30
31
  PERIOD_FORMAT = ".10g"
@@ -40,7 +41,6 @@ class ZMMHeader(object):
40
41
  """
41
42
 
42
43
  def __init__(self, fn=None, **kwargs):
43
-
44
44
  self.logger = logger
45
45
  self.processing_type = None
46
46
  self.num_channels = None
@@ -73,9 +73,7 @@ class ZMMHeader(object):
73
73
  if value.suffix.lower() in [".zmm", ".zrr", ".zss"]:
74
74
  self._zfn = value
75
75
  else:
76
- msg = (
77
- f"Input file must be a *.zmm or *.zrr file not {value.suffix}"
78
- )
76
+ msg = f"Input file must be a *.zmm or *.zrr file not {value.suffix}"
79
77
  self.logger.error(msg)
80
78
  raise ValueError(msg)
81
79
 
@@ -255,6 +253,15 @@ class ZMMHeader(object):
255
253
  continue
256
254
  return lines
257
255
 
256
+ @property
257
+ def channel_dict(self):
258
+ channels = {}
259
+ for cc in ["ex", "ey", "hx", "hy", "hz"]:
260
+ ch = getattr(self, cc)
261
+ if ch is not None:
262
+ channels[cc] = ch.channel
263
+ return channels
264
+
258
265
  @property
259
266
  def channels_recorded(self):
260
267
  channels = {}
@@ -293,7 +300,6 @@ class ZMM(ZMMHeader):
293
300
  """
294
301
 
295
302
  def __init__(self, fn=None, **kwargs):
296
-
297
303
  super().__init__()
298
304
 
299
305
  self.fn = fn
@@ -304,23 +310,7 @@ class ZMM(ZMMHeader):
304
310
  self.periods = None
305
311
  self.dataset = None
306
312
  self.decimation_dict = {}
307
- self.channel_nomenclature = {}
308
-
309
- self._ch_input_dict = {
310
- "impedance": ["hx", "hy"],
311
- "tipper": ["hx", "hy"],
312
- "isp": ["hx", "hy"],
313
- "res": ["ex", "ey", "hz"],
314
- "tf": ["hx", "hy"],
315
- }
316
-
317
- self._ch_output_dict = {
318
- "impedance": ["ex", "ey"],
319
- "tipper": ["hz"],
320
- "isp": ["hx", "hy"],
321
- "res": ["ex", "ey", "hz"],
322
- "tf": ["ex", "ey", "hz"],
323
- }
313
+ self.channel_nomenclature = DEFAULT_CHANNEL_NOMENCLATURE
324
314
 
325
315
  self._transfer_function = self._initialize_transfer_function()
326
316
 
@@ -369,6 +359,78 @@ class ZMM(ZMMHeader):
369
359
 
370
360
  return f"MT( {(', ').join(lines)} )"
371
361
 
362
+ def __eq__(self, other):
363
+ """
364
+ compare equals
365
+
366
+ :param other: DESCRIPTION
367
+ :type other: TYPE
368
+ :return: DESCRIPTION
369
+ :rtype: TYPE
370
+
371
+ """
372
+
373
+ if not isinstance(other, ZMM):
374
+ raise TypeError(f"Cannot compare type {type(other)} with ZMM.")
375
+
376
+ is_equal = True
377
+ if self.station_metadata != other.station_metadata:
378
+ is_equal = False
379
+ if not self.dataset.equals(other.dataset):
380
+ is_equal = False
381
+ self.logger.info("Datasets are not equal")
382
+ print(self.dataset.fillna(0) != other.dataset.fillna(0).all())
383
+ return is_equal
384
+
385
+ @property
386
+ def channel_nomenclature(self):
387
+ return self._channel_nomenclature
388
+
389
+ @channel_nomenclature.setter
390
+ def channel_nomenclature(self, ch_dict):
391
+ """
392
+ channel dictionary
393
+ """
394
+
395
+ if not isinstance(ch_dict, dict):
396
+ raise TypeError(
397
+ "Channel_nomenclature must be a dictionary with keys "
398
+ "['ex', 'ey', 'hx', 'hy', 'hz']."
399
+ )
400
+
401
+ self._channel_nomenclature = ch_dict
402
+ # unpack channel nomenclature dict
403
+ for comp in DEFAULT_CHANNEL_NOMENCLATURE.keys():
404
+ try:
405
+ setattr(self, f"_{comp}", ch_dict[comp])
406
+ except KeyError:
407
+ setattr(self, f"_{comp}", comp)
408
+ ch_dict[comp] = comp
409
+
410
+ self._ex_ey = [self._ex, self._ey]
411
+ self._hx_hy = [self._hx, self._hy]
412
+ self._ex_ey_hz = [self._ex, self._ey, self._hz]
413
+
414
+ @property
415
+ def _ch_input_dict(self):
416
+ return {
417
+ "isp": self._hx_hy,
418
+ "res": self._ex_ey_hz,
419
+ "tf": self._hx_hy,
420
+ "tf_error": self._hx_hy,
421
+ "all": [self._ex, self._ey, self._hz, self._hx, self._hy],
422
+ }
423
+
424
+ @property
425
+ def _ch_output_dict(self):
426
+ return {
427
+ "isp": self._hx_hy,
428
+ "res": self._ex_ey_hz,
429
+ "tf": self._ex_ey_hz,
430
+ "tf_error": self._ex_ey_hz,
431
+ "all": [self._ex, self._ey, self._hz, self._hx, self._hy],
432
+ }
433
+
372
434
  def _initialize_transfer_function(self, periods=[1]):
373
435
  """
374
436
  create an empty x array for the data. For now this accommodates
@@ -381,45 +443,45 @@ class ZMM(ZMMHeader):
381
443
  """
382
444
  # create an empty array for the transfer function
383
445
  tf = xr.DataArray(
384
- data=0 + 0j,
446
+ data=0.0 + 0j,
385
447
  dims=["period", "output", "input"],
386
448
  coords={
387
449
  "period": periods,
388
- "output": self._ch_output_dict["tf"],
389
- "input": self._ch_input_dict["tf"],
450
+ "output": self._ch_output_dict["all"],
451
+ "input": self._ch_input_dict["all"],
390
452
  },
391
453
  name="transfer_function",
392
454
  )
393
455
 
394
456
  tf_err = xr.DataArray(
395
- data=0,
457
+ data=0.0,
396
458
  dims=["period", "output", "input"],
397
459
  coords={
398
460
  "period": periods,
399
- "output": self._ch_output_dict["tf"],
400
- "input": self._ch_input_dict["tf"],
461
+ "output": self._ch_output_dict["all"],
462
+ "input": self._ch_input_dict["all"],
401
463
  },
402
464
  name="error",
403
465
  )
404
466
 
405
467
  inv_signal_power = xr.DataArray(
406
- data=0 + 0j,
468
+ data=0.0 + 0j,
407
469
  dims=["period", "output", "input"],
408
470
  coords={
409
471
  "period": periods,
410
- "output": self._ch_output_dict["isp"],
411
- "input": self._ch_input_dict["isp"],
472
+ "output": self._ch_output_dict["all"],
473
+ "input": self._ch_input_dict["all"],
412
474
  },
413
475
  name="inverse_signal_power",
414
476
  )
415
477
 
416
478
  residual_covariance = xr.DataArray(
417
- data=0 + 0j,
479
+ data=0.0 + 0j,
418
480
  dims=["period", "output", "input"],
419
481
  coords={
420
482
  "period": periods,
421
- "output": self._ch_output_dict["res"],
422
- "input": self._ch_input_dict["res"],
483
+ "output": self._ch_output_dict["all"],
484
+ "input": self._ch_input_dict["all"],
423
485
  },
424
486
  name="residual_covariance",
425
487
  )
@@ -431,7 +493,12 @@ class ZMM(ZMMHeader):
431
493
  tf_err.name: tf_err,
432
494
  inv_signal_power.name: inv_signal_power,
433
495
  residual_covariance.name: residual_covariance,
434
- }
496
+ },
497
+ coords={
498
+ "period": periods,
499
+ "output": self._ch_output_dict["all"],
500
+ "input": self._ch_input_dict["all"],
501
+ },
435
502
  )
436
503
 
437
504
  @property
@@ -464,7 +531,7 @@ class ZMM(ZMMHeader):
464
531
  # this dimension is hard-coded
465
532
  self.sigma_s = np.zeros((self.num_freq, 2, 2), dtype=np.complex64)
466
533
 
467
- def read(self, fn=None, get_elevation=True):
534
+ def read(self, fn=None, get_elevation=False):
468
535
  """
469
536
  Read in Egbert zrr/zmm file
470
537
 
@@ -474,24 +541,9 @@ class ZMM(ZMMHeader):
474
541
  if fn is not None:
475
542
  self.fn = fn
476
543
  self.read_header()
544
+ self.channel_nomenclature = self.channel_dict
477
545
  self.initialize_arrays()
478
546
 
479
- self._ch_input_dict = {
480
- "impedance": self.input_channels,
481
- "tipper": self.input_channels,
482
- "isp": self.input_channels,
483
- "res": self.output_channels,
484
- "tf": self.input_channels,
485
- }
486
-
487
- self._ch_output_dict = {
488
- "impedance": ["ex", "ey"],
489
- "tipper": ["hz"],
490
- "isp": self.input_channels,
491
- "res": self.output_channels,
492
- "tf": self.output_channels,
493
- }
494
-
495
547
  self._transfer_function = self._initialize_transfer_function()
496
548
  self.dataset = self._initialize_transfer_function()
497
549
 
@@ -649,9 +701,7 @@ class ZMM(ZMMHeader):
649
701
  -0.2231E-05 -0.2863E-06 0.8866E-05 0.0000E+00
650
702
  """
651
703
 
652
- period = float(
653
- period_block[0].strip().split(":")[1].split()[0].strip()
654
- )
704
+ period = float(period_block[0].strip().split(":")[1].split()[0].strip())
655
705
  level = int(
656
706
  period_block[0].strip().split("level")[1].split()[0].strip()
657
707
  )
@@ -660,12 +710,8 @@ class ZMM(ZMMHeader):
660
710
  int(period_block[0].strip().split("to")[1].split()[0].strip()),
661
711
  )
662
712
 
663
- npts = int(
664
- period_block[1].strip().split("point")[1].split()[0].strip()
665
- )
666
- sr = float(
667
- period_block[1].strip().split("freq.")[1].split()[0].strip()
668
- )
713
+ npts = int(period_block[1].strip().split("point")[1].split()[0].strip())
714
+ sr = float(period_block[1].strip().split("freq.")[1].split()[0].strip())
669
715
  self.decimation_dict[f"{period:{PERIOD_FORMAT}}"] = {
670
716
  "level": level,
671
717
  "bands": bands,
@@ -722,7 +768,7 @@ class ZMM(ZMMHeader):
722
768
  sig_block = self._flatten_list(sig_block)
723
769
  self.sigma_s[index, 0, 0] = sig_block[0]
724
770
  self.sigma_s[index, 1, 0] = sig_block[1]
725
- self.sigma_s[index, 0, 1] = sig_block[1]
771
+ self.sigma_s[index, 0, 1] = sig_block[1].conjugate()
726
772
  self.sigma_s[index, 1, 1] = sig_block[2]
727
773
 
728
774
  def _fill_res_array_from_block(self, res_block, index):
@@ -28,6 +28,7 @@ from mt_metadata.transfer_functions.tf import (
28
28
  )
29
29
  from mt_metadata.transfer_functions.io.tools import get_nm_elev
30
30
 
31
+
31
32
  # ==============================================================================
32
33
  # deal with avg files output from mtedit
33
34
  # ==============================================================================
@@ -37,7 +38,6 @@ class ZongeMTAvg:
37
38
  """
38
39
 
39
40
  def __init__(self, fn=None, **kwargs):
40
-
41
41
  self.logger = logger
42
42
  self.header = Header()
43
43
 
@@ -122,7 +122,7 @@ class ZongeMTAvg:
122
122
  else:
123
123
  self._fn = None
124
124
 
125
- def read(self, fn=None, get_elevation=True):
125
+ def read(self, fn=None, get_elevation=False):
126
126
  """
127
127
  Read into a pandas data frame
128
128
 
@@ -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
 
@@ -25,6 +25,13 @@ STANDARD_OUTPUT_NAMES = [
25
25
  ]
26
26
 
27
27
  def load_channel_maps():
28
+ """
29
+ :return: Keys are the channel_nomenclature schema keywords.
30
+ Values are dictionaries which map the STANDARD_INPUT_NAMES, \
31
+ STANDARD_OUTPUT_NAMES to the channel names associated with a given
32
+ channel nomenclature
33
+ :rtype: dict
34
+ """
28
35
  import json
29
36
  import pathlib
30
37
  fn = pathlib.Path(__file__).parent.joinpath("standards", "channel_nomenclatures.json")
@@ -35,6 +42,12 @@ def load_channel_maps():
35
42
  CHANNEL_MAPS = load_channel_maps()
36
43
 
37
44
  def get_allowed_channel_names(standard_names):
45
+ """
46
+ :param standard_names: one of STANDARD_INPUT_NAMES, or STANDARD_OUTPUT_NAMES
47
+ :type standard_names: list
48
+ :return: allowed_names: list of channel names that are supported
49
+ :rtype: list
50
+ """
38
51
  allowed_names = []
39
52
  for ch in standard_names:
40
53
  for _, channel_map in CHANNEL_MAPS.items():
@@ -49,25 +62,12 @@ ALLOWED_OUTPUT_CHANNELS = get_allowed_channel_names(STANDARD_OUTPUT_NAMES)
49
62
  class ChannelNomenclature(Base):
50
63
  __doc__ = write_lines(attr_dict)
51
64
 
52
- def __init__(self, **kwargs):
53
-
54
- super().__init__(attr_dict=attr_dict, **kwargs)
55
- self._keyword = None
56
-
57
- def update_by_keyword(self, keyword):
58
- """
59
- Assign the HEXY values "ex", "ey" etc based on a pre-defined dict that
60
- corresponds to a common use case
61
- Parameters
62
- ----------
63
- keyword
64
-
65
- Returns
66
- -------
67
-
68
- """
69
- self._update_by_keyword(keyword)
65
+ def __init__(self, keyword=None):
70
66
 
67
+ super().__init__(attr_dict=attr_dict)
68
+ self._keyword = keyword
69
+ if self._keyword is not None:
70
+ self.update()
71
71
 
72
72
  @property
73
73
  def ex_ey(self):
@@ -100,28 +100,31 @@ class ChannelNomenclature(Base):
100
100
  @keyword.setter
101
101
  def keyword(self, keyword):
102
102
  self._keyword = keyword
103
- self._update_by_keyword(keyword)
103
+ self.update()
104
104
 
105
- def get_channel_map(self, keyword):
106
- if keyword == "default":
105
+ def get_channel_map(self):
106
+ if self.keyword == "default":
107
107
  channel_map = CHANNEL_MAPS["default"]
108
- elif keyword.upper() == "LEMI12":
108
+ elif self.keyword.upper() == "LEMI12":
109
109
  channel_map = CHANNEL_MAPS["lemi12"]
110
- elif keyword.upper() == "LEMI34":
110
+ elif self.keyword.upper() == "LEMI34":
111
111
  channel_map = CHANNEL_MAPS["lemi34"]
112
- elif keyword.upper() == "NIMS":
112
+ elif self.keyword.upper() == "NIMS":
113
113
  channel_map = CHANNEL_MAPS["default"]
114
- elif keyword.upper() == "PHOENIX123":
114
+ elif self.keyword.upper() == "PHOENIX123":
115
115
  channel_map = CHANNEL_MAPS["phoenix123"]
116
- elif keyword.upper() == "MUSGRAVES":
116
+ elif self.keyword.upper() == "MUSGRAVES":
117
117
  channel_map = CHANNEL_MAPS["musgraves"]
118
118
  else:
119
- print(f"whoops mt_system {keyword} unknown")
120
- raise NotImplementedError
119
+ msg = f"channel mt_system {self.keyword} unknown"
120
+ raise NotImplementedError(msg)
121
121
  return channel_map
122
122
 
123
- def _update_by_keyword(self, keyword):
124
- channel_map = self.get_channel_map(keyword)
123
+ def update(self):
124
+ """
125
+ Assign values to standard channel names "ex", "ey" etc based on channel_map dict
126
+ """
127
+ channel_map = self.get_channel_map()
125
128
  self.ex = channel_map["ex"]
126
129
  self.ey = channel_map["ey"]
127
130
  self.hx = channel_map["hx"]
@@ -133,5 +136,5 @@ class ChannelNomenclature(Base):
133
136
 
134
137
  @property
135
138
  def channels(self):
136
- channels = list(self.get_channel_map(self.keyword).values())
139
+ channels = list(self.get_channel_map().values())
137
140
  return channels
@@ -28,7 +28,6 @@ class Processing(Base):
28
28
  __doc__ = write_lines(attr_dict)
29
29
 
30
30
  def __init__(self, **kwargs):
31
-
32
31
  self.stations = Stations()
33
32
  self._decimations = []
34
33
  self.channel_nomenclature = ChannelNomenclature()
@@ -86,6 +85,12 @@ class Processing(Base):
86
85
  f"List entry must be a DecimationLevel or dict object not {type(obj)}"
87
86
  )
88
87
 
88
+ elif isinstance(value, str):
89
+ if len(value) > 4:
90
+ raise TypeError(f"Not sure what to do with {type(value)}")
91
+ else:
92
+ self._decimations = []
93
+
89
94
  else:
90
95
  raise TypeError(f"Not sure what to do with {type(value)}")
91
96
 
@@ -224,7 +229,6 @@ class Processing(Base):
224
229
  decimation_obj.add_band(band)
225
230
  self.add_decimation_level(decimation_obj)
226
231
 
227
-
228
232
  def json_fn(self):
229
233
  json_fn = self.id + "_processing_config.json"
230
234
  return json_fn
@@ -280,11 +284,14 @@ class Processing(Base):
280
284
  if not self.stations.remote:
281
285
  for decimation in self.decimations:
282
286
  if decimation.estimator.engine == "RME_RR":
283
- print("No RR station specified, switching RME_RR to RME")
287
+ self.logger.info(
288
+ "No RR station specified, switching RME_RR to RME"
289
+ )
284
290
  decimation.estimator.engine = "RME"
285
291
 
286
292
  # Make sure that a local station is defined
287
293
  if not self.stations.local.id:
288
- print("WARNING: Local station not specified")
289
- print("Local station should be set from Kernel Dataset")
294
+ self.logger.warning(
295
+ "Local station not specified, should be set from Kernel Dataset"
296
+ )
290
297
  self.stations.from_dataset_dataframe(kernel_dataset.df)
@@ -5,6 +5,7 @@ Created on Thu Feb 24 13:58:07 2022
5
5
  @author: jpeacock
6
6
  """
7
7
  import pandas as pd
8
+
8
9
  # =============================================================================
9
10
  # Imports
10
11
  # =============================================================================
@@ -16,6 +17,7 @@ from .station import Station
16
17
  attr_dict = get_schema("stations", SCHEMA_FN_PATHS)
17
18
  attr_dict.add_dict(get_schema("station", SCHEMA_FN_PATHS), "local")
18
19
 
20
+
19
21
  # =============================================================================
20
22
  class Stations(Base):
21
23
  """
@@ -66,6 +68,11 @@ class Stations(Base):
66
68
  elif isinstance(rr_station, Station):
67
69
  rr_station.remote = True
68
70
  self._remote.append(rr_station)
71
+
72
+ elif isinstance(rr_station, str):
73
+ if len(rr_station) > 4:
74
+ raise ValueError(f"not sure to do with {type(rr_station)}")
75
+
69
76
  else:
70
77
  raise ValueError(f"not sure to do with {type(rr_station)}")
71
78
 
@@ -75,7 +82,9 @@ class Stations(Base):
75
82
  """
76
83
 
77
84
  if not isinstance(rr, (Station, dict)):
78
- raise TypeError(f"List entry must be a Station object not {type(rr)}")
85
+ raise TypeError(
86
+ f"List entry must be a Station object not {type(rr)}"
87
+ )
79
88
  if isinstance(rr, dict):
80
89
  obj = Station()
81
90
  obj.from_dict(rr)
@@ -130,7 +139,7 @@ class Stations(Base):
130
139
  df = self.local.to_dataset_dataframe()
131
140
  for rr in self.remote:
132
141
  remote_df = rr.to_dataset_dataframe()
133
- df = pd.concat([df, remote_df]) #, axis=1, ignore_index=True)
142
+ df = pd.concat([df, remote_df]) # , axis=1, ignore_index=True)
134
143
 
135
144
  df.reset_index(inplace=True, drop=True)
136
145
 
@@ -320,7 +320,7 @@ class Decimation(Base):
320
320
  else:
321
321
  required_channels = decimation_level.local_channels
322
322
  try:
323
- assert set(self.channels_estimated) == set(required_channels)
323
+ assert set(required_channels).issubset(self.channels_estimated)
324
324
  except AssertionError:
325
325
  msg = (
326
326
  f"required_channels for processing {required_channels} not available"
@@ -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",
@@ -9,13 +9,35 @@
9
9
  "alias": [],
10
10
  "example": "ex",
11
11
  "default": null
12
+ },
13
+ "frequency_max": {
14
+ "type": "float",
15
+ "required": true,
16
+ "style": "number",
17
+ "units": "samples per second",
18
+ "description": "Highest frequency present in the sprectrogam data.",
19
+ "options": [],
20
+ "alias": [],
21
+ "example": 77.0,
22
+ "default": 0.0
23
+ },
24
+ "frequency_min": {
25
+ "type": "float",
26
+ "required": true,
27
+ "style": "number",
28
+ "units": "samples per second",
29
+ "description": "Lowest frequency present in the sprectrogam data.",
30
+ "options": [],
31
+ "alias": [],
32
+ "example": 99.0,
33
+ "default": 0.0
12
34
  },
13
35
  "sample_rate_decimation_level": {
14
36
  "type": "float",
15
37
  "required": true,
16
38
  "style": "number",
17
39
  "units": "samples per second",
18
- "description": "Sample rate of the decimation level.",
40
+ "description": "Sample rate of the time series that was Fourier transformed to generate the FC decimation level.",
19
41
  "options": [],
20
42
  "alias": [],
21
43
  "example": 60,