From e377116f2a1e2ada17b6eea80c698c08d223bc1d Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 10:57:18 -0500 Subject: [PATCH 01/23] ADD: MRR2 backend --- pyproject.toml | 2 +- tests/conftest.py | 41 ++ tests/io/test_metek.py | 203 ++++++++++ xradar/io/backends/__init__.py | 2 + xradar/io/backends/metek.py | 666 +++++++++++++++++++++++++++++++++ 5 files changed, 913 insertions(+), 1 deletion(-) create mode 100644 tests/io/test_metek.py create mode 100644 xradar/io/backends/metek.py diff --git a/pyproject.toml b/pyproject.toml index 634c4a10..1f79e2f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ rainbow = "xradar.io.backends:RainbowBackendEntrypoint" hpl = "xradar.io.backends:HPLBackendEntrypoint" nexradlevel2 = "xradar.io.backends:NexradLevel2BackendEntrypoint" datamet = "xradar.io.backends:DataMetBackendEntrypoint" - +metek = "xradar.io.backends:MRRBackendEntrypoint" [build-system] requires = [ diff --git a/tests/conftest.py b/tests/conftest.py index e1b1778c..ef4d6c9b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,8 @@ # Copyright (c) 2022-2023, openradar developers. # Distributed under the MIT License. See LICENSE for more info. import pytest +import gzip +import shutil from open_radar_data import DATASETS @@ -75,6 +77,45 @@ def nexradlevel2_file(): return DATASETS.fetch("KATX20130717_195021_V06") +@pytest.fixture(scope="session") +def metek_ave_gz_file(): + fnamei = DATASETS.fetch("0308.ave.gz") + fnameo = f"{fnamei[:-3]}_gz" + import gzip + import shutil + + with gzip.open(fnamei) as fin: + with open(fnameo, "wb") as fout: + shutil.copyfileobj(fin, fout) + return fnameo + + +@pytest.fixture(scope="session") +def metek_pro_gz_file(): + fnamei = DATASETS.fetch("0308.pro.gz") + fnameo = f"{fnamei[:-3]}_gz" + import gzip + import shutil + + with gzip.open(fnamei) as fin: + with open(fnameo, "wb") as fout: + shutil.copyfileobj(fin, fout) + return fnameo + + +@pytest.fixture(scope="session") +def metek_raw_gz_file(): + fnamei = DATASETS.fetch("0308.raw.gz") + fnameo = f"{fnamei[:-3]}_gz" + import gzip + import shutil + + with gzip.open(fnamei) as fin: + with open(fnameo, "wb") as fout: + shutil.copyfileobj(fin, fout) + return fnameo + + @pytest.fixture(scope="session") def nexradlevel2_gzfile(): fnamei = DATASETS.fetch("KLBB20160601_150025_V06.gz") diff --git a/tests/io/test_metek.py b/tests/io/test_metek.py new file mode 100644 index 00000000..47775d05 --- /dev/null +++ b/tests/io/test_metek.py @@ -0,0 +1,203 @@ +""" +Tests for the MRR2 backend for xradar +""" + +import numpy as np +import xarray as xr + +from xradar.io.backends import metek + +test_arr_ave = np.array( + [ + 25.4, + 24.87, + 24.63, + 25.12, + 25.39, + 26.09, + 27.21, + 28.34, + 29.41, + 31.21, + 32.29, + 28.85, + 21.96, + 19.27, + 20.19, + 21.32, + 21.49, + 20.58, + 19.43, + 18.07, + 16.79, + 15.9, + 14.59, + 14.35, + 13.41, + 11.71, + 10.63, + 10.48, + 7.84, + 4.25, + 4.23, + ] +) + +test_arr = np.array( + [ + 24.46, + 25.31, + 26.33, + 26.31, + 26.85, + 27.93, + 29.12, + 30.17, + 30.99, + 32.58, + 33.13, + 28.84, + 22.16, + 19.81, + 21.26, + 21.33, + 20.33, + 18.93, + 17.92, + 18.04, + 16.86, + 14.46, + 13.17, + 13.13, + 11.75, + 10.53, + 9.3, + 5.92, + -4.77, + np.nan, + 6.74, + ] +) + +test_raw = np.array( + [ + 1.090e03, + 6.330e02, + 1.250e02, + 1.000e01, + 2.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 3.000e00, + 3.000e00, + 3.000e00, + 4.000e00, + 6.000e00, + 8.000e00, + 1.100e01, + 1.600e01, + 2.700e01, + 6.200e01, + 1.370e02, + 2.130e02, + 2.560e02, + 3.550e02, + 5.880e02, + 1.087e03, + 1.554e03, + 1.767e03, + 1.910e03, + 1.977e03, + 2.002e03, + 2.039e03, + 1.926e03, + 1.837e03, + 1.893e03, + 1.837e03, + 1.926e03, + 2.039e03, + 2.002e03, + 1.977e03, + 1.910e03, + 1.767e03, + 1.554e03, + 1.087e03, + 5.880e02, + 3.550e02, + 2.560e02, + 2.130e02, + 1.370e02, + 6.200e01, + 2.700e01, + 1.600e01, + 1.100e01, + 8.000e00, + 6.000e00, + 4.000e00, + 3.000e00, + 3.000e00, + 3.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 1.000e01, + 1.250e02, + 6.330e02, + ] +) + + +def test_open_average(metek_ave_gz_file): + ds = xr.open_dataset(metek_ave_gz_file, engine="metek") + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 60.0 + np.testing.assert_allclose(rainfall.values[-1], 0.938) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr_ave) + ds.close() + + +def test_open_average_datatree(metek_ave_gz_file): + ds = metek.open_metek_datatree(metek_ave_gz_file) + assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() + assert "velocity" in ds["sweep_0"].variables.keys() + rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 60.0 + np.testing.assert_allclose(rainfall.values[-1], 0.938) + np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr_ave) + del ds + + +def test_open_processed(metek_pro_gz_file): + ds = xr.open_dataset(metek_pro_gz_file, engine="metek") + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 360.0 + np.testing.assert_allclose(rainfall.values[-1], 0.93) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr) + ds.close() + + +def test_open_processed_datatree(metek_pro_gz_file): + ds = metek.open_metek_datatree(metek_pro_gz_file) + assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() + assert "velocity" in ds["sweep_0"].variables.keys() + rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 360.0 + np.testing.assert_allclose(rainfall.values[-1], 0.93) + np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr) + del ds + + +def test_open_raw(metek_raw_gz_file): + ds = xr.open_dataset(metek_raw_gz_file, engine="metek") + assert "raw_spectra_counts" in ds.variables.keys() + np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) + ds.close() + + +def test_open_raw_datatree(metek_raw_gz_file): + ds = metek.open_metek_datatree(metek_raw_gz_file) + assert "raw_spectra_counts" in ds["sweep_0"].variables.keys() + np.testing.assert_allclose(ds["sweep_0"]["raw_spectra_counts"].values[0], test_raw) + del ds diff --git a/xradar/io/backends/__init__.py b/xradar/io/backends/__init__.py index 6ce42e33..946d0b54 100644 --- a/xradar/io/backends/__init__.py +++ b/xradar/io/backends/__init__.py @@ -18,6 +18,7 @@ .. automodule:: xradar.io.backends.hpl .. automodule:: xradar.io.backends.nexrad_level2 .. automodule:: xradar.io.backends.datamet +.. automodule:: xradar.io.backends.metek """ @@ -30,5 +31,6 @@ from .hpl import * # noqa from .nexrad_level2 import * # noqa from .datamet import * # noqa +from .metek import * # noqa __all__ = [s for s in dir() if not s.startswith("_")] diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py new file mode 100644 index 00000000..44ec8ee1 --- /dev/null +++ b/xradar/io/backends/metek.py @@ -0,0 +1,666 @@ +""" +Metek MRR2 raw and processed data +================================= +Read data from METEK's MRR-2 raw (.raw) and processed (.pro, .avg) files. + +Example:: + + import xradar as xd + ds = xr.open_dataset('0308.pro', engine='metek') + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + {} +""" + +import io +import warnings +from datetime import datetime + +import numpy as np +import xarray as xr +from datatree import DataTree +from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint +from xarray.backends.file_manager import CachingFileManager +from xarray.backends.store import StoreBackendEntrypoint +from xarray.core import indexing +from xarray.core.utils import FrozenDict + +from ...model import ( + get_altitude_attrs, + get_azimuth_attrs, + get_elevation_attrs, + get_latitude_attrs, + get_longitude_attrs, + get_time_attrs, +) +from .common import _assign_root, _attach_sweep_groups + +__all__ = [ + "MRRBackendEntrypoint", + "open_metek_datatree", +] + +__doc__ = __doc__.format("\n ".join(__all__)) + +variable_attr_dict = dict( + transfer_function={ + "long_name": "Transfer function", + "standard_name": "transfer_function", + "units": "1", + "dims": ("time", "range"), + }, + spectral_reflectivity={ + "long_name": "Spectral reflectivity", + "standard_name": "equivalent_reflectivity_factor", + "units": "dB", + "dims": ("index", "sample"), + }, + raw_spectra_counts={ + "long_name": "Raw spectra counts", + "standard_name": "raw_spectra", + "units": "", + "dims": ("index", "sample"), + }, + drop_size={ + "long_name": "Drop size", + "standard_name": "drop_size", + "units": "mm", + "dims": ("index", "sample"), + }, + drop_number_density={ + "long_name": "Raindrop number density", + "standard_name": "raindrop_number_density", + "units": "m-4", + "dims": ("index", "sample"), + }, + rainfall_rate={ + "long_name": "Rainfall rate", + "standard_name": "rainfall_rate", + "units": "mm hr-1", + "dims": ("time", "range"), + }, + liquid_water_content={ + "long_name": "Liquid water content", + "standard_name": "liquid_water_content", + "units": "g m-3", + "dims": ("time", "range"), + }, + path_integrated_attenuation={ + "long_name": "Path integrated attenuation", + "standard_name": "path_integrated_attenuation", + "units": "dB", + "dims": ("time", "range"), + }, + corrected_reflectivity={ + "long_name": "Attenuation-corrected Radar reflectivity factor", + "standard_name": "equivalent_radar_reflectivity_factor", + "units": "dBZ", + "dims": ("time", "range"), + }, + reflectivity={ + "long_name": "Radar reflectivity factor", + "standard_name": "equivalent_radar_reflectivity_factor", + "units": "dBZ", + "dims": ("time", "range"), + }, + spectrum_index={ + "long_name": "Spectrum index", + "standard_name": "spectrum_index", + "units": "1", + "dims": ("time", "range"), + }, + percentage_valid_spectra={ + "long_name": "Percentage of spectra that are valid", + "standard_name": "percentage_valid_spectra", + "units": "percent", + "dims": ("time"), + }, + number_valid_spectra={ + "long_name": "number of spectra that are valid", + "standard_name": "number_valid_spectra", + "units": "1", + "dims": ("time"), + }, + total_number_spectra={ + "long_name": "Total number of spectra", + "standard_name": "total_number_spectra", + "units": "1", + "dims": ("time"), + }, + velocity_bins={ + "long_name": "Doppler velocity bins", + "standard_name": "doppler_velocity_bins", + "units": "m s-1", + "dims": ("sample"), + }, + range={ + "long_name": "Range from radar", + "standard_name": "range", + "units": "m", + "dims": ("range",), + }, + time=get_time_attrs(), + azimuth=get_azimuth_attrs(), + elevation=get_elevation_attrs(), + velocity={ + "long_name": "Radial velocity of scatterers toward instrument", + "standard_name": "radial_velocity_of_scatterers_toward_instrument", + "units": "m s-1", + }, + latitude=get_latitude_attrs(), + longitude=get_longitude_attrs(), + altitude=get_altitude_attrs(), +) + +variable_attr_dict["time"]["dims"] = ("time",) +variable_attr_dict["azimuth"]["dims"] = ("time",) +variable_attr_dict["elevation"]["dims"] = ("time",) +variable_attr_dict["velocity"]["dims"] = ("time", "range") +variable_attr_dict["latitude"]["dims"] = () +variable_attr_dict["longitude"]["dims"] = () +variable_attr_dict["altitude"]["dims"] = () + + +def _parse_spectra_line(input_str, num_gates): + out_array = np.zeros(num_gates) + increment = {32: 9, 31: 7}[num_gates] + for i, pos in enumerate(range(3, len(input_str) - increment, increment)): + input_num_str = input_str[pos : pos + increment] + try: + out_array[i] = float(input_num_str) + except ValueError: + out_array[i] = np.nan + + return out_array + + +class MRR2File: + def __init__(self, file_name="", **kwargs): + self.vel_bin_spacing = 0.1887 + self.nyquist_velocity = self.vel_bin_spacing * 64 + self._data = {} + self._data["velocity_bins"] = np.arange( + 0, 64 * self.vel_bin_spacing, self.vel_bin_spacing + ) + self._data["range"] = [] + self._data["transfer_function"] = [] + self._data["spectral_reflectivity"] = [] + self._data["raw_spectra_counts"] = [] + self._data["drop_size"] = [] + self._data["drop_number_density"] = [] + self._data["time"] = [] + self.filetype = "" + self.device_version = "" + self.device_serial_number = "" + self.bandwidth = 0 + self._fp = None + self.calibration_constant = [] + self._data["percentage_valid_spectra"] = [] + self._data["number_valid_spectra"] = [] + self._data["total_number_spectra"] = [] + self.spectra_index = 0 + self.altitude = None + self.sampling_rate = 125000.0 + self._data["path_integrated_attenuation"] = [] + self._data["corrected_reflectivity"] = [] + self._data["reflectivity"] = [] + self._data["rainfall_rate"] = [] + self._data["liquid_water_content"] = [] + self._data["velocity"] = [] + self._data["altitude"] = np.array(np.nan) + self._data["longitude"] = np.array(np.nan) + self._data["latitude"] = np.array(np.nan) + self.filename = None + self.n_gates = 32 + if not file_name == "": + self.filename = file_name + self.open(file_name) + + def open(self, filename_or_obj): + if isinstance(filename_or_obj, io.IOBase): + filename_or_obj.seek(0) + self._fp = filename_or_obj + + if isinstance(filename_or_obj, str): + self.filename = filename_or_obj + self._fp = open(filename_or_obj) + + num_times = 0 + temp_spectra = np.zeros((self.n_gates, 64)) + temp_drops = np.zeros((self.n_gates, 64)) + temp_number = np.zeros((self.n_gates, 64)) + spec_var = "" + for file_line in self._fp: + if file_line[:3] == "MRR": + if num_times > 0: + self._data[spec_var].append(temp_spectra) + self._data["drop_number_density"].append(temp_number) + self._data["drop_size"].append(temp_drops) + + string_split = file_line.split() + time_str = string_split[1] + parsed_datetime = datetime.strptime(time_str, "%y%m%d%H%M%S") + self._data["time"] = self._data["time"] + [parsed_datetime] + self.filetype = string_split[-1] + if self.filetype == "RAW": + self.device_version = string_split[4] + self.device_serial_number = string_split[6] + self.bandwidth = int(string_split[8]) + self.calibration_constant.append(int(string_split[10])) + self._data["percentage_valid_spectra"].append(int(string_split[12])) + self._data["number_valid_spectra"].append(int(string_split[13])) + self._data["total_number_spectra"].append(int(string_split[14])) + self.n_gates = 32 + spec_var = "raw_spectra_counts" + elif self.filetype == "AVE" or self.filetype == "PRO": + self._data["altitude"] = np.array(float(string_split[8])) + self.sampling_rate = float(string_split[10]) + self.mrr_service_version = string_split[12] + self.device_version = string_split[14] + self.calibration_constant.append(int(string_split[16])) + self._data["percentage_valid_spectra"].append(int(string_split[18])) + self.n_gates = 31 + spec_var = "spectral_reflectivity" + else: + raise OSError( + "Invalid file type flag in file! Must be RAW, AVG, or PRO!" + ) + temp_spectra = np.zeros((self.n_gates, 64)) + temp_drops = np.zeros((self.n_gates, 64)) + temp_number = np.zeros((self.n_gates, 64)) + num_times = num_times + 1 + + if file_line[0] == "H": + in_array = _parse_spectra_line(file_line, self.n_gates) + if num_times > 1: + after_res = in_array[1] - in_array[0] + before_res = self._data["range"][1] - self._data["range"][0] + if not after_res == before_res: + warnings.warn( + f"MRR2 resolution was changed mid file. Before time period " + f"{parsed_datetime} the resolution was {before_res}, " + f"and {after_res} after.", + UserWarning, + ) + self._data["range"] = in_array + + if file_line[0:2] == "TF": + self._data["transfer_function"].append( + _parse_spectra_line(file_line, self.n_gates) + ) + if file_line[0] == "F": + spectra_bin_no = int(file_line[1:3]) + temp_spectra[:, spectra_bin_no] = _parse_spectra_line( + file_line, self.n_gates + ) + if file_line[0] == "D": + spectra_bin_no = int(file_line[1:3]) + temp_drops[:, spectra_bin_no] = _parse_spectra_line( + file_line, self.n_gates + ) + if file_line[0] == "N": + spectra_bin_no = int(file_line[1:3]) + temp_number[:, spectra_bin_no] = _parse_spectra_line( + file_line, self.n_gates + ) + if file_line[0:3] == "PIA": + self._data["path_integrated_attenuation"] = self._data[ + "path_integrated_attenuation" + ] + [_parse_spectra_line(file_line, self.n_gates)] + if file_line[0:3] == "z ": + self._data["reflectivity"] = self._data["reflectivity"] + [ + _parse_spectra_line(file_line, self.n_gates) + ] + if file_line[0:3] == "Z ": + self._data["corrected_reflectivity"] = self._data[ + "corrected_reflectivity" + ] + [_parse_spectra_line(file_line, self.n_gates)] + if file_line[0:3] == "RR ": + self._data["rainfall_rate"] = self._data["rainfall_rate"] + [ + _parse_spectra_line(file_line, self.n_gates) + ] + if file_line[0:3] == "LWC": + self._data["liquid_water_content"] = self._data[ + "liquid_water_content" + ] + [_parse_spectra_line(file_line, self.n_gates)] + if file_line[0:3] == "W ": + self._data["velocity"] = self._data["velocity"] + [ + _parse_spectra_line(file_line, self.n_gates) + ] + + self._data[spec_var].append(temp_spectra) + self._data["drop_number_density"].append(temp_number) + self._data["drop_size"].append(temp_drops) + self._data["transfer_function"] = np.stack( + self._data["transfer_function"], axis=0 + ) + self._data[spec_var] = np.stack(self._data[spec_var], axis=0) + self._data["drop_number_density"] = np.stack( + self._data["drop_number_density"], axis=0 + ) + self._data["drop_size"] = np.stack(self._data["drop_size"], axis=0) + + if self.filetype == "RAW": + self._data["total_number_spectra"] = np.stack( + self._data["total_number_spectra"], axis=0 + ) + self._data["number_valid_spectra"] = np.stack( + self._data["number_valid_spectra"], axis=0 + ) + + del self._data["reflectivity"] + del self._data["corrected_reflectivity"] + del self._data["liquid_water_content"] + del self._data["rainfall_rate"] + del self._data["percentage_valid_spectra"] + del self._data["drop_number_density"] + del self._data["drop_size"] + del self._data["path_integrated_attenuation"] + del self._data["velocity"] + del self._data["spectral_reflectivity"] + else: + del self._data["total_number_spectra"], self._data["number_valid_spectra"] + self._data["reflectivity"] = np.stack(self._data["reflectivity"], axis=0) + self._data["path_integrated_attenuation"] = np.stack( + self._data["path_integrated_attenuation"], axis=0 + ) + self._data["corrected_reflectivity"] = np.stack( + self._data["corrected_reflectivity"], axis=0 + ) + + self._data["liquid_water_content"] = np.stack( + self._data["liquid_water_content"], axis=0 + ) + self._data["velocity"] = np.stack(self._data["velocity"], axis=0) + self._data["rainfall_rate"] = np.stack(self._data["rainfall_rate"], axis=0) + self._data["percentage_valid_spectra"] = np.stack( + self._data["percentage_valid_spectra"], axis=0 + ) + self._data["drop_number_density"] = np.stack( + self._data["drop_number_density"], axis=0 + ) + self._data["drop_size"] = np.stack(self._data["drop_size"], axis=0) + del self._data["raw_spectra_counts"] + + self._data["range"] = np.squeeze(self._data["range"]) + # Now we compress the spectrum variables to remove invalid spectra + self._data[spec_var] = self._data[spec_var].reshape( + self._data[spec_var].shape[0] * self._data[spec_var].shape[1], + self._data[spec_var].shape[2], + ) + where_valid_spectra = np.any(np.isfinite(self._data[spec_var]), axis=1) + inds = np.where(where_valid_spectra, 1, -1) + + self._data[spec_var] = self._data[spec_var][where_valid_spectra] + if self.filetype == "PRO" or self.filetype == "AVE": + self._data["drop_number_density"] = self._data[ + "drop_number_density" + ].reshape( + self._data["drop_number_density"].shape[0] + * self._data["drop_number_density"].shape[1], + self._data["drop_number_density"].shape[2], + ) + self._data["drop_number_density"] = self._data["drop_number_density"][ + where_valid_spectra + ] + self._data["drop_size"] = self._data["drop_size"].reshape( + self._data["drop_size"].shape[0] * self._data["drop_size"].shape[1], + self._data["drop_size"].shape[2], + ) + self._data["drop_size"] = self._data["drop_size"][where_valid_spectra] + cur_index = 0 + for i in range(len(inds)): + if inds[i] > -1: + inds[i] = cur_index + cur_index += 1 + self._data["spectrum_index"] = inds.reshape( + (len(self._data["time"]), len(self._data["range"])) + ) + + self._data["azimuth"] = np.zeros_like(self._data["time"]) + self._data["elevation"] = 90 * np.ones_like(self._data["time"]) + self._data["time"] = np.array(self._data["time"]) + + def close(self): + if self._fp is not None: + self._fp.close() + del self._data + + __del__ = close + + @property + def data(self): + return self._data + + @property + def coordinates(self): + return self._coordinates + + @property + def fixed_angle(self): + return self._data["elevation"] + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + +class MRR2ArrayWrapper(BackendArray): + def __init__( + self, + data, + ): + self.data = data + self.shape = data.shape + self.dtype = np.dtype("float64") + + def __getitem__(self, key: tuple): + return indexing.explicit_indexing_adapter( + key, + self.shape, + indexing.IndexingSupport.OUTER_1VECTOR, + self._raw_indexing_method, + ) + + def _raw_indexing_method(self, key: tuple): + return self.data[key] + + +class MRR2DataStore(AbstractDataStore): + def __init__(self, manager, group=None): + self._manager = manager + self._group = group + self._filename = self.filename + self._need_time_recalc = False + + @classmethod + def open(cls, filename, mode="r", group=None, **kwargs): + manager = CachingFileManager(MRR2File, filename, mode=mode, kwargs=kwargs) + return cls(manager, group=group) + + @property + def filename(self): + with self._manager.acquire_context(False) as root: + return root.filename + + @property + def root(self): + with self._manager.acquire_context(False) as root: + return root + + def _acquire(self, needs_lock=True): + with self._manager.acquire_context(needs_lock) as root: + return root + + @property + def ds(self): + return self._acquire() + + def open_store_variable(self, name, var): + data = indexing.LazilyOuterIndexedArray(MRR2ArrayWrapper(var)) + encoding = {"group": self._group, "source": self._filename} + attrs = variable_attr_dict[name].copy() + dims = attrs["dims"] + del attrs["dims"] + return xr.Variable(dims, data, attrs, encoding) + + def open_store_coordinates(self): + coord_keys = ["time", "range", "velocity_bins"] + coords = {} + for k in coord_keys: + attrs = variable_attr_dict[k].copy() + dims = attrs["dims"] + del attrs["dims"] + coords[k] = xr.Variable(dims, self.ds.data[k], attrs=attrs) + + return coords + + def get_variables(self): + return FrozenDict( + (k1, v1) + for k1, v1 in { + **{k: self.open_store_variable(k, v) for k, v in self.ds.data.items()}, + **self.open_store_coordinates(), + }.items() + ) + + def get_attrs(self): + return FrozenDict() + + +class MRRBackendEntrypoint(BackendEntrypoint): + """Xarray BackendEntrypoint for Metek MRR2 data. + + Keyword Arguments + ----------------- + first_dim : str + Can be ``time`` or ``auto`` first dimension. If set to ``auto``, + first dimension will be either ``azimuth`` or ``elevation`` depending on + type of sweep. Defaults to ``auto``. + site_coords : bool + Attach radar site-coordinates to Dataset, defaults to ``True``. + kwargs : dict + Additional kwargs are fed to :py:func:`xarray.open_dataset`. + """ + + description = "Backend for reading Metek MRR2 processed and raw data" + url = "https://xradar.rtfd.io/en/latest/io.html#metek" + + def open_dataset( + self, + filename_or_obj, + *, + mask_and_scale=True, + decode_times=True, + concat_characters=True, + decode_coords=True, + drop_variables=None, + use_cftime=None, + decode_timedelta=None, + format=None, + group="/", + invalid_netcdf=None, + phony_dims="access", + decode_vlen_strings=True, + first_dim="auto", + site_coords=True, + optional=True, + ): + store_entrypoint = StoreBackendEntrypoint() + + store = MRR2DataStore.open( + filename_or_obj, + format=format, + group=group, + invalid_netcdf=invalid_netcdf, + phony_dims=phony_dims, + decode_vlen_strings=decode_vlen_strings, + ) + + ds = store_entrypoint.open_dataset( + store, + mask_and_scale=mask_and_scale, + decode_times=decode_times, + concat_characters=concat_characters, + decode_coords=decode_coords, + drop_variables=drop_variables, + use_cftime=use_cftime, + decode_timedelta=decode_timedelta, + ) + + ds = ds.assign_coords({"range": ds.range}) + ds = ds.assign_coords({"time": ds.time}) + ds = ds.assign_coords({"velocity_bins": ds.velocity_bins}) + ds.encoding["engine"] = "metek" + + return ds + + +def open_metek_datatree(filename_or_obj, **kwargs): + """Open Metek MRR2 dataset as :py:class:`datatree.DataTree`. + + Parameters + ---------- + filename_or_obj : str, Path, file-like or DataStore + Strings and Path objects are interpreted as a path to a local or remote + radar file + + Keyword Arguments + ----------------- + sweep : int, list of int, optional + Sweep number(s) to extract, default to first sweep. If None, all sweeps are + extracted into a list. + first_dim : str + Can be ``time`` or ``auto`` first dimension. If set to ``auto``, + first dimension will be either ``azimuth`` or ``elevation`` depending on + type of sweep. Defaults to ``auto``. + reindex_angle : bool or dict + Defaults to False, no reindexing. Given dict should contain the kwargs to + reindex_angle. Only invoked if `decode_coord=True`. + fix_second_angle : bool + If True, fixes erroneous second angle data. Defaults to ``False``. + site_coords : bool + Attach radar site-coordinates to Dataset, defaults to ``True``. + kwargs : dict + Additional kwargs are fed to :py:func:`xarray.open_dataset`. + + Returns + ------- + dtree: datatree.DataTree + DataTree + """ + # handle kwargs, extract first_dim + backend_kwargs = kwargs.pop("backend_kwargs", {}) + # first_dim = backend_kwargs.pop("first_dim", None) + sweep = kwargs.pop("sweep", None) + sweeps = [] + kwargs["backend_kwargs"] = backend_kwargs + + if isinstance(sweep, str): + sweeps = [sweep] + elif isinstance(sweep, int): + sweeps = [f"sweep_{sweep}"] + elif isinstance(sweep, list): + if isinstance(sweep[0], int): + sweeps = [f"sweep_{i + 1}" for i in sweep] + else: + sweeps.extend(sweep) + else: + sweeps = ["sweep_0"] + + ds = [ + xr.open_dataset(filename_or_obj, group=swp, engine="metek", **kwargs) + for swp in sweeps + ] + + ds.insert(0, xr.Dataset()) # open_dataset(filename_or_obj, group="/")) + + # create datatree root node with required data + dtree = DataTree(data=_assign_root(ds), name="root") + # return datatree with attached sweep child nodes + return _attach_sweep_groups(dtree, ds[1:]) From 3e61a87b3471a2863688c598978d354e2be19637 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 11:07:50 -0500 Subject: [PATCH 02/23] Revert "ADD: MRR2 backend" This reverts commit e377116f2a1e2ada17b6eea80c698c08d223bc1d. --- pyproject.toml | 2 +- tests/conftest.py | 41 -- tests/io/test_metek.py | 203 ---------- xradar/io/backends/__init__.py | 2 - xradar/io/backends/metek.py | 666 --------------------------------- 5 files changed, 1 insertion(+), 913 deletions(-) delete mode 100644 tests/io/test_metek.py delete mode 100644 xradar/io/backends/metek.py diff --git a/pyproject.toml b/pyproject.toml index 1f79e2f0..634c4a10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ rainbow = "xradar.io.backends:RainbowBackendEntrypoint" hpl = "xradar.io.backends:HPLBackendEntrypoint" nexradlevel2 = "xradar.io.backends:NexradLevel2BackendEntrypoint" datamet = "xradar.io.backends:DataMetBackendEntrypoint" -metek = "xradar.io.backends:MRRBackendEntrypoint" + [build-system] requires = [ diff --git a/tests/conftest.py b/tests/conftest.py index ef4d6c9b..e1b1778c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,8 +2,6 @@ # Copyright (c) 2022-2023, openradar developers. # Distributed under the MIT License. See LICENSE for more info. import pytest -import gzip -import shutil from open_radar_data import DATASETS @@ -77,45 +75,6 @@ def nexradlevel2_file(): return DATASETS.fetch("KATX20130717_195021_V06") -@pytest.fixture(scope="session") -def metek_ave_gz_file(): - fnamei = DATASETS.fetch("0308.ave.gz") - fnameo = f"{fnamei[:-3]}_gz" - import gzip - import shutil - - with gzip.open(fnamei) as fin: - with open(fnameo, "wb") as fout: - shutil.copyfileobj(fin, fout) - return fnameo - - -@pytest.fixture(scope="session") -def metek_pro_gz_file(): - fnamei = DATASETS.fetch("0308.pro.gz") - fnameo = f"{fnamei[:-3]}_gz" - import gzip - import shutil - - with gzip.open(fnamei) as fin: - with open(fnameo, "wb") as fout: - shutil.copyfileobj(fin, fout) - return fnameo - - -@pytest.fixture(scope="session") -def metek_raw_gz_file(): - fnamei = DATASETS.fetch("0308.raw.gz") - fnameo = f"{fnamei[:-3]}_gz" - import gzip - import shutil - - with gzip.open(fnamei) as fin: - with open(fnameo, "wb") as fout: - shutil.copyfileobj(fin, fout) - return fnameo - - @pytest.fixture(scope="session") def nexradlevel2_gzfile(): fnamei = DATASETS.fetch("KLBB20160601_150025_V06.gz") diff --git a/tests/io/test_metek.py b/tests/io/test_metek.py deleted file mode 100644 index 47775d05..00000000 --- a/tests/io/test_metek.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -Tests for the MRR2 backend for xradar -""" - -import numpy as np -import xarray as xr - -from xradar.io.backends import metek - -test_arr_ave = np.array( - [ - 25.4, - 24.87, - 24.63, - 25.12, - 25.39, - 26.09, - 27.21, - 28.34, - 29.41, - 31.21, - 32.29, - 28.85, - 21.96, - 19.27, - 20.19, - 21.32, - 21.49, - 20.58, - 19.43, - 18.07, - 16.79, - 15.9, - 14.59, - 14.35, - 13.41, - 11.71, - 10.63, - 10.48, - 7.84, - 4.25, - 4.23, - ] -) - -test_arr = np.array( - [ - 24.46, - 25.31, - 26.33, - 26.31, - 26.85, - 27.93, - 29.12, - 30.17, - 30.99, - 32.58, - 33.13, - 28.84, - 22.16, - 19.81, - 21.26, - 21.33, - 20.33, - 18.93, - 17.92, - 18.04, - 16.86, - 14.46, - 13.17, - 13.13, - 11.75, - 10.53, - 9.3, - 5.92, - -4.77, - np.nan, - 6.74, - ] -) - -test_raw = np.array( - [ - 1.090e03, - 6.330e02, - 1.250e02, - 1.000e01, - 2.000e00, - 2.000e00, - 2.000e00, - 2.000e00, - 3.000e00, - 3.000e00, - 3.000e00, - 4.000e00, - 6.000e00, - 8.000e00, - 1.100e01, - 1.600e01, - 2.700e01, - 6.200e01, - 1.370e02, - 2.130e02, - 2.560e02, - 3.550e02, - 5.880e02, - 1.087e03, - 1.554e03, - 1.767e03, - 1.910e03, - 1.977e03, - 2.002e03, - 2.039e03, - 1.926e03, - 1.837e03, - 1.893e03, - 1.837e03, - 1.926e03, - 2.039e03, - 2.002e03, - 1.977e03, - 1.910e03, - 1.767e03, - 1.554e03, - 1.087e03, - 5.880e02, - 3.550e02, - 2.560e02, - 2.130e02, - 1.370e02, - 6.200e01, - 2.700e01, - 1.600e01, - 1.100e01, - 8.000e00, - 6.000e00, - 4.000e00, - 3.000e00, - 3.000e00, - 3.000e00, - 2.000e00, - 2.000e00, - 2.000e00, - 2.000e00, - 1.000e01, - 1.250e02, - 6.330e02, - ] -) - - -def test_open_average(metek_ave_gz_file): - ds = xr.open_dataset(metek_ave_gz_file, engine="metek") - assert "corrected_reflectivity" in ds.variables.keys() - assert "velocity" in ds.variables.keys() - rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 60.0 - np.testing.assert_allclose(rainfall.values[-1], 0.938) - np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr_ave) - ds.close() - - -def test_open_average_datatree(metek_ave_gz_file): - ds = metek.open_metek_datatree(metek_ave_gz_file) - assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() - assert "velocity" in ds["sweep_0"].variables.keys() - rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 60.0 - np.testing.assert_allclose(rainfall.values[-1], 0.938) - np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr_ave) - del ds - - -def test_open_processed(metek_pro_gz_file): - ds = xr.open_dataset(metek_pro_gz_file, engine="metek") - assert "corrected_reflectivity" in ds.variables.keys() - assert "velocity" in ds.variables.keys() - rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 360.0 - np.testing.assert_allclose(rainfall.values[-1], 0.93) - np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr) - ds.close() - - -def test_open_processed_datatree(metek_pro_gz_file): - ds = metek.open_metek_datatree(metek_pro_gz_file) - assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() - assert "velocity" in ds["sweep_0"].variables.keys() - rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 360.0 - np.testing.assert_allclose(rainfall.values[-1], 0.93) - np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr) - del ds - - -def test_open_raw(metek_raw_gz_file): - ds = xr.open_dataset(metek_raw_gz_file, engine="metek") - assert "raw_spectra_counts" in ds.variables.keys() - np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) - ds.close() - - -def test_open_raw_datatree(metek_raw_gz_file): - ds = metek.open_metek_datatree(metek_raw_gz_file) - assert "raw_spectra_counts" in ds["sweep_0"].variables.keys() - np.testing.assert_allclose(ds["sweep_0"]["raw_spectra_counts"].values[0], test_raw) - del ds diff --git a/xradar/io/backends/__init__.py b/xradar/io/backends/__init__.py index 946d0b54..6ce42e33 100644 --- a/xradar/io/backends/__init__.py +++ b/xradar/io/backends/__init__.py @@ -18,7 +18,6 @@ .. automodule:: xradar.io.backends.hpl .. automodule:: xradar.io.backends.nexrad_level2 .. automodule:: xradar.io.backends.datamet -.. automodule:: xradar.io.backends.metek """ @@ -31,6 +30,5 @@ from .hpl import * # noqa from .nexrad_level2 import * # noqa from .datamet import * # noqa -from .metek import * # noqa __all__ = [s for s in dir() if not s.startswith("_")] diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py deleted file mode 100644 index 44ec8ee1..00000000 --- a/xradar/io/backends/metek.py +++ /dev/null @@ -1,666 +0,0 @@ -""" -Metek MRR2 raw and processed data -================================= -Read data from METEK's MRR-2 raw (.raw) and processed (.pro, .avg) files. - -Example:: - - import xradar as xd - ds = xr.open_dataset('0308.pro', engine='metek') - -.. autosummary:: - :nosignatures: - :toctree: generated/ - - {} -""" - -import io -import warnings -from datetime import datetime - -import numpy as np -import xarray as xr -from datatree import DataTree -from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint -from xarray.backends.file_manager import CachingFileManager -from xarray.backends.store import StoreBackendEntrypoint -from xarray.core import indexing -from xarray.core.utils import FrozenDict - -from ...model import ( - get_altitude_attrs, - get_azimuth_attrs, - get_elevation_attrs, - get_latitude_attrs, - get_longitude_attrs, - get_time_attrs, -) -from .common import _assign_root, _attach_sweep_groups - -__all__ = [ - "MRRBackendEntrypoint", - "open_metek_datatree", -] - -__doc__ = __doc__.format("\n ".join(__all__)) - -variable_attr_dict = dict( - transfer_function={ - "long_name": "Transfer function", - "standard_name": "transfer_function", - "units": "1", - "dims": ("time", "range"), - }, - spectral_reflectivity={ - "long_name": "Spectral reflectivity", - "standard_name": "equivalent_reflectivity_factor", - "units": "dB", - "dims": ("index", "sample"), - }, - raw_spectra_counts={ - "long_name": "Raw spectra counts", - "standard_name": "raw_spectra", - "units": "", - "dims": ("index", "sample"), - }, - drop_size={ - "long_name": "Drop size", - "standard_name": "drop_size", - "units": "mm", - "dims": ("index", "sample"), - }, - drop_number_density={ - "long_name": "Raindrop number density", - "standard_name": "raindrop_number_density", - "units": "m-4", - "dims": ("index", "sample"), - }, - rainfall_rate={ - "long_name": "Rainfall rate", - "standard_name": "rainfall_rate", - "units": "mm hr-1", - "dims": ("time", "range"), - }, - liquid_water_content={ - "long_name": "Liquid water content", - "standard_name": "liquid_water_content", - "units": "g m-3", - "dims": ("time", "range"), - }, - path_integrated_attenuation={ - "long_name": "Path integrated attenuation", - "standard_name": "path_integrated_attenuation", - "units": "dB", - "dims": ("time", "range"), - }, - corrected_reflectivity={ - "long_name": "Attenuation-corrected Radar reflectivity factor", - "standard_name": "equivalent_radar_reflectivity_factor", - "units": "dBZ", - "dims": ("time", "range"), - }, - reflectivity={ - "long_name": "Radar reflectivity factor", - "standard_name": "equivalent_radar_reflectivity_factor", - "units": "dBZ", - "dims": ("time", "range"), - }, - spectrum_index={ - "long_name": "Spectrum index", - "standard_name": "spectrum_index", - "units": "1", - "dims": ("time", "range"), - }, - percentage_valid_spectra={ - "long_name": "Percentage of spectra that are valid", - "standard_name": "percentage_valid_spectra", - "units": "percent", - "dims": ("time"), - }, - number_valid_spectra={ - "long_name": "number of spectra that are valid", - "standard_name": "number_valid_spectra", - "units": "1", - "dims": ("time"), - }, - total_number_spectra={ - "long_name": "Total number of spectra", - "standard_name": "total_number_spectra", - "units": "1", - "dims": ("time"), - }, - velocity_bins={ - "long_name": "Doppler velocity bins", - "standard_name": "doppler_velocity_bins", - "units": "m s-1", - "dims": ("sample"), - }, - range={ - "long_name": "Range from radar", - "standard_name": "range", - "units": "m", - "dims": ("range",), - }, - time=get_time_attrs(), - azimuth=get_azimuth_attrs(), - elevation=get_elevation_attrs(), - velocity={ - "long_name": "Radial velocity of scatterers toward instrument", - "standard_name": "radial_velocity_of_scatterers_toward_instrument", - "units": "m s-1", - }, - latitude=get_latitude_attrs(), - longitude=get_longitude_attrs(), - altitude=get_altitude_attrs(), -) - -variable_attr_dict["time"]["dims"] = ("time",) -variable_attr_dict["azimuth"]["dims"] = ("time",) -variable_attr_dict["elevation"]["dims"] = ("time",) -variable_attr_dict["velocity"]["dims"] = ("time", "range") -variable_attr_dict["latitude"]["dims"] = () -variable_attr_dict["longitude"]["dims"] = () -variable_attr_dict["altitude"]["dims"] = () - - -def _parse_spectra_line(input_str, num_gates): - out_array = np.zeros(num_gates) - increment = {32: 9, 31: 7}[num_gates] - for i, pos in enumerate(range(3, len(input_str) - increment, increment)): - input_num_str = input_str[pos : pos + increment] - try: - out_array[i] = float(input_num_str) - except ValueError: - out_array[i] = np.nan - - return out_array - - -class MRR2File: - def __init__(self, file_name="", **kwargs): - self.vel_bin_spacing = 0.1887 - self.nyquist_velocity = self.vel_bin_spacing * 64 - self._data = {} - self._data["velocity_bins"] = np.arange( - 0, 64 * self.vel_bin_spacing, self.vel_bin_spacing - ) - self._data["range"] = [] - self._data["transfer_function"] = [] - self._data["spectral_reflectivity"] = [] - self._data["raw_spectra_counts"] = [] - self._data["drop_size"] = [] - self._data["drop_number_density"] = [] - self._data["time"] = [] - self.filetype = "" - self.device_version = "" - self.device_serial_number = "" - self.bandwidth = 0 - self._fp = None - self.calibration_constant = [] - self._data["percentage_valid_spectra"] = [] - self._data["number_valid_spectra"] = [] - self._data["total_number_spectra"] = [] - self.spectra_index = 0 - self.altitude = None - self.sampling_rate = 125000.0 - self._data["path_integrated_attenuation"] = [] - self._data["corrected_reflectivity"] = [] - self._data["reflectivity"] = [] - self._data["rainfall_rate"] = [] - self._data["liquid_water_content"] = [] - self._data["velocity"] = [] - self._data["altitude"] = np.array(np.nan) - self._data["longitude"] = np.array(np.nan) - self._data["latitude"] = np.array(np.nan) - self.filename = None - self.n_gates = 32 - if not file_name == "": - self.filename = file_name - self.open(file_name) - - def open(self, filename_or_obj): - if isinstance(filename_or_obj, io.IOBase): - filename_or_obj.seek(0) - self._fp = filename_or_obj - - if isinstance(filename_or_obj, str): - self.filename = filename_or_obj - self._fp = open(filename_or_obj) - - num_times = 0 - temp_spectra = np.zeros((self.n_gates, 64)) - temp_drops = np.zeros((self.n_gates, 64)) - temp_number = np.zeros((self.n_gates, 64)) - spec_var = "" - for file_line in self._fp: - if file_line[:3] == "MRR": - if num_times > 0: - self._data[spec_var].append(temp_spectra) - self._data["drop_number_density"].append(temp_number) - self._data["drop_size"].append(temp_drops) - - string_split = file_line.split() - time_str = string_split[1] - parsed_datetime = datetime.strptime(time_str, "%y%m%d%H%M%S") - self._data["time"] = self._data["time"] + [parsed_datetime] - self.filetype = string_split[-1] - if self.filetype == "RAW": - self.device_version = string_split[4] - self.device_serial_number = string_split[6] - self.bandwidth = int(string_split[8]) - self.calibration_constant.append(int(string_split[10])) - self._data["percentage_valid_spectra"].append(int(string_split[12])) - self._data["number_valid_spectra"].append(int(string_split[13])) - self._data["total_number_spectra"].append(int(string_split[14])) - self.n_gates = 32 - spec_var = "raw_spectra_counts" - elif self.filetype == "AVE" or self.filetype == "PRO": - self._data["altitude"] = np.array(float(string_split[8])) - self.sampling_rate = float(string_split[10]) - self.mrr_service_version = string_split[12] - self.device_version = string_split[14] - self.calibration_constant.append(int(string_split[16])) - self._data["percentage_valid_spectra"].append(int(string_split[18])) - self.n_gates = 31 - spec_var = "spectral_reflectivity" - else: - raise OSError( - "Invalid file type flag in file! Must be RAW, AVG, or PRO!" - ) - temp_spectra = np.zeros((self.n_gates, 64)) - temp_drops = np.zeros((self.n_gates, 64)) - temp_number = np.zeros((self.n_gates, 64)) - num_times = num_times + 1 - - if file_line[0] == "H": - in_array = _parse_spectra_line(file_line, self.n_gates) - if num_times > 1: - after_res = in_array[1] - in_array[0] - before_res = self._data["range"][1] - self._data["range"][0] - if not after_res == before_res: - warnings.warn( - f"MRR2 resolution was changed mid file. Before time period " - f"{parsed_datetime} the resolution was {before_res}, " - f"and {after_res} after.", - UserWarning, - ) - self._data["range"] = in_array - - if file_line[0:2] == "TF": - self._data["transfer_function"].append( - _parse_spectra_line(file_line, self.n_gates) - ) - if file_line[0] == "F": - spectra_bin_no = int(file_line[1:3]) - temp_spectra[:, spectra_bin_no] = _parse_spectra_line( - file_line, self.n_gates - ) - if file_line[0] == "D": - spectra_bin_no = int(file_line[1:3]) - temp_drops[:, spectra_bin_no] = _parse_spectra_line( - file_line, self.n_gates - ) - if file_line[0] == "N": - spectra_bin_no = int(file_line[1:3]) - temp_number[:, spectra_bin_no] = _parse_spectra_line( - file_line, self.n_gates - ) - if file_line[0:3] == "PIA": - self._data["path_integrated_attenuation"] = self._data[ - "path_integrated_attenuation" - ] + [_parse_spectra_line(file_line, self.n_gates)] - if file_line[0:3] == "z ": - self._data["reflectivity"] = self._data["reflectivity"] + [ - _parse_spectra_line(file_line, self.n_gates) - ] - if file_line[0:3] == "Z ": - self._data["corrected_reflectivity"] = self._data[ - "corrected_reflectivity" - ] + [_parse_spectra_line(file_line, self.n_gates)] - if file_line[0:3] == "RR ": - self._data["rainfall_rate"] = self._data["rainfall_rate"] + [ - _parse_spectra_line(file_line, self.n_gates) - ] - if file_line[0:3] == "LWC": - self._data["liquid_water_content"] = self._data[ - "liquid_water_content" - ] + [_parse_spectra_line(file_line, self.n_gates)] - if file_line[0:3] == "W ": - self._data["velocity"] = self._data["velocity"] + [ - _parse_spectra_line(file_line, self.n_gates) - ] - - self._data[spec_var].append(temp_spectra) - self._data["drop_number_density"].append(temp_number) - self._data["drop_size"].append(temp_drops) - self._data["transfer_function"] = np.stack( - self._data["transfer_function"], axis=0 - ) - self._data[spec_var] = np.stack(self._data[spec_var], axis=0) - self._data["drop_number_density"] = np.stack( - self._data["drop_number_density"], axis=0 - ) - self._data["drop_size"] = np.stack(self._data["drop_size"], axis=0) - - if self.filetype == "RAW": - self._data["total_number_spectra"] = np.stack( - self._data["total_number_spectra"], axis=0 - ) - self._data["number_valid_spectra"] = np.stack( - self._data["number_valid_spectra"], axis=0 - ) - - del self._data["reflectivity"] - del self._data["corrected_reflectivity"] - del self._data["liquid_water_content"] - del self._data["rainfall_rate"] - del self._data["percentage_valid_spectra"] - del self._data["drop_number_density"] - del self._data["drop_size"] - del self._data["path_integrated_attenuation"] - del self._data["velocity"] - del self._data["spectral_reflectivity"] - else: - del self._data["total_number_spectra"], self._data["number_valid_spectra"] - self._data["reflectivity"] = np.stack(self._data["reflectivity"], axis=0) - self._data["path_integrated_attenuation"] = np.stack( - self._data["path_integrated_attenuation"], axis=0 - ) - self._data["corrected_reflectivity"] = np.stack( - self._data["corrected_reflectivity"], axis=0 - ) - - self._data["liquid_water_content"] = np.stack( - self._data["liquid_water_content"], axis=0 - ) - self._data["velocity"] = np.stack(self._data["velocity"], axis=0) - self._data["rainfall_rate"] = np.stack(self._data["rainfall_rate"], axis=0) - self._data["percentage_valid_spectra"] = np.stack( - self._data["percentage_valid_spectra"], axis=0 - ) - self._data["drop_number_density"] = np.stack( - self._data["drop_number_density"], axis=0 - ) - self._data["drop_size"] = np.stack(self._data["drop_size"], axis=0) - del self._data["raw_spectra_counts"] - - self._data["range"] = np.squeeze(self._data["range"]) - # Now we compress the spectrum variables to remove invalid spectra - self._data[spec_var] = self._data[spec_var].reshape( - self._data[spec_var].shape[0] * self._data[spec_var].shape[1], - self._data[spec_var].shape[2], - ) - where_valid_spectra = np.any(np.isfinite(self._data[spec_var]), axis=1) - inds = np.where(where_valid_spectra, 1, -1) - - self._data[spec_var] = self._data[spec_var][where_valid_spectra] - if self.filetype == "PRO" or self.filetype == "AVE": - self._data["drop_number_density"] = self._data[ - "drop_number_density" - ].reshape( - self._data["drop_number_density"].shape[0] - * self._data["drop_number_density"].shape[1], - self._data["drop_number_density"].shape[2], - ) - self._data["drop_number_density"] = self._data["drop_number_density"][ - where_valid_spectra - ] - self._data["drop_size"] = self._data["drop_size"].reshape( - self._data["drop_size"].shape[0] * self._data["drop_size"].shape[1], - self._data["drop_size"].shape[2], - ) - self._data["drop_size"] = self._data["drop_size"][where_valid_spectra] - cur_index = 0 - for i in range(len(inds)): - if inds[i] > -1: - inds[i] = cur_index - cur_index += 1 - self._data["spectrum_index"] = inds.reshape( - (len(self._data["time"]), len(self._data["range"])) - ) - - self._data["azimuth"] = np.zeros_like(self._data["time"]) - self._data["elevation"] = 90 * np.ones_like(self._data["time"]) - self._data["time"] = np.array(self._data["time"]) - - def close(self): - if self._fp is not None: - self._fp.close() - del self._data - - __del__ = close - - @property - def data(self): - return self._data - - @property - def coordinates(self): - return self._coordinates - - @property - def fixed_angle(self): - return self._data["elevation"] - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.close() - - -class MRR2ArrayWrapper(BackendArray): - def __init__( - self, - data, - ): - self.data = data - self.shape = data.shape - self.dtype = np.dtype("float64") - - def __getitem__(self, key: tuple): - return indexing.explicit_indexing_adapter( - key, - self.shape, - indexing.IndexingSupport.OUTER_1VECTOR, - self._raw_indexing_method, - ) - - def _raw_indexing_method(self, key: tuple): - return self.data[key] - - -class MRR2DataStore(AbstractDataStore): - def __init__(self, manager, group=None): - self._manager = manager - self._group = group - self._filename = self.filename - self._need_time_recalc = False - - @classmethod - def open(cls, filename, mode="r", group=None, **kwargs): - manager = CachingFileManager(MRR2File, filename, mode=mode, kwargs=kwargs) - return cls(manager, group=group) - - @property - def filename(self): - with self._manager.acquire_context(False) as root: - return root.filename - - @property - def root(self): - with self._manager.acquire_context(False) as root: - return root - - def _acquire(self, needs_lock=True): - with self._manager.acquire_context(needs_lock) as root: - return root - - @property - def ds(self): - return self._acquire() - - def open_store_variable(self, name, var): - data = indexing.LazilyOuterIndexedArray(MRR2ArrayWrapper(var)) - encoding = {"group": self._group, "source": self._filename} - attrs = variable_attr_dict[name].copy() - dims = attrs["dims"] - del attrs["dims"] - return xr.Variable(dims, data, attrs, encoding) - - def open_store_coordinates(self): - coord_keys = ["time", "range", "velocity_bins"] - coords = {} - for k in coord_keys: - attrs = variable_attr_dict[k].copy() - dims = attrs["dims"] - del attrs["dims"] - coords[k] = xr.Variable(dims, self.ds.data[k], attrs=attrs) - - return coords - - def get_variables(self): - return FrozenDict( - (k1, v1) - for k1, v1 in { - **{k: self.open_store_variable(k, v) for k, v in self.ds.data.items()}, - **self.open_store_coordinates(), - }.items() - ) - - def get_attrs(self): - return FrozenDict() - - -class MRRBackendEntrypoint(BackendEntrypoint): - """Xarray BackendEntrypoint for Metek MRR2 data. - - Keyword Arguments - ----------------- - first_dim : str - Can be ``time`` or ``auto`` first dimension. If set to ``auto``, - first dimension will be either ``azimuth`` or ``elevation`` depending on - type of sweep. Defaults to ``auto``. - site_coords : bool - Attach radar site-coordinates to Dataset, defaults to ``True``. - kwargs : dict - Additional kwargs are fed to :py:func:`xarray.open_dataset`. - """ - - description = "Backend for reading Metek MRR2 processed and raw data" - url = "https://xradar.rtfd.io/en/latest/io.html#metek" - - def open_dataset( - self, - filename_or_obj, - *, - mask_and_scale=True, - decode_times=True, - concat_characters=True, - decode_coords=True, - drop_variables=None, - use_cftime=None, - decode_timedelta=None, - format=None, - group="/", - invalid_netcdf=None, - phony_dims="access", - decode_vlen_strings=True, - first_dim="auto", - site_coords=True, - optional=True, - ): - store_entrypoint = StoreBackendEntrypoint() - - store = MRR2DataStore.open( - filename_or_obj, - format=format, - group=group, - invalid_netcdf=invalid_netcdf, - phony_dims=phony_dims, - decode_vlen_strings=decode_vlen_strings, - ) - - ds = store_entrypoint.open_dataset( - store, - mask_and_scale=mask_and_scale, - decode_times=decode_times, - concat_characters=concat_characters, - decode_coords=decode_coords, - drop_variables=drop_variables, - use_cftime=use_cftime, - decode_timedelta=decode_timedelta, - ) - - ds = ds.assign_coords({"range": ds.range}) - ds = ds.assign_coords({"time": ds.time}) - ds = ds.assign_coords({"velocity_bins": ds.velocity_bins}) - ds.encoding["engine"] = "metek" - - return ds - - -def open_metek_datatree(filename_or_obj, **kwargs): - """Open Metek MRR2 dataset as :py:class:`datatree.DataTree`. - - Parameters - ---------- - filename_or_obj : str, Path, file-like or DataStore - Strings and Path objects are interpreted as a path to a local or remote - radar file - - Keyword Arguments - ----------------- - sweep : int, list of int, optional - Sweep number(s) to extract, default to first sweep. If None, all sweeps are - extracted into a list. - first_dim : str - Can be ``time`` or ``auto`` first dimension. If set to ``auto``, - first dimension will be either ``azimuth`` or ``elevation`` depending on - type of sweep. Defaults to ``auto``. - reindex_angle : bool or dict - Defaults to False, no reindexing. Given dict should contain the kwargs to - reindex_angle. Only invoked if `decode_coord=True`. - fix_second_angle : bool - If True, fixes erroneous second angle data. Defaults to ``False``. - site_coords : bool - Attach radar site-coordinates to Dataset, defaults to ``True``. - kwargs : dict - Additional kwargs are fed to :py:func:`xarray.open_dataset`. - - Returns - ------- - dtree: datatree.DataTree - DataTree - """ - # handle kwargs, extract first_dim - backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) - sweep = kwargs.pop("sweep", None) - sweeps = [] - kwargs["backend_kwargs"] = backend_kwargs - - if isinstance(sweep, str): - sweeps = [sweep] - elif isinstance(sweep, int): - sweeps = [f"sweep_{sweep}"] - elif isinstance(sweep, list): - if isinstance(sweep[0], int): - sweeps = [f"sweep_{i + 1}" for i in sweep] - else: - sweeps.extend(sweep) - else: - sweeps = ["sweep_0"] - - ds = [ - xr.open_dataset(filename_or_obj, group=swp, engine="metek", **kwargs) - for swp in sweeps - ] - - ds.insert(0, xr.Dataset()) # open_dataset(filename_or_obj, group="/")) - - # create datatree root node with required data - dtree = DataTree(data=_assign_root(ds), name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) From 1162a0758cd6a6a69b4360ba28ed342d8f873503 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 13:37:07 -0500 Subject: [PATCH 03/23] FIX: Style compliance for Jupyter Notebooks (#199) * ADD: MRR2 backend * Revert "ADD: MRR2 backend" This reverts commit e377116f2a1e2ada17b6eea80c698c08d223bc1d. * Fixed format to pass ruff. * ADD: Documenting the changes in history.md * FIX: Cmweather imports * Cmweather oh cmweather * ADD: MVC * ADD: Link to PR in history.md --- docs/history.md | 4 +++ examples/notebooks/Accessors.ipynb | 5 +-- examples/notebooks/CfRadial1.ipynb | 5 +-- examples/notebooks/CfRadial1_Export.ipynb | 11 +++---- .../CfRadial1_Model_Transformation.ipynb | 8 +++-- examples/notebooks/Furuno.ipynb | 5 +-- examples/notebooks/GAMIC.ipynb | 5 +-- examples/notebooks/HaloPhotonics.ipynb | 8 ++--- examples/notebooks/Iris.ipynb | 5 +-- .../Multi-Volume-Concatenation.ipynb | 14 ++++---- examples/notebooks/NexradLevel2.ipynb | 7 ++-- examples/notebooks/ODIM_H5.ipynb | 5 +-- examples/notebooks/Rainbow.ipynb | 6 ++-- .../Read-plot-Sigmet-data-from-AWS.ipynb | 33 +++++++++---------- examples/notebooks/angle_reindexing.ipynb | 8 ++--- .../multiple-sweeps-into-volume-scan.ipynb | 26 +++++++-------- examples/notebooks/plot-ppi.ipynb | 13 ++++---- 17 files changed, 88 insertions(+), 80 deletions(-) diff --git a/docs/history.md b/docs/history.md index 1722d183..7f97eaa7 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # History +## Development version (2024-08-23) + +FIX: Notebooks are now conforming to ruff's style checks by [@rcjackson](https://github.com/rcjackson), ({pull}`199`) by [@rcjackson](https://github.com/rcjackson). + ## 0.6.3 (2024-08-13) FIX: use rstart in meter for ODIM_H5/V2_4 ({issue}`196`) by [@kmuehlbauer](https://github.com/kmuehlbauer), ({pull}`197`) by [@kmuehlbauer](https://github.com/kmuehlbauer). diff --git a/examples/notebooks/Accessors.ipynb b/examples/notebooks/Accessors.ipynb index 135dfe1c..405ccb34 100644 --- a/examples/notebooks/Accessors.ipynb +++ b/examples/notebooks/Accessors.ipynb @@ -34,8 +34,9 @@ "source": [ "import numpy as np\n", "import xarray as xr\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/CfRadial1.ipynb b/examples/notebooks/CfRadial1.ipynb index 55729508..aabe7464 100644 --- a/examples/notebooks/CfRadial1.ipynb +++ b/examples/notebooks/CfRadial1.ipynb @@ -16,8 +16,9 @@ "outputs": [], "source": [ "import xarray as xr\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/CfRadial1_Export.ipynb b/examples/notebooks/CfRadial1_Export.ipynb index 8cf6ae51..58b9e027 100644 --- a/examples/notebooks/CfRadial1_Export.ipynb +++ b/examples/notebooks/CfRadial1_Export.ipynb @@ -20,14 +20,11 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "import tempfile\n", - "import cmweather\n", - "import numpy as np\n", - "import xarray as xr\n", - "import xradar as xd\n", + "import cmweather # noqa\n", "import datatree as xt\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/CfRadial1_Model_Transformation.ipynb b/examples/notebooks/CfRadial1_Model_Transformation.ipynb index c6df150c..e59f821e 100644 --- a/examples/notebooks/CfRadial1_Model_Transformation.ipynb +++ b/examples/notebooks/CfRadial1_Model_Transformation.ipynb @@ -22,10 +22,12 @@ "outputs": [], "source": [ "import os\n", - "import xarray as xr\n", - "import xradar as xd\n", + "\n", "import datatree as xt\n", - "from open_radar_data import DATASETS" + "import xarray as xr\n", + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/Furuno.ipynb b/examples/notebooks/Furuno.ipynb index 3ba94908..7f6f79e0 100644 --- a/examples/notebooks/Furuno.ipynb +++ b/examples/notebooks/Furuno.ipynb @@ -16,8 +16,9 @@ "outputs": [], "source": [ "import xarray as xr\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/GAMIC.ipynb b/examples/notebooks/GAMIC.ipynb index 9214c016..1d5f6560 100644 --- a/examples/notebooks/GAMIC.ipynb +++ b/examples/notebooks/GAMIC.ipynb @@ -16,8 +16,9 @@ "outputs": [], "source": [ "import xarray as xr\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/HaloPhotonics.ipynb b/examples/notebooks/HaloPhotonics.ipynb index d4757e3c..eb9d705d 100644 --- a/examples/notebooks/HaloPhotonics.ipynb +++ b/examples/notebooks/HaloPhotonics.ipynb @@ -13,10 +13,10 @@ "metadata": {}, "outputs": [], "source": [ - "import xradar as xd\n", - "import xarray as xr\n", "import matplotlib.pyplot as plt\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { @@ -569,7 +569,7 @@ " x=\"x\", y=\"y\", ax=ax[int(sweep / 3), sweep % 3]\n", " )\n", " ax[int(sweep / 3), sweep % 3].set_title(\n", - " \"%2.1f degree scan\" % sweep_ds[\"fixed_angle\"].values[sweep]\n", + " \"{angle:2.1f} degree scan\".format(angle=sweep_ds[\"fixed_angle\"].values[sweep])\n", " )\n", " ax[int(sweep / 3), sweep % 3].set_ylim([-4000, 0])\n", " ax[int(sweep / 3), sweep % 3].set_xlim([-4000, 1000])\n", diff --git a/examples/notebooks/Iris.ipynb b/examples/notebooks/Iris.ipynb index b11ef90c..9a22054d 100644 --- a/examples/notebooks/Iris.ipynb +++ b/examples/notebooks/Iris.ipynb @@ -16,8 +16,9 @@ "outputs": [], "source": [ "import xarray as xr\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/Multi-Volume-Concatenation.ipynb b/examples/notebooks/Multi-Volume-Concatenation.ipynb index be9d98c4..7da7b134 100644 --- a/examples/notebooks/Multi-Volume-Concatenation.ipynb +++ b/examples/notebooks/Multi-Volume-Concatenation.ipynb @@ -23,14 +23,16 @@ "metadata": {}, "outputs": [], "source": [ - "import xradar as xd\n", - "import xarray as xr\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import cmweather\n", + "import warnings\n", + "\n", "import cartopy.crs as ccrs\n", + "import cmweather # noqa\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import xarray as xr\n", "from open_radar_data import DATASETS\n", - "import warnings\n", + "\n", + "import xradar as xd\n", "\n", "warnings.filterwarnings(\"ignore\")" ] diff --git a/examples/notebooks/NexradLevel2.ipynb b/examples/notebooks/NexradLevel2.ipynb index d17b3539..2a404609 100644 --- a/examples/notebooks/NexradLevel2.ipynb +++ b/examples/notebooks/NexradLevel2.ipynb @@ -15,10 +15,11 @@ "metadata": {}, "outputs": [], "source": [ + "import cmweather # noqa\n", "import xarray as xr\n", - "import xradar as xd\n", - "import cmweather\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/ODIM_H5.ipynb b/examples/notebooks/ODIM_H5.ipynb index 3a7a7505..26c2fd8e 100644 --- a/examples/notebooks/ODIM_H5.ipynb +++ b/examples/notebooks/ODIM_H5.ipynb @@ -16,8 +16,9 @@ "outputs": [], "source": [ "import xarray as xr\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/Rainbow.ipynb b/examples/notebooks/Rainbow.ipynb index 2a772e7a..d60e2001 100644 --- a/examples/notebooks/Rainbow.ipynb +++ b/examples/notebooks/Rainbow.ipynb @@ -15,10 +15,10 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", "import xarray as xr\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/Read-plot-Sigmet-data-from-AWS.ipynb b/examples/notebooks/Read-plot-Sigmet-data-from-AWS.ipynb index 30b219b3..3f083f20 100644 --- a/examples/notebooks/Read-plot-Sigmet-data-from-AWS.ipynb +++ b/examples/notebooks/Read-plot-Sigmet-data-from-AWS.ipynb @@ -24,19 +24,18 @@ "metadata": {}, "outputs": [], "source": [ - "import fsspec\n", - "import xarray as xr\n", - "import xradar as xd\n", + "from datetime import datetime\n", + "\n", "import boto3\n", "import botocore\n", - "import numpy as np\n", - "from pandas import to_datetime\n", + "import cmweather # noqa\n", + "import fsspec\n", "import matplotlib.pyplot as plt\n", - "import cmweather\n", + "import xarray as xr\n", "from botocore.client import Config\n", - "from datetime import datetime\n", - "from matplotlib import pyplot\n", - "from pyart.graph import cm" + "from pandas import to_datetime\n", + "\n", + "import xradar as xd" ] }, { @@ -320,7 +319,7 @@ " vmin=-10,\n", " vmax=50,\n", " ax=ax,\n", - " cbar_kwargs={\"label\": \"$Reflectivity \\ [dBZ]$\"},\n", + " cbar_kwargs={\"label\": r\"$Reflectivity \\ [dBZ]$\"},\n", ")\n", "\n", "ds.RHOHV.where(ds.DBZH >= -10).where(ds.RHOHV >= 0.85).plot(\n", @@ -330,7 +329,7 @@ " vmin=0,\n", " vmax=1,\n", " ax=ax1,\n", - " cbar_kwargs={\"label\": \"$Corr. \\ Coef. \\ [unitless]$\"},\n", + " cbar_kwargs={\"label\": r\"$Corr. \\ Coef. \\ [unitless]$\"},\n", ")\n", "\n", "# lambda fucntion for unit trasformation m->km\n", @@ -345,20 +344,20 @@ "ax1.set_title(\"\")\n", "\n", "# renaming the axis\n", - "ax.set_ylabel(\"$North - South \\ distance \\ [km]$\")\n", - "ax.set_xlabel(\"$East - West \\ distance \\ [km]$\")\n", - "ax1.set_ylabel(\"$North - South \\ distance \\ [km]$\")\n", - "ax1.set_xlabel(\"$East - West \\ distance \\ [km]$\")\n", + "ax.set_ylabel(r\"$North - South \\ distance \\ [km]$\")\n", + "ax.set_xlabel(r\"$East - West \\ distance \\ [km]$\")\n", + "ax1.set_ylabel(r\"$North - South \\ distance \\ [km]$\")\n", + "ax1.set_xlabel(r\"$East - West \\ distance \\ [km]$\")\n", "\n", "# setting up the title\n", "ax.set_title(\n", - " f\"$Guaviare \\ radar$\"\n", + " r\"$Guaviare \\ radar$\"\n", " + \"\\n\"\n", " + f\"${to_datetime(ds.time.values[0]): %Y-%m-%d - %X}$\"\n", " + \"$ UTC$\"\n", ")\n", "ax1.set_title(\n", - " f\"$Guaviare \\ radar$\"\n", + " r\"$Guaviare \\ radar$\"\n", " + \"\\n\"\n", " + f\"${to_datetime(ds.time.values[0]): %Y-%m-%d - %X}$\"\n", " + \"$ UTC$\"\n", diff --git a/examples/notebooks/angle_reindexing.ipynb b/examples/notebooks/angle_reindexing.ipynb index 060bb237..e13905ee 100644 --- a/examples/notebooks/angle_reindexing.ipynb +++ b/examples/notebooks/angle_reindexing.ipynb @@ -50,11 +50,11 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "import xarray as xr\n", "import matplotlib.pyplot as plt\n", - "import xradar as xd\n", - "from open_radar_data import DATASETS" + "import xarray as xr\n", + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { diff --git a/examples/notebooks/multiple-sweeps-into-volume-scan.ipynb b/examples/notebooks/multiple-sweeps-into-volume-scan.ipynb index 48c32967..02f65046 100644 --- a/examples/notebooks/multiple-sweeps-into-volume-scan.ipynb +++ b/examples/notebooks/multiple-sweeps-into-volume-scan.ipynb @@ -24,20 +24,18 @@ "metadata": {}, "outputs": [], "source": [ - "import fsspec\n", - "import xradar as xd\n", - "import numpy as np\n", - "import cartopy.crs as ccrs\n", - "import cartopy.feature as cfeature\n", - "import matplotlib.pyplot as plt\n", - "import cmweather\n", "import warnings\n", - "from pandas import to_datetime\n", "from datetime import datetime\n", - "from matplotlib import pyplot\n", + "\n", + "import cartopy.crs as ccrs\n", + "import cmweather # noqa\n", + "import fsspec\n", + "import matplotlib.pyplot as plt\n", "from datatree import DataTree, open_datatree\n", - "from matplotlib.animation import FuncAnimation\n", "from IPython.display import HTML\n", + "from matplotlib.animation import FuncAnimation\n", + "\n", + "import xradar as xd\n", "\n", "warnings.simplefilter(\"ignore\")" ] @@ -222,8 +220,8 @@ "m2km = lambda x, _: f\"{x/1000:g}\"\n", "ax.xaxis.set_major_formatter(m2km)\n", "ax.yaxis.set_major_formatter(m2km)\n", - "ax.set_ylabel(\"$North - South \\ distance \\ [km]$\")\n", - "ax.set_xlabel(\"$East - West \\ distance \\ [km]$\")\n", + "ax.set_ylabel(r\"$North - South \\ distance \\ [km]$\")\n", + "ax.set_xlabel(r\"$East - West \\ distance \\ [km]$\")\n", "\n", "# Dictionary-key method\n", "vcp_dt[\"PRECB\"][\"sweep_0\"].DBZH.plot(\n", @@ -238,8 +236,8 @@ "ax1.yaxis.set_major_formatter(m2km)\n", "ax1.set_xlim(ax.get_xlim())\n", "ax1.set_ylim(ax.get_ylim())\n", - "ax1.set_ylabel(\"$North - South \\ distance \\ [km]$\")\n", - "ax1.set_xlabel(\"$East - West \\ distance \\ [km]$\")\n", + "ax1.set_ylabel(r\"$North - South \\ distance \\ [km]$\")\n", + "ax1.set_xlabel(r\"$East - West \\ distance \\ [km]$\")\n", "fig.tight_layout()" ] }, diff --git a/examples/notebooks/plot-ppi.ipynb b/examples/notebooks/plot-ppi.ipynb index 34663de3..a1325325 100644 --- a/examples/notebooks/plot-ppi.ipynb +++ b/examples/notebooks/plot-ppi.ipynb @@ -24,10 +24,10 @@ "metadata": {}, "outputs": [], "source": [ - "import xarray as xr\n", - "import xradar as xd\n", - "import cmweather\n", - "from open_radar_data import DATASETS" + "import cmweather # noqa\n", + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" ] }, { @@ -37,9 +37,8 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "import pyproj\n", - "import cartopy" + "import cartopy\n", + "import matplotlib.pyplot as plt" ] }, { From 01e9473f0cd6724abc72d975b6fd598f252d0669 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 10:59:12 -0500 Subject: [PATCH 04/23] ADD: History.md --- docs/history.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/history.md b/docs/history.md index 7f97eaa7..349bd260 100644 --- a/docs/history.md +++ b/docs/history.md @@ -4,6 +4,8 @@ FIX: Notebooks are now conforming to ruff's style checks by [@rcjackson](https://github.com/rcjackson), ({pull}`199`) by [@rcjackson](https://github.com/rcjackson). +* ADD: Metek Micro Rain Radar 2 reader by [@rcjackson] + ## 0.6.3 (2024-08-13) FIX: use rstart in meter for ODIM_H5/V2_4 ({issue}`196`) by [@kmuehlbauer](https://github.com/kmuehlbauer), ({pull}`197`) by [@kmuehlbauer](https://github.com/kmuehlbauer). From 8c2a94b572a6bc61144aee394be24d13f4c1fcc7 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 15:12:07 -0500 Subject: [PATCH 05/23] New MRR2 branch --- examples/notebooks/MRR.ipynb | 632 +++++++++++++++++++++++++++++++ pyproject.toml | 2 +- tests/conftest.py | 38 ++ tests/io/test_metek.py | 205 ++++++++++ xradar/io/backends/__init__.py | 2 + xradar/io/backends/metek.py | 665 +++++++++++++++++++++++++++++++++ 6 files changed, 1543 insertions(+), 1 deletion(-) create mode 100644 examples/notebooks/MRR.ipynb create mode 100644 tests/io/test_metek.py create mode 100644 xradar/io/backends/metek.py diff --git a/examples/notebooks/MRR.ipynb b/examples/notebooks/MRR.ipynb new file mode 100644 index 00000000..9f315458 --- /dev/null +++ b/examples/notebooks/MRR.ipynb @@ -0,0 +1,632 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading Metek MRR2 data" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import gzip\n", + "\n", + "import cmweather # noqa\n", + "import matplotlib.pyplot as plt\n", + "from open_radar_data import DATASETS\n", + "\n", + "import xradar as xd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`xd.io.open_metek_datatree` supports the Metek MRR2 processed (.pro, .ave) and raw (.raw) files. The initalized datatree will contain all of the vertically pointing radar data in one sweep. \n", + "\n", + "In this example, we are loading the 60 s average files from the MRR2 sampling a rain event over the Argonne Testbed for Multiscale Observational Science at Argonne National Laboratory in the Chicago suburbs." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "mrr_test_file = DATASETS.fetch(\"0308.pro.gz\")\n", + "with gzip.open(mrr_test_file, \"rt\") as test_file:\n", + " ds = xd.io.open_metek_datatree(test_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "View the structure of the loaded datatree. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.DatasetView> Size: 18MB\n",
+       "Dimensions:                       (sample: 64, time: 362, range: 31,\n",
+       "                                   index: 11222)\n",
+       "Coordinates:\n",
+       "    velocity_bins                 (sample) float64 512B ...\n",
+       "  * range                         (range) float64 248B 150.0 300.0 ... 4.65e+03\n",
+       "  * time                          (time) datetime64[ns] 3kB 2024-03-08T23:00:...\n",
+       "Dimensions without coordinates: sample, index\n",
+       "Data variables: (12/17)\n",
+       "    transfer_function             (time, range) float64 90kB ...\n",
+       "    spectral_reflectivity         (index, sample) float64 6MB ...\n",
+       "    drop_size                     (index, sample) float64 6MB ...\n",
+       "    drop_number_density           (index, sample) float64 6MB ...\n",
+       "    percentage_valid_spectra      (time) float64 3kB ...\n",
+       "    path_integrated_attenuation   (time, range) float64 90kB ...\n",
+       "    ...                            ...\n",
+       "    altitude                      float64 8B ...\n",
+       "    longitude                     float64 8B ...\n",
+       "    latitude                      float64 8B ...\n",
+       "    spectrum_index                (time, range) float64 90kB ...\n",
+       "    azimuth                       (time) float64 3kB ...\n",
+       "    elevation                     (time) float64 3kB ...
" + ], + "text/plain": [ + "DataTree('sweep_0', parent=\"root\")\n", + " Dimensions: (sample: 64, time: 362, range: 31,\n", + " index: 11222)\n", + " Coordinates:\n", + " velocity_bins (sample) float64 512B ...\n", + " * range (range) float64 248B 150.0 300.0 ... 4.65e+03\n", + " * time (time) datetime64[ns] 3kB 2024-03-08T23:00:...\n", + " Dimensions without coordinates: sample, index\n", + " Data variables: (12/17)\n", + " transfer_function (time, range) float64 90kB ...\n", + " spectral_reflectivity (index, sample) float64 6MB ...\n", + " drop_size (index, sample) float64 6MB ...\n", + " drop_number_density (index, sample) float64 6MB ...\n", + " percentage_valid_spectra (time) float64 3kB ...\n", + " path_integrated_attenuation (time, range) float64 90kB ...\n", + " ... ...\n", + " altitude float64 8B ...\n", + " longitude float64 8B ...\n", + " latitude float64 8B ...\n", + " spectrum_index (time, range) float64 90kB ...\n", + " azimuth (time) float64 3kB ...\n", + " elevation (time) float64 3kB ..." + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds[\"sweep_0\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot MRR timeseries\n", + "\n", + "One can use the typical xarray plotting functions for plotting the velocity or other MRR2 variables." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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V0MxOsHVV1+NXbt2YdGiRQCAm2++GUOGDMEnn3wSb/UJgiAIgiAIotrILk8VDDHjuyUsOTkZ06dP/8eM6xctWoSkpCTxPMzzzz+Pl19+Ge3atcPzzz9foXLxRRddBABYt24dBg4ciKSkJLHO4/GgWbNmuPTSS6sdVzVty02aNGmCJk2aVGvbCy64wPb9sccewwsvvICVK1eiXbt2mD59Ou6//35ccsklANhEp379+pg/fz6GDx+OgoICzJ49G6+//rqYzb3xxhvIzs7GV199hYEDB2Ljxo1YtGgRVq5cie7duwMAXn75ZfTs2RObNm1CmzZtHGMrLy+3PZREHjkEQRAEQRDEYROHallt4a677sITTzwBAPj1118xZswYjB07FkuWLMGYMWMwZ84cx+0eeughAMyg84orroDP56vRuKo0kXn22WernOHIkSOrFYiqqnj33XdRUlKCnj17YuvWrcjJycHZZ58t0ni9XvTr1w8rVqzA8OHDsXr1aoRCIVuarKwsdOjQAStWrMDAgQPxww8/IDU1VUxiAKZ5nZqaihUrVlQ4kZkyZQomTZpUrboQBEEQBEEQhBNVuXUs3lvL/mm2bt2Kdu3aAQDef/99XHDBBZg8eTLWrFlTJdNNLgEdDAaRm5sbJUJQ3YsiVZrITJs2zfZ9//79KC0tFQ/35+fnIyEhAZmZmXFPZH799Vf07NkTZWVlSEpKwocffoh27doJ99D69evb0tevXx/bt28HAOTk5MDj8URdzqpfvz5ycnJEmkiFBADIzMwUaZwYP348xowZI74XFhYiOzs7rroRBEEQBEEQhBVJkmOrlklxP8b+j+LxeFBaygRnvvrqK6EylpGRUaW7lrZs2YIbbrhB/L7ncBGA6nrRVGkis3XrVvF5/vz5mDlzJmbPni2uZmzatAk33XQThg8fHncAbdq0wbp165Cfn4/3338f1113HZYtWybWRyocVKR6UFkap/Sx8vF6vRVqdRMEQRAEQRBEtTgGby3r06cPxowZg969e+Onn37C22+/DQDYvHkzGjduHHP766+/Hi6XC59++ikaNmwY87d8VYn7GZkHH3wQ7733nu2WrDZt2mDatGm47LLLcM0118SVn8fjEQ/7d+vWDatWrcIzzzyDe+65BwC7otKwYUORPjc3V1yladCgAYLBIPLy8mxXZXJzc9GrVy+RZt++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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(10, 3))\n", + "ds[\"sweep_0\"][\"velocity\"].T.plot(cmap=\"balance\", vmin=0, vmax=12)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot MRR spectra\n", + "\n", + "In order to plot the spectra, you first need to locate the index that corresponds to the given time period. This is done using xarray .sel() functionality to get the indicies." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlAAAAHFCAYAAAA9occoAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACO1klEQVR4nO3dd3RURd8H8O9mk930ThJC7y10BAI8hg7SRUGlS5cSkSbYCPgA0gSlKAoCigiKgogSQZoi1UAEAekQSkIoIYXU3Z33D97sw5K5IXd3k5Dw/Zyz55DfnTsz9+5mmczMndEIIQSIiIiIKM8cCrsCREREREUNG1BEREREKrEBRURERKQSG1BEREREKrEBRURERKQSG1BEREREKrEBRURERKQSG1BEREREKrEBRURERKRSsWxA7d+/HxEREbh3716OYy1btkTLli0LvE4F4ezZs5g4cSIaNmwIb29v+Pr6onnz5ti4caM0fXx8PAYNGgR/f3+4uroiNDQUO3futEiTlJSEmTNnomXLlggKCoK7uztq166NOXPmID09Pdf6/Pbbb9BoNNBoNLh9+3aeryMv9QKAt99+G/Xr14evry+cnZ1RsWJFDB8+HFeuXMlzWQCwePFiVK9eHXq9HhUqVMD06dORlZWVI93u3bvRrl07BAQEwN3dHXXq1MHHH38Mo9EIAIiIiDBfb26v7M/fihUr0KNHD5QvXx4uLi6oXLkyXnvtNcTGxuYoe+jQoQgJCYG3tzdcXFxQtWpVTJo0SdV9vXjxInr27Alvb2+4u7ujXbt2OHr0aI50X375JV5++WVUq1YNDg4OKF++fJ7LUFsWAKxfvx716tWDs7MzgoODMW7cOKSkpDy2jNWrV1vcVzX3oijTaDSIiIgokLIGDRpk1ftfUPbt24ehQ4eiYcOG0Ov10Gg0uHz5sjRtbGwsBg0ahICAADg7O6NOnTpYuXJljnQtW7bM9fc3Li7OIv1vv/2G0NBQuLq6wt/fH4MGDUJ8fHx+XC49aUQxNG/ePAFAXLp0KcexkydPipMnTxZ8pQrA4sWLRfXq1cXMmTPF9u3bxS+//CIGDhwoAIjp06dbpE1PTxchISGidOnSYu3atWL79u2ie/fuwtHRUezZs8ec7sSJE8Lf31+88cYb4scffxQ7d+4UERERwtnZWbRp00aYTCZpXZKTk0X58uVFcHCwACBu3bqVp2vIa72EEGLUqFFizpw5YsuWLWL37t1i6dKlomTJkiIwMFDcvn07T+X997//FRqNRkydOlXs3r1bzJ07V+h0OjFs2DCLdDt27BAODg6iZcuWYvPmzWLHjh1i7NixAoAIDw8XQghx9epVceDAAfPrhx9+EADE2LFjLeLZn7/g4GDRt29f8fXXX4s9e/aI5cuXi9KlS4uSJUuKuLg4i/Jffvll8dFHH4mff/5Z7Ny5U8yZM0d4enqKmjVrioyMjMdeZ3x8vAgODha1atUS33//vfj5559FixYthIeHh/j3338t0rZt21aEhISIfv36icqVK4ty5crl6V5aU9batWsFADF06FCxa9cu8emnnwovLy/Rrl27x5azatUqAUD88MMP4sCBAyIrK0tVPYsqAGLatGkFUtb58+fF0aNHC6Qsa0RERIhy5cqJHj16iJYtWyp+79+7d09UrFhRlC5dWqxatUpERkaavxsXLFhgkfbkyZMWv68HDhwQO3fuFE5OTqJp06YWaffs2SMcHR1F9+7dxfbt28XatWtFqVKlREhIiEhPT8/PS6cnwFPXgCrObt26JW3QdO7cWbi6ulr8Qi9dulQAEPv37zfHsrKyRM2aNUXjxo3NsZSUFJGSkpIjz+x7/Mcff0jrMnr0aFG/fn3xzjvvqGpA5bVeSn755RcBQKxcufKxaW/fvi2cnZ3F8OHDLeIzZ84UGo3GoqHdt29fodfrc9yL9u3bC09PT2n+ly5dEgDEvHnzpMdv3ryZI3bkyBEBQLz//vuPrf+yZcsEALFz587Hpp00aZJwcnISly9fNscSExOFv7+/6N27t0Vao9Fo/nfnzp1VN6DyWpbBYBAlS5YU7du3tzj/66+/FgDEL7/8kms52Q2op+33vCAbUE+6hz+ruX3vz549WwAQf/31l0W8ffv2ws3NTSQkJORazurVqwUAsWLFCov4M888I2rWrGnReP/zzz8FALFs2TL1F0RFSrEbwouIiMCkSZMAABUqVDB3u+7ZswdAziG8y5cvQ6PRYN68eZgzZ455OKVly5Y4e/YssrKyMGXKFAQHB8PLywvPP/+8tHt2w4YNCA0NhZubG9zd3dGhQwccO3asIC7ZzN/fHxqNJke8cePGSE1Nxd27d82xTZs2oVq1aggNDTXHHB0d0a9fPxw+fBjXr18HALi5ucHNzU2aJwBcvXo1x7E//vgDn332GVasWAGtVqvqGvJaLyUlSpQwn/M4kZGRSE9Px6uvvmoRf/XVVyGEwObNm80xJycn6HQ6uLi4WKT19vaGs7PzY8uSCQgIyBFr2LAhtFqt9L4+Ss21btq0Ca1bt0a5cuXMMU9PT/Ts2RM//fQTDAaDOe7gYNvXQl7LOnjwIGJjY3Pc/169esHd3R2bNm2yug7Hjh1Dly5dEBAQAL1ej+DgYHTu3BnXrl0zpxFCYNmyZahXrx5cXFzg4+ODF198ERcvXsyRX2RkJNq0aQMvLy+4urqiRo0amD17tkWaLVu2mIdyPDw80K5dOxw4cMAiTfYw78mTJ/HKK6/Ay8sLgYGBGDx4MBITEy3SJiUlYdiwYfDz84O7uzs6duyIs2fP5qjbrVu3MHz4cJQpUwZ6vR4lSpRA8+bN8dtvv+V6j/JynmwIT6PRYMyYMfjqq69Qo0YNuLq6om7duti6dWuOMv7991+88sorCAwMhF6vR9myZTFgwABkZGSY08TFxWHEiBEoXbo0dDqdeRj94c+kkrx+Vv/8808EBgaiYcOGFvEuXbrg/v37iIyMzPX8lStXwt3dHS+99JI5dv36dRw5cgT9+/e3+B1s1qwZqlatatPnl4qGYteAGjp0KMaOHQsA+OGHH3DgwAEcOHAADRo0yPW8pUuX4s8//8TSpUuxYsUK/Pvvv+jatSuGDBmCW7du4YsvvsDcuXPx22+/YejQoRbnzpo1C6+88gpq1qyJb7/9Fl999RWSk5Pxn//8B6dOnXpsnQ0GQ55eQgir7snu3btRokQJi/+w//nnH9SpUydH2uzYyZMnc81z165dAIBatWpZxNPS0jBkyBCMGzfusfdcxpp6GQwGpKWl4dixYxg3bhyqVq2Knj175qksAKhdu7ZFvGTJkvD39zcfB4CRI0ciMzMT4eHhuHHjBu7du4evvvoKmzZtwuTJk1VdY2727t0Lo9GY475mMxgMuH//Pv7880+8++67aNGiBZo3b26R5uF5VsCD9+TChQuK9zUtLU3aaMgLW8rKvr+PpnVyckL16tUt7r8a9+/fR7t27XDz5k0sXboUO3bswKJFi1C2bFkkJyeb040YMQLjxo1D27ZtsXnzZixbtgwnT55Es2bNcPPmTXO6lStXolOnTjCZTPj000/x008/ITw83KIxtm7dOnTv3h2enp745ptvsHLlSiQkJKBly5bYt29fjjq+8MILqFq1Kr7//ntMmTIF69atwxtvvGE+LoRAjx498NVXX2HChAnYtGkTmjZtiueeey5HXv3798fmzZvx3nvvYfv27VixYgXatm2LO3fu5HqfrD0PAH7++WcsWbIEM2bMwPfffw9fX188//zzFp+jv//+G8888wwOHjyIGTNmYNu2bZg9ezYyMjKQmZkJ4EHjqXHjxvj111/x3nvvYdu2bRgyZAhmz56NYcOGPbYeeZWZmQm9Xp8jnh07fvy44rnnzp3DH3/8gZdffhnu7u7muNLnNztm7eeXipDC7QDLH7l15YaFhYmwsDDzz9nDLHXr1rXoDl60aJEAILp162Zx/rhx4wQAkZiYKIQQIiYmRjg6OoqxY8dapEtOThZBQUE5hkcelV1+Xl67d+9WdyOEEJ9//rkAID766COLuJOTkxgxYkSO9Pv37xcAxLp16xTz/Pvvv4WLi4t4/vnncxybMGGCqFixokhNTRVCCDFt2jRVQ3hq6xUbG2txj5o0aSKuX7+ep7KGDRsm9Hq99FjVqlVzDC39+eef5jldAIRWqxVz585VzP9xQ3iPSkpKEjVq1BBlypQRycnJOY4fOHDA4lo7deokkpKScqTTarWidevW5p+vX78uAIjZs2fnSLtu3bocQ6YPe9wQni1lzZw5UwAQsbGxOdK2b99eVK1aVbFcIZSH8P766y8BQGzevFnx3Ox7+ej8l6tXrwoXFxcxefJkIcSD32NPT0/RokULxfl+RqNRBAcHi9q1a1t8hyQnJ4uAgADRrFkzcyz79+HRz82oUaOEs7OzuYxt27ZJf2+z79nDQ3ju7u5i3LhxiteqJC/nDRw4MMf7D0AEBgZafPbi4uKEg4ODxfveunVr4e3tLeLj4xXzHzFihHB3dxdXrlyxiM+fP18AUDVfNbfv/XHjxgkHB4cc5fTv318AyDGM/7A333xTABAHDhywiGcPNT8aF0KI4cOHC51Ol+e6U9H0+L7/p0SnTp0suoNr1KgBAOjcubNFuux4TEwMQkJC8Ouvv8JgMGDAgAEWXc7Ozs4ICwvD7t27cy03ODgYR44cyVMdq1Wrlqd02bZt24bRo0fjxRdfNPfKPUw23Pe4Y5cvX0aXLl1QpkwZrFixwuLY4cOHsWjRIkRGRuYY6nqYEML85Fq2h7vA1dTL398fR44cQUZGBk6fPo25c+eiVatW2LNnD0qWLAkAOYYCtFqtOZ+8lhUVFYXnn38eTZo0wfLly+Hm5oZdu3bhnXfeQXp6Ot59913FfPIiPT0dPXv2xJUrV7Br1y6Lv3Sz1a5dG0eOHEFqaiqio6PxwQcfoF27dti1axdcXV3N6ZSGPqx5vx/HHmUppbW2TpUrV4aPjw/efPNNxMbG4tlnn0XNmjUt0mzduhUajQb9+vWzuIagoCDUrVvXPOS/f/9+JCUlYdSoUYr1OXPmDG7cuIFx48ZZfIe4u7vjhRdewPLly5GammrxHnXr1s0ijzp16iA9PR3x8fEIDAw0f2/07dvXIl2fPn3w9ttvW8QaN26M1atXw8/PD23btkXDhg3h5OT02Ptk7XkA0KpVK3h4eJh/DgwMREBAgPkJ2NTUVOzduxdDhgwxDzXLbN26Fa1atUJwcLDF+/Dcc89h4sSJ2Lt3b473zhrDhw/HJ598gr59++LTTz9FUFAQ1q9fjw0bNgBQHgo0GAxYs2YNatWqhaZNm0rT5OXzm9t3EBVdxW4Iz1q+vr4WP+t0ulzj2Y/wZ3f1P/PMM3BycrJ4bdiw4bGPVut0OtSrVy9PL9l/qkp+/fVX9OzZE+3atcPXX3+d45fVz89P2lWfPU/q0esGgCtXrqBVq1ZwdHTEzp07c6QZPHgwevbsiUaNGuHevXu4d++e+T4lJSWZh0/WrFmT415ZWy9HR0c0atQIzZs3x9ChQ7Fr1y5cvHgRH3zwgTnNo2WtWbPGXFZ6ejpSU1Ol5T1c1ujRoxEYGIhNmzahS5cuaNWqFd5//31MmTIFERERVg+BAUBGRgaef/557Nu3D1u2bEGTJk2k6dzc3NCoUSM8++yzCA8Px6ZNm3Do0CEsX7481/x9fHyg0WhUv9/WUFOWn58fACimtbZOXl5e2Lt3L+rVq4e33noLtWrVQnBwMKZNm2ZenuLmzZsQQiAwMDDH5+PgwYPm39tbt24BAEqXLq1YXnb9sxvsDwsODobJZEJCQoJFPPvas2UPJaWlpZnzdHR0zJEuKCgoRxkbNmzAwIEDsWLFCoSGhsLX1xcDBgzI8bi9vc6T1T/7GrLrn5CQAKPRmOt9Ax68Dz/99FOO9yB7CNteS1PUqFEDmzZtwpUrVxASEgJ/f3/MmTMHCxYsAACUKlVKet4vv/yCuLi4HNM2AHWfX6XvICra2ANlI39/fwDAxo0bLSbN5tXly5dRoUKFPKXdvXt3ntaw+vXXX9GjRw+EhYXh+++/Nzf6Hla7dm2cOHEiRzw7FhISYhG/cuUKWrZsCSEE9uzZI/1iPHnyJE6ePInvvvsux7FKlSqhbt26iI6ORteuXRV73dTW61GlS5dGcHCwxWTbR8vKvt/Zc59OnDhh0WiJi4vD7du3LcqKjo7GK6+8kmNS/DPPPAOTyYTTp0+jYsWKudZNJiMjAz169MDu3bvx448/ok2bNnk+t1GjRnBwcJBOLH5Y9hpTSvfVxcXFqrrbWtbD9//hXgaDwWCefGyt2rVrY/369RBC4Pjx41i9ejVmzJgBFxcXTJkyxfzAxR9//JHr3Jjs3pOH5zs9Kvs/Utn6XTdu3ICDgwN8fHxU1d/Pzw8GgwF37tyxaKzIGjf+/v5YtGgRFi1ahJiYGGzZsgVTpkxBfHx8rpOjrT0vL3x9faHVanO9b9l1qFOnDmbOnCk9HhwcbFM9Hvbcc8/hypUrOH/+PAwGA6pWrYpvv/0WAPDss89Kz1m5ciV0Oh369++f41j298OJEyfQqVMni2MnTpyw+P5Q+g6iIq5wRxDzx8cffywAiFOnTuU4pjQH6tF5Krt37xYAxHfffWcRz553ceTIEfP5jo6OYs6cOVbVNSMjQxw5ciRPL9l8l0f9+uuvwtnZWbRt21akpaUppst+BP7gwYPmWFZWlqhVq5Zo0qSJRdorV66I8uXLizJlyogLFy4o5rl79+4cr+y1VjZv3my+Z7lRUy+Zc+fOCQcHBzFmzJjHpr1z545wdnYWI0eOtIjPnj07xzIGFSpUECEhIcJgMFikfeuttwQAER0dnSP/x82BSk9PF88995zQ6XRi69atj63vo3bu3CkAiPnz5z827eTJk4VOpxMxMTHmWFJSkihRooR46aWXFM+zZhmDvJaVvYxBx44dLc7/5ptvBACxbdu2XMtRu4yBt7e36NWrlxBCiH379gkAYsOGDbmek5ycLLy8vMSzzz6b6xyoUqVKiXr16lmkSUlJEQEBAaJ58+bmmNKcwEevRc0cKJkePXqIEiVK5JomL+cpzYEaPXp0jnPLlSsnBg4caP65devWwsfHJ9f5j0OHDhXBwcHi7t27quv6KLXL12RkZIgmTZqIevXqSY/HxsYKR0fHXOexNm7cOMf3Qvb8uk8++URV/anoKZY9UNl/2X700UcYOHAgnJycUK1aNYsxe3spX748ZsyYgbfffhsXL15Ex44d4ePjg5s3b+Lw4cNwc3PD9OnTFc/X6XRo1KiRXeqyb98+9OjRA0FBQXjrrbcQHR1tcbxmzZrw9PQE8GC4benSpejVqxc++OADBAQEYNmyZThz5ozFY8zx8fFo1aoVYmNjsXLlSsTHx1ss41C6dGlzb5Ssdyx7Lknz5s3NvXW5yWu9jh8/jjfeeAMvvvgiKlasCAcHB5w4cQILFy6En58fJk6c+NiyfH198c477+Ddd9+Fr68v2rdvjyNHjiAiIgJDhw616BV54403EB4ejq5du2LEiBFwdXXFzp07sWDBArRt2xZ169Z9bHmPevHFF7Ft2za8/fbb8PPzw8GDB83HPD09zeVv3boVn3/+Obp164Zy5cohKysLf/31FxYtWoTKlSvnGF5wdHREWFiYxertEydOxFdffYXOnTtjxowZ0Ov1+OCDD5Cenp5jVetTp06Znx6Ni4tDamqqeTX7mjVrWtwXW8rSarWYO3cu+vfvjxEjRuCVV17BuXPnMHnyZLRr1w4dO3ZUfU+z79eyZcvQo0cPVKxYEUII/PDDD7h37x7atWsH4MHncfjw4Xj11Vfx119/4dlnn4WbmxtiY2Oxb98+1K5dG6+99hrc3d2xYMECDB06FG3btsWwYcMQGBiI8+fP4++//8aSJUvg4OCAuXPnom/fvujSpQtGjBiBjIwMzJs3D/fu3bMYTs6r9u3b49lnn8XkyZNx//59NGrUCH/++Se++uori3SJiYlo1aoV+vTpg+rVq8PDwwNHjhxBZGRkrk+iWnueGh9++CFatGiBJk2aYMqUKahcuTJu3ryJLVu2YPny5fDw8MCMGTOwY8cONGvWDOHh4ahWrRrS09Nx+fJl/PLLL/j0009zHQa8desW9u7dC+B/vdTbtm1DiRIlUKJECYSFhZnTjh07Fi1btoSfnx8uXryIjz/+GNeuXTOf/6g1a9bAYDBIh++yzZkzB+3atUOvXr0watQoxMfHY8qUKQgJCcmxPIcS2e9QmzZtsHfvXou5UzNmzMCMGTOwc+dOi+uiQlTYLbj8MnXqVBEcHCwcHBwsnmCzdw9Uts2bN4tWrVoJT09PodfrRbly5cSLL74ofvvtt3y5Ppnsv26VXo8+xRcXFycGDBggfH19hbOzs2jatKnYsWOHRZrs+6D0etxfwmqfwstrveLi4kS/fv1EpUqVhKurq9DpdKJixYpi5MiRFj0fefHRRx+JqlWrCp1OJ8qWLSumTZsmMjMzc6T7/vvvRYsWLYS/v79wc3MTtWrVEu+//750oVEhHt8Dldt9ffgzevr0afHiiy+KcuXKCWdnZ+Hs7CyqV68uJk2aJO7cuSPN9+Hzs50/f1706NFDeHp6CldXV9GmTRsRFRWVI11un6NH329byxLiwdN5derUETqdTgQFBYnw8HDpU4iPUuqB+vfff8Urr7wiKlWqJFxcXISXl5do3LixWL16dY48vvjiC9GkSRPh5uYmXFxcRKVKlcSAAQNyLLj4yy+/iLCwMOHm5iZcXV1FzZo1c/Q6b968WTRp0kQ4OzsLNzc30aZNG/Hnn39apMlrD5QQD1bPHjx4sPD29haurq6iXbt24t9//7V4H9LT08XIkSNFnTp1hKenp3BxcRHVqlUT06ZNE/fv31e8d3k9z5YeKCGEOHXqlOjVq5fw8/Mz/34NGjTIYlHfW7duifDwcFGhQgXh5OQkfH19RcOGDcXbb7+t+LuVLbfvp0c/l927dxclS5YUTk5OIigoSAwaNMhisddHVa1aVZQvX16x5zHb9u3bRdOmTYWzs7Pw9fUVAwYMkC6Sq0RW17CwMPHof8/Znx1rnsam/KERwsrFhYiICtHq1avx6quv4vz58yhXrlyeFhQlIrIXPoVHREVa5cqV4eTk9NRsJkxETwb2QBFRkXTnzh1cunTJ/HO9evXYC0VEBYYNKCIiIiKVOIRHREREpBIbUEREREQqsQFFREREpBJnXOaRyWTCjRs34OHhwU0giYgoV0IIJCcnIzg4WHGzYlulp6cjMzPTLnnpdDo4OzvbJa+nBRtQeXTjxg2UKVOmsKtBRERFyNWrVx+7qbI10tPT4eXijUxk2CW/oKAgXLp0iY0oFdiAyqPsbWD2df0Q7k4uFseu/X1Oek7p6n9J4959yyqWo2/Y2MoaPvlcgwbmexmpcep3OS+Iej2JlO7V03o/lFjzmVLrSf3dsJeMqMPSuNL33ZP4Gby79CVpfOeHftJ4mikLY+9+my9biAFAZmYmMpGBULSBo43/lRtgwIG4ncjMzGQDSgU2oPIoe9jO3ckFHo80oNy0OXdzBwAPJ/nt9XTVKZaj93BRPFbUuf7/Pnz5yfG++vtXEPV6Eindq6f1fiix5jOl1pP6u2EvGQrfeUrfd0/iZ9Dg7CSNuzoof58DyPcpHy5aVzhq5HXLK4PIAox2qtBThA0oIiKiIsrJyQlONjagNAJsQFmBDSgiIqIiSqdzgpMm916wx9EIAOn2qc/ThMsYEBEREanEHigiIqIiSueog9Nj5mE9jsZkp8o8ZdiAIiIiKqKcnHTQ2diAAhtQVuEQHhEREZFK7IEiIiIqonROOugc5Evp5Bl7oKzCBhQREVERpbPLEJ6wT2WeMhzCIyIiIlKJPVAquXmshZvO8rbVmShfyl8TUF4aF/H3FPNPX71JfsBfvry+JsBbMS+1vLvJtyroN1HdSrpr58v/muk7YYfqOinllXT8T2ncs05zaTz3a1BXr68XtJPGla5PKX1u1N4rtfcJUL5X9rwOJWqvz55lK1Gq09r5ylsv5XfZ9lUuX3PP7T0a9rtC2b8rnVEQ90NO6XdJ27q+NN7B/R9pPCktC3jdbtVS5OSkg05rWw+UMLIHyhpsQBERERVROicn2xtQDpwEZQ0O4RERERGpxB4oIiKiIspJp4OTjT1QJiN7oKzBBhQREVER5eToBJ2jbZsJmzTcSdgabEAREREVUY56Jzg62tYD5ahlA8oanANFREREpBJ7oIiIiIooJ50TnGwcwjM6GOxUm6cLG1BERERFlJPOCU5ObEAVBg7hEREREanEHigiIqIiSqtzhKOTbf+VazVsCliDPVBERERFlKPe8cGTeDa98rcB9fPPP6NJkyZwcXGBv78/evbsaXE8JiYGXbt2hZubG/z9/REeHo7MzMx8rZM9aIQQ3AQnD5KSkuDl5YWXNiyCztXF4tgXneR7ZSVv/FoaN/19U7GctMtZ0rizfLs9ONTwVsxLnl55X6/wlFqq8lKidD8KQtr549K4S+U6iucM/iUmv6rzWF92Ga4qvcEUKY0rXXduRp/1lsaV6jRg62eqy1Ci9jOi9B6pvX/WULpue37OC+L67PX+FcQ9V2LNNaitr9r3W+m9y0xNw4aXxiExMRGenp6q6pAX2f8nfdhrBVycXG3KKy0rFeO/G5ovdf3+++8xbNgwzJo1C61bt4YQAidOnMCLL74IADAajahXrx5KlCiBBQsW4M6dOxg4cCB69uyJxYsX27Uu9sZ+OyIioiLKUecIR51t/5U75tMQnsFgwOuvv4558+ZhyJAh5ni1atXM/96+fTtOnTqFq1evIjg4GACwYMECDBo0CDNnzsyXxqe9cAiPiIioiNLqHe3yyg9Hjx7F9evX4eDggPr166NkyZJ47rnncPLkSXOaAwcOICQkxNx4AoAOHTogIyMDUVFR+VIve2EDioiIiJCUlGTxysjIsCm/ixcvAgAiIiLwzjvvYOvWrfDx8UFYWBju3r0LAIiLi0NgYKDFeT4+PtDpdIiLi7Op/PzGBhQREVER5aizdQK5Exx1D9aRKlOmDLy8vMyv2bNnS8uMiIiARqPJ9fXXX3/BZHqwSfHbb7+NF154AQ0bNsSqVaug0Wjw3XffmfPTaDQ5yhBCSONPEs6BIiIiKqK0Oq3Nc6C0QgsAuHr1qsWcI71eL00/ZswYvPzyy7nmWb58eSQnJwMAatasaZFnxYoVERPzYPJ9UFAQDh06ZHFuQkICsrKycvRMPWnYgCIiIiqinPSOcLJxDpPT/zcFPD098zRp29/fH/7+/o9N17BhQ+j1epw5cwYtWrQAAGRlZeHy5csoV64cACA0NBQzZ85EbGwsSpYsCeDBxHK9Xo+GDRtae0kFgg0oIiIisjtPT0+MHDkS06ZNQ5kyZVCuXDnMmzcPANCrVy8AQPv27VGzZk30798f8+bNw927dzFx4kQMGzbsiX4CD2ADioiIqMhycnKEk41DeE6m/GsKzJs3D46Ojujfvz/S0tLQpEkT7Nq1Cz4+PgAArVaLn3/+GaNGjULz5s3h4uKCPn36YP78+flWJ3thA4qIiKiIctI5wUln22bCTibbzs81bycnzJ8/P9cGUdmyZbF169Z8q0N+4VN4RERERCqxB4qIiKiI0um00Ou0NuVhMNl2/tOKe+HlUfa+Q7K9gg5fWSg9p3pqmjRuuqm8F55x97/SeNp5dRsrupSW/0Jon1Pe78675UeqylC67volKqrKBwDSrpyUxl3Kyet77NZFVfk3LveG6jopUbruJ7Hs3Kitl9qyc/sc3D+8Sxp3UHhs+d9H9p98nIK450rXp/RZBpQ/z06u3dVXjPKFvX6/c/s/wx6y8/917m64ubjblNf9tBR0mNwq3+paXHEIj4iIiEglDuEREREVUc5OWrg42TYEZzRwCM8abEAREREVUa5OWrja2IAysQFlFQ7hEREREanEHigiIqIiyslkhM5ktDkPUo8NKCIioiJKazBAazDYnAepxwYUERFREeWQlQkHbYbNeZB6nANFREREpBJ7oIiIiIqqjCxAk2V7HqQaG1BERERFlCYzCxoH2xpAmkw2oKzBITwiIiIildgDZQdKeyEZTJHSeIb7JcW8DD8cl8ZvXtCpqlM5P/mkQKdK1VXlk5uYZPnHJyY5xorcPOThi0p5yct+MWSsFWXLbfxnsaqylcQo5gP0qFlFXV4K99ye161E7f5ySp9/AIDC3nZadx9pvEGpCtL45lPnpPHc7rnae6V03Uqfjx41G6jKP7e8CuJ9JUv23EexIIgsI4SDbU/RiSwuY2ANNqCIiIiKqkwDoLFxGYJMLmNgDQ7hEREREanEHigiIqIiSmQYIWDbEJzI4BCeNdiAIiIiKqJElgHCxiE8kcUhPGs8MUN4s2fPhkajwbhx48wxIQQiIiIQHBwMFxcXtGzZEidPnrQ4LyMjA2PHjoW/vz/c3NzQrVs3XLt2zSJNQkIC+vfvDy8vL3h5eaF///64d+9eAVwVERERFUdPRAPqyJEj+Oyzz1CnTh2L+Ny5c/Hhhx9iyZIlOHLkCIKCgtCuXTskJyeb04wbNw6bNm3C+vXrsW/fPqSkpKBLly4wGv/XJdmnTx9ER0cjMjISkZGRiI6ORv/+/Qvs+oiIiPKDyDTa5UXqFXoDKiUlBX379sXnn38OH5//PcIshMCiRYvw9ttvo2fPnggJCcGaNWuQmpqKdevWAQASExOxcuVKLFiwAG3btkX9+vWxdu1anDhxAr/99hsA4PTp04iMjMSKFSsQGhqK0NBQfP7559i6dSvOnDlTKNdMRERkDyLDaJcXqVfoDajRo0ejc+fOaNu2rUX80qVLiIuLQ/v27c0xvV6PsLAw7N+/HwAQFRWFrKwsizTBwcEICQkxpzlw4AC8vLzQpEkTc5qmTZvCy8vLnIaIiKgoEplGiAyDbS/2QFmlUCeRr1+/HkePHsWRI0dyHIuLiwMABAYGWsQDAwNx5coVcxqdTmfRc5WdJvv8uLg4BAQE5Mg/ICDAnEYmIyMDGRn/2+E6KSkpj1dFRERExV2hNaCuXr2K119/Hdu3b4ezs7NiOo1GY/GzECJH7FGPppGlf1w+s2fPxvTp03Mth4iIqDCZMgwwmWx7is7Ep/CsUmhDeFFRUYiPj0fDhg3h6OgIR0dH7N27Fx9//DEcHR3NPU+P9hLFx8ebjwUFBSEzMxMJCQm5prl582aO8m/dupWjd+thU6dORWJiovl19epVm66XiIjI3kSmASYbX4IrkVul0Hqg2rRpgxMnTljEXn31VVSvXh1vvvkmKlasiKCgIOzYsQP169cHAGRmZmLv3r2YM2cOAKBhw4ZwcnLCjh070Lt3bwBAbGws/vnnH8ydOxcAEBoaisTERBw+fBiNGzcGABw6dAiJiYlo1qyZYv30ej30er1N16hRaNVnHlCee5V+Rx7XKrxTniXkZTjULyGNxzkrj3W/Pfk3xWMy6+eq26frZZX5515228cnyiPletVQlc/MSf9K4+U9yyufpPAZuZx0WRrf+KW8Ti/OlWdfmPe83xTlr5eZk0orHJF/Pt9WyEvtZxBQvidqr09pnzrr7rn8fd2Iwnv/lK5D6XOem1x/ByT6visflbDn7z2RrQqtAeXh4YGQkBCLmJubG/z8/MzxcePGYdasWahSpQqqVKmCWbNmwdXVFX369AEAeHl5YciQIZgwYQL8/Pzg6+uLiRMnonbt2uZJ6TVq1EDHjh0xbNgwLF++HAAwfPhwdOnSBdWqVSvAKyYiIrIvU6YdhvAM7IGyxhO9EvnkyZORlpaGUaNGISEhAU2aNMH27dvh4eFhTrNw4UI4Ojqid+/eSEtLQ5s2bbB69WpotVpzmq+//hrh4eHmp/W6deuGJUuWFPj1EBER2ZMx0wijjQ0oo4FP4VnjiWpA7dmzx+JnjUaDiIgIREREKJ7j7OyMxYsXY/HixYppfH19sXbtWjvVkoiIiJ52T1QDioiIiPLOmJkFo0n7+IS55WHIslNtni5sQBERERVRBoMBBmHbEJ7ByDlQ1ij0lciJiIiIihr2QBERERVRBkMWDMK2ITyDkUN41mADioiIqIgyGIzIsnkIj0/hWYMNKCIioiIqy5CFLGHbbJws9kBZhXOgiIiIiFRiDxQREVERlWUyIAu2zYHKsnEhzqeVRgghCrsSRUFSUhK8vLyQmJgIT0/PPJ2Tmfy9NH5/83eK56T9FieNXzsl3xtK6XMfXC1DGvfoXU6xbKe6daXx9MAAaXzD6UTFvPLb6GdGFVrZBWHpkWWFVvaw6vJNtpM06dK4v3tf1WXY6/qK++egKMntPX2phpeqvJS+W6x5v5XqpVQnTyH/rv3835yb0udWJ2v+z1AjO//vyr4GVwfb9m1NNWWgV8wn+VbX4opDeEREREQqcQiPiIioiMo0GuFo41N4mSY+hWcNNqCIiIiKqCxjJrJsnIiTZcq0T2WeMhzCIyIiIlKJPVBERERFlMGQBYODxrY8TFwHyhpsQBERERVRBkMWsmxrP8Eg2ICyBofwiIiIiFRiDxQREVERZUzPgFFjsi0P9kBZhQ0oIiKiIsqUlQmTxrbH8ExsQFmFDSgiIqIiSiMM0MC2SVAaG9eRelpxDhQRERGRSuyBykerz8r3tetdPljxHJ2XfL+l+0ny9JcuyDeRvHJRJ41XuxarWHaZNvJjLkN7SONODuq6jbtXcVc89uO5FGl8eMPRqsooLl6s5iaNuyemSuPOAWWk8ZX/XFEsQ+n90Ork+2oZM+TvkTXU7mn2WdRSVfHcPmtKlD6Daj2tn1l77ks4+hm7ZWW3z9roZ57M91WrFdDaOAdKKwRgWxZPJTagiIiIiiito4CjjXOgtEIAnAalGofwiIiIiFRiDxQREVER5WiHHihHYeNmek8pNqCIiIiKKK0W0Nq4ErmW7SercAiPiIiISCX2QBERERVRjg4PXjblwSfwrMIGFBERURGldQS0NjagtGxAWYVDeEREREQqsQeKiIioiNJq7dADZeMk9KcVG1BERERFlIMdhvAcOIRnFTagiIiIiij2QBUeNqDy0eEVFaXxfu/J9xoDAKPPSWlcJ9/aDhqdqzRucvaQxs+fuq5YtneQfC1/h+PHpPGeYWGKeamldK+GN7RbEar9evYjabxD1dftVsbQ17ZJ4x/Mk/9qKu15l5CVKI33rOatWLaPk1fulXuEx33558Oo3yqN/3blgmJeau9hOQ/5bvENg/2l8dyuTeleqS0j6sZtaVzpc2ON7xZWtVteKz55zi75KH1m7ZV/QZWtlFevN+Sfg4L4PqCihQ0oIiKiIkqr1UBrYxeS1sguKGvwKTwiIqIiSqu1zys/7NmzBxqNRvo6cuSIOV1MTAy6du0KNzc3+Pv7Izw8HJmZmflTKTtiDxQRERHZXbNmzRAbG2sRe/fdd/Hbb7+hUaNGAACj0YjOnTujRIkS2LdvH+7cuYOBAwdCCIHFixcXRrXzjA0oIiKiIsoePUj51AEFnU6HoKAg889ZWVnYsmULxowZA43mwbDh9u3bcerUKVy9ehXBwcEAgAULFmDQoEGYOXMmPD0986l2tmMDioiIqIjSOtqhAfX/U6CSkpIs4nq9Hnq98kNPam3ZsgW3b9/GoEGDzLEDBw4gJCTE3HgCgA4dOiAjIwNRUVFo1aqV3cq3NzagiIiICGXKWD7lO23aNERERNgt/5UrV6JDhw4W5cTFxSEwMNAinY+PD3Q6HeLi4qwuq0GDBqrSazQabNmyBaVKlcrzOWxAERERFVEaR8DBxh6o/x9Nw9WrVy2GzJR6nyIiIjB9+vRc8zxy5Ih5nhMAXLt2Db/++iu+/fZbSfk5nwIUQkjjeRUdHY0JEybA3d39sWmFEPjggw+QkZGhqgw2oIiIiIoorYOAVitsy0M8ON/T0zNPc47GjBmDl19+Odc05cuXt/h51apV8PPzQ7du3SziQUFBOHTokEUsISEBWVlZOXqm1Jo0aRICAgLylHbBggWq82cDioiIiPLM398f/v7yBWZlhBBYtWoVBgwYACcnJ4tjoaGhmDlzJmJjY1GyZEkADyaW6/V6NGxo/UrKly5dQokSJfKc/tSpUxbzsPKCDSgiIqIiSuv44GVTHvapiqJdu3bh0qVLGDJkSI5j7du3R82aNdG/f3/MmzcPd+/excSJEzFs2DCbnsArV66cqvSPzv/KCzag8pHS9gJCbFc8J91bPubs7CYfmzVlyRcb83KVb2URe+2OYtlJt+TdwF5JKYrnyAz6Uf7ruLWvcpfvik9UFWFXXb5eL41PCpXfD6X0StenlB4A0EIePhl/S+EEebxWgPwvLT+3vP8Flu3OfXkZJ9Pkn4NaClujKG2BAgB7Ly6SxucdCJLGld4LJUrbtQDKn09APlwwKVQ+kbVhsPp7q1bDeXftlpfSPQ+rOE5VPtZsm5Lr74DE6u5Gabz/JKXfC/V1imsh/4w4O8o/a2rvU0Fx0Ao42DiE5yBsO/9xVq5ciWbNmqFGjRo5jmm1Wvz8888YNWoUmjdvDhcXF/Tp0wfz58/Pl7rcv38fGzZsQFpaGtq3b48qVapYnRcbUEREREWUg+ODl0152KcqitatW5fr8bJly2LrVvmemraIiYlB//79cfToUTRt2hQrV65Eu3btcO7cOQCAi4sLtm3bhmeffdaq/LmVCxERERU7EydORGZmJj755BO4urqiQ4cOqFKlCmJjY3Hz5k106tTJpmUa2ANFRERURGm19nsKr7j5/fffsWXLFjRu3BidOnWCv78/vvjiC/PTfe+88w7atGljdf55akAVxIJUREREpI6DVsBBYd5WnvMopg2oW7dumSeT+/r6wtXV1WJphKCgICQkJFidf54aUAWxIBURERGRvTy6GKctC3PK5HkIL78XpCIiIiJ1HLS2r0TuYLJPXZ5E7733HlxdXQEAmZmZmDlzJry8HjylnpqaalPeeWpAFcSCVERERKSOg6OAlkN4Us8++yzOnDlj/rlZs2a4ePFijjTWylMDqiAWpCIiIiKylz179uRr/lY9hZeeno7jx48jPj4eJpNl39+j+9wQERFR/rDLQpqm4tkDld9UN6AiIyMxYMAA3L59O8cxjUYDo1G+giwRERHZl4Mjn8KTGT9+fJ7Tfvjhh1aVoboBNWbMGPTq1QvvvfeezTslExEREdnbsWPHLH6OioqC0WhEtWrVAABnz56FVqu1acNi1Q2o+Ph4jB8/no0nG+TW1tf4u0rjWkf5shAOTjpp3FXnIo37lSyrWPaduHPSeOlrSYrnyOS2511hmbpHebO991qkq8pL7fUV5v3I7bqfryC/7k2XnKXx2S3HSeO3U76Wxj0Myl8vSnv3vdciVhpvXO4NxbzU2trXPvko3Vul+woo31slSnlV9JM/EX3xTrxiXmEV7XcP1bLX70DY41fSybPapeTr/zype94p4VN4crt37zb/+8MPP4SHhwfWrFkDHx8fAEBCQgJeffVV/Oc//7G6DNVbubz44ov5PjGLiIiIHi97IU2bXjbOoXrSLViwALNnzzY3ngDAx8cH//3vf21adkl1D9SSJUvQq1cv/PHHH6hduzacnJwsjoeHh1tdGSIiIso7u/RA2Xj+ky4pKQk3b95ErVq1LOLx8fFITk62Ol/VDah169bh119/hYuLC/bs2ZNjlU82oIiIiOhJ8fzzz+PVV1/FggUL0LRpUwDAwYMHMWnSJPTs2dPqfFU3oN555x3MmDEDU6ZMgYOD6hFAIiIishON44OXTXkUwzlQD/v0008xceJE9OvXD1lZWQAAR0dHDBkyBPPmzbM6X9W3PTMzEy+99BIbT0RERIXMwVEDB0fb9nhzMNl3j7gnjaurK5YtW4Z58+bhwoULEEKgcuXKcHNzsylf1a2ggQMHYsOGDTYVSkRERFSQ3NzcUKdOHdStW9fmxhNgRQ+U0WjE3Llz8euvv6JOnTo5JpFbuyAVERERqcMhPLmePXti9erV8PT0zFP6vn37YuHChQgIkC8RIqP6tp84cQL169cHAPzzzz8Wxx6eUE5ERET5TOsAONo4pcZY/Kbk/Pjjj7h161ae0goh8NNPP+H999/P3wbUw4tTERERET1phBCoWrVqvpZhY8cfERERFRaNkwYaJ9tGfzTFcBK5NZ09pUqVUpU+Tw2oghhLJCIiIpUcOYQnExYWlu9l5KkBVRBjiU+TmLsXFI95pmdJ4/cT5X8huPv4SuOuzvK98FIz0xTLNhrULeefkiHfI8/fjvtV2cvslq8pHtt5flHBVaSA5Xbdo7fL93Jb2l75HJlp++Wfg54V5fs35k7+ObdXXXPLS6m+P1zU263sxuXU1SnAXb533t9x8u/jHy4q77W35kzhXXd+U7p/gPL1tS2dX7Whp0WeGlAFMZZIREREKjlqHrxsYSx+Q3gFIU8NqPwaS/zkk0/wySef4PLlywCAWrVq4b333sNzzz0H4EHDbfr06fjss8+QkJCAJk2aYOnSpRb72WRkZGDixIn45ptvkJaWhjZt2mDZsmUoXfp/f14kJCQgPDwcW7ZsAQB069YNixcvhre3t+rrIiIiemJoHaCxdQjPUPyG8ApCnhpQ+TWWWLp0aXzwwQeoXLkyAGDNmjXo3r07jh07hlq1amHu3Ln48MMPsXr1alStWhX//e9/0a5dO5w5cwYeHh4AgHHjxuGnn37C+vXr4efnhwkTJqBLly6IioqCVvtgh8Q+ffrg2rVriIyMBAAMHz4c/fv3x08//ZQv10VERFQgHDW2z4GytQfrKVWozc6uXbuiU6dOqFq1KqpWrYqZM2fC3d0dBw8ehBACixYtwttvv42ePXsiJCQEa9asQWpqKtatWwcASExMxMqVK7FgwQK0bdsW9evXx9q1a3HixAn89ttvAIDTp08jMjISK1asQGhoKEJDQ/H5559j69atOHPmTGFePhEREeWziIgIXLlyxe75PjH9dkajEevXr8f9+/cRGhqKS5cuIS4uDu3btzen0ev1CAsLw/79+wEAUVFRyMrKskgTHByMkJAQc5oDBw7Ay8sLTZo0Madp2rQpvLy8zGlkMjIykJSUZPEiIiJ6omTPgbL1VYz99NNPqFSpEtq0aYN169YhPT3dLvkWegPqxIkTcHd3h16vx8iRI7Fp0ybUrFkTcXFxAIDAwECL9IGBgeZjcXFx0Ol08PHxyTWN7GnAgIAAcxqZ2bNnw8vLy/wqU6aMTddJRERkd9nLGNj6KsaioqJw9OhR1KlTB2+88QZKliyJ1157DUeOHLEpX1V3TQiBK1euIC1N+VF4tapVq4bo6GgcPHgQr732GgYOHIhTp06Zjz+6PYwQ4rFbxjyaRpb+cflMnToViYmJ5tfVq1fzeklERET0BKlTpw4WLlyI69ev44svvsD169fRvHlz1K5dGx999BESExNV56m6AVWlShVcu3ZNdUFKdDodKleujEaNGmH27NmoW7cuPvroIwQFBQFAjl6i+Ph4c69UUFAQMjMzkZCQkGuamzdv5ij31q1bOXq3HqbX6+Hp6WnxIiIieqJo7dD7pC3ePVAPM5lMyMzMREZGBoQQ8PX1xSeffIIyZcpgw4YNqvJSddccHBxQpUoV3LlzR1UhagghkJGRgQoVKiAoKAg7duwwH8vMzMTevXvRrFkzAEDDhg3h5ORkkSY2Nhb//POPOU1oaCgSExNx+PBhc5pDhw4hMTHRnIaIiKhI4hyoPImKisKYMWNQsmRJvPHGG6hfvz5Onz6NvXv34t9//8W0adMQHh6uKk/Ve+HNnTsXkyZNwieffIKQkBC1p1t466238Nxzz6FMmTJITk7G+vXrsWfPHkRGRkKj0WDcuHGYNWsWqlSpgipVqmDWrFlwdXVFnz59AABeXl4YMmQIJkyYAD8/P/j6+mLixImoXbs22rZtCwCoUaMGOnbsiGHDhmH58uUAHixj0KVLF1SrVs2m+hMREdGTrU6dOjh9+jTat2+PlStXomvXruZljrINGDAAkyZNUpWv6gZUv379kJqairp160Kn08HFxXLLkLt37+Y5r5s3b6J///6IjY2Fl5cX6tSpg8jISLRr1w4AMHnyZKSlpWHUqFHmhTS3b99uXgMKABYuXAhHR0f07t3bvJDm6tWrLW7O119/jfDwcPPTet26dcOSJUvUXjoREdGTxR6TwIv5JPJevXph8ODBuS7wXaJECZhMJlX5qm5ALVq0SO0pilauXJnrcY1Gg4iICERERCimcXZ2xuLFi7F48WLFNL6+vli7dq211bQ7n2vKT//d3ycfHo29Lt+XytvXSxpPTZdP9I+7elax7MZh8r3wNEHyze2S0q3Z6+zJ06byOGm8z5bPpfGEdPlnTWkfsnXdhllVr/yWkC7/9d/4j/Lvkjwf+XW3qWy/PdPaVLZbVqr3crNn2UqfqS86qXvKNyn9vDTeKtiguk4rT7mpPkctpeu21+9Gbu+pUtlK7Pl+FwhH7YOXTXmoazgUNUKIHE/rA0BaWhrmzZuH9957z6p8VTegBg4caFVBREREZF8aR9u3crF5K5gn3PTp0zFy5Ei4urpaxFNTUzF9+vSCa0A9LC0tDVlZWRYxPq1GRERETwqlZYv+/vtv+Pr6Wp2v6gbU/fv38eabb+Lbb7+VPo1nNBqtrgwRERGpoLXDHKhiuoyBj48PNBoNNBoNqlatatGIMhqNSElJwciRI63OX3UDavLkydi9ezeWLVuGAQMGYOnSpbh+/TqWL1+ODz74wOqKEBERkUpaB0Br4xwobfHs+Fi0aBGEEBg8eDCmT58OL6//zRnW6XQoX748QkNDrc5fdQPqp59+wpdffomWLVti8ODB+M9//oPKlSujXLly+Prrr9G3b1+rK0NERERkD9lztitUqIBmzZrBycnJrvmrbkDdvXsXFSpUAPBgvlP2sgUtWrTAa6/Z76kbIiIiegy7PIVn4/lPoKSkJPOc7Pr16yMtLU1xGzpr526rbkBVrFgRly9fRrly5VCzZk18++23aNy4MX766Sd4e3tbVQkiIiKyglZrhyG84teA8vHxQWxsLAICAuDt7Z3rnrjWzt1W3YB69dVX8ffffyMsLAxTp05F586dsXjxYhgMBnz44YdWVYKIiIjIXnbt2mV+wm7Xrl3SBpStVDeg3njjDfO/W7VqhX///Rd//fUXKlWqhLp169q1ckRERJQLDuFJhYWFmf/dsmXLfCnDpnWgAKBs2bIoW7asPepCREREamjt0IAqhkN4D6tYsSL69u2Lfv362XUP3Dw1oD7++OM8Z6h2N2MiIiKi/DJmzBh88803mDlzJurXr4/+/fvjpZdeQsmSJW3KN08NqIULF1r8fOvWLaSmpponjd+7dw+urq4ICAhgA+oht1O+lsa1x88onnP/rvwtsdf6pFqt8p5HTnqFY77yJxSm7JJvzPhLf9XVeiIp7dO19Mgyafxeonc+1iZ3nb76ThrvXP2W4jn3EktI4zfTlM+Rsedef0rX8Wbz69L4P3d00vjP/8qvLTdKZdQKkOd1Ml5+n+b8qbxhKeAtjR6Kke9tp1S2m07+PVE/ULnka8nyffKUPre/9O+lnJlKhbkf5JO6F6XdcBL5Y40fPx7jx4/H2bNn8fXXX+OTTz7BpEmT0KpVK/Tr1w8DBgywKt88LT966dIl82vmzJmoV68eTp8+jbt37+Lu3bs4ffo0GjRogPfff9+qShAREZF6D/bC09r4Kp4rkT+qatWqmD59Os6cOYM//vgDt27dwquvvmp1fqrnQL377rvYuHGjxThitWrVsHDhQrz44otcSJOIiKigsAdKlcOHD2PdunXYsGEDEhMT8eKLL1qdl+oGVGxsbI4NhIEH+8rcvHnT6ooQERER2Vv20N26detw+fJltGrVCh988AF69uwJDw8Pq/NV3YBq06YNhg0bhpUrV6Jhw4bQaDT466+/MGLECLRt29bqihAREZFKXMbgsapXr45GjRph9OjRePnllxEUFGSXfFU3oL744gsMHDgQjRs3Nu8rYzAY0KFDB6xYscIulSIiIqI8cLDDEJ5D8W5A/fvvv6hatard81XdgCpRogR++eUXnD17Fv/++y+EEKhRo0a+VI6IiIjIFvnVPrF6Ic2qVauy0URERFSYOIlcytfXF2fPnoW/vz98fHxy3crl7t27VpVhVQPq2rVr2LJlC2JiYpCZmWlxjPvhERERFQyNoyM0jrZtKqJxtNNCg0+QhQsXmieIL1y48MnYC2/nzp3o1q0bKlSogDNnziAkJASXL1+GEAINGjSwewWJiIiI1Bg4cKD534MGDcqXMlSvnjV16lRMmDAB//zzD5ydnfH999/j6tWrCAsLQ69e9lu5loiIiB5D6/C/YTyrX8V7IU2tVov4+Pgc8Tt37kBrw/Cl6rt2+vRpc8vO0dERaWlpcHd3x4wZMzBnzhyrK0JERETqaLSOdnkVZ0IIaTwjIwM6nXw7qLxQfdfc3NyQkZEBAAgODsaFCxdQq1YtAMDt27etrkhxpHWQ315xO1XxnIQ4+TkZWn9pPDhYvu+WVmGvrNTMNOWyY09L4wFJKdL48IaxinkVllYf75DGd4e3s1sZo58ZpRC3WxGqr8OafcvU1nfzKfmm4kevLpTGN19yVsxrxrOvSeNK16FUdik3+f5uud0PpXubkC7/PKdkJEnj5bz00nijcsrfg0rXrVSnBc//I41X8AuWxi/duaFYdsTWEGncnr8bRE+Sjz9+8L2h0WiwYsUKuLu7m48ZjUb8/vvvqF69utX5q25ANW3aFH/++Sdq1qyJzp07Y8KECThx4gR++OEHNG3a1OqKEBERkTr26EEqrj1QCxc++ONOCIFPP/3UYrhOp9OhfPny+PTTT63OX/Vd+/DDD5GS8qBHIiIiAikpKdiwYQMqV65sriwRERHlP42DEzRaJxvzKH5P4QHApUuXAACtWrXCDz/8AB8fH7vmr6oBZTQacfXqVdSpUwcA4OrqimXLltm1QkRERJRHWi1gaw9SMVwH6mG7d+/Ol3xVTSLXarXo0KED7t27ly+VISIiIrKnF198ER988EGO+Lx582xaPUD1U3i1a9fGxYsXrS6QiIiI7ONJfwrv7Nmz6N69O/z9/eHp6YnmzZvn6BGKiYlB165d4ebmBn9/f4SHh+dYpNsWe/fuRefOnXPEO3bsiN9//93qfFU3oGbOnImJEydi69atiI2NRVJSksWLiIiICobG0ckur/zSuXNnGAwG7Nq1C1FRUahXrx66dOmCuLg4AA+mBnXu3Bn379/Hvn37sH79enz//feYMGGC3eqQkpIiXa7AycnJpnaL6gZUx44d8ffff6Nbt24oXbo0fHx84OPjA29vb7tP0CIiIqKi6fbt2zh//jymTJmCOnXqoEqVKvjggw+QmpqKkydPAgC2b9+OU6dOYe3atahfvz7atm2LBQsW4PPPP7dbp0xISAg2bNiQI75+/XrUrFnT6nxV99vl12QsIiIiUudJXsbAz88PNWrUwJdffokGDRpAr9dj+fLlCAwMRMOGDQEABw4cQEhICIKD/7e2WYcOHZCRkYGoqCi0atXK5nq8++67eOGFF3DhwgW0bt0awINt6b755ht89913Vuer+q6FhYVZXRgRERHZz4MGlI3LGGizACBHj49er4deL18wNk/5ajTYsWMHunfvDg8PDzg4OCAwMBCRkZHw9vYGAMTFxSEwMNDiPB8fH+h0OvMwn626deuGzZs3Y9asWdi4cSNcXFxQp04d/Pbbbza1aYr3BjhERESUJ2XKlIGXl5f5NXv2bGm6iIgIaDSaXF9//fUXhBAYNWoUAgIC8Mcff+Dw4cPo3r07unTpgtjY/638r9FocpQhhJDGrdW5c2f8+eefuH//Pm7fvo1du3bZ3CFUPJcfJSIiegpoHOwwhPf/245dvXoVnp6e5rhS79OYMWPw8ssv55pn+fLlsWvXLmzduhUJCQnmfJctW4YdO3ZgzZo1mDJlCoKCgnDo0CGLcxMSEpCVlZWjZ8oW9+7dw8aNG3Hx4kVMnDgRvr6+OHr0KAIDA1GqlHxLtMdhAyofvbPvrjT+35rKb5ZXiXPSuPFkhjSutOedvpS3NF4RlRXLPrD/mjTu81XOXawBIMz1b2k8vepWafybE1cUy361/mjFYzLtZ8nn4jkV4kd69PZPpPFzfynvtbT9Lfn4vtL+ZErXbY1Xnjul8gz5YnstyvpJ48Pc76vMX1mPmuF2y8spRf4ZWba5tqp8lN67Gc+qrhIG/OesNF7C3U0aN5rkewAmZCj/xc4974one86B8vT0tGhAKfH394e/v3x/1oelpj7Y99XBwXKwy8HBASaTCQAQGhqKmTNnIjY2FiVLlgTwYGK5Xq83z5Oy1fHjx9G2bVt4eXnh8uXLGDp0KHx9fbFp0yZcuXIFX375pVX5cgiPiIiI7C40NBQ+Pj4YOHAg/v77b5w9exaTJk3CpUuXzOsytW/fHjVr1kT//v1x7Ngx7Ny5ExMnTsSwYcPy1JjLi/Hjx2PQoEE4d+4cnJ3/t8n5c889V7DrQBEREdGT4UleSNPf3x+RkZFISUlB69at0ahRI+zbtw8//vgj6tatC+DBDic///wznJ2d0bx5c/Tu3Rs9evTA/Pnz7VaPI0eOYMSIETnipUqVsmmiuuq7dufOHbz33nvYvXs34uPjzd1w2e7elQ9bERERkX05aLVwsHEvO1vPz02jRo3w66+/5pqmbNmy2LpVPvXDHpydnaVrSp05cwYlSpSwOl/VDah+/frhwoULGDJkCAIDA+06S56IiIjyTqPV2mEOVPHeTLh79+6YMWMGvv32WwAPnvqLiYnBlClT8MILL1idr+q7vm/fPuzbt8/c/UZERET0pJo/fz46deqEgIAApKWlISwsDHFxceYJ7NZS3YCqXr060tLSrC6QiIiI7EOjdYBGa9t0ZlvPf9J5enpi37592LVrF44ePQqTyYQGDRqgbdu2NuWrugG1bNkyTJkyBe+99x5CQkLg5GS5Aqq9Zs0TERFR7sT/v2zN42nQunVr81Yu9qC6AeXt7Y3ExMQclcheNdRoNNqtckRERERqffzxx3lOGx5u3TpzqhtQffv2hU6nw7p16ziJnIiIqBCZhAkmYXp8wsfkUdwsXLgwT+k0Gk3BNaD++ecfHDt2DNWqVbOqQCIiIrIPg8kIg8LK9GryKG6io6Ph5eWVr2WonjnWqFEjXL16NT/qQkRERGQzX19f3Lp1C8CDuU/37t2zexkaIYSq+WPfffcdIiIiMGnSJNSuXTvHJPI6derYtYJPiqSkJHh5eSExMdHmifJXEz5TPOa+dZc0fmV5gjR+/JR8EbDGDUKlcZfqAYplG2+nysvY/5c0XsJLvm9f9Z7yj5Tz0B6KZZ80pUvja844S+OnzlWUxneP7qBYRn5rtTT3xeJk8ru+SvvzAUB43SxVef0Wo67DuryH8l/F83fKe7DV3g+le16zykXFc57Ez4695PYZVLonxfl+FCZ7/p+RW/43bq2Dp6erjXmlIrhEn3yra2Hw8vLCwYMHUaNGDTg4OODmzZs2LZopo3oI76WXXgIADB482BzTaDScRE5ERFTAjCaD4ubSavIobtq2bYtWrVqhRo0aAIDnn38eOp1OmnbXLnnHxeOobkBdunTJqoKIiIiICsLatWuxZs0aXLhwAXv37kWtWrXg6mpbT92jVDegypUrZ9cKEBERkXXYAyXn4uKCkSNHAgD++usvzJkzB97e3nYtw6oNdC5cuIBFixbh9OnT0Gg0qFGjBl5//XVUqlTJrpUjIiIiZSY7NKBMxbAB9bDdu3cDADIzM3Hp0iVUqlQJjo627R8IWPEU3q+//oqaNWvi8OHDqFOnDkJCQnDo0CHUqlULO3bssLlCRERElDdGk9HcC2X9q3jPXU5LS8OQIUPg6uqKWrVqISYmBsCDBTQ/+OADq/NV3YCaMmUK3njjDRw6dAgffvghFi5ciEOHDmHcuHF48803ra4IERERkb1NmTIFf//9N/bs2QNn5/892d22bVts2LDB6nxV92GdPn0a3377bY744MGDsWjRIqsrQkREROoYhRFGYVsPkq3nP+k2b96MDRs2oGnTpha7p9SsWRMXLlywOl/VDagSJUogOjoaVapUsYhHR0cjIEB5jSEiIiKyL04if7xbt25J2yf379+3aTs61Q2oYcOGYfjw4bh48SKaNWsGjUaDffv2Yc6cOZgwYYLVFSEiIiKyt2eeeQY///wzxo4dCwDmRtPnn3+O0FD5otN5oboB9e6778LDwwMLFizA1KlTAQDBwcGIiIiwekM+IiIiUs8ojDZPAi/uQ3izZ89Gx44dcerUKRgMBnz00Uc4efIkDhw4gL1791qdr6pJ5AaDAV9++SVeeeUVXLt2DYmJiUhMTMS1a9fw+uuv29QVRkREROo8eArP9ldx1qxZM+zfvx+pqamoVKkStm/fjsDAQBw4cAANGza0Ol9VPVCOjo547bXXcPr0aQCAh4eH1QU/zcr4DFc8ZuhbVhovG7dEGk+4dVsav3r5ijReyd9dsWytv3yV1pq1Q6TxqL/uS+POv16Xxit4b1Msu3bfntJ4oxLyMpa2V7dP14tv7VQ8tnFWG1V5KbFm77AXv18tPxBVRhpWW9dGJUyKx87Lt1dUVL9Ehqr0CelaxWN+QbGq8lJ6//wUvsJuBrko5vWf2sr75NmD4nsKoGaJNGn8VGRVaVzp/Va6H0vG/qtYdrBXkDR+uuophTO4Fx4VfVlZWRg+fDjeffddrFmzxq55q17GoEmTJjh27JhdK0FERETqGU0CBpPJppfRJN8AvjhwcnLCpk2b8iVv1XOgRo0ahQkTJuDatWto2LAh3NzcLI7XqVPHbpUjIiIiZQaTgMHGBpCt5z/pnn/+eWzevBnjx4+3a76qG1AvvfQSAFhMGNdoNBBCQKPRwGgs3mOpREREVHRUrlwZ77//Pvbv3y/t+LH2ATjVDahLly5ZVRARERHZl9EkbB6CK85DeACwYsUKeHt7IyoqClFRURbHNBpN/jagGjRogJ07d8LHxwdr1qzBxIkT4eoqn3BMREREBYNDeI+XXx0/eZpEfvr0ady//+BpqOnTpyMlJSVfKkNERER5ZzDZ50Xq5akHql69enj11VfRokULCCEwf/58uLvLH4d/77337FpBIiIioidNnhpQq1evxrRp07B161ZoNBps27YNjo45T9VoNGxAERERFRCjAAw2jsAZi/cIXr7J0xBetWrVsH79ehw5cgRCCOzcuRPHjh3L8Tp69KiqwmfPno1nnnkGHh4eCAgIQI8ePXDmzBmLNEIIREREIDg4GC4uLmjZsiVOnjxpkSYjIwNjx46Fv78/3Nzc0K1bN1y7ds0iTUJCAvr37w8vLy94eXmhf//+uHfvnqr6EhERPUmyTBq7vEg91Qtpmkwm6a7G1ti7dy9Gjx6NgwcPYseOHTAYDGjfvr15vhUAzJ07Fx9++CGWLFmCI0eOICgoCO3atUNycrI5zbhx47Bp0yasX78e+/btQ0pKCrp06WKxpEKfPn0QHR2NyMhIREZGIjo6Gv3797fLdRAREdHTRfUyBvYUGRlp8fOqVasQEBCAqKgoPPvssxBCYNGiRXj77bfRs+eDrT7WrFmDwMBArFu3DiNGjEBiYiJWrlyJr776Cm3btgUArF27FmXKlMFvv/2GDh064PTp04iMjMTBgwfRpEkTAP/bhfnMmTOoVq1awV44ERGRHdhjEnhxnER+/PjxPKe1dgHwQm1APSoxMREA4OvrC+DBo4dxcXFo3769OY1er0dYWBj279+PESNGICoqCllZWRZpgoODERISgv3796NDhw44cOAAvLy8zI0nAGjatCm8vLywf//+J6oBdeHWWWm8hMI+dW6eSdL4jZvyPbdyoy3pJY27+LlJ43WS06Xxw38lS+MJt+R1BYDKf34pjXerJv+I3o38RZ6Ru5M0/HkNZ8Wy736+TBrXOMvzgq+nPL2bwv5rrsr7sq2q7SuNOz/nI43fTJLv5XQzWb6xXcNcOotTstRNfPBzke9tF+ARKI3HJ99UzOvVWvLPwuz98vfC/4r8QppPjpPGW5bVKZadmpmlquypzUYp5iWz8YVBisd+/vdjabzJgJPS+PDIc/IyZo2Uxkf2Vd7g0NRfvm8mIP98NiuvmNUTZ2TfjYrHlK778vWS0vj2Id3tUqeCYhAaGGwcgjOI4jeEV69ePfMi3zL2WAD8iWlACSEwfvx4tGjRAiEhDzavjYt78OUYGGj5BR0YGIgrV66Y0+h0Ovj4+ORIk31+XFycdNgxICDAnOZRGRkZyMj43+apSUnK//kTERHRk6MgFv1+YhpQY8aMwfHjx7Fv374cxzQay9ZxdqsxN4+mkaXPLZ/Zs2dj+vTpeak6ERFRoTCabO+BMhbDSeTlypXL9zKsakDdu3cPGzduxIULFzBp0iT4+vri6NGjCAwMRKlSpVTnN3bsWGzZsgW///47SpcubY4HBQUBeNCDVLLk/7pb4+Pjzb1SQUFByMzMREJCgkUvVHx8PJo1a2ZOc/NmzuGEW7du5ejdyjZ16lSLjQeTkpJQpkwZ1ddGRESUX7JMD1625vE0OHXqFGJiYpCZmWkR79atm1X5qW5AHT9+HG3btoWXlxcuX76MYcOGwdfXF5s2bcKVK1fw5ZfyuSwyQgiMHTsWmzZtwp49e1ChQgWL4xUqVEBQUBB27NiB+vXrAwAyMzOxd+9ezJkzBwDQsGFDODk5YceOHejduzcAIDY2Fv/88w/mzp0LAAgNDUViYiIOHz6Mxo0bAwAOHTqExMREcyPrUXq9Hnq9Xt3NISIioifKxYsX8fzzz+PEiRMW86KyR6CsnQOlehmD8ePHY9CgQTh37hycnf83Mfe5557D77//riqv0aNHY+3atVi3bh08PDwQFxeHuLg4pKU9mACt0Wgwbtw4zJo1C5s2bcI///yDQYMGwdXVFX369AEAeHl5YciQIZgwYYJ5fap+/fqhdu3a5qfyatSogY4dO2LYsGE4ePAgDh48iGHDhqFLly5P1ARyIiIiNR48haex8VXYV5G/Xn/9dVSoUAE3b96Eq6srTp48id9//x2NGjXCnj17rM5XdQ/UkSNHsHz58hzxUqVKKU7IVvLJJ58AAFq2bGkRX7VqFQYNGgQAmDx5MtLS0jBq1CgkJCSgSZMm2L59Ozw8PMzpFy5cCEdHR/Tu3RtpaWlo06YNVq9eDa32f08Nff311wgPDzc/rdetWzcsWbJEVX2JiIieJFlCgywbn6Kz9fwn3YEDB7Br1y6UKFECDg4OcHBwQIsWLTB79myEh4fj2LFjVuWrugHl7OwsfSLtzJkzKFGihKq8lB4vfJhGo0FERAQiIiJyrdPixYuxePFixTS+vr5Yu3atqvoRERE9yYx2WAfKWMx7oIxGo3n/Xn9/f9y4cQPVqlVDuXLlcux+oobqIbzu3btjxowZyMp6sJaKRqNBTEwMpkyZghdeeMHqihARERHZW0hIiHlhzSZNmmDu3Ln4888/MWPGDFSsWNHqfFU3oObPn49bt24hICAAaWlpCAsLQ+XKleHh4YGZM2daXREiIiJSx/b5T7Yvg/Cke+edd2AyPehm++9//4srV67gP//5D3755Rd8/LF8cdu8UD2E5+npiX379mHXrl04evQoTCYTGjRoYJ6wTURERAUjywQ4chmDXHXo0MH874oVK+LUqVO4e/cufHx8HrumZG6sXkizdevWaN26tdUFExEREeUng8EAZ2dnREdHm3c5Af63ZZwtVDeglLq7NBoNnJ2dUblyZTz77LMWT8BR3vm5+ckPGKxbp0INkWmQxjU6+cfEvU6wNB4W3EUaNyjsnQcA10/fkB84LQ8//BTmw5z95HEHhWsAAKdS8r3t1HLwUFg3LJeyRXKGNG68kyKNZ92W30MPf3UPcACAe4b8/Vai9P5dvXNVGk9Olu+JCABuBvl+dHUz5Xs4lk/9SxpP2yDfb++Qp/K1BQTJ/+LsUVr+O3ZO/4M0blL4ldS7Kv8537SSfNaEprz8c9usrPx9Tbksf2Bm7kr57yQAbD8vv+cfDZLfW3SU77f3JLpeReF7E8DM2tekcbeG8s/t4SsL5fkckO/H+OPLfR9Tu/yVZdLA0cYhuKxiPITn6OiIcuXKWb3WU655qz1h4cKFuHXrFlJTU+Hj4wMhBO7duwdXV1e4u7sjPj4eFStWxO7du7lyNxERUT4yCjs8haduX/Ei55133sHUqVOxdu1au/Q8ZVM9iXzWrFl45plncO7cOdy5cwd3797F2bNn0aRJE3z00UeIiYlBUFAQ3njjDbtVkoiIiMgaH3/8Mf744w8EBwejWrVqaNCggcXLWqp7oN555x18//33qFSpkjlWuXJlzJ8/Hy+88AIuXryIuXPnckkDIiKifJYlAK2NPUhZxbwHqnv37jZNFleiugEVGxsLgyHn/AKDwWBeiTw4ODjX+Q9ERERkuywToOVTeLnKbSFuW6gewmvVqhVGjBhhsfT5sWPH8Nprr5mfyjtx4kSOjYGJiIiIClrFihVx586dHPF79+4V7EKaK1euhK+vLxo2bAi9Xg+9Xo9GjRrB19cXK1euBAC4u7tjwYIFVleKiIiIHs9gss+rOLt8+bL0KbyMjAxcuyZ/SjMvVA/hBQUFYceOHfj3339x9uxZCCFQvXp1VKtWzZymVatWVleIiIiI8sZg0ti8DEFxXYl8y5Yt5n//+uuv8PLyMv9sNBqxc+dOm0bLrF5Is3r16qhevbrVBRMREZFtskyAA+dASfXo0QPAg3UqBw4caHHMyckJ5cuXt2m0THUDymg0YvXq1di5cyfi4+PN+8tk27Vrl9WVISIiIrKH7PZJhQoVcOTIEfj7+9s1f9UNqNdffx2rV69G586dERISki+PBhIREdHjsQfq8S5dupQv+apuQK1fvx7ffvstOnXqlB/1ISIiojxiA+rxwsPDUblyZYSHh1vElyxZgvPnz2PRokVW5au6AaXT6VC5cmWrCqPH83eX76u0v128NF4jWL5ZXNBX/0jjF46dUyw7fpt8NbX79+UfE6GRx119lfelUpJ6N+cjprnlVRbybYK0Oid5PhWUu261Jb2k8cxA+T5ksenyPZVuJ8j3r0vNZQ9Ag0leX/jI66QUT7x2V56/wv6GuUlNTlWV3slLvo9blouz4jmGjExVZTjqK0njTgrvt/zT9MA1vfwcZw8XhbLVfU3qPeX5AICfr4807mWU76NYMla+p6jjzsvS+O29ZxTLzrp2Vhqf7BAnjZ9u304aD6gv/55waFJasWznFi2l8V9vJ0njPWqGS+NK7rsrHxv3TV1pfNdE+cNO8k8a8GM5VVWiJ8j3339vMaE8W7NmzfDBBx9Y3YBSvYzBhAkT8NFHH0GIYr50KRER0RPOaIclDIzFvAfqzp07Fk/gZfP09MTt27etzld1D9S+ffuwe/dubNu2DbVq1YKTk+Vfcz/8IN+5nIiIiOwry6SBxsZlCGxdBuFJV7lyZURGRmLMmDEW8W3bttm0kKbqBpS3tzeef/55qwskIiIiKijjx4/HmDFjcOvWLfOOKTt37sSCBQusHr4DrGhArVq1yurCiIiIyH4MQgMHWxfSFMW7B2rw4MHIyMjAzJkz8f777wMAypcvj08++QQDBgywOl+rF9IkIiKiwmUw2aEBVcyH8ADgtddew2uvvYZbt27BxcUF7u65PHmQR1Y1oDZu3Ihvv/0WMTExyMy0fJrm6NGjNleKiIiIyF4MBgP27NmDCxcuoE+fPgCAGzduwNPT0+rGlOqn8D7++GO8+uqrCAgIwLFjx9C4cWP4+fnh4sWLeO6556yqBBEREalnNGlgsPFlLOY9UFeuXEHt2rXRvXt3jB49Grdu3QIAzJ07FxMnTrQ6X9UNqGXLluGzzz7DkiVLoNPpMHnyZOzYsQPh4eFITEy0uiJERESkjtHkYJdXfjl69CjatWsHb29v+Pn5Yfjw4UhJsVwzLyYmBl27doWbmxv8/f0RHh6eY3TLFq+//joaNWqEhIQEuLj8b522559/Hjt37rQ6X9V3LSYmBs2aNQMAuLi4IDk5GQDQv39/fPPNN1ZXhIiIiNQxGrV2eeWHGzduoG3btqhcuTIOHTqEyMhInDx5EoMGDXqo/kZ07twZ9+/fx759+7B+/Xp8//33mDBhgt3qsW/fPrzzzjvQ6XQW8XLlyuH69etW56t6DlRQUBDu3LmDcuXKoVy5cjh48CDq1q2LS5cucXFNIiIiAgBs3boVTk5OWLp0KRwcHvTXLF26FPXr18f58+dRuXJlbN++HadOncLVq1cRHBwMAFiwYAEGDRqEmTNnwtPT0+Z6mEwmGI05d5C4du0aPDzkOynkheoGVOvWrfHTTz+hQYMGGDJkCN544w1s3LgRf/31F3r27Gl1RYqK8B0roXOz3KphZK00adpd1+XbM5xYr7wVTlJz+bYKm3q9IT+hvDzs01keL6tYMvBZ1FJpvE1ZecO4on9VaTz537+kcVPqfcWyNY7yrVlEUrI8fk2+JY24LB9GzjynXHbcNvn7dPOaQvpYecetzrukNO7vobyNTKB/gDTu7Cf/pXbyl092dKziLY1r3JW3UxEK26CYPORlpOnk9+niLfl7dCc2QbHs+wrnGDKy5HGFLWmyFNKnJSu/35cv3JTGU1LldcrMkg8lKMZzGXp49C9gawX5yz9r5SspLwro37irNO4aLN9ext1L/t9D4v4L0njMZuVtZC5c3CSNV6mSIY3f7vijNO748jPS+OYx8jgAeDq/oHisODCaHOBg4xBc9hBeUpLl1jp6vR56vfz3Pi8yMjKg0+nMjScA5iG0ffv2oXLlyjhw4ABCQkLMjScA6NChAzIyMhAVFYVWreRb7qjRrl07LFq0CJ999hkAQKPRICUlBdOmTbNpX1/VDajPPvsMJtODdd9HjhwJX19f7Nu3D127dsXIkSOtrggRERGpYzI6wGTjEJzJ+KCBU6aM5R+y06ZNQ0REhNX5tm7dGuPHj8e8efPw+uuv4/79+3jrrbcAALGxsQCAuLg4BAYGWpzn4+MDnU6HuDh5h4JaCxcuRKtWrVCzZk2kp6ejT58+OHfuHPz9/W2aeqS6AeXg4GDRmuzduzd69+4NALh+/TpKlSpldWWIiIiocFy9etViyEyp9ykiIgLTp0/PNa8jR46gUaNGWLNmDcaPH4+pU6dCq9UiPDwcgYGB0Gr/1+jTaHI+BSiEkMatERwcjOjoaHzzzTc4evQoTCYThgwZgr59+1pMKlfLLgtpxsXFYebMmVixYgXS0uTDWURERGRfRqMWDjb2QGVPIvf09MzTnKMxY8bg5ZdfzjVN+fLlAQB9+vRBnz59cPPmTbi5uUGj0eDDDz9EhQoVADyYV33o0CGLcxMSEpCVlZWjZ8oWLi4uGDx4MAYPHmy3PPPcgLp37x5Gjx6N7du3w8nJCVOmTMGYMWMQERGB+fPno1atWvjiiy/sVjEiIiLKnUloYTLZOIQn1J3v7+8Pf3/luZ0y2Y2hL774As7OzmjXrh0AIDQ0FDNnzkRsbCxKlnwwt2/79u3Q6/Vo2LChqjJyc+bMGSxevBinT5+GRqNB9erVMWbMGFSvXt3qPPPcgHrrrbfw+++/Y+DAgYiMjMQbb7yByMhIpKenY9u2bQgLC7O6EkRERFT8LFmyBM2aNYO7uzt27NiBSZMm4YMPPoC3tzcAoH379qhZsyb69++PefPm4e7du5g4cSKGDRtmlyfwgAe7p7zyyito1KgRQkNDAQAHDx5E7dq1sW7dOvTq1cuqfPPcgPr555+xatUqtG3bFqNGjULlypVRtWpVm3YyJiIiIusJo9bmSeQin9aBAoDDhw9j2rRpSElJQfXq1bF8+XL079/ffFyr1eLnn3/GqFGj0Lx5c7i4uKBPnz6YP3++3eowefJkTJ06FTNmzLCIT5s2DW+++Wb+N6Bu3LiBmjVrAgAqVqwIZ2dnDB061KpCiYiIyHYmOzSgbD0/N19++eVj05QtWxZbt27NtzrExcVhwIABOeL9+vXDvHnzrM43z4tHmEwmODn9b90YrVYLNzc3qwsmIiIiym8tW7bEH3/8kSO+b98+/Oc//7E63zz3QAkhMGjQIPNjjenp6Rg5cmSORtQPP/xgdWWIiIgo70wmO0wit/H8J123bt3w5ptvIioqCk2bNgXwYA7Ud999h+nTp2PLli0WafMqzw2ogQMHWvzcr1+/PBdCRERE9ieMWpvnMOXnHKgnwahRowAAy5Ytw7Jly6THgAfrUcm2fFGS5wbUqlWr8pwpERER5T+TUWNeSdyWPIqz7N1T7M0uC2k+TVqXyoKru+VtO3lXvqfYuY/8pPGubyvvGdWjZhVp/HbK19L4htPyvd9+PC5fEX77kO6KZUfNl6/rcWfsHWl89xb5oqnlS/lK4xGhQYpl+zt4SeOOLvJ5dnH3b0jjmQb53lpaB+W/sOTvEuBhkO9p1l5hD8B7O7dI4+LYKcWyTbFR0nj6Tfkv/P1b8uu4e0YeT7ilWDTSUuXxVIW4yVn+mapWWn4/Kvoob9LpoJd/9ehKyT8H2iryvRLv+8nfveuJChcBIOZsrDR+9e9L0vjpc/L3L+W+fL+9LIXPTW7c3eT7D6bcT5HG7ybKfyd97sp/9wAgK1O+b+Dti/HS+C2F3+MGrepI4xWDlR85L/GXfEHEw3/tk8Y9DydJ42WryL87Heoo74WX30a8skEaz8xS/gxS8WBbs5WIiIgKj8kBMNr4snEz4ifVoUOHsG3bNovYl19+iQoVKiAgIADDhw9HRob8j+68KJ53jYiI6Glga+Mp+1UMRURE4Pjx4+afT5w4gSFDhqBt27aYMmUKfvrpJ8yePdvq/IvnXSMiIqKnWnR0NNq0aWP+ef369WjSpAk+//xzjB8/Hh9//DG+/fZbq/PnHCgiIqIiSmN0gMbGHiRbz39SJSQkWGxIvHfvXnTs2NH88zPPPIOrV69anX/xvGtERERPAQejfV7FUWBgIC5devBwSGZmJo4ePWreCw8AkpOTLRYIV4sNKCIiIip2OnbsiClTpuCPP/7A1KlT4erqarHy+PHjx1GpUiWr8+cQHhERURFljx6k4toD9d///hc9e/ZEWFgY3N3dsWbNGuh0OvPxL774Au3bt7c6fzagiIiIiiit8cHLFqKYNqBKlCiBP/74A4mJiXB3d4dWa7le3nfffQd3d/kabHnBBhQREREVW15e8gV6fX2VF57NCzagiIiIiiitCXC0tQcqf3Y6KfbYgCIiIiqitEYBrVHYlIfJxvOfVmxAqbT9mg46V93jEwIYOlu+X9szARUVz0k9c1QaVypxSMVy0vhrjVyk8ZOxHymW3W2afEPJ8l7yX67wRvJ8HO45S+OGm/I9twAg3Rgnz0snvw6lXbcM927LD6TK9+3LlcKu3Pdi90vjpn/kZd+/IN+D7EFe8kdo01Pl8ax0eT6JCfL3KCFBeZPQ9Ey9NO4ZIN+zsIJ/aWncPchHGtdXUO4e15aUd6mnlJSXfeDIBWn86qZj0nhmlvI9V5KQcFchL/nedtbseadWUImS0niZUvK9AcvUraCYl4e/fG9CH1/5HJDKLvL9FY27j0vj/xyKViw7M0W+n2BYB4M07t3JWxp37vKcNP7Lefl3LQC8GCKPt2oyTBp3Gt5FGnfzlO876tNf/t2SlZoGfK9YLbtxNAo42tgAEmxAWYXLGBARERGpxB4oIiKiIsrRIOCotbEHysAeKGuwAUVERFREcQ5U4eEQHhEREZFK7IEiIiIqorR2mETOHijrsAFFRERURGmNJmgNti3kpDVyIShrcAiPiIiISCX2QBERERVRDzYTtm0IrrhuJpzf2IAiIiIqohyMJjjYOARn6/lPKw7hEREREanEHigiIqIiij1QhYcNKJWWtR8MT0/LndjaztktTXusxGlpvIzXTcX8fSvVksaNJvmeUUq7cRmz5PszBXvJ9xoDAE9n+V5PRpN8gDwl/Z40XsJXXoYwyq8BADRa+UdR6yLfvyszPkZexm35fmbixh3FsnE7VSEv+cZzWbfk15GaIN87LCFWee/E2/ItAJGcJJ/TcP++vNNY5x0sjQeU9Fcs20nhnitxdJTvz+egU3jvFPa7A4Aok/yenN1wQBq/fOGiNH43Uf6+Ku1fBwDurvK939xd5Z81Xy8/aVznJL8GpTgA+HrL83Jzk9fJv2QJabzCM/L9NOuVcFMsW3P2mjSeeuCMNL7/RJS87GD5vnNNBijPw9H1bSONb7wvr++r9Ucr5iUTOWOb4rG156ZJ47sPfS6ND31Nntfl8vL9Bze92UoaT0pKwtd4Q7Fe9qIxmqCxsQFk6/lPKzagiIiIiiqj6cHL1jxINc6BIiIiIlKJPVBERERFlBBGCIVpFmryIPXYgCIiIiqihMkODSgbz39acQiPiIiISKVCbUD9/vvv6Nq1K4KDg6HRaLB582aL40IIREREIDg4GC4uLmjZsiVOnjxpkSYjIwNjx46Fv78/3Nzc0K1bN1y7Zvm0SUJCAvr37w8vLy94eXmhf//+uHfvXj5fHRERUf7K7oGy9UXqFWoD6v79+6hbty6WLFkiPT537lx8+OGHWLJkCY4cOYKgoCC0a9cOycnJ5jTjxo3Dpk2bsH79euzbtw8pKSno0qULjMb/fSD69OmD6OhoREZGIjIyEtHR0ejfv3++Xx8REVF+MplMMJmMNr74FJ41CnUO1HPPPYfnnntOekwIgUWLFuHtt99Gz549AQBr1qxBYGAg1q1bhxEjRiAxMRErV67EV199hbZt2wIA1q5dizJlyuC3335Dhw4dcPr0aURGRuLgwYNo0qQJAODzzz9HaGgozpw5g2rVqhXMxRIREVGx8cTOgbp06RLi4uLQvn17c0yv1yMsLAz79+8HAERFRSErK8siTXBwMEJCQsxpDhw4AC8vL3PjCQCaNm0KLy8vcxqZjIwMJCUlWbyIiIieJCZhtMuL1HtiG1BxcQ+WaA4MDLSIBwYGmo/FxcVBp9PBx8cn1zQBAQE58g8ICDCnkZk9e7Z5zpSXlxfKlJGvQktERFRYTCaDXV6k3hPbgMqm0WgsfhZC5Ig96tE0svSPy2fq1KlITEw0v65evaqy5kRERFRcPbHrQAUFPdhPLS4uDiVLljTH4+Pjzb1SQUFByMzMREJCgkUvVHx8PJo1a2ZOc/Nmzr3nbt26laN362F6vR56vT5PdXVPke8BZVCYl3f2jnzvNQA4fPq6NH5lac5etNzoX5XvV1XRQ3m/qnIe8r9CPHTyCzGY5PuNlfeS76lX2q+cYtlKtVKc2qhyHzeNo/LfCiKXY2o4aJXvrRKl7QGNKnvUlfapU7vfHQCkZsr3UXQ1uMjLVth/LTNQvo8bANzaf14avx0XL89LYW+7oBIlpXEfbx9pHAAc9fK96gwZ8jLu378vjft6+0rjXr7KZfsGy/fC03s6S+OB5eW/93UM8jqlbDwpjQPAgf175HnVkU9PaD9Sno/z0B7S+NgTyr9HX1QdIY2/qniGOo5J8vsBAMn3lfcelVnxiXxe7pOK60AVnie2B6pChQoICgrCjh07zLHMzEzs3bvX3Dhq2LAhnJycLNLExsbin3/+MacJDQ1FYmIiDh8+bE5z6NAhJCYmmtMQEREVRZwDVXgKtQcqJSUF58//7y/RS5cuITo6Gr6+vihbtizGjRuHWbNmoUqVKqhSpQpmzZoFV1dX9OnTBwDg5eWFIUOGYMKECfDz84Ovry8mTpyI2rVrm5/Kq1GjBjp27Ihhw4Zh+fLlAIDhw4ejS5cufAKPiIiKNGEy2jyHiT1Q1inUBtRff/2FVq1amX8eP348AGDgwIFYvXo1Jk+ejLS0NIwaNQoJCQlo0qQJtm/fDg8PD/M5CxcuhKOjI3r37o20tDS0adMGq1evhlarNaf5+uuvER4ebn5ar1u3boprTxERERE9TqE2oFq2bAkhlOeNaDQaREREICIiQjGNs7MzFi9ejMWLFyum8fX1xdq1a22pKhER0RMnezFMW/Mg9Z7YSeRERESUO3vMYeIcKOs8sZPIiYiIiJ5U7IEiIiIqokzC9oUwTYILaVqDDSgiIqIiikN4hYdDeEREREQqsQeKiIioiOJTeIWHDSgiIqIiSggThI1DcEIobppFuWADyg42v99aGm+1UL7G1R+3lPNK+v1HhSP/SqNZL3SXxr0T5PvtxSQol13JT3k/KZkLd+R7oLnoM6TxnhVjFPOq6iP/AijjI9/rzNVbvkeYsZT8AnP9eriXntvRHDR2/K1R2qpOYbs2KF1J8s1r0nhKwl3Fsn38g6RxbxcPadzNy1Ma15aUxy8kyz8HAKDVyy+8ZLlSquKOOnk+jnr53oC5cVSokxK9p3xvQBcP+b52AOCkUF9/H3dpvFyi/P1L/PW0NB71zxHFssuUlO95V66ZfAKxW/hgabzfAvnnZuOsNopl57dPv34xl6O5HSOyHhtQRERERZTJZIDJpH18wsfkQeqxAUVERFREcQ5U4WEDioiIqIjiMgaFh8sYEBEREanEHigiIqIiSthhCE9wCM8qbEAREREVUQ+G8GzdyoUNKGtwCI+IiIhIJfZAERERFVHCZLR5CI5DeNZhA4qIiKiI4lN4hYdDeEREREQqsQFFRERURJmE0byYptWvfOyBmjlzJpo1awZXV1d4e3tL08TExKBr165wc3ODv78/wsPDkZmZaZHmxIkTCAsLg4uLC0qVKoUZM2ZACPl2aQWFQ3h28FKn+dJ4ldI1pPEVn8v3rwOg+DSFUhmpifI28D2tfA85vWeyYtn/3Jfvx+XiliKPK+x5V9s/Sxr30CnvSKd3lF+Hh7OXNJ6ZKN/7Tauwj5vwV96XTcTES+OmZPmXStpdeV0zUuVxV0/l6w5UuO70+xppPDFB/oWh18k/NxmZ8v3PACDhtjzuFizf402J6Y58D8WqfvI98gCg1DOVpPHrVeV7OCbclX8G05Ll+xgmXlPeA9CQKb9Xjhnqvg4NGQr3PClN8Ryl/fOcdPK9+zIDS0jjnq3kZTfNZR++v6OPSuMnfr4ijddM+VwaXzG0qTS+8/wJxbLbVZkojdv69Bg9+UN4mZmZ6NWrF0JDQ7Fy5cocx41GIzp37owSJUpg3759uHPnDgYOHAghBBYvXgwASEpKQrt27dCqVSscOXIEZ8+exaBBg+Dm5oYJEybkW90fhw0oIiIiyhfTp08HAKxevVp6fPv27Th16hSuXr2K4OBgAMCCBQswaNAgzJw5E56envj666+Rnp6O1atXQ6/XIyQkBGfPnsWHH36I8ePHQ6OR/8GZ3ziER0REVETZPHz30EKcSUlJFq+MDOWee3s5cOAAQkJCzI0nAOjQoQMyMjIQFRVlThMWFga9Xm+R5saNG7h8+XK+11EJG1BERERFlBAmCGG08fVgmkGZMmXg5eVlfs2ePTvf6x8XF4fAwECLmI+PD3Q6HeLi4hTTZP+cnaYwcAiPiIioiDKZjDBpbOsLye6Bunr1Kjw9/zd38eEen4dFRESYh+aUHDlyBI0aNcpT+bIhOCGERfzRNNkTyAtr+A5gA4qIiIgAeHp6WjSglIwZMwYvv/xyrmnKly+fpzKDgoJw6NAhi1hCQgKysrLMvUxBQUE5epri4x88/PNoz1RBYgOKiIioiHrwFJ6NPVAqn8Lz9/eHv7+/TWVmCw0NxcyZMxEbG4uSJR88Pb59+3bo9Xo0bNjQnOatt95CZmYmdDqdOU1wcHCeG2r5gXOgiIiIiihhhwnk+bmVS0xMDKKjoxETEwOj0Yjo6GhER0cjJeXB0iTt27dHzZo10b9/fxw7dgw7d+7ExIkTMWzYMHNvWJ8+faDX6zFo0CD8888/2LRpE2bNmlWoT+AB7IEiIiKifPLee+9hzZo15p/r168PANi9ezdatmwJrVaLn3/+GaNGjULz5s3h4uKCPn36YP78/6196OXlhR07dmD06NFo1KgRfHx8MH78eIwfP77Ar+dhbEAREREVUQ+eorNtMEnk40Kaq1evVlwDKlvZsmWxdevWXNPUrl0bv//+ux1rZjs2oIiIiIook8kIjZ2ewiN1OAeKiIiISCX2QKnUY/7vcHR2s4gZu9WSpk24K9+Xypr9nzb8It9LqnGdF6TxGq1HKOQk3+8uN9eqy/fvCq54WRov7Sbf+62km/LGjx7O8j3sHBT+stJoFT66CnGth3xPPQBA9XLKxyQ8Lsv3E3RLkO8BaDIoX7fSRyE9SWFfvVvy60tW2J9Pae88AEi/Jd9fLubKGVVxv8tlpfFgnwDFsv1ryM+pWk2+95uplDx+R+MnjZ9ULFl5n7y0ZPkedmnJ8r3+lHj6eyseU9oLLzVFXvaJTPlnyr+E/N6W7iq/TwDQuIKvNH5t9z/S+OGfDkrjzxjk8YavNlAsOzYx5x5oANDpq++k8V/691LMSy2l78iQql2l8S82DlKV/+AXV0vjmVnKeyLak0kYoSngp/DoATagiIiIiiiTMEFj82bCyhuekzIO4RERERGpxB4oIiKiIkqYjBA2roWUn+tAFWdsQBERERVRD+ZA2daA4hwo67ABRUREVESZhBEaExtQhYFzoIiIiIhUYg8UERFREfVgJXIb50CxB8oqbEAREREVUSaTERrYOITHSeRW4RAeERERkUrsgSIiIiqihDDZPAlccCFNq7ABRUREVESZhAmweRkDNqCswQaUStrSd+DoYrnHkfayvzTt2RXy/evwVpTqciuVa6gq/b5NU1WX0azeEGm8QkYFafyUsaI0npx5RXXZDRzk+8u5OCVI414lgqVxY5p8/6mM+Bjlwh210rDGy1We3l++P5mDQf4llBWnvPdh5n35KHpGqjxuMsq/KJW2BjTm8oep0agwgq/wXeysy5DGE2+el8bddPJ93wAg0F/h3vp5SsNGR6ULlIc9/ZT3fDRkyN8/Q6b8fbp84aY0Hnc7VhovG6y8t6JXKR9p3Kec/DtELYdM+XsEAEr/Rbo4K+zPlypPn6ywH6PuX+Xfe596zaXx/1SS31t7Onz8e2lc6Tu1UrnF0rjS9+O19pWkcUPafWBLHipIRRYbUEREREWUMBmhvGV43vMg9diAIiIiKqJMwghbW1BcSNM6fAqPiIiISCX2QBERERVR9ljDietAWYcNKCIioiJKCJMdViLnU3jWYAOKiIioiHqwjIEd8iDVOAeKiIiISCX2QBERERVRD5YxsK0LSpjYA2UNNqCIiIiKKA7hFR4O4RERERGpxB4oIiKiIsokjIDJti4o9kBZhw0olT47ugIeOsvb5lDbV5rWdHK8NP5a/02K+W/b/Z71lXvI5WsnpPHypWsrnpOekSSNZ3k4SePaFPkecvG35ft6HdQp73vlopV/AWQYbkjjpbzSpXFfR29p3JQm32sPAERSijx+U35O6l/y+N1r8vsE6BTLVtrbLv2+useSjQrb7SnFAcDRU74vm79XCVVle7nK968rWVu+VyIAaEt6SeOZrm7SeGy6fJ2a2Jv3pPG0ZPnnIzeOOvnXYWZWpjSekCjfp9HXy0+xDN9g+T2vlir/3RMK+/MZY+XpY/48rVj22UunpPFAX3leoc/Jr9uje0lp3L37S4plf3T8ujQe84k8r/K95d9TSt9r1rhwRb4nqdL387tL5O+3y5ZV0niSUxbKW1UzdYQw2b6VCxtQVuEQHhEREZFK7IEiIiIqooTJCJPGxqfw2ANlFTagiIiIiiij0QgNG1CFgkN4RERERCqxB4qIiKiIMplMduiBsnUa+tOJDSgiIqIiymgyQqOxdTNhNqCswQYUERFREWU0GtiAKiScA0VERESkEnugiIiIiqgHT+GxB6owsAFFRERURBmFARphYwPK5rXMn05sQOVRdgs9WbK1giYtS35OUqo0ninfIQEAYDLJt6xQKylJvj1DbvlnGeTbX5gy7kvjhnT5Vi5IU7ju+2mKZaemyLesSHGQ1zdZI8/L0VG+bUpaivLWHsb78jdEKLyvaVkKdTWo/xJT2solQyGuxKjwtqbmskdWmpBfX6opQ1XZjkZ5+uQs5fdbly7/TGWmuUjj9xW2cklV+GymZyhfgyFT6X2V1zdT4X4YIM9HKT0ApCpcd7KDwmcwS37dxkz579h9hfcCyO39ln+ekxU+5yJVno9J4fsOANIVfvczFa5D6XtK6XvNnpTqlJwkv4Yshe+J5P+PF0TvDhtAhUMj2HeXJ9euXUOZMmUKuxpERFSEXL16FaVLl7Z7vunp6ahQoQLi4uLskl9QUBAuXboEZ2dnu+T3NGADKo9MJhNu3LgBDw8PJCcno0yZMrh69So8PeUbqT4NkpKSnvr7wHvwAO/DA7wPvAfZhBBITk5GcHAwHBzy53mt9PR0ZOY2pKGCTqdj40klDuHlkYODg/mviOwJe56enk/1F0Q23gfeg2y8Dw/wPvAeAICXl1e+5u/s7MxGTyHiMgZEREREKrEBRURERKQSG1BW0Ov1mDZtGvR6fWFXpVDxPvAeZON9eID3gfeAnh6cRE5ERESkEnugiIiIiFRiA4qIiIhIJTagiIiIiFRiA4qIiIhIJTagVFq2bBkqVKgAZ2dnNGzYEH/88UdhV6lAzZ49G8888ww8PDwQEBCAHj164MyZM4VdrUI3e/ZsaDQajBs3rrCrUuCuX7+Ofv36wc/PD66urqhXrx6ioqIKu1oFxmAw4J133kGFChXg4uKCihUrYsaMGTCZTIVdtXz1+++/o2vXrggODoZGo8HmzZstjgshEBERgeDgYLi4uKBly5Y4efJk4VSWKB+wAaXChg0bMG7cOLz99ts4duwY/vOf/+C5555DTExMYVetwOzduxejR4/GwYMHsWPHDhgMBrRv3x7378s3SX0aHDlyBJ999hnq1KlT2FUpcAkJCWjevDmcnJywbds2nDp1CgsWLIC3t3dhV63AzJkzB59++imWLFmC06dPY+7cuZg3bx4WL15c2FXLV/fv30fdunWxZMkS6fG5c+fiww8/xJIlS3DkyBEEBQWhXbt2SE5OLuCaEuUTQXnWuHFjMXLkSItY9erVxZQpUwqpRoUvPj5eABB79+4t7KoUiuTkZFGlShWxY8cOERYWJl5//fXCrlKBevPNN0WLFi0KuxqFqnPnzmLw4MEWsZ49e4p+/foVUo0KHgCxadMm888mk0kEBQWJDz74wBxLT08XXl5e4tNPPy2EGhLZH3ug8igzMxNRUVFo3769Rbx9+/bYv39/IdWq8CUmJgIAfH19C7kmhWP06NHo3Lkz2rZtW9hVKRRbtmxBo0aN0KtXLwQEBKB+/fr4/PPPC7taBapFixbYuXMnzp49CwD4+++/sW/fPnTq1KmQa1Z4Ll26hLi4OIvvS71ej7CwsKf6+5KKF24mnEe3b9+G0WhEYGCgRTwwMBBxcXGFVKvCJYTA+PHj0aJFC4SEhBR2dQrc+vXrcfToURw5cqSwq1JoLl68iE8++QTjx4/HW2+9hcOHDyM8PBx6vR4DBgwo7OoViDfffBOJiYmoXr06tFotjEYjZs6ciVdeeaWwq1Zosr8TZd+XV65cKYwqEdkdG1AqaTQai5+FEDliT4sxY8bg+PHj2LdvX2FXpcBdvXoVr7/+OrZv3/5U74ZuMpnQqFEjzJo1CwBQv359nDx5Ep988slT04DasGED1q5di3Xr1qFWrVqIjo7GuHHjEBwcjIEDBxZ29QoVvy+pOGMDKo/8/f2h1Wpz9DbFx8fn+CvraTB27Fhs2bIFv//+O0qXLl3Y1SlwUVFRiI+PR8OGDc0xo9GI33//HUuWLEFGRga0Wm0h1rBglCxZEjVr1rSI1ahRA99//30h1ajgTZo0CVOmTMHLL78MAKhduzauXLmC2bNnP7UNqKCgIAAPeqJKlixpjj+t35dUPHEOVB7pdDo0bNgQO3bssIjv2LEDzZo1K6RaFTwhBMaMGYMffvgBu3btQoUKFQq7SoWiTZs2OHHiBKKjo82vRo0aoW/fvoiOjn4qGk8A0Lx58xzLWJw9exblypUrpBoVvNTUVDg4WH6VarXaYr+MQW4qVKiAoKAgi+/LzMxM7N2796n6vqTijT1QKowfPx79+/dHo0aNEBoais8++wwxMTEYOXJkYVetwIwePRrr1q3Djz/+CA8PD3OPnJeXF1xcXAq5dgXHw8Mjx7wvNzc3+Pn5PVXzwd544w00a9YMs2bNQu/evXH48GF89tln+Oyzzwq7agWma9eumDlzJsqWLYtatWrh2LFj+PDDDzF48ODCrlq+SklJwfnz580/X7p0CdHR0fD19UXZsmUxbtw4zJo1C1WqVEGVKlUwa9YsuLq6ok+fPoVYayI7KtyHAIuepUuXinLlygmdTicaNGjw1D2+D0D6WrVqVWFXrdA9jcsYCCHETz/9JEJCQoRerxfVq1cXn332WWFXqUAlJSWJ119/XZQtW1Y4OzuLihUrirfffltkZGQUdtXy1e7du6XfBQMHDhRCPFjKYNq0aSIoKEjo9Xrx7LPPihMnThRupYnsSCOEEIXUdiMiIiIqkjgHioiIiEglNqCIiIiIVGIDioiIiEglNqCIiIiIVGIDioiIiEglNqCIiIiIVGIDioiIiEglNqCI7KRly5YYN25cYVdDKiIiAvXq1Xti81OT76BBg9CjRw+7l/1wHTQaDTQaDRYtWpRv5djL5cuXzfXNj/eEiOTYgKIiadCgQeb/NJycnBAYGIh27drhiy++eKr3ICsoEydOxM6dO80/53ej5mEfffQRVq9ena9l1KpVC7GxsRg+fHi+lpMXM2fORLNmzeDq6gpvb+8cx8uUKYPY2FhMmDCh4CtH9BRjA4qKrI4dOyI2NhaXL1/Gtm3b0KpVK7z++uvo0qULDAZDYVdPNaPRWGQaf+7u7vDz8yuUsr28vKQNCXtydHREUFAQXF1d87WcvMjMzESvXr3w2muvSY9rtVoEBQXB3d29gGtG9HRjA4qKLL1ej6CgIJQqVQoNGjTAW2+9hR9//BHbtm2z6KGIiYlB9+7d4e7uDk9PT/Tu3Rs3b940H88eNlq+fDnKlCkDV1dX9OrVC/fu3TOnye5hmT59OgICAuDp6YkRI0YgMzNTsX6ZmZmYPHkySpUqBTc3NzRp0gR79uwxH1+9ejW8vb2xdetW1KxZE3q9HleuXLHIw2QyoXTp0vj0008t4kePHoVGo8HFixcBAImJiRg+fLi5bq1bt8bff/+tWDeTyYQZM2agdOnS0Ov1qFevHiIjIy3SXLt2DS+//DJ8fX3h5uaGRo0a4dChQxb3LPvfa9aswY8//mjuFdyzZw9at26NMWPGWOR5584d6PV67Nq1S7FuAPL0XmRr2bIlwsPDMXnyZPj6+iIoKAgREREW+UVERKBs2bLQ6/UIDg5GeHh4ruXLaDQaLF++HF26dIGrqytq1KiBAwcO4Pz582jZsiXc3NwQGhqKCxcuKOaRmZmJMWPGoGTJknB2dkb58uUxe/bsXMudPn063njjDdSuXVt1nYko/7ABRcVK69atUbduXfzwww8AACEEevTogbt372Lv3r3YsWMHLly4gJdeesnivPPnz+Pbb7/FTz/9hMjISERHR2P06NEWaXbu3InTp09j9+7d+Oabb7Bp0yZMnz5dsS6vvvoq/vzzT6xfvx7Hjx9Hr1690LFjR5w7d86cJjU1FbNnz8aKFStw8uRJBAQEWOTh4OCAl19+GV9//bVFfN26dQgNDUXFihUhhEDnzp0RFxeHX375BVFRUWjQoAHatGmDu3fvSuv20UcfYcGCBZg/fz6OHz+ODh06oFu3bua6paSkICwsDDdu3MCWLVvw999/Y/LkydIesokTJ6J3797mHsHY2Fg0a9YMQ4cOxbp165CRkWFO+/XXXyM4OBitWrVSvG95eS8etWbNGri5ueHQoUOYO3cuZsyYgR07dgAANm7ciIULF2L58uU4d+4cNm/ebHVj5P3338eAAQMQHR2N6tWro0+fPhgxYgSmTp2Kv/76CwByNBof9vHHH2PLli349ttvcebMGaxduxbly5e3qi5EVMgKdy9jIusMHDhQdO/eXXrspZdeEjVq1BBCCLF9+3ah1WpFTEyM+fjJkycFAHH48GEhhBDTpk0TWq1WXL161Zxm27ZtwsHBQcTGxprL8/X1Fffv3zen+eSTT4S7u7swGo1CCCHCwsLE66+/LoQQ4vz580Kj0Yjr169b1K1NmzZi6tSpQgghVq1aJQCI6OjoXK/16NGjQqPRiMuXLwshhDAajaJUqVJi6dKlQgghdu7cKTw9PUV6errFeZUqVRLLly83X2PdunXNx4KDg8XMmTMt0j/zzDNi1KhRQgghli9fLjw8PMSdO3ekdXo0P9n7kZ6eLnx9fcWGDRvMsXr16omIiAjFa83re/FwWWFhYaJFixY5ruXNN98UQgixYMECUbVqVZGZmalYbm7Xlg2AeOedd8w/HzhwQAAQK1euNMe++eYb4ezsrJj32LFjRevWrYXJZMpTXR62atUq4eXlpbreRJQ/2ANFxY4QAhqNBgBw+vRplClTBmXKlDEfr1mzJry9vXH69GlzrGzZsihdurT559DQUJhMJpw5c8Ycq1u3rsWcmNDQUKSkpODq1as56nD06FEIIVC1alW4u7ubX3v37rUY4tHpdKhTp06u11O/fn1Ur14d33zzDQBg7969iI+PR+/evQEAUVFRSElJgZ+fn0VZly5dkg4nJSUl4caNG2jevLlFvHnz5uZ7Eh0djfr168PX1zfXuuVGr9ejX79++OKLL8x5/v333xg0aFCu5+XlvXjUo/ewZMmSiI+PBwD06tULaWlpqFixIoYNG4ZNmzZZPUfu4XICAwMBwKI3KzAwEOnp6UhKSpKeP2jQIERHR6NatWoIDw/H9u3bzcdGjhxp8f4R0ZPNsbArQGRvp0+fRoUKFQBYNqYephTPln0stzSPpn2YyWSCVqtFVFQUtFqtxbGH/3N0cXHJUxl9+/bFunXrMGXKFKxbtw4dOnSAv7+/uaySJUtazK/Klttk60fLffieuLi4PLZOeTF06FDUq1cP165dwxdffIE2bdqgXLlyqvLIy3vh5OSU45zs4cYyZcrgzJkz2LFjB3777TeMGjUK8+bNw969e3Oc9zgPp8+ujyym9DBAgwYNcOnSJWzbtg2//fYbevfujbZt22Ljxo2YMWMGJk6cqKo+RFR42ANFxcquXbtw4sQJvPDCCwAe9DbFxMRY9BKdOnUKiYmJqFGjhjkWExODGzdumH8+cOAAHBwcULVqVXPs77//RlpamvnngwcPwt3d3aK3JFv9+vVhNBoRHx+PypUrW7yCgoJUX1efPn1w4sQJREVFYePGjejbt6/5WIMGDRAXFwdHR8ccZWU3sh7m6emJ4OBg7Nu3zyK+f/9+8z2pU6cOoqOjFedQPUqn08FoNOaI165dG40aNcLnn3+OdevWYfDgwY/NKy/vhVouLi7o1q0bPv74Y+zZswcHDhzAiRMnrM7PFp6ennjppZfw+eefY8OGDfj+++9x9+5dBAQEWLx3RPRkYw8UFVkZGRmIi4uD0WjEzZs3ERkZidmzZ6NLly4YMGAAAKBt27aoU6cO+vbti0WLFsFgMGDUqFEICwtDo0aNzHk5Oztj4MCBmD9/PpKSkhAeHo7evXtbNHYyMzMxZMgQvPPOO7hy5QqmTZuGMWPGwMEh598hVatWRd++fTFgwAAsWLAA9evXx+3bt7Fr1y7Url0bnTp1UnWtFSpUQLNmzTBkyBAYDAZ0797dfKxt27YIDQ1Fjx49MGfOHFSrVg03btzAL7/8gh49elhcZ7ZJkyZh2rRpqFSpEurVq4dVq1YhOjraPFn9lVdewaxZs9CjRw/Mnj0bJUuWxLFjxxAcHIzQ0NAc+ZUvXx6//vorzpw5Az8/P3h5eZl7ZoYOHYoxY8bA1dUVzz///GOvNS/vhRqrV6+G0WhEkyZN4Orqiq+++gouLi6qe8LsYeHChShZsiTq1asHBwcHfPfddwgKCsq1pzAmJgZ3795FTEwMjEYjoqOjAQCVK1fmUB9RIWIDioqsyMhIlCxZEo6OjvDx8UHdunXx8ccfY+DAgeZGjUajwebNmzF27Fg8++yzcHBwQMeOHbF48WKLvCpXroyePXuiU6dOuHv3Ljp16oRly5ZZpGnTpg2qVKmCZ599FhkZGXj55ZdzPC7/sFWrVuG///0vJkyYgOvXr8PPzw+hoaGqG0/Z+vbti9GjR2PAgAEWQ2wajQa//PIL3n77bQwePBi3bt1CUFAQnn32WfM8nUeFh4cjKSkJEyZMQHx8PGrWrIktW7agSpUqAB70KG3fvh0TJkxAp06dYDAYULNmTSxdulSa37Bhw7Bnzx40atQIKSkp2L17N1q2bAngQWNs3Lhx6NOnD5ydnR97nXl5L9Tw9vbGBx98gPHjx8NoNKJ27dr46aefCmUdK3d3d8yZMwfnzp2DVqvFM888g19++UXaCM/23nvvYc2aNeaf69evDwAW95iICp5GCCEKuxJEhSkiIgKbN282/2UvM2jQINy7dw+bN28usHoVF1evXkX58uVx5MgRNGjQoLCr81h5+Tw8iYpqvYmKKs6BIqJ8kZWVhZiYGLz55pto2rRpkWg8ZTtx4gTc3d1t6vkqKDExMXB3d8esWbMKuypETxUO4RFRvvjzzz/RqlUrVK1aFRs3bizs6uRZeHg4+vXrBwAoUaJEIdfm8YKDg829Tnq9vnArQ/QU4RAeERERkUocwiMiIiJSiQ0oIiIiIpXYgCIiIiJSiQ0oIiIiIpXYgCIiIiJSiQ0oIiIiIpXYgCIiIiJSiQ0oIiIiIpXYgCIiIiJS6f8A2qorvU656ysAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "indicies = ds[\"sweep_0\"][\"spectrum_index\"].sel(\n", + " time=\"2024-03-08T23:01:00\", method=\"nearest\"\n", + ")\n", + "indicies\n", + "ds[\"sweep_0\"][\"spectral_reflectivity\"].isel(index=indicies).T.plot(\n", + " cmap=\"ChaseSpectral\", x=\"velocity_bins\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calculate rainfall accumulation estimated from Doppler velocity spectra" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Cumulative rainfall [mm]')" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "rainfall = ds[\"sweep_0\"][\"rainfall_rate\"].isel(range=0).cumsum() / 60.0\n", + "rainfall.plot()\n", + "plt.ylabel(\"Cumulative rainfall [mm]\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mrr2_env", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pyproject.toml b/pyproject.toml index 634c4a10..1f79e2f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ rainbow = "xradar.io.backends:RainbowBackendEntrypoint" hpl = "xradar.io.backends:HPLBackendEntrypoint" nexradlevel2 = "xradar.io.backends:NexradLevel2BackendEntrypoint" datamet = "xradar.io.backends:DataMetBackendEntrypoint" - +metek = "xradar.io.backends:MRRBackendEntrypoint" [build-system] requires = [ diff --git a/tests/conftest.py b/tests/conftest.py index e1b1778c..aef17666 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -96,3 +96,41 @@ def nexradlevel2_bzfile(): @pytest.fixture def nexradlevel2_files(request): return request.getfixturevalue(request.param) + +@pytest.fixture(scope="session") +def metek_ave_gz_file(): + fnamei = DATASETS.fetch("0308.ave.gz") + fnameo = f"{fnamei[:-3]}_gz" + import gzip + import shutil + + with gzip.open(fnamei) as fin: + with open(fnameo, "wb") as fout: + shutil.copyfileobj(fin, fout) + return fnameo + + +@pytest.fixture(scope="session") +def metek_pro_gz_file(): + fnamei = DATASETS.fetch("0308.pro.gz") + fnameo = f"{fnamei[:-3]}_gz" + import gzip + import shutil + + with gzip.open(fnamei) as fin: + with open(fnameo, "wb") as fout: + shutil.copyfileobj(fin, fout) + return fnameo + + +@pytest.fixture(scope="session") +def metek_raw_gz_file(): + fnamei = DATASETS.fetch("0308.raw.gz") + fnameo = f"{fnamei[:-3]}_gz" + import gzip + import shutil + + with gzip.open(fnamei) as fin: + with open(fnameo, "wb") as fout: + shutil.copyfileobj(fin, fout) + return fnameo diff --git a/tests/io/test_metek.py b/tests/io/test_metek.py new file mode 100644 index 00000000..fa139521 --- /dev/null +++ b/tests/io/test_metek.py @@ -0,0 +1,205 @@ +""" +Tests for the MRR2 backend for xradar +""" + +import numpy as np +import xarray as xr + +from xradar.io.backends import metek + +test_arr_ave = np.array( + [ + 25.4, + 24.87, + 24.63, + 25.12, + 25.39, + 26.09, + 27.21, + 28.34, + 29.41, + 31.21, + 32.29, + 28.85, + 21.96, + 19.27, + 20.19, + 21.32, + 21.49, + 20.58, + 19.43, + 18.07, + 16.79, + 15.9, + 14.59, + 14.35, + 13.41, + 11.71, + 10.63, + 10.48, + 7.84, + 4.25, + 4.23, + ] +) + +test_arr = np.array( + [ + 24.46, + 25.31, + 26.33, + 26.31, + 26.85, + 27.93, + 29.12, + 30.17, + 30.99, + 32.58, + 33.13, + 28.84, + 22.16, + 19.81, + 21.26, + 21.33, + 20.33, + 18.93, + 17.92, + 18.04, + 16.86, + 14.46, + 13.17, + 13.13, + 11.75, + 10.53, + 9.3, + 5.92, + -4.77, + np.nan, + 6.74, + ] +) + +test_raw = np.array( + [ + 1.090e03, + 6.330e02, + 1.250e02, + 1.000e01, + 2.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 3.000e00, + 3.000e00, + 3.000e00, + 4.000e00, + 6.000e00, + 8.000e00, + 1.100e01, + 1.600e01, + 2.700e01, + 6.200e01, + 1.370e02, + 2.130e02, + 2.560e02, + 3.550e02, + 5.880e02, + 1.087e03, + 1.554e03, + 1.767e03, + 1.910e03, + 1.977e03, + 2.002e03, + 2.039e03, + 1.926e03, + 1.837e03, + 1.893e03, + 1.837e03, + 1.926e03, + 2.039e03, + 2.002e03, + 1.977e03, + 1.910e03, + 1.767e03, + 1.554e03, + 1.087e03, + 5.880e02, + 3.550e02, + 2.560e02, + 2.130e02, + 1.370e02, + 6.200e01, + 2.700e01, + 1.600e01, + 1.100e01, + 8.000e00, + 6.000e00, + 4.000e00, + 3.000e00, + 3.000e00, + 3.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 1.000e01, + 1.250e02, + 6.330e02, + ] +) + + +def test_open_average(metek_ave_gz_file): + ds = xr.open_dataset(metek_ave_gz_file, engine="metek") + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 60.0 + np.testing.assert_allclose(rainfall.values[-1], 0.938) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr_ave) + ds.close() + ds = None + + +def test_open_average_datatree(metek_ave_gz_file): + ds = metek.open_metek_datatree(metek_ave_gz_file) + assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() + assert "velocity" in ds["sweep_0"].variables.keys() + rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 60.0 + np.testing.assert_allclose(rainfall.values[-1], 0.938) + np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr_ave) + ds = None + + +def test_open_processed(metek_pro_gz_file): + ds = xr.open_dataset(metek_pro_gz_file, engine="metek") + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 360.0 + np.testing.assert_allclose(rainfall.values[-1], 0.93) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr) + ds.close() + ds = None + + +def test_open_processed_datatree(metek_pro_gz_file): + ds = metek.open_metek_datatree(metek_pro_gz_file) + assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() + assert "velocity" in ds["sweep_0"].variables.keys() + rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 360.0 + np.testing.assert_allclose(rainfall.values[-1], 0.93) + np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr) + ds = None + + +def test_open_raw(metek_raw_gz_file): + ds = xr.open_dataset(metek_raw_gz_file, engine="metek") + assert "raw_spectra_counts" in ds.variables.keys() + np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) + ds.close() + + +def test_open_raw_datatree(metek_raw_gz_file): + ds = metek.open_metek_datatree(metek_raw_gz_file) + assert "raw_spectra_counts" in ds["sweep_0"].variables.keys() + np.testing.assert_allclose(ds["sweep_0"]["raw_spectra_counts"].values[0], test_raw) + del ds diff --git a/xradar/io/backends/__init__.py b/xradar/io/backends/__init__.py index 6ce42e33..eede58fe 100644 --- a/xradar/io/backends/__init__.py +++ b/xradar/io/backends/__init__.py @@ -18,6 +18,7 @@ .. automodule:: xradar.io.backends.hpl .. automodule:: xradar.io.backends.nexrad_level2 .. automodule:: xradar.io.backends.datamet +.. automodule:: xradar.io.backends.metek """ @@ -30,5 +31,6 @@ from .hpl import * # noqa from .nexrad_level2 import * # noqa from .datamet import * # noqa +from .metek import * # noqa __all__ = [s for s in dir() if not s.startswith("_")] diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py new file mode 100644 index 00000000..bfaf33f4 --- /dev/null +++ b/xradar/io/backends/metek.py @@ -0,0 +1,665 @@ +""" +Metek MRR2 raw and processed data +================================= +Read data from METEK's MRR-2 raw (.raw) and processed (.pro, .avg) files. + +Example:: + + import xradar as xd + ds = xr.open_dataset('0308.pro', engine='metek') + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + {} +""" + +import io +import warnings +from datetime import datetime + +import numpy as np +import xarray as xr +from datatree import DataTree +from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint +from xarray.backends.file_manager import CachingFileManager +from xarray.backends.store import StoreBackendEntrypoint +from xarray.core import indexing +from xarray.core.utils import FrozenDict + +from ...model import ( + get_altitude_attrs, + get_azimuth_attrs, + get_elevation_attrs, + get_latitude_attrs, + get_longitude_attrs, + get_time_attrs, +) +from .common import _assign_root, _attach_sweep_groups + +__all__ = [ + "MRRBackendEntrypoint", + "open_metek_datatree", +] + +__doc__ = __doc__.format("\n ".join(__all__)) + +variable_attr_dict = dict( + transfer_function={ + "long_name": "Transfer function", + "standard_name": "transfer_function", + "units": "1", + "dims": ("time", "range"), + }, + spectral_reflectivity={ + "long_name": "Spectral reflectivity", + "standard_name": "equivalent_reflectivity_factor", + "units": "dB", + "dims": ("index", "sample"), + }, + raw_spectra_counts={ + "long_name": "Raw spectra counts", + "standard_name": "raw_spectra", + "units": "", + "dims": ("index", "sample"), + }, + drop_size={ + "long_name": "Drop size", + "standard_name": "drop_size", + "units": "mm", + "dims": ("index", "sample"), + }, + drop_number_density={ + "long_name": "Raindrop number density", + "standard_name": "raindrop_number_density", + "units": "m-4", + "dims": ("index", "sample"), + }, + rainfall_rate={ + "long_name": "Rainfall rate", + "standard_name": "rainfall_rate", + "units": "mm hr-1", + "dims": ("time", "range"), + }, + liquid_water_content={ + "long_name": "Liquid water content", + "standard_name": "liquid_water_content", + "units": "g m-3", + "dims": ("time", "range"), + }, + path_integrated_attenuation={ + "long_name": "Path integrated attenuation", + "standard_name": "path_integrated_attenuation", + "units": "dB", + "dims": ("time", "range"), + }, + corrected_reflectivity={ + "long_name": "Attenuation-corrected Radar reflectivity factor", + "standard_name": "equivalent_radar_reflectivity_factor", + "units": "dBZ", + "dims": ("time", "range"), + }, + reflectivity={ + "long_name": "Radar reflectivity factor", + "standard_name": "equivalent_radar_reflectivity_factor", + "units": "dBZ", + "dims": ("time", "range"), + }, + spectrum_index={ + "long_name": "Spectrum index", + "standard_name": "spectrum_index", + "units": "1", + "dims": ("time", "range"), + }, + percentage_valid_spectra={ + "long_name": "Percentage of spectra that are valid", + "standard_name": "percentage_valid_spectra", + "units": "percent", + "dims": ("time"), + }, + number_valid_spectra={ + "long_name": "number of spectra that are valid", + "standard_name": "number_valid_spectra", + "units": "1", + "dims": ("time"), + }, + total_number_spectra={ + "long_name": "Total number of spectra", + "standard_name": "total_number_spectra", + "units": "1", + "dims": ("time"), + }, + velocity_bins={ + "long_name": "Doppler velocity bins", + "standard_name": "doppler_velocity_bins", + "units": "m s-1", + "dims": ("sample"), + }, + range={ + "long_name": "Range from radar", + "standard_name": "range", + "units": "m", + "dims": ("range",), + }, + time=get_time_attrs(), + azimuth=get_azimuth_attrs(), + elevation=get_elevation_attrs(), + velocity={ + "long_name": "Radial velocity of scatterers toward instrument", + "standard_name": "radial_velocity_of_scatterers_toward_instrument", + "units": "m s-1", + }, + latitude=get_latitude_attrs(), + longitude=get_longitude_attrs(), + altitude=get_altitude_attrs(), +) + +variable_attr_dict["time"]["dims"] = ("time",) +variable_attr_dict["azimuth"]["dims"] = ("time",) +variable_attr_dict["elevation"]["dims"] = ("time",) +variable_attr_dict["velocity"]["dims"] = ("time", "range") +variable_attr_dict["latitude"]["dims"] = () +variable_attr_dict["longitude"]["dims"] = () +variable_attr_dict["altitude"]["dims"] = () + + +def _parse_spectra_line(input_str, num_gates): + out_array = np.zeros(num_gates) + increment = {32: 9, 31: 7}[num_gates] + for i, pos in enumerate(range(3, len(input_str) - increment, increment)): + input_num_str = input_str[pos : pos + increment] + try: + out_array[i] = float(input_num_str) + except ValueError: + out_array[i] = np.nan + + return out_array + + +class MRR2File: + def __init__(self, file_name="", **kwargs): + self.vel_bin_spacing = 0.1887 + self.nyquist_velocity = self.vel_bin_spacing * 64 + self._data = {} + self._data["velocity_bins"] = np.arange( + 0, 64 * self.vel_bin_spacing, self.vel_bin_spacing + ) + self._data["range"] = [] + self._data["transfer_function"] = [] + self._data["spectral_reflectivity"] = [] + self._data["raw_spectra_counts"] = [] + self._data["drop_size"] = [] + self._data["drop_number_density"] = [] + self._data["time"] = [] + self.filetype = "" + self.device_version = "" + self.device_serial_number = "" + self.bandwidth = 0 + self._fp = None + self.calibration_constant = [] + self._data["percentage_valid_spectra"] = [] + self._data["number_valid_spectra"] = [] + self._data["total_number_spectra"] = [] + self.spectra_index = 0 + self.altitude = None + self.sampling_rate = 125000.0 + self._data["path_integrated_attenuation"] = [] + self._data["corrected_reflectivity"] = [] + self._data["reflectivity"] = [] + self._data["rainfall_rate"] = [] + self._data["liquid_water_content"] = [] + self._data["velocity"] = [] + self._data["altitude"] = np.array(np.nan) + self._data["longitude"] = np.array(np.nan) + self._data["latitude"] = np.array(np.nan) + self.filename = None + self.n_gates = 32 + if not file_name == "": + self.filename = file_name + self.open(file_name) + + def open(self, filename_or_obj): + if isinstance(filename_or_obj, io.IOBase): + filename_or_obj.seek(0) + self._fp = filename_or_obj + + if isinstance(filename_or_obj, str): + self.filename = filename_or_obj + self._fp = open(filename_or_obj) + + num_times = 0 + temp_spectra = np.zeros((self.n_gates, 64)) + temp_drops = np.zeros((self.n_gates, 64)) + temp_number = np.zeros((self.n_gates, 64)) + spec_var = "" + for file_line in self._fp: + if file_line[:3] == "MRR": + if num_times > 0: + self._data[spec_var].append(temp_spectra) + self._data["drop_number_density"].append(temp_number) + self._data["drop_size"].append(temp_drops) + + string_split = file_line.split() + time_str = string_split[1] + parsed_datetime = datetime.strptime(time_str, "%y%m%d%H%M%S") + self._data["time"] = self._data["time"] + [parsed_datetime] + self.filetype = string_split[-1] + if self.filetype == "RAW": + self.device_version = string_split[4] + self.device_serial_number = string_split[6] + self.bandwidth = int(string_split[8]) + self.calibration_constant.append(int(string_split[10])) + self._data["percentage_valid_spectra"].append(int(string_split[12])) + self._data["number_valid_spectra"].append(int(string_split[13])) + self._data["total_number_spectra"].append(int(string_split[14])) + self.n_gates = 32 + spec_var = "raw_spectra_counts" + elif self.filetype == "AVE" or self.filetype == "PRO": + self._data["altitude"] = np.array(float(string_split[8])) + self.sampling_rate = float(string_split[10]) + self.mrr_service_version = string_split[12] + self.device_version = string_split[14] + self.calibration_constant.append(int(string_split[16])) + self._data["percentage_valid_spectra"].append(int(string_split[18])) + self.n_gates = 31 + spec_var = "spectral_reflectivity" + else: + raise OSError( + "Invalid file type flag in file! Must be RAW, AVG, or PRO!" + ) + temp_spectra = np.zeros((self.n_gates, 64)) + temp_drops = np.zeros((self.n_gates, 64)) + temp_number = np.zeros((self.n_gates, 64)) + num_times = num_times + 1 + + if file_line[0] == "H": + in_array = _parse_spectra_line(file_line, self.n_gates) + if num_times > 1: + after_res = in_array[1] - in_array[0] + before_res = self._data["range"][1] - self._data["range"][0] + if not after_res == before_res: + warnings.warn( + f"MRR2 resolution was changed mid file. Before time period " + f"{parsed_datetime} the resolution was {before_res}, " + f"and {after_res} after.", + UserWarning, + ) + self._data["range"] = in_array + + if file_line[0:2] == "TF": + self._data["transfer_function"].append( + _parse_spectra_line(file_line, self.n_gates) + ) + if file_line[0] == "F": + spectra_bin_no = int(file_line[1:3]) + temp_spectra[:, spectra_bin_no] = _parse_spectra_line( + file_line, self.n_gates + ) + if file_line[0] == "D": + spectra_bin_no = int(file_line[1:3]) + temp_drops[:, spectra_bin_no] = _parse_spectra_line( + file_line, self.n_gates + ) + if file_line[0] == "N": + spectra_bin_no = int(file_line[1:3]) + temp_number[:, spectra_bin_no] = _parse_spectra_line( + file_line, self.n_gates + ) + if file_line[0:3] == "PIA": + self._data["path_integrated_attenuation"] = self._data[ + "path_integrated_attenuation" + ] + [_parse_spectra_line(file_line, self.n_gates)] + if file_line[0:3] == "z ": + self._data["reflectivity"] = self._data["reflectivity"] + [ + _parse_spectra_line(file_line, self.n_gates) + ] + if file_line[0:3] == "Z ": + self._data["corrected_reflectivity"] = self._data[ + "corrected_reflectivity" + ] + [_parse_spectra_line(file_line, self.n_gates)] + if file_line[0:3] == "RR ": + self._data["rainfall_rate"] = self._data["rainfall_rate"] + [ + _parse_spectra_line(file_line, self.n_gates) + ] + if file_line[0:3] == "LWC": + self._data["liquid_water_content"] = self._data[ + "liquid_water_content" + ] + [_parse_spectra_line(file_line, self.n_gates)] + if file_line[0:3] == "W ": + self._data["velocity"] = self._data["velocity"] + [ + _parse_spectra_line(file_line, self.n_gates) + ] + + self._data[spec_var].append(temp_spectra) + self._data["drop_number_density"].append(temp_number) + self._data["drop_size"].append(temp_drops) + self._data["transfer_function"] = np.stack( + self._data["transfer_function"], axis=0 + ) + self._data[spec_var] = np.stack(self._data[spec_var], axis=0) + self._data["drop_number_density"] = np.stack( + self._data["drop_number_density"], axis=0 + ) + self._data["drop_size"] = np.stack(self._data["drop_size"], axis=0) + + if self.filetype == "RAW": + self._data["total_number_spectra"] = np.stack( + self._data["total_number_spectra"], axis=0 + ) + self._data["number_valid_spectra"] = np.stack( + self._data["number_valid_spectra"], axis=0 + ) + + del self._data["reflectivity"] + del self._data["corrected_reflectivity"] + del self._data["liquid_water_content"] + del self._data["rainfall_rate"] + del self._data["percentage_valid_spectra"] + del self._data["drop_number_density"] + del self._data["drop_size"] + del self._data["path_integrated_attenuation"] + del self._data["velocity"] + del self._data["spectral_reflectivity"] + else: + del self._data["total_number_spectra"], self._data["number_valid_spectra"] + self._data["reflectivity"] = np.stack(self._data["reflectivity"], axis=0) + self._data["path_integrated_attenuation"] = np.stack( + self._data["path_integrated_attenuation"], axis=0 + ) + self._data["corrected_reflectivity"] = np.stack( + self._data["corrected_reflectivity"], axis=0 + ) + + self._data["liquid_water_content"] = np.stack( + self._data["liquid_water_content"], axis=0 + ) + self._data["velocity"] = np.stack(self._data["velocity"], axis=0) + self._data["rainfall_rate"] = np.stack(self._data["rainfall_rate"], axis=0) + self._data["percentage_valid_spectra"] = np.stack( + self._data["percentage_valid_spectra"], axis=0 + ) + self._data["drop_number_density"] = np.stack( + self._data["drop_number_density"], axis=0 + ) + self._data["drop_size"] = np.stack(self._data["drop_size"], axis=0) + del self._data["raw_spectra_counts"] + + self._data["range"] = np.squeeze(self._data["range"]) + # Now we compress the spectrum variables to remove invalid spectra + self._data[spec_var] = self._data[spec_var].reshape( + self._data[spec_var].shape[0] * self._data[spec_var].shape[1], + self._data[spec_var].shape[2], + ) + where_valid_spectra = np.any(np.isfinite(self._data[spec_var]), axis=1) + inds = np.where(where_valid_spectra, 1, -1) + + self._data[spec_var] = self._data[spec_var][where_valid_spectra] + if self.filetype == "PRO" or self.filetype == "AVE": + self._data["drop_number_density"] = self._data[ + "drop_number_density" + ].reshape( + self._data["drop_number_density"].shape[0] + * self._data["drop_number_density"].shape[1], + self._data["drop_number_density"].shape[2], + ) + self._data["drop_number_density"] = self._data["drop_number_density"][ + where_valid_spectra + ] + self._data["drop_size"] = self._data["drop_size"].reshape( + self._data["drop_size"].shape[0] * self._data["drop_size"].shape[1], + self._data["drop_size"].shape[2], + ) + self._data["drop_size"] = self._data["drop_size"][where_valid_spectra] + cur_index = 0 + for i in range(len(inds)): + if inds[i] > -1: + inds[i] = cur_index + cur_index += 1 + self._data["spectrum_index"] = inds.reshape( + (len(self._data["time"]), len(self._data["range"])) + ) + + self._data["azimuth"] = np.zeros_like(self._data["time"]) + self._data["elevation"] = 90 * np.ones_like(self._data["time"]) + self._data["time"] = np.array(self._data["time"]) + + def close(self): + if self._fp is not None: + self._fp.close() + + __del__ = close + + @property + def data(self): + return self._data + + @property + def coordinates(self): + return self._coordinates + + @property + def fixed_angle(self): + return self._data["elevation"] + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + +class MRR2ArrayWrapper(BackendArray): + def __init__( + self, + data, + ): + self.data = data + self.shape = data.shape + self.dtype = np.dtype("float64") + + def __getitem__(self, key: tuple): + return indexing.explicit_indexing_adapter( + key, + self.shape, + indexing.IndexingSupport.OUTER_1VECTOR, + self._raw_indexing_method, + ) + + def _raw_indexing_method(self, key: tuple): + return self.data[key] + + +class MRR2DataStore(AbstractDataStore): + def __init__(self, manager, group=None): + self._manager = manager + self._group = group + self._filename = self.filename + self._need_time_recalc = False + + @classmethod + def open(cls, filename, mode="r", group=None, **kwargs): + manager = CachingFileManager(MRR2File, filename, mode=mode, kwargs=kwargs) + return cls(manager, group=group) + + @property + def filename(self): + with self._manager.acquire_context(False) as root: + return root.filename + + @property + def root(self): + with self._manager.acquire_context(False) as root: + return root + + def _acquire(self, needs_lock=True): + with self._manager.acquire_context(needs_lock) as root: + return root + + @property + def ds(self): + return self._acquire() + + def open_store_variable(self, name, var): + data = indexing.LazilyOuterIndexedArray(MRR2ArrayWrapper(var)) + encoding = {"group": self._group, "source": self._filename} + attrs = variable_attr_dict[name].copy() + dims = attrs["dims"] + del attrs["dims"] + return xr.Variable(dims, data, attrs, encoding) + + def open_store_coordinates(self): + coord_keys = ["time", "range", "velocity_bins"] + coords = {} + for k in coord_keys: + attrs = variable_attr_dict[k].copy() + dims = attrs["dims"] + del attrs["dims"] + coords[k] = xr.Variable(dims, self.ds.data[k], attrs=attrs) + + return coords + + def get_variables(self): + return FrozenDict( + (k1, v1) + for k1, v1 in { + **{k: self.open_store_variable(k, v) for k, v in self.ds.data.items()}, + **self.open_store_coordinates(), + }.items() + ) + + def get_attrs(self): + return FrozenDict() + + +class MRRBackendEntrypoint(BackendEntrypoint): + """Xarray BackendEntrypoint for Metek MRR2 data. + + Keyword Arguments + ----------------- + first_dim : str + Can be ``time`` or ``auto`` first dimension. If set to ``auto``, + first dimension will be either ``azimuth`` or ``elevation`` depending on + type of sweep. Defaults to ``auto``. + site_coords : bool + Attach radar site-coordinates to Dataset, defaults to ``True``. + kwargs : dict + Additional kwargs are fed to :py:func:`xarray.open_dataset`. + """ + + description = "Backend for reading Metek MRR2 processed and raw data" + url = "https://xradar.rtfd.io/en/latest/io.html#metek" + + def open_dataset( + self, + filename_or_obj, + *, + mask_and_scale=True, + decode_times=True, + concat_characters=True, + decode_coords=True, + drop_variables=None, + use_cftime=None, + decode_timedelta=None, + format=None, + group="/", + invalid_netcdf=None, + phony_dims="access", + decode_vlen_strings=True, + first_dim="auto", + site_coords=True, + optional=True, + ): + store_entrypoint = StoreBackendEntrypoint() + + store = MRR2DataStore.open( + filename_or_obj, + format=format, + group=group, + invalid_netcdf=invalid_netcdf, + phony_dims=phony_dims, + decode_vlen_strings=decode_vlen_strings, + ) + + ds = store_entrypoint.open_dataset( + store, + mask_and_scale=mask_and_scale, + decode_times=decode_times, + concat_characters=concat_characters, + decode_coords=decode_coords, + drop_variables=drop_variables, + use_cftime=use_cftime, + decode_timedelta=decode_timedelta, + ) + + ds = ds.assign_coords({"range": ds.range}) + ds = ds.assign_coords({"time": ds.time}) + ds = ds.assign_coords({"velocity_bins": ds.velocity_bins}) + ds.encoding["engine"] = "metek" + + return ds + + +def open_metek_datatree(filename_or_obj, **kwargs): + """Open Metek MRR2 dataset as :py:class:`datatree.DataTree`. + + Parameters + ---------- + filename_or_obj : str, Path, file-like or DataStore + Strings and Path objects are interpreted as a path to a local or remote + radar file + + Keyword Arguments + ----------------- + sweep : int, list of int, optional + Sweep number(s) to extract, default to first sweep. If None, all sweeps are + extracted into a list. + first_dim : str + Can be ``time`` or ``auto`` first dimension. If set to ``auto``, + first dimension will be either ``azimuth`` or ``elevation`` depending on + type of sweep. Defaults to ``auto``. + reindex_angle : bool or dict + Defaults to False, no reindexing. Given dict should contain the kwargs to + reindex_angle. Only invoked if `decode_coord=True`. + fix_second_angle : bool + If True, fixes erroneous second angle data. Defaults to ``False``. + site_coords : bool + Attach radar site-coordinates to Dataset, defaults to ``True``. + kwargs : dict + Additional kwargs are fed to :py:func:`xarray.open_dataset`. + + Returns + ------- + dtree: datatree.DataTree + DataTree + """ + # handle kwargs, extract first_dim + backend_kwargs = kwargs.pop("backend_kwargs", {}) + # first_dim = backend_kwargs.pop("first_dim", None) + sweep = kwargs.pop("sweep", None) + sweeps = [] + kwargs["backend_kwargs"] = backend_kwargs + + if isinstance(sweep, str): + sweeps = [sweep] + elif isinstance(sweep, int): + sweeps = [f"sweep_{sweep}"] + elif isinstance(sweep, list): + if isinstance(sweep[0], int): + sweeps = [f"sweep_{i + 1}" for i in sweep] + else: + sweeps.extend(sweep) + else: + sweeps = ["sweep_0"] + + ds = [ + xr.open_dataset(filename_or_obj, group=swp, engine="metek", **kwargs) + for swp in sweeps + ] + + ds.insert(0, xr.Dataset()) # open_dataset(filename_or_obj, group="/")) + + # create datatree root node with required data + dtree = DataTree(data=_assign_root(ds), name="root") + # return datatree with attached sweep child nodes + return _attach_sweep_groups(dtree, ds[1:]) From 960c82da4307ebe727009f6697ee72f4c7fdfebd Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 15:17:23 -0500 Subject: [PATCH 06/23] ADD: Black --- tests/conftest.py | 1 + xradar/io/backends/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index aef17666..d953bff8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -97,6 +97,7 @@ def nexradlevel2_bzfile(): def nexradlevel2_files(request): return request.getfixturevalue(request.param) + @pytest.fixture(scope="session") def metek_ave_gz_file(): fnamei = DATASETS.fetch("0308.ave.gz") diff --git a/xradar/io/backends/__init__.py b/xradar/io/backends/__init__.py index eede58fe..946d0b54 100644 --- a/xradar/io/backends/__init__.py +++ b/xradar/io/backends/__init__.py @@ -31,6 +31,6 @@ from .hpl import * # noqa from .nexrad_level2 import * # noqa from .datamet import * # noqa -from .metek import * # noqa +from .metek import * # noqa __all__ = [s for s in dir() if not s.startswith("_")] From 30565e2cf28e0dbe6870943f222abe3b4574b727 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 15:23:35 -0500 Subject: [PATCH 07/23] Try ds = None --- tests/io/test_metek.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/io/test_metek.py b/tests/io/test_metek.py index fa139521..9bf2d537 100644 --- a/tests/io/test_metek.py +++ b/tests/io/test_metek.py @@ -196,10 +196,11 @@ def test_open_raw(metek_raw_gz_file): assert "raw_spectra_counts" in ds.variables.keys() np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) ds.close() + ds = None def test_open_raw_datatree(metek_raw_gz_file): ds = metek.open_metek_datatree(metek_raw_gz_file) assert "raw_spectra_counts" in ds["sweep_0"].variables.keys() np.testing.assert_allclose(ds["sweep_0"]["raw_spectra_counts"].values[0], test_raw) - del ds + ds = None From aa8b68d1e586e178ca6119ddac152fbe10b82012 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 15:31:45 -0500 Subject: [PATCH 08/23] FIX: Delete .gz file after download --- tests/conftest.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d953bff8..add5c9c6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ # Copyright (c) 2022-2023, openradar developers. # Distributed under the MIT License. See LICENSE for more info. import pytest +import os from open_radar_data import DATASETS @@ -101,37 +102,40 @@ def nexradlevel2_files(request): @pytest.fixture(scope="session") def metek_ave_gz_file(): fnamei = DATASETS.fetch("0308.ave.gz") - fnameo = f"{fnamei[:-3]}_gz" + fnameo = f"{fnamei[:-3]}" import gzip import shutil with gzip.open(fnamei) as fin: with open(fnameo, "wb") as fout: shutil.copyfileobj(fin, fout) + os.remove(fnamei) return fnameo @pytest.fixture(scope="session") def metek_pro_gz_file(): fnamei = DATASETS.fetch("0308.pro.gz") - fnameo = f"{fnamei[:-3]}_gz" + fnameo = f"{fnamei[:-3]}" import gzip import shutil with gzip.open(fnamei) as fin: with open(fnameo, "wb") as fout: shutil.copyfileobj(fin, fout) + os.remove(fnamei) return fnameo @pytest.fixture(scope="session") def metek_raw_gz_file(): fnamei = DATASETS.fetch("0308.raw.gz") - fnameo = f"{fnamei[:-3]}_gz" + fnameo = f"{fnamei[:-3]}" import gzip import shutil with gzip.open(fnamei) as fin: with open(fnameo, "wb") as fout: shutil.copyfileobj(fin, fout) + os.remove(fnamei) return fnameo From caaf89845ca252ab1489df27d45b433e57e04451 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 15:33:37 -0500 Subject: [PATCH 09/23] Ruff! --- tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index add5c9c6..825aef31 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # Copyright (c) 2022-2023, openradar developers. # Distributed under the MIT License. See LICENSE for more info. -import pytest import os + +import pytest from open_radar_data import DATASETS From eaa9cc9743f059c2788f4a5b305f36cc0c845d5c Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 15:42:19 -0500 Subject: [PATCH 10/23] ADD: Set data variables to None upon closing. --- xradar/io/backends/metek.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index bfaf33f4..fdada9a9 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -350,7 +350,7 @@ def open(self, filename_or_obj): self._data["number_valid_spectra"] = np.stack( self._data["number_valid_spectra"], axis=0 ) - + del self._data["reflectivity"] del self._data["corrected_reflectivity"] del self._data["liquid_water_content"] @@ -423,10 +423,18 @@ def open(self, filename_or_obj): self._data["azimuth"] = np.zeros_like(self._data["time"]) self._data["elevation"] = 90 * np.ones_like(self._data["time"]) self._data["time"] = np.array(self._data["time"]) + temp_drops = None + temp_number = None + temp_spectra = None + def close(self): if self._fp is not None: self._fp.close() + + for k in self._data.keys(): + self._data[k] = None + __del__ = close From 221eff0f953f21d2e6d3f1d96c37f4290bc52ed6 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Fri, 23 Aug 2024 15:43:42 -0500 Subject: [PATCH 11/23] Black --- xradar/io/backends/metek.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index fdada9a9..c2eb61a6 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -350,7 +350,7 @@ def open(self, filename_or_obj): self._data["number_valid_spectra"] = np.stack( self._data["number_valid_spectra"], axis=0 ) - + del self._data["reflectivity"] del self._data["corrected_reflectivity"] del self._data["liquid_water_content"] @@ -426,15 +426,13 @@ def open(self, filename_or_obj): temp_drops = None temp_number = None temp_spectra = None - def close(self): if self._fp is not None: self._fp.close() - + for k in self._data.keys(): self._data[k] = None - __del__ = close From 04edf8121e00446f24bcb1cc6ec4fa1ac042e82a Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Wed, 28 Aug 2024 09:30:14 -0500 Subject: [PATCH 12/23] FIX: Trying context managers for Datasets. --- tests/conftest.py | 5 ----- tests/io/test_metek.py | 37 +++++++++++++++---------------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 825aef31..f07bf82f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # Copyright (c) 2022-2023, openradar developers. # Distributed under the MIT License. See LICENSE for more info. -import os - import pytest from open_radar_data import DATASETS @@ -110,7 +108,6 @@ def metek_ave_gz_file(): with gzip.open(fnamei) as fin: with open(fnameo, "wb") as fout: shutil.copyfileobj(fin, fout) - os.remove(fnamei) return fnameo @@ -124,7 +121,6 @@ def metek_pro_gz_file(): with gzip.open(fnamei) as fin: with open(fnameo, "wb") as fout: shutil.copyfileobj(fin, fout) - os.remove(fnamei) return fnameo @@ -138,5 +134,4 @@ def metek_raw_gz_file(): with gzip.open(fnamei) as fin: with open(fnameo, "wb") as fout: shutil.copyfileobj(fin, fout) - os.remove(fnamei) return fnameo diff --git a/tests/io/test_metek.py b/tests/io/test_metek.py index 9bf2d537..68573bfc 100644 --- a/tests/io/test_metek.py +++ b/tests/io/test_metek.py @@ -150,14 +150,12 @@ def test_open_average(metek_ave_gz_file): - ds = xr.open_dataset(metek_ave_gz_file, engine="metek") - assert "corrected_reflectivity" in ds.variables.keys() - assert "velocity" in ds.variables.keys() - rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 60.0 - np.testing.assert_allclose(rainfall.values[-1], 0.938) - np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr_ave) - ds.close() - ds = None + with xr.open_dataset(metek_ave_gz_file, engine="metek") as ds: + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 60.0 + np.testing.assert_allclose(rainfall.values[-1], 0.938) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr_ave) def test_open_average_datatree(metek_ave_gz_file): @@ -166,19 +164,16 @@ def test_open_average_datatree(metek_ave_gz_file): assert "velocity" in ds["sweep_0"].variables.keys() rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 60.0 np.testing.assert_allclose(rainfall.values[-1], 0.938) - np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr_ave) ds = None def test_open_processed(metek_pro_gz_file): - ds = xr.open_dataset(metek_pro_gz_file, engine="metek") - assert "corrected_reflectivity" in ds.variables.keys() - assert "velocity" in ds.variables.keys() - rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 360.0 - np.testing.assert_allclose(rainfall.values[-1], 0.93) - np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr) - ds.close() - ds = None + with xr.open_dataset(metek_pro_gz_file, engine="metek") as ds: + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 360.0 + np.testing.assert_allclose(rainfall.values[-1], 0.93) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr) def test_open_processed_datatree(metek_pro_gz_file): @@ -192,11 +187,9 @@ def test_open_processed_datatree(metek_pro_gz_file): def test_open_raw(metek_raw_gz_file): - ds = xr.open_dataset(metek_raw_gz_file, engine="metek") - assert "raw_spectra_counts" in ds.variables.keys() - np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) - ds.close() - ds = None + with xr.open_dataset(metek_raw_gz_file, engine="metek") as ds: + assert "raw_spectra_counts" in ds.variables.keys() + np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) def test_open_raw_datatree(metek_raw_gz_file): From be3fec70ec164acb7a3ba1ec666a66c3d07742da Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Wed, 28 Aug 2024 09:38:04 -0500 Subject: [PATCH 13/23] Set raw data variables to None --- xradar/io/backends/metek.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index c2eb61a6..ad5a4f23 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -350,6 +350,16 @@ def open(self, filename_or_obj): self._data["number_valid_spectra"] = np.stack( self._data["number_valid_spectra"], axis=0 ) + self._data["reflectivity"] = None + self._data["corrected_reflectivity"] = None + self._data["liquid_water_content"] = None + self._data["rainfall_rate"] = None + self._data["percentage_valid_spectra"] = None + self._data["drop_number_density"] = None + self._data["drop_size"] = None + self._data["path_integrated_attenuation"] = None + self._data["velocity"] = None + self._data["spectral_reflectivity"] = None del self._data["reflectivity"] del self._data["corrected_reflectivity"] @@ -383,6 +393,7 @@ def open(self, filename_or_obj): self._data["drop_number_density"], axis=0 ) self._data["drop_size"] = np.stack(self._data["drop_size"], axis=0) + self._data["raw_spectra_counts"] = None del self._data["raw_spectra_counts"] self._data["range"] = np.squeeze(self._data["range"]) From 3cf503ef15a1fdbdb403c5f2b4d8f4ce6f106627 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Wed, 28 Aug 2024 09:52:56 -0500 Subject: [PATCH 14/23] Close file earlier? --- xradar/io/backends/metek.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index ad5a4f23..fff9e8e0 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -437,6 +437,8 @@ def open(self, filename_or_obj): temp_drops = None temp_number = None temp_spectra = None + self._fp.close() + self._fp = None def close(self): if self._fp is not None: @@ -444,6 +446,7 @@ def close(self): for k in self._data.keys(): self._data[k] = None + del self._data __del__ = close From 9875e83c15f9370ba42e2a419a05db46d55e164b Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Wed, 28 Aug 2024 10:53:42 -0500 Subject: [PATCH 15/23] ADD: ds.ds.close for DataTrees, otherwise we have > 4.2 million references remaining --- tests/io/test_metek.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/io/test_metek.py b/tests/io/test_metek.py index 68573bfc..4eaec1e2 100644 --- a/tests/io/test_metek.py +++ b/tests/io/test_metek.py @@ -164,7 +164,7 @@ def test_open_average_datatree(metek_ave_gz_file): assert "velocity" in ds["sweep_0"].variables.keys() rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 60.0 np.testing.assert_allclose(rainfall.values[-1], 0.938) - ds = None + ds.ds.close() def test_open_processed(metek_pro_gz_file): @@ -183,7 +183,7 @@ def test_open_processed_datatree(metek_pro_gz_file): rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 360.0 np.testing.assert_allclose(rainfall.values[-1], 0.93) np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr) - ds = None + ds.ds.close() def test_open_raw(metek_raw_gz_file): @@ -196,4 +196,4 @@ def test_open_raw_datatree(metek_raw_gz_file): ds = metek.open_metek_datatree(metek_raw_gz_file) assert "raw_spectra_counts" in ds["sweep_0"].variables.keys() np.testing.assert_allclose(ds["sweep_0"]["raw_spectra_counts"].values[0], test_raw) - ds = None + ds.ds.close() From a17a3d8af6075786f6545f09dea8df97b38472e4 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Wed, 28 Aug 2024 11:25:38 -0500 Subject: [PATCH 16/23] ADD: No more _data --- xradar/io/backends/metek.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index fff9e8e0..5f6a6e50 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -437,17 +437,11 @@ def open(self, filename_or_obj): temp_drops = None temp_number = None temp_spectra = None - self._fp.close() - self._fp = None def close(self): if self._fp is not None: self._fp.close() - - for k in self._data.keys(): - self._data[k] = None - del self._data - + __del__ = close @property From a68e3f947512bd7f8fd13f5688de8cf71237a9e6 Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Wed, 28 Aug 2024 11:28:19 -0500 Subject: [PATCH 17/23] TST: Remove -n auto --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa5c782c..71df37e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: python -c "import xradar; print(xradar.version.version)" - name: Test with pytest run: | - pytest -n auto --dist loadfile --verbose --durations=15 --cov-report xml:coverage_unit.xml --cov=xradar --pyargs tests + pytest --dist loadfile --verbose --durations=15 --cov-report xml:coverage_unit.xml --cov=xradar --pyargs tests - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: From 36fd895a855f95b64511c3ec9f626e352519610a Mon Sep 17 00:00:00 2001 From: Bobby Jackson Date: Wed, 28 Aug 2024 11:29:36 -0500 Subject: [PATCH 18/23] STY: Black --- xradar/io/backends/metek.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index 5f6a6e50..c2d17e54 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -441,7 +441,7 @@ def open(self, filename_or_obj): def close(self): if self._fp is not None: self._fp.close() - + __del__ = close @property From 624c32b53ef3919eb67ac325a812e0b2479ec53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20M=C3=BChlbauer?= Date: Wed, 16 Oct 2024 11:45:16 +0200 Subject: [PATCH 19/23] unlock parallel processing in CI again --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70d695d8..29b2a604 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: python -c "import xradar; print(xradar.version.version)" - name: Test with pytest run: | - pytest --dist loadfile --verbose --durations=15 --cov-report xml:coverage_unit.xml --cov=xradar --pyargs tests + pytest -n auto --dist loadfile --verbose --durations=15 --cov-report xml:coverage_unit.xml --cov=xradar --pyargs tests - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: From bef9c04fd1029aefd49814880eb10205a857b476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20M=C3=BChlbauer?= Date: Wed, 16 Oct 2024 12:22:40 +0200 Subject: [PATCH 20/23] reorganize metek tests --- tests/io/test_io.py | 195 +++++++++++++++++++++++++++++++++++++++++ tests/io/test_metek.py | 28 +++++- 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index 400b69d2..08e74dc7 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -20,6 +20,7 @@ open_datamet_datatree, open_gamic_datatree, open_iris_datatree, + open_metek_datatree, open_nexradlevel2_datatree, open_odim_datatree, open_rainbow_datatree, @@ -1094,3 +1095,197 @@ def test_open_nexradlevel2_datatree(nexradlevel2_files): } assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] assert ds.sweep_number.values == int(grp[7:]) + + +test_arr_ave = np.array( + [ + 25.4, + 24.87, + 24.63, + 25.12, + 25.39, + 26.09, + 27.21, + 28.34, + 29.41, + 31.21, + 32.29, + 28.85, + 21.96, + 19.27, + 20.19, + 21.32, + 21.49, + 20.58, + 19.43, + 18.07, + 16.79, + 15.9, + 14.59, + 14.35, + 13.41, + 11.71, + 10.63, + 10.48, + 7.84, + 4.25, + 4.23, + ] +) + + +test_arr = np.array( + [ + 24.46, + 25.31, + 26.33, + 26.31, + 26.85, + 27.93, + 29.12, + 30.17, + 30.99, + 32.58, + 33.13, + 28.84, + 22.16, + 19.81, + 21.26, + 21.33, + 20.33, + 18.93, + 17.92, + 18.04, + 16.86, + 14.46, + 13.17, + 13.13, + 11.75, + 10.53, + 9.3, + 5.92, + -4.77, + np.nan, + 6.74, + ] +) + + +test_raw = np.array( + [ + 1.090e03, + 6.330e02, + 1.250e02, + 1.000e01, + 2.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 3.000e00, + 3.000e00, + 3.000e00, + 4.000e00, + 6.000e00, + 8.000e00, + 1.100e01, + 1.600e01, + 2.700e01, + 6.200e01, + 1.370e02, + 2.130e02, + 2.560e02, + 3.550e02, + 5.880e02, + 1.087e03, + 1.554e03, + 1.767e03, + 1.910e03, + 1.977e03, + 2.002e03, + 2.039e03, + 1.926e03, + 1.837e03, + 1.893e03, + 1.837e03, + 1.926e03, + 2.039e03, + 2.002e03, + 1.977e03, + 1.910e03, + 1.767e03, + 1.554e03, + 1.087e03, + 5.880e02, + 3.550e02, + 2.560e02, + 2.130e02, + 1.370e02, + 6.200e01, + 2.700e01, + 1.600e01, + 1.100e01, + 8.000e00, + 6.000e00, + 4.000e00, + 3.000e00, + 3.000e00, + 3.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 2.000e00, + 1.000e01, + 1.250e02, + 6.330e02, + ] +) + + +def test_open_metek_average_dataset(metek_ave_gz_file): + with xr.open_dataset(metek_ave_gz_file, engine="metek") as ds: + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 60.0 + np.testing.assert_allclose(rainfall.values[-1], 0.938) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr_ave) + + +def test_open_metek_average_datatree(metek_ave_gz_file): + ds = open_metek_datatree(metek_ave_gz_file) + assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() + assert "velocity" in ds["sweep_0"].variables.keys() + rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 60.0 + np.testing.assert_allclose(rainfall.values[-1], 0.938) + ds.ds.close() + + +def test_open_metek_processed_dataset(metek_pro_gz_file): + with xr.open_dataset(metek_pro_gz_file, engine="metek") as ds: + assert "corrected_reflectivity" in ds.variables.keys() + assert "velocity" in ds.variables.keys() + rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 360.0 + np.testing.assert_allclose(rainfall.values[-1], 0.93) + np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr) + + +def test_open_metek_processed_datatree(metek_pro_gz_file): + ds = open_metek_datatree(metek_pro_gz_file) + assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() + assert "velocity" in ds["sweep_0"].variables.keys() + rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 360.0 + np.testing.assert_allclose(rainfall.values[-1], 0.93) + np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr) + ds.ds.close() + + +def test_open_metek_raw_dataset(metek_raw_gz_file): + with xr.open_dataset(metek_raw_gz_file, engine="metek") as ds: + assert "raw_spectra_counts" in ds.variables.keys() + np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) + + +def test_open_metek_raw_datatree(metek_raw_gz_file): + ds = open_metek_datatree(metek_raw_gz_file) + assert "raw_spectra_counts" in ds["sweep_0"].variables.keys() + np.testing.assert_allclose(ds["sweep_0"]["raw_spectra_counts"].values[0], test_raw) + ds.ds.close() diff --git a/tests/io/test_metek.py b/tests/io/test_metek.py index 4eaec1e2..62c44d4d 100644 --- a/tests/io/test_metek.py +++ b/tests/io/test_metek.py @@ -150,6 +150,30 @@ def test_open_average(metek_ave_gz_file): + with metek.MRR2File(metek_ave_gz_file) as file: + assert "corrected_reflectivity" in file._data + assert "velocity" in file._data + rainfall = np.cumsum(file._data["rainfall_rate"][:, 0]) / 60.0 + np.testing.assert_allclose(rainfall[-1], 0.938) + np.testing.assert_allclose(file._data["reflectivity"][0], test_arr_ave) + + +def test_open_processed(metek_pro_gz_file): + with metek.MRR2File(metek_pro_gz_file) as file: + assert "corrected_reflectivity" in file._data + assert "velocity" in file._data + rainfall = np.cumsum(file._data["rainfall_rate"][:, 0]) / 360.0 + np.testing.assert_allclose(rainfall[-1], 0.93) + np.testing.assert_allclose(file._data["reflectivity"][0], test_arr) + + +def test_open_raw(metek_raw_gz_file): + with metek.MRR2File(metek_raw_gz_file) as file: + assert "raw_spectra_counts" in file._data + np.testing.assert_allclose(file._data["raw_spectra_counts"][0], test_raw) + + +def test_open_average_dataset(metek_ave_gz_file): with xr.open_dataset(metek_ave_gz_file, engine="metek") as ds: assert "corrected_reflectivity" in ds.variables.keys() assert "velocity" in ds.variables.keys() @@ -167,7 +191,7 @@ def test_open_average_datatree(metek_ave_gz_file): ds.ds.close() -def test_open_processed(metek_pro_gz_file): +def test_open_processed_dataset(metek_pro_gz_file): with xr.open_dataset(metek_pro_gz_file, engine="metek") as ds: assert "corrected_reflectivity" in ds.variables.keys() assert "velocity" in ds.variables.keys() @@ -186,7 +210,7 @@ def test_open_processed_datatree(metek_pro_gz_file): ds.ds.close() -def test_open_raw(metek_raw_gz_file): +def test_open_raw_dataset(metek_raw_gz_file): with xr.open_dataset(metek_raw_gz_file, engine="metek") as ds: assert "raw_spectra_counts" in ds.variables.keys() np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) From fe8ec77b525e5f6cbba04f901c9b516f8f5b3789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20M=C3=BChlbauer?= Date: Wed, 16 Oct 2024 12:36:08 +0200 Subject: [PATCH 21/23] tmp_path_factory in tests --- tests/conftest.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0b8237bc..dc737b80 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # Copyright (c) 2022-2023, openradar developers. # Distributed under the MIT License. See LICENSE for more info. +import os.path + import pytest from open_radar_data import DATASETS @@ -76,9 +78,11 @@ def nexradlevel2_file(): @pytest.fixture(scope="session") -def nexradlevel2_gzfile(): +def nexradlevel2_gzfile(tmp_path_factory): fnamei = DATASETS.fetch("KLBB20160601_150025_V06.gz") - fnameo = f"{fnamei[:-3]}_gz" + fnameo = os.path.join( + tmp_path_factory.mktemp("data"), f"{os.path.basename(fnamei)[:-3]}_gz" + ) import gzip import shutil @@ -99,9 +103,11 @@ def nexradlevel2_files(request): @pytest.fixture(scope="session") -def metek_ave_gz_file(): +def metek_ave_gz_file(tmp_path_factory): fnamei = DATASETS.fetch("0308.ave.gz") - fnameo = f"{fnamei[:-3]}" + fnameo = os.path.join( + tmp_path_factory.mktemp("data"), f"{os.path.basename(fnamei)[:-3]}" + ) import gzip import shutil @@ -112,9 +118,11 @@ def metek_ave_gz_file(): @pytest.fixture(scope="session") -def metek_pro_gz_file(): +def metek_pro_gz_file(tmp_path_factory): fnamei = DATASETS.fetch("0308.pro.gz") - fnameo = f"{fnamei[:-3]}" + fnameo = os.path.join( + tmp_path_factory.mktemp("data"), f"{os.path.basename(fnamei)[:-3]}" + ) import gzip import shutil @@ -125,9 +133,11 @@ def metek_pro_gz_file(): @pytest.fixture(scope="session") -def metek_raw_gz_file(): +def metek_raw_gz_file(tmp_path_factory): fnamei = DATASETS.fetch("0308.raw.gz") - fnameo = f"{fnamei[:-3]}" + fnameo = os.path.join( + tmp_path_factory.mktemp("data"), f"{os.path.basename(fnamei)[:-3]}" + ) import gzip import shutil From 0e933ed4e92e68efa192413c7874391f3ebfabd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20M=C3=BChlbauer?= Date: Wed, 16 Oct 2024 12:46:30 +0200 Subject: [PATCH 22/23] keep metek tests in test_metek.py --- tests/io/test_io.py | 195 -------------------------------------------- 1 file changed, 195 deletions(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index 08e74dc7..400b69d2 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -20,7 +20,6 @@ open_datamet_datatree, open_gamic_datatree, open_iris_datatree, - open_metek_datatree, open_nexradlevel2_datatree, open_odim_datatree, open_rainbow_datatree, @@ -1095,197 +1094,3 @@ def test_open_nexradlevel2_datatree(nexradlevel2_files): } assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] assert ds.sweep_number.values == int(grp[7:]) - - -test_arr_ave = np.array( - [ - 25.4, - 24.87, - 24.63, - 25.12, - 25.39, - 26.09, - 27.21, - 28.34, - 29.41, - 31.21, - 32.29, - 28.85, - 21.96, - 19.27, - 20.19, - 21.32, - 21.49, - 20.58, - 19.43, - 18.07, - 16.79, - 15.9, - 14.59, - 14.35, - 13.41, - 11.71, - 10.63, - 10.48, - 7.84, - 4.25, - 4.23, - ] -) - - -test_arr = np.array( - [ - 24.46, - 25.31, - 26.33, - 26.31, - 26.85, - 27.93, - 29.12, - 30.17, - 30.99, - 32.58, - 33.13, - 28.84, - 22.16, - 19.81, - 21.26, - 21.33, - 20.33, - 18.93, - 17.92, - 18.04, - 16.86, - 14.46, - 13.17, - 13.13, - 11.75, - 10.53, - 9.3, - 5.92, - -4.77, - np.nan, - 6.74, - ] -) - - -test_raw = np.array( - [ - 1.090e03, - 6.330e02, - 1.250e02, - 1.000e01, - 2.000e00, - 2.000e00, - 2.000e00, - 2.000e00, - 3.000e00, - 3.000e00, - 3.000e00, - 4.000e00, - 6.000e00, - 8.000e00, - 1.100e01, - 1.600e01, - 2.700e01, - 6.200e01, - 1.370e02, - 2.130e02, - 2.560e02, - 3.550e02, - 5.880e02, - 1.087e03, - 1.554e03, - 1.767e03, - 1.910e03, - 1.977e03, - 2.002e03, - 2.039e03, - 1.926e03, - 1.837e03, - 1.893e03, - 1.837e03, - 1.926e03, - 2.039e03, - 2.002e03, - 1.977e03, - 1.910e03, - 1.767e03, - 1.554e03, - 1.087e03, - 5.880e02, - 3.550e02, - 2.560e02, - 2.130e02, - 1.370e02, - 6.200e01, - 2.700e01, - 1.600e01, - 1.100e01, - 8.000e00, - 6.000e00, - 4.000e00, - 3.000e00, - 3.000e00, - 3.000e00, - 2.000e00, - 2.000e00, - 2.000e00, - 2.000e00, - 1.000e01, - 1.250e02, - 6.330e02, - ] -) - - -def test_open_metek_average_dataset(metek_ave_gz_file): - with xr.open_dataset(metek_ave_gz_file, engine="metek") as ds: - assert "corrected_reflectivity" in ds.variables.keys() - assert "velocity" in ds.variables.keys() - rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 60.0 - np.testing.assert_allclose(rainfall.values[-1], 0.938) - np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr_ave) - - -def test_open_metek_average_datatree(metek_ave_gz_file): - ds = open_metek_datatree(metek_ave_gz_file) - assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() - assert "velocity" in ds["sweep_0"].variables.keys() - rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 60.0 - np.testing.assert_allclose(rainfall.values[-1], 0.938) - ds.ds.close() - - -def test_open_metek_processed_dataset(metek_pro_gz_file): - with xr.open_dataset(metek_pro_gz_file, engine="metek") as ds: - assert "corrected_reflectivity" in ds.variables.keys() - assert "velocity" in ds.variables.keys() - rainfall = ds["rainfall_rate"].isel(range=0).cumsum() / 360.0 - np.testing.assert_allclose(rainfall.values[-1], 0.93) - np.testing.assert_allclose(ds["reflectivity"].values[0], test_arr) - - -def test_open_metek_processed_datatree(metek_pro_gz_file): - ds = open_metek_datatree(metek_pro_gz_file) - assert "corrected_reflectivity" in ds["sweep_0"].variables.keys() - assert "velocity" in ds["sweep_0"].variables.keys() - rainfall = ds["sweep_0"]["rainfall_rate"].isel(range=0).cumsum() / 360.0 - np.testing.assert_allclose(rainfall.values[-1], 0.93) - np.testing.assert_allclose(ds["sweep_0"]["reflectivity"].values[0], test_arr) - ds.ds.close() - - -def test_open_metek_raw_dataset(metek_raw_gz_file): - with xr.open_dataset(metek_raw_gz_file, engine="metek") as ds: - assert "raw_spectra_counts" in ds.variables.keys() - np.testing.assert_allclose(ds["raw_spectra_counts"].values[0], test_raw) - - -def test_open_metek_raw_datatree(metek_raw_gz_file): - ds = open_metek_datatree(metek_raw_gz_file) - assert "raw_spectra_counts" in ds["sweep_0"].variables.keys() - np.testing.assert_allclose(ds["sweep_0"]["raw_spectra_counts"].values[0], test_raw) - ds.ds.close() From d4feaee3b5ccae4b28bc33388636bae8435cffe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20M=C3=BChlbauer?= Date: Wed, 16 Oct 2024 13:23:52 +0200 Subject: [PATCH 23/23] link missing notebooks to toc --- docs/usage.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 540ad1ff..6d7803f3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -32,6 +32,8 @@ notebooks/GAMIC notebooks/Furuno notebooks/Rainbow notebooks/Iris +notebooks/HaloPhotonics +notebooks/MRR notebooks/NexradLevel2 notebooks/Read-plot-Sigmet-data-from-AWS notebooks/plot-ppi