diff --git a/nexus/obspyImproved.py b/nexus/obspyImproved.py index f1c6bd62caec3b63fb91fa94d65bad13d17582b7..fa136b5e5dbcf54152bdfe84bfc56e0e9f15652a 100644 --- a/nexus/obspyImproved.py +++ b/nexus/obspyImproved.py @@ -6,9 +6,14 @@ Lloyd Carothers """ from collections import defaultdict import os +import io +from urllib.parse import urlparse +import operator +import obspy from obspy import read as obspy_read from obspy import Inventory +from obspy.clients.nrl.client import NRLPath, NRL, RemoteNRL, LocalNRL from obspy import UTCDateTime from obspy.core.inventory import (Network, Station, Channel, Site, Equipment) @@ -17,6 +22,7 @@ from obspy.io.mseed.core import _is_mseed from obspy.io.mseed import util from . import hardware +from .hardware import NRL_ROOT MODULE = 'Nexus.2023.4.0.0' MODULE_URI = 'www.passcal.nmt.edu' @@ -423,8 +429,7 @@ class InventoryIm(Inventory): sr = '{:g}'.format(chan.sample_rate) dl_keys = dl.get_nrl_keys(gain, sr) try: - response = hardware.get_nrl_response(dl_keys, - sensor_keys) + response = get_nrl_response(dl_keys, sensor_keys) chan.response = response print('Response computed.') except Exception as e: @@ -739,8 +744,180 @@ def scan_ms(dir_name, status_message=print): print(f'Scan took: {time.time() - start}') return streams, mseed_files + def quick_scan_ms(dir_name, status_message=print): import time start = time.time() print(f'Scan took: {time.time() - start}') + + +class NRLIm(NRL): + """ + Improve NRL object to create instance of RemoteNRLIm and LocalNRLIm + """ + def __new__(cls, root=None): + # root provided and it's no web URL + if root: + scheme = urlparse(root).scheme + if scheme in ('http', 'https'): + instance = super(NRL, cls).__new__(RemoteNRLIm) + else: + # Check if it's really a folder on the file-system. + if not os.path.isdir(root): + msg = ("Provided path '{}' seems to be a local file path " + "but the directory does not exist.").format(root) + raise ValueError(msg) + instance = super(NRL, cls).__new__(LocalNRLIm) + else: + # Otherwise delegate to the remote NRL client to deal with all + # kinds of remote resources (currently only HTTP). + instance = super(NRL, cls).__new__(RemoteNRLIm) + instance.root = root + cls.__init__(instance) + + return instance + + +class RemoteNRLIm(RemoteNRL): + """ + Improve RemoteNRLIm to use new _get_response() + """ + def _get_response(self, base, keys): + """ + Improve _get_response to use _get_value() function instead of + NRLDict's __getitem__() + + Internal helper method to fetch a response + + This circumvents the warning message that is shown for NRL v2 when a + datalogger-only response is fetched + + :type base: str + :param base: either "sensors" or "dataloggers" + :type keys: list of str + :param keys: list of lookup keys + """ + node = getattr(self, base) + for key in keys: + node = _get_value(node, key) + + # Parse to an inventory object and return a response object. + description, path, resp_type = node + with io.BytesIO(self._read_resp(path).encode()) as buf: + buf.seek(0, 0) + return obspy.read_inventory( + buf, format=resp_type)[0][0][0].response, resp_type + + +class LocalNRLIm(LocalNRL): + """ + Improve LocalNRLIm to use new _get_response() + """ + def _get_response(self, base, keys): + """ + Improve _get_response to use _get_value() function instead of + NRLDict's __getitem__() + + Internal helper method to fetch a response + + This circumvents the warning message that is shown for NRL v2 when a + datalogger-only response is fetched + + :type base: str + :param base: either "sensors" or "dataloggers" + :type keys: list of str + :param keys: list of lookup keys + """ + node = getattr(self, base) + for key in keys: + node = _get_value(node, key) + + # Parse to an inventory object and return a response object. + description, path, resp_type = node + with io.BytesIO(self._read_resp(path).encode()) as buf: + buf.seek(0, 0) + return obspy.read_inventory( + buf, format=resp_type)[0][0][0].response, resp_type + + +def get_nrl_response(dl_keys, sensor_keys): + """ + improve hardware.py's get_nrl_response() + """ + nrl = NRLIm(NRL_ROOT) + return nrl.get_response(dl_keys, sensor_keys) + + +def _get_nrl_path_from_comparison_key(nrl_dict, key, compared_value, op): + """ + In case the key start with comparison operator "<=" or ">", + compare the given compared_value with the value in the key + and return the corresponding value of the key which isNRLPath. + + :param nrl_dict: an NRLDict instance + :param key: key with comparison operator. Ex "<= 200 sps" + :param compared_value: the value to be compared + :param op: operator to be checked. Ex: "<=" or ">" + :return an NRLPath + """ + comparison_ops = { + '<=': operator.le, + '>': operator.gt + } + org_key = key + if key.strip().startswith(op): + standard_value_str = \ + key.replace(op, '').replace('sps', '').strip() + standard_value = float(standard_value_str) + if comparison_ops[op](compared_value, standard_value): + return nrl_dict.__getitem__(org_key) + + +def _get_value(nrl_dict, name): + """ + Improve from obspy.client.nrl.client.NRLDict's __getitem__ + This function along with NRLIm, RemoteNRLIm, LocalNRLIm, get_nrl_response, + _get_path should be removed and replace get_nrl_response with + hardware.get_nrl_response when obspy release an update for getting response + for Q8. + + Getting path from nrl_dict by using name as key + Normally name will be one of the keys of the nrl_dict. + """ + try: + value = nrl_dict.__getitem__(name) + except KeyError as e: + """ + The improvement is for the case of Q8 when name won't be one of + the key of the nrl_dict anymore. The NRL documentation provides + question "Is the highest simultaneous sampling rate recorded" with + different keys that are comparison expression strings with a standard + value, e.g. "<= 200 sps", typically the maximum or minimum sample rate. + This ensures that the downsampling of the sample rate aligns with the + specified value. In such cases, the program must apply the comparison + using the given key to determine the correct path. + """ + try: + name_float = float(name) + value = None + keys = nrl_dict.keys() + for key in keys: + value = _get_nrl_path_from_comparison_key( + nrl_dict, key, name_float, "<=") + if value is not None: + break + value = _get_nrl_path_from_comparison_key( + nrl_dict, key, name_float, ">") + if value is not None: + break + if value is None: + raise e + except ValueError: + raise e + + # if encountering a not yet parsed NRL Path, expand it now + if isinstance(value, NRLPath): + value = nrl_dict._nrl._parse_ini(value) + nrl_dict[name] = value + return value