Source code for mth5.io.phoenix.readers.calibrations

# -*- coding: utf-8 -*-
"""
Created on Thu Jun 15 15:21:35 2023

@author: jpeacock

Calibrations can come in json files.  the JSON file includes filters
for all lowpass filters, so you need to match the lowpass filter used in the 
setup with the lowpass filter.  Then you need to add the dipole length and
sensor calibrations.
"""

# =============================================================================
# Imports
# =============================================================================
from pathlib import Path

import numpy as np
from mt_metadata.timeseries.filters import FrequencyResponseTableFilter
from mt_metadata.utils.mttime import MTime

from .helpers import read_json_to_object

# =============================================================================


[docs]class PhoenixCalibration: def __init__(self, cal_fn=None, **kwargs): self.obj = None for key, value in kwargs.items(): setattr(self, key, value) self.cal_fn = cal_fn def __str__(self): lines = ["Phoenix Response Filters"] return "\n".join(lines) def __repr__(self): return self.__str__() @property def cal_fn(self): return self._cal_fn @cal_fn.setter def cal_fn(self, cal_fn): if cal_fn is not None: self._cal_fn = Path(cal_fn) if self._cal_fn.exists(): self.read() @property def calibration_date(self): if self._has_read(): return MTime(self.obj.timestamp_utc) def _has_read(self): if self.obj is not None: return True return False
[docs] def get_max_freq(self, freq): """ Name the filter {ch}_{max_freq}_lp_ :param freq: DESCRIPTION :type freq: TYPE :return: DESCRIPTION :rtype: TYPE """ return int(10 ** np.floor(np.log10(freq.max())))
@property def base_filter_name(self): if self._has_read(): return ( f"{self.obj.instrument_type}_" f"{self.obj.instrument_model}_" f"{self.obj.inst_serial}" ).lower()
[docs] def get_filter_lp_name(self, channel, max_freq): """ get the filter name as {instrument_model}_{instrument_type}_{inst_serial}_{channel}_{max_freq}_lp :param channel: DESCRIPTION :type channel: TYPE :param max_freq: DESCRIPTION :type max_freq: TYPE :return: DESCRIPTION :rtype: TYPE """ return ( f"{self.base_filter_name}_{channel}_{max_freq}hz_lowpass".lower() )
[docs] def get_filter_sensor_name(self, sensor): """ get the filter name as {instrument_model}_{instrument_type}_{inst_serial}_{sensor} :param max_freq: DESCRIPTION :type max_freq: TYPE :return: DESCRIPTION :rtype: TYPE """ return f"{self.base_filter_name}_{sensor}".lower()
[docs] def read(self, cal_fn=None): """ :param cal_fn: DESCRIPTION, defaults to None :type cal_fn: TYPE, optional :return: DESCRIPTION :rtype: TYPE """ if cal_fn is not None: self._cal_fn = Path(cal_fn) if not self.cal_fn.exists(): raise IOError(f"Could not find {self.cal_fn}") self.obj = read_json_to_object(self.cal_fn) for channel in self.obj.cal_data: comp = channel.tag.lower() ch_cal_dict = {} for cal in channel.chan_data: ch_fap = FrequencyResponseTableFilter( frequencies=cal.freq_Hz, amplitudes=cal.magnitude, phases=np.deg2rad(cal.phs_deg), ) max_freq = self.get_max_freq(ch_fap.frequencies) if self.obj.file_type in ["receiver calibration"]: ch_fap.name = self.get_filter_lp_name(comp, max_freq) else: ch_fap.name = self.get_filter_sensor_name( self.obj.sensor_serial ) ch_fap.calibration_date = self.obj.timestamp_utc ch_cal_dict[max_freq] = ch_fap ch_fap.units_in = "volts" ch_fap.units_out = "volts" if "sensor" in self.obj.file_type: ch_fap.units_in = "millivolts" ch_fap.units_out = "nanotesla" setattr(self, comp, ch_fap) else: setattr(self, comp, ch_cal_dict)
[docs] def get_filter(self, channel, filter_name): """ get the lowpass filter for the given channel and lowpass value :param channel: DESCRIPTION :type channel: TYPE :param lp_name: DESCRIPTION :type lp_name: TYPE :return: DESCRIPTION :rtype: TYPE """ try: filter_name = int(filter_name) except ValueError: pass try: return getattr(self, channel)[filter_name] except AttributeError: raise AttributeError(f"Could not find {channel}") except KeyError: raise KeyError(f"Could not find lowpass filter {filter_name}")