From 509b54845ca11ec50264e557b06747d5d6d3e761 Mon Sep 17 00:00:00 2001 From: jmcvey3 <53623232+jmcvey3@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:50:58 -0800 Subject: [PATCH 1/6] Functional --- mhkit/dolfyn/io/base.py | 329 +++++++++++++-------------------- mhkit/dolfyn/io/nortek2.py | 124 +++++++++++-- mhkit/dolfyn/io/nortek2_lib.py | 83 +++++---- 3 files changed, 285 insertions(+), 251 deletions(-) diff --git a/mhkit/dolfyn/io/base.py b/mhkit/dolfyn/io/base.py index a4414cbe7..ae392192d 100644 --- a/mhkit/dolfyn/io/base.py +++ b/mhkit/dolfyn/io/base.py @@ -117,234 +117,171 @@ def _create_dataset(data): readers. Direction 'dir' coordinates are set in `set_coords` """ - ds = xr.Dataset() - tag = ["_avg", "_b5", "_echo", "_bt", "_gps", "_altraw", "_sl"] - - FoR = {} - try: - beams = data["attrs"]["n_beams"] - except: - beams = data["attrs"]["n_beams_avg"] + tag = ['_avg', '_b5', '_echo', '_bt', '_gps', '_altraw', '_sl'] + + ds_dict = {} + for key in data['coords']: + ds_dict[key] = {"dims": (key), "data": data['coords'][key]} + + # Set various coordinate frames + if 'n_beams_avg' in data['attrs']: + beams = data['attrs']['n_beams_avg'] + else: + beams = data['attrs']['n_beams'] n_beams = max(min(beams, 4), 3) beams = np.arange(1, n_beams + 1, dtype=np.int32) - FoR["beam"] = xr.DataArray( - beams, - dims=["beam"], - name="beam", - attrs={"units": "1", "long_name": "Beam Reference Frame"}, - ) - FoR["dir"] = xr.DataArray( - beams, - dims=["dir"], - name="dir", - attrs={"units": "1", "long_name": "Reference Frame"}, - ) - for key in data["data_vars"]: + ds_dict['beam'] = {"dims": ('beam'), "data": beams} + ds_dict['dir'] = {"dims": ('dir'), "data": beams} + ds_dict['earth'] = {"dims": ('earth'), "data": ['E', 'N', 'U']} + ds_dict['inst'] = {"dims": ('inst'), "data": ['X', 'Y', 'Z']} + + data['units'].update({'beam': '1', + 'dir': '1', + 'earth': '1', + 'inst': '1'}) + data['long_name'].update({'beam': 'Beam Reference Frame', + 'dir': 'Reference Frame', + 'earth': 'Earth Reference Frame', + 'inst': 'Instrument Reference Frame'}) + + # Iterate through data variables and add them to new dictionary + for key in data['data_vars']: # orientation matrices - if "mat" in key: - if "inst" in key: # beam2inst & inst2head orientation matrices - ds[key] = xr.DataArray( - data["data_vars"][key], - coords={"x1": beams, "x2": beams}, - dims=["x1", "x2"], - attrs={"units": "1", "long_name": "Rotation Matrix"}, - ) - elif "orientmat" in key: # earth2inst orientation matrix + if 'mat' in key: + if 'inst' in key: # beam2inst & inst2head orientation matrices + if 'x1' not in ds_dict: + ds_dict['x1'] = {"dims": ('x1'), "data": beams} + ds_dict['x2'] = {"dims": ('x2'), "data": beams} + + ds_dict[key] = {"dims": ('x1', 'x2'), "data": data['data_vars'][key]} + data['units'].update({key: '1'}) + data['long_name'].update({key: 'Rotation Matrix'}) + + elif 'orientmat' in key: # earth2inst orientation matrix if any(val in key for val in tag): - tg = "_" + key.rsplit("_")[-1] + tg = '_' + key.rsplit('_')[-1] else: - tg = "" - earth = xr.DataArray( - ["E", "N", "U"], - dims=["earth"], - name="earth", - attrs={"units": "1", "long_name": "Earth Reference Frame"}, - ) - inst = xr.DataArray( - ["X", "Y", "Z"], - dims=["inst"], - name="inst", - attrs={"units": "1", "long_name": "Instrument Reference Frame"}, - ) - time = data["coords"]["time" + tg] - ds[key] = xr.DataArray( - data["data_vars"][key], - coords={"earth": earth, "inst": inst, "time" + tg: time}, - dims=["earth", "inst", "time" + tg], - attrs={ - "units": data["units"]["orientmat"], - "long_name": data["long_name"]["orientmat"], - }, - ) + tg = '' + + ds_dict[key] = {"dims": ('earth', 'inst', 'time' + tg), "data": data['data_vars'][key]} + data['units'].update({key: data['units']['orientmat']}) + data['long_name'].update({key: data['long_name']['orientmat']}) # quaternion units never change - elif "quaternions" in key: + elif 'quaternions' in key: if any(val in key for val in tag): - tg = "_" + key.rsplit("_")[-1] + tg = '_' + key.rsplit('_')[-1] else: - tg = "" - q = xr.DataArray( - ["w", "x", "y", "z"], - dims=["q"], - name="q", - attrs={"units": "1", "long_name": "Quaternion Vector Components"}, - ) - time = data["coords"]["time" + tg] - ds[key] = xr.DataArray( - data["data_vars"][key], - coords={"q": q, "time" + tg: time}, - dims=["q", "time" + tg], - attrs={ - "units": data["units"]["quaternions"], - "long_name": data["long_name"]["quaternions"], - }, - ) + tg = '' + + if 'q' not in ds_dict: + ds_dict['q'] = {"dims": ("q"), "data": ['w', 'x', 'y', 'z']} + data['units'].update({'q': '1'}) + data['long_name'].update({'q': 'Quaternion Vector Components'}) + + ds_dict[key] = {"dims": ("q", "time" + tg), "data": data['data_vars'][key]} + data['units'].update({key: data['units']['quaternions']}) + data['long_name'].update({key: data['long_name']['quaternions']}) + else: - # Assign each variable to a dataArray - ds[key] = xr.DataArray(data["data_vars"][key]) - # Assign metadata to each dataArray - for md in ["units", "long_name", "standard_name"]: - if key in data[md]: - ds[key].attrs[md] = data[md][key] - try: # make sure ones with tags get units - tg = "_" + key.rsplit("_")[-1] - if any(val in key for val in tag): - ds[key].attrs[md] = data[md][key[: -len(tg)]] - except: - pass - - # Fill in dimensions and coordinates for each dataArray - shp = data["data_vars"][key].shape - l = len(shp) - if l == 1: # 1D variables + shp = data['data_vars'][key].shape + if len(shp) == 1: # 1D variables if any(val in key for val in tag): - tg = "_" + key.rsplit("_")[-1] + tg = '_' + key.rsplit('_')[-1] else: - tg = "" - ds[key] = ds[key].rename({"dim_0": "time" + tg}) - ds[key] = ds[key].assign_coords( - {"time" + tg: data["coords"]["time" + tg]} - ) - - elif l == 2: # 2D variables - if key == "echo": - ds[key] = ds[key].rename( - {"dim_0": "range_echo", "dim_1": "time_echo"} - ) - ds[key] = ds[key].assign_coords( - { - "range_echo": data["coords"]["range_echo"], - "time_echo": data["coords"]["time_echo"], - } - ) - elif key == "samp_altraw": # raw altimeter samples - ds[key] = ds[key].rename( - {"dim_0": "n_altraw", "dim_1": "time_altraw"} - ) - ds[key] = ds[key].assign_coords( - {"time_altraw": data["coords"]["time_altraw"]} - ) + tg = '' + ds_dict[key] = {"dims": ("time" + tg), "data": data['data_vars'][key]} + + elif len(shp) == 2: # 2D variables + if key == 'echo': + ds_dict[key] = {"dims": ("range_echo", "time_echo"), "data": data['data_vars'][key]} + elif key == 'samp_altraw': + ds_dict[key] = {"dims": ("n_altraw", "time_altraw"), "data": data['data_vars'][key]} # ADV/ADCP instrument vector data, bottom tracking elif shp[0] == n_beams and not any(val in key for val in tag[:3]): - if "bt" in key and "time_bt" in data["coords"]: - tg = "_bt" + if 'bt' in key and 'time_bt' in data['coords']: + tg = '_bt' else: - tg = "" - if any( - key.rsplit("_")[0] in s - for s in ["amp", "corr", "dist", "prcnt_gd"] - ): - dim0 = "beam" + tg = '' + if any(key.rsplit('_')[0] in s for s in ['amp', 'corr', 'dist', 'prcnt_gd']): + dim0 = 'beam' else: - dim0 = "dir" - ds[key] = ds[key].rename({"dim_0": dim0, "dim_1": "time" + tg}) - ds[key] = ds[key].assign_coords( - {dim0: FoR[dim0], "time" + tg: data["coords"]["time" + tg]} - ) + dim0 = 'dir' + ds_dict[key] = {"dims": (dim0, "time" + tg), "data": data['data_vars'][key]} + # ADCP IMU data elif shp[0] == 3: if not any(val in key for val in tag): - tg = "" + tg = '' else: tg = [val for val in tag if val in key] tg = tg[0] - dirIMU = xr.DataArray( - [1, 2, 3], - dims=["dirIMU"], - name="dirIMU", - attrs={"units": "1", "long_name": "Reference Frame"}, - ) - ds[key] = ds[key].rename({"dim_0": "dirIMU", "dim_1": "time" + tg}) - ds[key] = ds[key].assign_coords( - {"dirIMU": dirIMU, "time" + tg: data["coords"]["time" + tg]} - ) - - ds[key].attrs["coverage_content_type"] = "physicalMeasurement" - - elif l == 3: # 3D variables - if "vel" in key: - dim0 = "dir" + + if 'dirIMU' not in ds_dict: + ds_dict['dirIMU'] = {"dims": ("dirIMU"), "data": [1, 2, 3]} + data['units'].update({'dirIMU': '1'}) + data['long_name'].update({'dirIMU': 'Reference Frame'}) + + ds_dict[key] = {"dims": ("dirIMU", "time" + tg), "data": data['data_vars'][key]} + + elif 'b5' in tg: + ds_dict[key] = {"dims": ("range_b5", "time_b5"), "data": data['data_vars'][key]} + + elif len(shp) == 3: # 3D variables + if 'vel' in key: + dim0 = 'dir' else: # amp, corr, prcnt_gd, status - dim0 = "beam" + dim0 = 'beam' - if not any(val in key for val in tag) or ("_avg" in key): - if "_avg" in key: - tg = "_avg" + if not any(val in key for val in tag) or ('_avg' in key): + if '_avg' in key: + tg = '_avg' else: - tg = "" - ds[key] = ds[key].rename( - {"dim_0": dim0, "dim_1": "range" + tg, "dim_2": "time" + tg} - ) - ds[key] = ds[key].assign_coords( - { - dim0: FoR[dim0], - "range" + tg: data["coords"]["range" + tg], - "time" + tg: data["coords"]["time" + tg], - } - ) - elif "b5" in key: - # xarray can't handle coords of length 1 - ds[key] = ds[key][0] - ds[key] = ds[key].rename({"dim_1": "range_b5", "dim_2": "time_b5"}) - ds[key] = ds[key].assign_coords( - { - "range_b5": data["coords"]["range_b5"], - "time_b5": data["coords"]["time_b5"], - } - ) - elif "sl" in key: - ds[key] = ds[key].rename( - {"dim_0": dim0, "dim_1": "range_sl", "dim_2": "time"} - ) - ds[key] = ds[key].assign_coords( - { - "range_sl": data["coords"]["range_sl"], - "time": data["coords"]["time"], - } - ) + tg = '' + ds_dict[key] = {"dims": (dim0, "range" + tg, "time" + tg), "data": data['data_vars'][key]} + + elif 'b5' in key: + # "vel_b5" sometimes stored as (1, range_b5, time_b5) + ds_dict[key] = {"dims": ("range_b5", "time_b5"), "data": data['data_vars'][key][0]} + elif 'sl' in key: + ds_dict[key] = {"dims": (dim0, "range_sl", "time"), "data": data['data_vars'][key]} else: - ds = ds.drop_vars(key) - warnings.warn(f"Variable not included in dataset: {key}") - - ds[key].attrs["coverage_content_type"] = "physicalMeasurement" + warnings.warn(f'Variable not included in dataset: {key}') + + # Create dataset + ds = xr.Dataset.from_dict(ds_dict) + + # Assign data array attributes + for key in ds.variables: + for md in ['units', 'long_name', 'standard_name']: + if key in data[md]: + ds[key].attrs[md] = data[md][key] + if len(ds[key].shape) > 1: + ds[key].attrs['coverage_content_type'] = 'physicalMeasurement' + try: # make sure ones with tags get units + tg = '_' + key.rsplit('_')[-1] + if any(val in key for val in tag): + ds[key].attrs[md] = data[md][key[:-len(tg)]] + except: + pass - # coordinate attributes + # Assign coordinate attributes for ky in ds.dims: - ds[ky].attrs["coverage_content_type"] = "coordinate" - r_list = [r for r in ds.coords if "range" in r] + ds[ky].attrs['coverage_content_type'] = 'coordinate' + r_list = [r for r in ds.coords if 'range' in r] for ky in r_list: - ds[ky].attrs["units"] = "m" - ds[ky].attrs["long_name"] = "Profile Range" - ds[ky].attrs["description"] = "Distance to the center of each depth bin" - time_list = [t for t in ds.coords if "time" in t] + ds[ky].attrs['units'] = 'm' + ds[ky].attrs['long_name'] = 'Profile Range' + ds[ky].attrs['description'] = 'Distance to the center of each depth bin' + time_list = [t for t in ds.coords if 'time' in t] for ky in time_list: - ds[ky].attrs["units"] = "seconds since 1970-01-01 00:00:00" - ds[ky].attrs["long_name"] = "Time" - ds[ky].attrs["standard_name"] = "time" + ds[ky].attrs['units'] = 'seconds since 1970-01-01 00:00:00' + ds[ky].attrs['long_name'] = 'Time' + ds[ky].attrs['standard_name'] = 'time' - # dataset metadata - ds.attrs = data["attrs"] + # Set dataset metadata + ds.attrs = data['attrs'] return ds diff --git a/mhkit/dolfyn/io/nortek2.py b/mhkit/dolfyn/io/nortek2.py index f9f0aa5b1..c26956d27 100644 --- a/mhkit/dolfyn/io/nortek2.py +++ b/mhkit/dolfyn/io/nortek2.py @@ -15,7 +15,13 @@ def read_signature( - filename, userdata=True, nens=None, rebuild_index=False, debug=False, **kwargs + filename, + userdata=True, + nens=None, + rebuild_index=False, + debug=False, + dual_profile=False, + **kwargs ): """ Read a Nortek Signature (.ad2cp) datafile @@ -34,6 +40,8 @@ def read_signature( Default = False debug : bool Logs debugger ouput if true. Default = False + dual_profile : bool (default: True) + Set to true if instrument is running multiple profiles Returns ------- @@ -68,9 +76,13 @@ def read_signature( userdata = _find_userdata(filename, userdata) - rdr = _Ad2cpReader(filename, rebuild_index=rebuild_index, debug=debug) + rdr = _Ad2cpReader( + filename, rebuild_index=rebuild_index, debug=debug, dual_profile=dual_profile + ) d = rdr.readfile(nens[0], nens[1]) rdr.sci_data(d) + if rdr._dp: + _clean_dp_skips(d) out = _reorg(d) _reduce(out) @@ -120,19 +132,31 @@ def read_signature( logging.root.removeHandler(handler) handler.close() - return ds + # Return two datasets if dual profile + if rdr._dp: + return split_dp_datasets(ds) + else: + return ds class _Ad2cpReader: def __init__( - self, fname, endian=None, bufsize=None, rebuild_index=False, debug=False + self, + fname, + endian=None, + bufsize=None, + rebuild_index=False, + debug=False, + dual_profile=False, ): self.fname = fname self.debug = debug self._check_nortek(endian) self.f.seek(0, 2) # Seek to end self._eof = self.f.tell() - self._index = lib.get_index(fname, reload=rebuild_index, debug=debug) + self._index, self._dp = lib.get_index( + fname, rebuild=rebuild_index, debug=debug, dp=dual_profile + ) self._reopen(bufsize) self.filehead_config = self._read_filehead_config_string() self._ens_pos = self._index["pos"][ @@ -409,19 +433,6 @@ def sci_data(self, dat): "float32" ) - def __exit__( - self, - type, - value, - trace, - ): - self.f.close() - - def __enter__( - self, - ): - return self - def _reorg(dat): """ @@ -647,6 +658,19 @@ def _reorg(dat): return outdat +def _clean_dp_skips(data): + """Removes zeros from interwoven measurements taken in a dual profile + configuration.""" + for id in data: + if id == "filehead_config": + continue + # Check where 'ver' is zero (should be 1 (for bt) or 3 (everything else)) + skips = np.where(data[id]["ver"] != 0) + for var in data[id]: + if var not in ["units", "long_name", "standard_name"]: + data[id][var] = np.squeeze(data[id][var][..., skips]) + + def _reduce(data): """This function takes the output from `reorg`, and further simplifies the data. Mostly this is combining system, environmental, and orientation data @@ -676,13 +700,15 @@ def _reduce(data): dc["range_avg"] = (np.arange(dv["vel_avg"].shape[1]) + 1) * da[ "cell_size_avg" ] + da["blank_dist_avg"] - dv["orientmat"] = dv.pop("orientmat_avg") + if "orientmat" not in dv: + dv["orientmat"] = dv.pop("orientmat_avg") tmat = da["filehead_config"]["XFAVG"] da["fs"] = da["filehead_config"]["PLAN"]["MIAVG"] da["avg_interval_sec"] = da["filehead_config"]["AVG"]["AI"] da["bandwidth"] = da["filehead_config"]["AVG"]["BW"] if "vel_b5" in dv: - dc["range_b5"] = (np.arange(dv["vel_b5"].shape[1]) + 1) * da[ + # vel_b5 is sometimes shape 2 and sometimes shape 3 + dc["range_b5"] = (np.arange(dv["vel_b5"].shape[-2]) + 1) * da[ "cell_size_b5" ] + da["blank_dist_b5"] if "echo_echo" in dv: @@ -715,3 +741,61 @@ def _reduce(data): if "time" in val: time = val dc["time"] = dc[time] + + +def split_dp_datasets(ds): + """Splits a dataset containing dual profiles into individual profiles""" + # Figure out which variables belong to which profile based on time variables + t_dict = {} + for t in ds.coords: + if "altraw" in t: + continue + elif "time" in t: + t_dict[t] = ds[t].size + other_coords = [] + for key, val in t_dict.items(): + if val != t_dict["time"]: + other_coords.append(key) + + # Fetch variables, coordinates, and attrs for second profiling configuration + other_vars = [ + v for v in ds.data_vars if any(x in ds[v].coords for x in other_coords) + ] + other_tags = [s.split("_")[-1] for s in other_coords] + other_coords += [v for v in ds.coords if any(x in v for x in other_tags)] + other_attrs = [s for s in ds.attrs if any(x in s for x in other_tags)] + critical_attrs = [ + "inst_model", + "inst_make", + "inst_type", + "fs", + "orientation", + "orient_status", + "has_imu", + "beam_angle", + ] + + # Create second dataset + ds2 = type(ds)() + for a in other_attrs + critical_attrs: + ds2.attrs[a] = ds.attrs[a] + for v in other_vars: + ds2[v] = ds[v] + # Set rotate_vars + rotate_vars2 = [v for v in ds.attrs["rotate_vars"] if v in other_vars] + ds2.attrs["rotate_vars"] = rotate_vars2 + # Set orientation matricies + ds2["beam2inst_orientmat"] = ds["beam2inst_orientmat"] + ds2 = ds2.rename({"orientmat_" + other_tags[0]: "orientmat"}) + # Set original coordinate system + cy = ds2.attrs["coord_sys_axes_" + other_tags[0]] + ds2.attrs["coord_sys"] = {"XYZ": "inst", "ENU": "earth", "beam": "beam"}[cy] + ds2 = _set_coords(ds2, ref_frame=ds2.coord_sys) + + # Clean up first dataset + [ds.attrs.pop(ky) for ky in other_attrs] + ds = ds.drop_vars(other_vars + other_coords) + for itm in rotate_vars2: + ds.attrs["rotate_vars"].remove(itm) + + return ds, ds2 diff --git a/mhkit/dolfyn/io/nortek2_lib.py b/mhkit/dolfyn/io/nortek2_lib.py index 30f747991..3f9f9305f 100644 --- a/mhkit/dolfyn/io/nortek2_lib.py +++ b/mhkit/dolfyn/io/nortek2_lib.py @@ -151,9 +151,11 @@ def _create_index(infile, outfile, N_ens, debug): fin.seek(seek_2ens[dat[2]], 1) ens[idk] = struct.unpack(" 0: - # Covers all id keys saved in "burst mode" - ens[idk] = last_ens[idk] + 1 + if last_ens[idk] > 0: + if (ens[idk] == 1) or (ens[idk] < last_ens[idk]): + # Covers all id keys saved in "burst mode" + # Covers ID keys not saved in sequential order + ens[idk] = last_ens[idk] + 1 if last_ens[idk] > 0 and last_ens[idk] != ens[idk]: N[idk] += 1 @@ -181,7 +183,8 @@ def _create_index(infile, outfile, N_ens, debug): fin.seek(dat[4] - (36 + seek_2ens[idk]), 1) last_ens[idk] = ens[idk] - if debug and N[idk] < 5: + if debug: + # File Position: Valid ID keys (1A, 10), Hex ID, Length in bytes, Ensemble #, Last Ensemble Found' # hex: [18, 15, 1C, 17] = [vel_b5, vel, echo, bt] logging.info( "%10d: %02X, %d, %02X, %d, %d, %d, %d\n" @@ -203,7 +206,7 @@ def _create_index(infile, outfile, N_ens, debug): print(" Done.") -def _check_index(idx, infile, fix_hw_ens=False): +def _check_index(idx, infile, fix_hw_ens=False, dp=False): uid = np.unique(idx["ID"]) if fix_hw_ens: hwe = idx["hw_ens"] @@ -213,35 +216,45 @@ def _check_index(idx, infile, fix_hw_ens=False): ens = idx["ens"] N_id = len(uid) FLAG = False + + # Are there better ways to detect dual profile? + if (21 in uid) and (22 in uid): + warnings.warn("Dual Profile detected...") + dp = True + # This loop fixes 'skips' inside the file - for id in uid: - # These are the indices for this ID - inds = np.nonzero(idx["ID"] == id)[0] - - # These are bad steps in the indices for this ID - ibad = np.nonzero(np.diff(inds) > N_id)[0] - for ib in ibad: - FLAG = True - # The ping number reported here may not be quite right if - # the ensemble count is wrong. - warnings.warn( - "Skipped ping (ID: {}) in file {} at ensemble {}.".format( - id, infile, idx["ens"][inds[ib + 1] - 1] + if not dp: + for id in uid: + # These are the indices for this ID + inds = np.nonzero(idx["ID"] == id)[0] + + # These are bad steps in the indices for this ID + ibad = np.nonzero(np.diff(inds) > N_id)[0] + # Will need to fix for dual profile ADCPs + for ib in ibad: + FLAG = True + # The ping number reported here may not be quite right if + # the ensemble count is wrong. + warnings.warn( + "Skipped ping (ID: {}) in file {} at ensemble {}.".format( + id, infile, idx["ens"][inds[ib + 1] - 1] + ) ) - ) - hwe[inds[(ib + 1) :]] += 1 - ens[inds[(ib + 1) :]] += 1 + hwe[inds[(ib + 1) :]] += 1 + ens[inds[(ib + 1) :]] += 1 + + # This block fixes skips that originate from before this file. + delta = max(hwe[:N_id]) - hwe[:N_id] + for d, id in zip(delta, idx["ID"][:N_id]): + if d != 0: + FLAG = True + hwe[id == idx["ID"]] += d + ens[id == idx["ID"]] += d - # This block fixes skips that originate from before this file. - delta = max(hwe[:N_id]) - hwe[:N_id] - for d, id in zip(delta, idx["ID"][:N_id]): - if d != 0: - FLAG = True - hwe[id == idx["ID"]] += d - ens[id == idx["ID"]] += d + if np.any(np.diff(ens) > 1) and FLAG: + idx["ens"] = np.unwrap(hwe.astype(np.int64), period=period) - hwe[0] - if np.any(np.diff(ens) > 1) and FLAG: - idx["ens"] = np.unwrap(hwe.astype(np.int64), period=period) - hwe[0] + return dp def _boolarray_firstensemble_ping(index): @@ -254,7 +267,7 @@ def _boolarray_firstensemble_ping(index): return dens -def get_index(infile, reload=False, debug=False): +def get_index(infile, rebuild=False, debug=False, dp=False): """ This function reads ad2cp.index files @@ -274,7 +287,7 @@ def get_index(infile, reload=False, debug=False): """ index_file = infile + ".index" - if not path.isfile(index_file) or reload: + if not path.isfile(index_file) or rebuild or debug: _create_index(infile, index_file, 2**32, debug) f = open(_abspath(index_file), "rb") file_head = f.read(12) @@ -286,8 +299,8 @@ def get_index(infile, reload=False, debug=False): f.seek(0, 0) out = np.fromfile(f, dtype=_index_dtype[index_ver]) f.close() - _check_index(out, infile) - return out + dp = _check_index(out, infile, dp=dp) + return out, dp def crop_ensembles(infile, outfile, range): @@ -309,7 +322,7 @@ def crop_ensembles(infile, outfile, range): 2 element list of start and end ensemble (or time index) """ - idx = get_index(infile) + idx, dp = get_index(infile) with open(_abspath(infile), "rb") as fin: with open(_abspath(outfile), "wb") as fout: fout.write(fin.read(idx["pos"][0])) From 5a152244d66f22ea093fac46a04578a607dbc49f Mon Sep 17 00:00:00 2001 From: jmcvey3 <53623232+jmcvey3@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:13:38 -0800 Subject: [PATCH 2/6] Reinstate some skipped ping logic --- mhkit/dolfyn/io/nortek2_lib.py | 44 +++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/mhkit/dolfyn/io/nortek2_lib.py b/mhkit/dolfyn/io/nortek2_lib.py index 3f9f9305f..cce361403 100644 --- a/mhkit/dolfyn/io/nortek2_lib.py +++ b/mhkit/dolfyn/io/nortek2_lib.py @@ -219,31 +219,37 @@ def _check_index(idx, infile, fix_hw_ens=False, dp=False): # Are there better ways to detect dual profile? if (21 in uid) and (22 in uid): - warnings.warn("Dual Profile detected...") + warnings.warn("Dual Profile detected... Two datasets will be returned.") dp = True # This loop fixes 'skips' inside the file - if not dp: - for id in uid: - # These are the indices for this ID - inds = np.nonzero(idx["ID"] == id)[0] - - # These are bad steps in the indices for this ID - ibad = np.nonzero(np.diff(inds) > N_id)[0] - # Will need to fix for dual profile ADCPs - for ib in ibad: - FLAG = True - # The ping number reported here may not be quite right if - # the ensemble count is wrong. - warnings.warn( - "Skipped ping (ID: {}) in file {} at ensemble {}.".format( - id, infile, idx["ens"][inds[ib + 1] - 1] - ) + for id in uid: + # These are the indices for this ID + inds = np.nonzero(idx["ID"] == id)[0] + # These are bad steps in the indices for this ID + ibad = np.nonzero(np.diff(inds) > N_id)[0] + # Check if spacing is equal for dual profiling ADCPs + if dp: + skip_size = np.diff(ibad) + avg_skip = np.median(skip_size) + # assume last "ibad" element is always good for dp's + mask = np.append(skip_size - avg_skip, 0).astype(bool) + ibad = ibad[mask] + for ib in ibad: + FLAG = True + # The ping number reported here may not be quite right if + # the ensemble count is wrong. + warnings.warn( + "Skipped ping (ID: {}) in file {} at ensemble {}.".format( + id, infile, idx["ens"][inds[ib + 1] - 1] ) - hwe[inds[(ib + 1) :]] += 1 - ens[inds[(ib + 1) :]] += 1 + ) + hwe[inds[(ib + 1) :]] += 1 + ens[inds[(ib + 1) :]] += 1 + if not dp: # This block fixes skips that originate from before this file. + # Check first N id's and correct delta = max(hwe[:N_id]) - hwe[:N_id] for d, id in zip(delta, idx["ID"][:N_id]): if d != 0: From 89f501e815ee5d42bc27ca1fe4ec2621406b2a67 Mon Sep 17 00:00:00 2001 From: jmcvey3 <53623232+jmcvey3@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:00:45 -0800 Subject: [PATCH 3/6] Add ability to read ID 31, clean up altimeter attrs --- mhkit/dolfyn/io/base.py | 12 +++- mhkit/dolfyn/io/nortek2.py | 126 +++++++++++++++++++-------------- mhkit/dolfyn/io/nortek2_lib.py | 4 +- 3 files changed, 85 insertions(+), 57 deletions(-) diff --git a/mhkit/dolfyn/io/base.py b/mhkit/dolfyn/io/base.py index ae392192d..ba37a8afe 100644 --- a/mhkit/dolfyn/io/base.py +++ b/mhkit/dolfyn/io/base.py @@ -113,11 +113,13 @@ def _handle_nan(data): def _create_dataset(data): - """Creates an xarray dataset from dictionary created from binary + """ + Creates an xarray dataset from dictionary created from binary readers. Direction 'dir' coordinates are set in `set_coords` """ - tag = ['_avg', '_b5', '_echo', '_bt', '_gps', '_altraw', '_sl'] + + tag = ['_avg', '_b5', '_echo', '_bt', '_gps', '_altraw', '_altraw_avg', '_sl'] ds_dict = {} for key in data['coords']: @@ -187,7 +189,9 @@ def _create_dataset(data): else: shp = data['data_vars'][key].shape if len(shp) == 1: # 1D variables - if any(val in key for val in tag): + if '_altraw_avg' in key: + tg = '_altraw_avg' + elif any(val in key for val in tag): tg = '_' + key.rsplit('_')[-1] else: tg = '' @@ -198,6 +202,8 @@ def _create_dataset(data): ds_dict[key] = {"dims": ("range_echo", "time_echo"), "data": data['data_vars'][key]} elif key == 'samp_altraw': ds_dict[key] = {"dims": ("n_altraw", "time_altraw"), "data": data['data_vars'][key]} + elif key == 'samp_altraw_avg': + ds_dict[key] = {"dims": ("n_altraw_avg", "time_altraw_avg"), "data": data['data_vars'][key]} # ADV/ADCP instrument vector data, bottom tracking elif shp[0] == n_beams and not any(val in key for val in tag[:3]): diff --git a/mhkit/dolfyn/io/nortek2.py b/mhkit/dolfyn/io/nortek2.py index c26956d27..41fe8b09f 100644 --- a/mhkit/dolfyn/io/nortek2.py +++ b/mhkit/dolfyn/io/nortek2.py @@ -260,19 +260,21 @@ def init_data(self, ens_start, ens_stop): outdat = {} nens = int(ens_stop - ens_start) - # ID 26 usually only recorded in first ensemble - n26 = ( - (self._index["ID"] == 26) - & (self._index["ens"] >= ens_start) - & (self._index["ens"] < ens_stop) - ).sum() - if not n26 and 26 in self._burst_readers: + # ID 26 and 31 recorded infrequently + def n_id(id): + return ((self._index['ID'] == id) & + (self._index['ens'] >= ens_start) & + (self._index['ens'] < ens_stop)).sum() + n_altraw = {26: n_id(26), 31: n_id(31)} + if not n_altraw[26] and 26 in self._burst_readers: self._burst_readers.pop(26) + if not n_altraw[31] and 31 in self._burst_readers: + self._burst_readers.pop(31) for ky in self._burst_readers: - if ky == 26: - n = n26 - ens = np.zeros(n, dtype="uint32") + if (ky == 26) or (ky == 31): + n = n_altraw[ky] + ens = np.zeros(n, dtype='uint32') else: ens = np.arange(ens_start, ens_stop).astype("uint32") n = nens @@ -313,7 +315,7 @@ def readfile(self, ens_start=0, ens_stop=None): outdat["filehead_config"] = self.filehead_config print("Reading file %s ..." % self.fname) c = 0 - c26 = 0 + c_altraw = {26: 0, 31: 0} self.f.seek(self._ens_pos[ens_start], 0) while True: try: @@ -325,9 +327,9 @@ def readfile(self, ens_start=0, ens_stop=None): # "avg data record" (vel_avg + ast_avg), "bottom track data record" (bt), # "interleaved burst data record" (vel_b5), "echosounder record" (echo) self._read_burst(id, outdat[id], c) - elif id in [26]: - # "burst altimeter raw record" (alt_raw) - recorded on nens==0 - rdr = self._burst_readers[26] + elif id in [26, 31]: + # "burst altimeter raw record" (_altraw), "avg altimeter raw record" (_altraw_avg) + rdr = self._burst_readers[id] if not hasattr(rdr, "_nsamp_index"): first_pass = True tmp_idx = rdr._nsamp_index = rdr._names.index("nsamp_alt") @@ -353,8 +355,8 @@ def readfile(self, ens_start=0, ens_stop=None): "<" + "{}H".format(int(rdr.nbyte // 2)) ) # Initialize the array - outdat[26]["samp_alt"] = defs._nans( - [rdr._N[tmp_idx], len(outdat[26]["samp_alt"])], dtype=np.uint16 + outdat[id]["samp_alt"] = defs._nans( + [rdr._N[tmp_idx], len(outdat[id]["samp_alt"])], dtype=np.uint16 ) else: if sz != rdr._N[tmp_idx]: @@ -362,12 +364,12 @@ def readfile(self, ens_start=0, ens_stop=None): "The number of samples in this 'Altimeter Raw' " "burst is different from prior bursts." ) - self._read_burst(id, outdat[id], c26) - outdat[id]["ensemble"][c26] = c - c26 += 1 + self._read_burst(id, outdat[id], c_altraw[id]) + outdat[id]["ensemble"][c_altraw[id]] = c + c_altraw[id] += 1 - elif id in [27, 29, 30, 31, 35, 36]: # unknown how to handle - # "bottom track record", DVL, "altimeter record", "avg altimeter raw record", + elif id in [27, 29, 30, 35, 36]: # unknown how to handle + # "bottom track record", DVL, "altimeter record", # "raw echosounder data record", "raw echosounder transmit data record" if self.debug: logging.debug("Skipped ID: 0x{:02X} ({:02d})\n".format(id, id)) @@ -434,6 +436,34 @@ def sci_data(self, dat): ) +def _altraw_reorg(outdat, tag=''): + """Submethod for `_reorg` particular to raw altimeter pings (ID 26 and 31) + """ + for ky in list(outdat['data_vars']): + if ky.endswith('raw' + tag) and not ky.endswith('_altraw' + tag): + outdat['data_vars'].pop(ky) + outdat['coords']['time_altraw' + tag] = outdat['coords'].pop('timeraw' + tag) + # convert "signed fractional" to float + outdat['data_vars']['samp_altraw' + tag] = outdat['data_vars']['samp_altraw' + tag].astype('float32') / 2**8 + + # Read altimeter status + outdat['data_vars'].pop('status_altraw' + tag) + status_alt = lib._alt_status2data(outdat['data_vars']['status_alt' + tag]) + for ky in status_alt: + outdat['attrs'][ky + tag] = lib._collapse( + status_alt[ky].astype('uint8'), name=ky) + outdat['data_vars'].pop('status_alt' + tag) + + # Power level index + power = {0: 'high', 1: 'med-high', 2: 'med-low', 3: 'low'} + outdat['attrs']['power_level_alt' + tag] = power[outdat['attrs'].pop('power_level_idx_alt' + tag)] + + # Other attrs + for ky in list(outdat['attrs']): + if ky.endswith('raw' + tag): + outdat['attrs'][ky.split('raw')[0] + '_alt' + tag] = outdat['attrs'].pop(ky) + + def _reorg(dat): """ This function grabs the data from the dictionary of data types @@ -463,6 +493,7 @@ def _reorg(dat): (24, "_b5"), (26, "raw"), (28, "_echo"), + (31, 'raw_avg') ]: if id in [24, 26]: collapse_exclude = [0] @@ -565,26 +596,9 @@ def _reorg(dat): # Move 'altimeter raw' data to its own down-sampled structure if 26 in dat: - for ky in list(outdat["data_vars"]): - if ky.endswith("raw") and not ky.endswith("_altraw"): - outdat["data_vars"].pop(ky) - outdat["coords"]["time_altraw"] = outdat["coords"].pop("timeraw") - outdat["data_vars"]["samp_altraw"] = ( - outdat["data_vars"]["samp_altraw"].astype("float32") / 2**8 - ) # convert "signed fractional" to float - - # Read altimeter status - outdat["data_vars"].pop("status_altraw") - status_alt = lib._alt_status2data(outdat["data_vars"]["status_alt"]) - for ky in status_alt: - outdat["attrs"][ky] = lib._collapse(status_alt[ky].astype("uint8"), name=ky) - outdat["data_vars"].pop("status_alt") - - # Power level index - power = {0: "high", 1: "med-high", 2: "med-low", 3: "low"} - outdat["attrs"]["power_level_alt"] = power[ - outdat["attrs"].pop("power_level_idx_alt") - ] + _altraw_reorg(outdat) + if 31 in dat: + _altraw_reorg(outdat, tag='_avg') # Read status data status0_vars = [x for x in outdat["data_vars"] if "status0" in x] @@ -631,7 +645,7 @@ def _reorg(dat): for ky in status0_data: outdat["attrs"][ky] = lib._collapse(status0_data[ky].astype("uint8"), name=ky) - # Remove status0 variables - keep status variables as they useful for finding missing pings + # Remove status0 variables - keep status variables as they are useful for finding missing pings [outdat["data_vars"].pop(var) for var in status0_vars] # Set coordinate system @@ -659,8 +673,11 @@ def _reorg(dat): def _clean_dp_skips(data): - """Removes zeros from interwoven measurements taken in a dual profile - configuration.""" + """ + Removes zeros from interwoven measurements taken in a dual profile + configuration. + """ + for id in data: if id == "filehead_config": continue @@ -672,7 +689,8 @@ def _clean_dp_skips(data): def _reduce(data): - """This function takes the output from `reorg`, and further simplifies the + """ + This function takes the output from `reorg`, and further simplifies the data. Mostly this is combining system, environmental, and orientation data --- from different data structures within the same ensemble --- by averaging. @@ -744,19 +762,23 @@ def _reduce(data): def split_dp_datasets(ds): - """Splits a dataset containing dual profiles into individual profiles""" - # Figure out which variables belong to which profile based on time variables + """ + Splits a dataset containing dual profiles into individual profiles + """ + + # Figure out which variables belong to which profile based on length of time variables t_dict = {} for t in ds.coords: - if "altraw" in t: - continue - elif "time" in t: + if 'time' in t: t_dict[t] = ds[t].size + other_coords = [] for key, val in t_dict.items(): - if val != t_dict["time"]: + if val != t_dict['time']: + if key.endswith('altraw'): + # altraw goes with burst, altraw_avg goes with avg + continue other_coords.append(key) - # Fetch variables, coordinates, and attrs for second profiling configuration other_vars = [ v for v in ds.data_vars if any(x in ds[v].coords for x in other_coords) diff --git a/mhkit/dolfyn/io/nortek2_lib.py b/mhkit/dolfyn/io/nortek2_lib.py index cce361403..a9bb46e35 100644 --- a/mhkit/dolfyn/io/nortek2_lib.py +++ b/mhkit/dolfyn/io/nortek2_lib.py @@ -553,11 +553,11 @@ def _calc_config(index): ids = np.unique(index["ID"]) config = {} for id in ids: - if id not in [21, 22, 23, 24, 26, 28]: + if id not in [21, 22, 23, 24, 26, 28, 31]: continue if id == 23: type = "bt" - elif id == 22: + elif (id == 22) or (id == 31): type = "avg" else: type = "burst" From db52c9f91f6a7e8f70b54376b3483e783600976a Mon Sep 17 00:00:00 2001 From: jmcvey3 <53623232+jmcvey3@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:26:29 -0800 Subject: [PATCH 4/6] Handle variable bottom track recorded specs --- mhkit/dolfyn/io/nortek2_lib.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mhkit/dolfyn/io/nortek2_lib.py b/mhkit/dolfyn/io/nortek2_lib.py index a9bb46e35..65ab33059 100644 --- a/mhkit/dolfyn/io/nortek2_lib.py +++ b/mhkit/dolfyn/io/nortek2_lib.py @@ -231,9 +231,13 @@ def _check_index(idx, infile, fix_hw_ens=False, dp=False): # Check if spacing is equal for dual profiling ADCPs if dp: skip_size = np.diff(ibad) - avg_skip = np.median(skip_size) + n_skip, count = np.unique(skip_size, return_counts=True) + # If multiple skips are of the same size, assume okay + for n, c in zip(n_skip, count): + if c > 1: + skip_size[skip_size == n] = 0 # assume last "ibad" element is always good for dp's - mask = np.append(skip_size - avg_skip, 0).astype(bool) + mask = np.append(skip_size, 0).astype(bool) ibad = ibad[mask] for ib in ibad: FLAG = True @@ -564,11 +568,21 @@ def _calc_config(index): inds = index["ID"] == id _config = index["config"][inds] _beams_cy = index["beams_cy"][inds] + # Check that these variables are consistent if not _isuniform(_config): raise Exception("config are not identical for id: 0x{:X}.".format(id)) if not _isuniform(_beams_cy): - raise Exception("beams_cy are not identical for id: 0x{:X}.".format(id)) + err = True + if id == 23: + # change in "n_cells" doesn't matter + lob = np.unique(_beams_cy) + beams = list(map(_beams_cy_int2dict, lob, 23 * np.ones(lob.size))) + if all([d['cy'] for d in beams]) and all([d['n_beams'] for d in beams]): + err = False + if err: + raise Exception("beams_cy are not identical for id: 0x{:X}.".format(id)) + # Now that we've confirmed they are the same: config[id] = _headconfig_int2dict(_config[0], mode=type) config[id].update(_beams_cy_int2dict(_beams_cy[0], id)) From f1fa3a3f9a3d8efbbb68b1a015383f3b29c1cfd7 Mon Sep 17 00:00:00 2001 From: jmcvey3 <53623232+jmcvey3@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:06:32 -0800 Subject: [PATCH 5/6] Update test data attributes --- examples/data/dolfyn/test_data/BenchFile01.nc | Bin 272340 -> 273346 bytes .../data/dolfyn/test_data/BenchFile01_avg.nc | Bin 89675 -> 89659 bytes .../test_data/BenchFile01_rotate_beam2inst.nc | Bin 272360 -> 273341 bytes .../BenchFile01_rotate_earth2principal.nc | Bin 272370 -> 273346 bytes .../BenchFile01_rotate_inst2earth.nc | Bin 272362 -> 273342 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/data/dolfyn/test_data/BenchFile01.nc b/examples/data/dolfyn/test_data/BenchFile01.nc index 57a73e44d851c7d2b05417bc4ecde79163330433..3b2af8fc48245ed98f2f56b3f7107c48744d654c 100644 GIT binary patch delta 14985 zcmeG?dwfmD)-!ug-bdca$@?uqgb4A9MVkiO2(0B+8|iOLJ_DS3SaQ zQuT@;z3oM{C@r@hSF2j;)-8Q1Ep^+{ersm-oa}Sx_wV=5H~D4HS+my6tXZ?xnzd%{ ze|_Nn_D9~eVNboCNDwQ@{E1NiD>v|n!Tm=mt&V#tF}zV7tmCSCiyuCHMBPoV-4%pc zOF$T6gQ+QvGnadkEvA?YtGpucZ(5{R0#4Dk75BF6oGyA2wBY`8oBNF!+#k6j1tA|8 zprB2%zrbRYB7UH;Gy8PpRBnu_m7of8q*6aWoj7nbjD?PCUwCy4OVAn4)<==`%>&Et zXvy9|ZpT%?v_&XX%kd7bKIsBbEJE#}N_cCHGYWKtU$8cyKqLOu+XV%(Ww~J#237Jq z#V%-wGyH;Cn1FhBIqHhe=nZku@W?$g96iGZZ3hEF^|vd}$Dphc@C#TpK}F|%5R1NX zgI@y>3ho4Tap$W^NMVBvLMR%llx|hswCmon)lrNLb|(p{(BQ?x(HO>EUp_c0xq9;{ zG>)yW8v;opWbS+UX%xt&f(8$$?mi-gXe1jW6`>AL?e&YQLWfxcKy24v+`DuZy1>Q& z?%bg||ANaxG|mZrfkdh!pbq;wc_CU4JHWULglbmP-NooSYv=)ULY14pcm*mL4!>F> zwpFOx`Ph}np9ujZ0Bz`-z+YCO(M<5&Va-q-Dd@T$y~rp))NP>p{FehSBP)|Z8s87J zai`lB5{s^GZc6a=R^o$M=bi|${r}-m$1Ta;@B>PCGGhsff5W<BSpe zF=!1!n;xFnf|j%Xz;6gtZM!5jpv7#BnFvKfRr_`NZlp00u0SYJI~7>!_Gcy}V78;y z*WAu6jd2=_P`tLtJS-kXvc51_I8>j7?0kUUWevc16jX1n@qd_=|FF(#t<#c&;+!J> zbAQk1M(anwSV-q;U1Z3(9y`g{af)op3Ip3|q8$&m4P*Odw=z49KS(Hk8IhgN=C+8LjbOAsWK?!LtwpxYV1110YqKL}MlP{$zh$+S zX~|)=Y?paq`(`Yzh^~AYM__ktVXQfr*$6A#fkDmefEAP07;DznF80<2bo1`vB^qr{ zUGr?PkN!)JGG2#$wQb?%U_S@!I34!a&WD+C0DlIu=hMuTjD@MXu`s(a7TY5A#!$U> z#b({Mnj6R;1EOQOJo$3baiWl0L_wUDA){?cgBfK(fP_Kj`q(AGH6;p?yMyXc+qk?NSr@ zyv#w#4p09<2WeoEQAt1H59lZULHnd9w8KFO2bv=S4o6ld^Jj~MFo8qp#>C8p^bVagq2`)3&&m zT&!D@wdD5BN}{6qYz?JbUDVXn^k)TK6gv)~$fzv=P3<6Be%9;Ajsiy8aijGWOiF@x zKY)nNeI7efp$zD#y-+-H{6QY|F}Kn)xzgPj)FBA!s6L1X4@X$HCCjg-8iP7c#k@C^ z7OJs#PTbYHmvmJj&eCR<1dwT`$KKw)tc0)y;TX+Y6-#R^YP?p{-nEM#Dy|d*{HE7& zRRVVPWwnYY>td9vMjNcw&GGXV`qT~_J$e9|yFifR5R7iJ_yzf7|J+{$!tH2@Ma@xP zoQdc$i)s!%vqaDa>ekmwuTdR{>2G-4qo0Pq?kI(QOGO`d9$~THs{2>3I7(sPUmU5X zP4XWpxQNBH)S?dN%Qd0tObxYJJ=j*W}G6xUyhV#n> z9XQDsis}R@GDXrdZxX0|mhP(cN=d;^T1ZAb-S7(Tt}V#u?UDTT#)*QJLb}k4^d2G= z#|?e^sTg9xRZ;KIYst;2a?x%(Hos!9t<`Gnu9y`4Wr)rAoDAHk8mnyM&-q+X7ZSj znUbUmI@FM;_3Yj?tZK06$GsE$ly$AvI!XI$_vlv4T|^*r%_||ugta%iN5)WSF{84w ztGxo>u04MG1h)Y|~d5 z6cP{~O>;EMmO_xHAVo;b?j^wj_z{F``#lf9P{{K1A{gVA$y)Zne<1P`NSOaP{0c(; z0!D}_ml;^rOAhc6!m|+G@GO94_|wY^oJ(8W^dmF}^K^sbUj}R-PXIXB1Yi&_rkD5G zP}Mxk<1L>99L^(r4hFzB1%Zh5k_2Ed&mDNfKmbEgl*C#94CI*{Z<)=(7>Qj0pqXV8 zqU-=L62)- z(uzvdb#{0LGjnNv?`}cb+|GUcX1ZJqFt(4~#@k85_LU~)*YCl4S(&>}%jh1l>d$>T z-Z~&!z`iCN`jvaiNqs~U2IAjYhs$ZJoP|+#*T#0uRO>FQT2y)fUGSkAs-5kctkw3K zOhG#9m}*$t4-7DW0Qk@Jojkl(&HZ-)_CzeB9+cJqrh z#`bQnAg!cJPkPRi5Rb{TmaBq2ALh0)-RAIY9-+U|j5|uPwko|Vo#9QQa#<9r^_>4a zU*!P()WSzF>Ib$1CfQ0sKXL&Tq^GpX8_LP9=?ae_tG_pblYtt4$4woBPJYEb4?c%c zvUa9_h(~R|%LjBR(u_JBMCbbu-|DlYej7S)aQ}WtUa+u%CO*nBVG$VsL@J9)3X_@5 zLBT{nfRPJp&dp1e0URdMAU!z7Qe0VXnJr@!Or(7}m0L=u7V1%WH4G>%)s-V)L>c*E zBCVF>6*C*8Itvs zjHEFUx&m=lX|BGA{(SAgtNsHib2`b`2@@HsD|1q0;Dm|v87}#`6*HrL4VCmAp9FmG9Ie({X_XKzQG)*HDF$RLcjeGs*IKDF$^y|wE(&h$_nPGhHY zQ+#<7u9l0@J0rOW8?19KVH?kSgN;`mmP|_e+mbE4;pqDk!|Q6a@B_OBbo-n=y!ncN z!F%x%cGDid^`$+0h&eVxIYI2qlX-RGAu927o{P$sWG+UkPiE1_qP1y_T%k4rd2xDTD4c4H^8VV%-|6kzu~! zH;Q-lRN;7ZWs!7VRe42aZvKqYDMeG6?rRTo?!ZD!@CQ~@nhR=HT)`0ZyfbO7s>n|} zmlx0u8u$^)Tn3XkvsM+(XMfhqQj!OQ%_=PB{r-JaP1HsXBmSOZ%+ZDAMV8|1suDh3 zG z%oOc114eVK)_HEnJVq?y?d&{p*H7dpI#g+LvHxO=o*p>1Ge_ z?{43}*DQND^MD;pQ1uD5(VB>9Xg!Y5-b!^<<}PRv<(U_xD4#p>{FjcRJfrVdi;xz0 z{5INAlxJ$h7%f!oEbdr{GK7orL>D2-y65Zr*Vn(Jl0be;Nc9li54M^n#JR5yXV7D( z)HD?|Ol#a})+OokuDv84owA3gyk`&lp0kIaKW`8J=Yl<)^)GvP+9f-fpz|N9&D-|i z1TM>X<(Z{ij$h=feLUY^F69^%S(8J5uE$-KGY7<~JFIhkp?O#X#w3}g>9(*8G~#zY z!=wvue9r|UvvNMxhf5}`YKC(&T#1pVkklc03Q73ZmzrgGVB^*`IEI_)MXTTjZ@_2@ zH`8v<&(fN3)k*tLpXAV%Y)CE$p$mUImkSV>BR!n2x+)i~ zwuO&xy;jm$T*_NHDp2cH0^4$B@aw~~4;>Y#^=e0|q$`Jq(oO|xSLOYegb;nUEbCam zs6eWYygT3>BMzVF!Bqzn+E|CvX=ahyU4=u0DGvoRETq2_so%s((~%rY(~&TkPNTIR zhjJOdr|17TK|vpLLC=5nr&n02l0zrABJFu(-f=l8`;>TpFpLlJ?%69W-e9MeNB73n zESwh^`N}YMqJacy*(Q?tYykpnF5g`Dr6e2onM(u z-@2uQxr;#d$CsvGZQ77ui?!@&K}~tn{MEQ{AZAOI=Lb7Rj4%rbn?}||soJutQOYx! zbm18_kna0c3FWM5eKxe!PRx4KSH!HQ`IoYU&V|~p?p)|wXVrG@x9VUdD7Bb&x~_ae zyS$}NqbvTcWMO!R(4mW0V$hVuqN~4x{Sn=~T`_C_OiJ-&v8_BkX7A8EI|PO9)mPeK zA0HBp(QC9ATdn<#mom}(QYPl{OBp9q2ZWrg){ai5REXoP)~>`93+l5it~EIyp7A%W z=D9p&%K+PQCt)}3&iZUR@u3n+AeU6N+Lh@9l3POW-cy3;#)pcxc5Y=>NhGjj6krr* z@#u?Xb&i&DM(5fMxrt?#RK2DI?&Qv8V$&Fi*wqRLT6&TXaZw=EfF1yiL2H-l6 zNCw~%mZzPB;k0YCVujsz(rv!#MCH!oQUryLb51eYSLP^$hW0|K1`$l8A_Jb0oOF?X2dpcRIK}}nq-$$BKU&5}THv7yN$OlAE zGL|@wn%*!vQER_`l(K4|(1MY7lm^iEgYK<4e&-P1=d?!~2CLs7qAgq>KwpYbCqcqP zwA?MRv_4#&=L6~i<=W}p-y5^%>blw;L-NR~1 z?$z(?!~L+cwr%H}7B>gW6mCvSea)=ZzE(jx?q1-+Vs$tvd3o|1h&PSt=_P8nWN7OyY5Ie_WQ$i!sjI@6gS`wO6v{6>s`ov{oCfAn<3N+ zV0TgSPM;63^3XVo2r!i*HG|%*!<|)d3(U3A%T)`d~0{rFFG%YK}dk^-|EomXcExQkfc zXQ#dr&WvE~*sEz;)J8YDcbgLG9(F>Absg5Mw5`qvO|MguKuN2{Y%1gKWUhXGV7LG^ z@eWfid<>_WEKP~KYrX@0a;1kq!{+AXCOrMSP!XNfC?Yzsc|Y*P3OmtoPl|tpvwiq( zH9@;OC!E&qR`X!Tv*^S9*gwU%sGfYD4Szk7Y`nyLy&4vNk1c5lMY70 zeWsbs%~d;;{b}lXeG6}}j zzxAXS?kdC8gC^1({e!X;`=s*@%RelN5d55Ed}C*t@jx|G{Y+|0zq_Zb@X6q$5lbJO z6r_l5iw4>4yRU?~&D4|9a#|Ci7PE7T4){rFo0YzScS3&_6{w5+!RoeyzVYZcW=J-2 zmcAH8gG$Aqz3GB1Y-Xr%Sy5e=6|ZfyUc$8Yzwot=@IXM53FvtKO&ae|1YkJ(!c3GU z07f9`deII5gZUS29DNwTP}^4+=rRXw8Xf>T9ELi|p*jH=AwLL&Gj0UH!Kjl&Pvao7 zOkNfO7=oB#^0E=YXr$XELjM3T#`dlXUE|oUlK6K33B+!FffokLW~s3czTTF2PwG zD|BEgG_D0W1oe~XeEn(&F* zpM^gvcxQj=eOGaHsTyO7w>erFV23c4fmvI|B6wa_Gqps-dE&r(13p^Q15n)Xzswk= z&!{D5njj*5ED#N0i#AR-7+|)!g_QOAm=~y@@?uizM zt6SWM@O}-O)_uqic082M#Kg>lMjKTu^@~*JR}U4Wk9suNM~R?z)&BGLVIobyO+u{FoE2Q7ZxJQ_|Fq4zzg!CkSscEK~(d{!OZuiYF5Zd7~eRfh#3xcc#z!OG*h*tIrGo1$I+ZvwY@EbQCI35 zum!f_lfy79fWj=ypcl*}N^Na=VzdFvr0KD6?PHvE3|l4&f9K2a=8b#QC@=LheS#J< z>vBXr><+Owl)s$mwT%9HL>*3(KUbX9Wbc-+KpUCfkHUQ;q4^)HgII8}f?q?pi!1uO z8#{eVlg_B4)P;H{cp*H=)?u~JSUy3`w?*&BGZwDE2WTVvxhlg?)10GlJ7ViGMMPWW z9|(ik%Dc*MHl`3J7XCG^XlKr_rG%6o%jPGq)51%y&z7gIeiTHPpA|!fhKHl|jnsAMzWPop ztMx2@C$D|KVj%5%R?Tu92R&ZDTz^MMnP6p}{O1VX_HzSJe?6HRA0&LW{$$W%siz@5sg-_H4{$T99MLv!m z7{8*q;8Qi*yv9s2)YruX!0X-^3#ZvL_rV2w2M(ubL219mnDnMT_tgLo`Q{FjKC}~S zkgx7A=}U9&tAW+>-5nK)m`EHo7p83DM>C@(53tt_!r zE)IcDoc_aI;O{@F8LFEf-wq}Q_dGt5WxPlE8Xq!99dAJ8b5zf1jc;<)YX(#SN4*ew z^E;ZMkWuPGfwRT*2k2at9P%mTWF!8xJXP4?7d)KL@^7(pc0qZ7RydJ8?o&8%@;4cu z(nMz%bF+c08OSE%sHvRff%(<8e9vs}v)R%wZT6Z#t=`~H=D8B@|9^%3fBOoX1$+AdR)n0>*_;utd=m6AK<10(9w zp4#CqY5Y5_#V1!@H==IL(L7VrBA7FoUv+kz5mkRkDBEREZDvwu?rOg;`SpR3ehv8S8zbmU#O9!{&A8o`$u;*l6WLMHp+_Z1<^*58IO_p9UT(i zU?lmZgUY{2G?n)H+}QqXvS{Ds{LvT2_JeT$1K%)`kDH%fOXqr#p?IFfi-b|MDy0BVznfgGRJS2mRN$>VnfW(4UmW$iM+ecrm>_G~Gy7dWF;ZOFA|# zE7^5>me93os*2b6JOs6Vw045% zW^2*f6}7Y!E%n(-X|-DIJu~yn>v_}9z4yQS&mF%!^PXk)bIzPIGY(#GKX=M~e(<1k zjVR(NN_-JoQ@EG*>DIZw$>lRwQ(b{1-F4c~*7C;#J@<~6u%iiK{>vvA@ybZc?PK0{ zrR2ha^aXCA_)8Y%)(E#Wn;Wr0H{VQ}EelcP`PqAJ$pgD}M)t^rkO$O}(>TgoQZZ^N ze;{vsgbFSP&gCMx{A+WKFK-*sWdPKL7PFgfErOT9FP?hK%#u%=cIpilbWQE$!x`~i^F#Gf37N_nMS5ekK@-Ng4ZkuUEy6sd56{Lyooi%NM% zVi9Tq*;cRca&(O&pj*+9&DuJ1Ec%i+14HElJTu*H8d}0<2Ws_&Y;5(TN|a~|zuF*F z2eORp%6CvsPx#dU@ij!P&(xdS-%g$ zl)K~%jN&ao@~^nDa`<-hSu4_QR3X73?iy$Bb+d0*e& zh_-V?GD4m~kf+rQ-HI5W_;(PBWTn1^4)5`KgEper9p5H_PxP4&2V_EN+!>V5I|*HG z#6tX1>EI`WZ<)=*S(JZ$n=t===UZ$yUx3jCwye;l&Z>Q@g|!-trB}RDRuW<*P*#Fq zC7H;)27&E6BRmf_|tkf^xO734wF|Wa>x5%hK zRzi#{3&G16rP)eHGwh#kFq^YkRPcOuJH#E^vFjmzY+Hs*Q`2_#n})w(LxhGm8~s-w6- z%l;!*d(n&i;oWd2_9(m&dl+JmQ$+u|h;LW^t>9LHC)I|U(_Rv7!B4g$0>h0JPqq2` zO0)&9`1`#C-(TV@{#IbXO9~9Gg&&bAJERs&bx1Wl9|ibfoTjEu^a$WQ zj)YVjKU$(K_`YDAdCHfW1+VHU5OP*1zD43&EWTsJw?uqP`8U42Rt^7fF^}x5#smi~Z5R?=F@%}ewwovJ3f%3BWmty=hf?ImJ1>sLPvFRmU_f1VwSinPUHtaw>H zHm7y-R*g`)z6|&D+_OLyvKNMs;3m4gm7%y`1ySdVW^VRnNPxkzI{aOS9N)UglKpbsNiCI34-__K5Vv;NP-Ak z)kV^LJ6%mS(rg7N?52lgzYG67v1c0C3PE_N)d zb0>!{zm_ZuwDxC76Lzt4l$*QoM(4EU{0NQG_IuG*!9t0+Y}QH*M#tH*ilm07gm|_! z$(MbS)Fpl7yOIf`zp7^T7SW9j_l%uHbhG-T-YJx%*L6H(Xq4cxxpYyK~yzwlde zIXz8p$A-0w3m)zvB}2FT+?N+=-|~LD*w%J+T=oGNiu|Uo3q)5CyV5SKZYf~=P;v2R zVaNw2IUK$7?ZUdq1!mC)buqitLu*-R`z1J%?P))X(!8wOX7k(NdfQakD&qk)LEIwT zXVF+cC>w|@6XGKpa1$i>j0UP7(!F|wrUMuRA)ua?1L%t^9#kZt1CYgmss^e*l7S*O zi^oJ=yaADJ02m_Oc@F!)H2I6LO;DQw7yt-8bpbF)L`MRV381ft2?Uq|Utgf>iSOzD zBG?j$all;RE5b7Y%Q+0ynbA8y_7kz5z^nx@423J|rvQeE@JL{;16W5yCjvBq1cZrj zMu2qytSbT$0d@kgArGb?(BPjufI~#A#3A%1fT28kl4&h~0ci8AsehTx{n^%J8|ILb z;dvlAZ=+F*md((jJ!qmck4aXv|7-`RvD5L*d^ut~8Jb~acF+W~pbZ;0WxSE*B57{C zT>gi@#YkOB=H|?S=z=+fbv%=g9l13dgk!X5Pug7D=1D2Uu~)u7v&~rD{pZkqNF9*2 z-Icb~R=QB?c5d#20$J}9au%FCxD== zAlE<5%*xMg9>w=9Rb3S@#E4J%Tpd2JyTthK)C4G=N9h2;Tt&?;c3ENCY{cl<4vYKs5zWfYET~XX0;Y|1yd7^fBP&+0b(qLLEMwCdIR{+_aQanZQkl8pS^vAOvfImxmcE!dn__p!7=VHmT*^a!?h z&<@sOt^?bU=1K2#diq=S{zJSeZJ~TbhS$Yo`MaPc+wRTmr^}aZPpvctj zS@%PNOFy9H4bCo-v=?06b3e&rDJ|lHvF?S5?F+b6e&MTGyVqzfsIQP%6-|h0y7lRu z&HBC9kv*P}RNXH1BV3`BPPPJuO=z2f09Fy2jM4qJ3ehm|s^8kGJh_<5E3CGrZi zO!wdv+F_;oCG!v7m$_IH_?fy*u7HBGSIJ)rKL4%yCCh!!9Rfr8={q9~X65`%BLe)^ zAFbizkF4PxkFDX#pRM6dkA+}an*8k-YuG~^?d#-*kVjxCCaOe7lQo<`DVu-U8$W>< zF_k2CMq2b!^2vf@5zO@dNBSAs(XfV%N0Twu#`uul+9f9vPRj>6H)MEYkTxfbxYKr^k}R`LeYWz4V6DS$(kqY~Kcl`$ zdmQh*DYE+b$J=Q2*0r-R+qc%fy7fBP<@c0?3-55B>(me&`Bles*S|XI@c?E&?nx(h zn+zR^mCLH!jyvzj=}1**UA5FCLH?_qODUT>Ig*{|WN%vh1`9EVvfIJ#=9T{9dgRVq ziua9s&$rapzB{_Qzfk1wuAFaGTl?1)r?w4xgPLVFm<->-!saVdf5ya>3%jwTo$GDsU0N-ePd};Nh1K8eH zTwUE2(zK_!lqPL|5DjM?KQ3TJD|$OhZ>B9e@dbPEMStSZ0x|O~AJ+FwmX_g1ide+4 zIgWBV>}SRwW&4hGBk7W7P1tcKSz4A+G-5<)ZdrzM<&=fORBZv$ff4;w<_c48 zYj|=RUyxUpkz1HsGID}4UYItLL}UvN6nKt8x?qmL>x$4R_QHt*8c3QzVG6f)BH4>2 z0@A0!*2YdyyL9Vr?LE^JS#0DonOB9$QolWs`O@m_z1&i;H%@gS8+Ew~J6@?xY(*wJ zO24?_>dXN4;B*hNu?lm!tL}TIGj^0qcfG^EN%gy5F2dDw$27s)&EQ1rJs(cY<{Vad zIg%f^v9*`;$sbjio6;z)X%J1e;o3xmF$B%2uXRWuHf-(kI&8!@ZdSU3^VygcQCi_) zQ!qRDO+(KKLv4o|osQUf{NU{su2}1uJUUm~q$I556r%_6{Oe2U!Ueg_qjgBQkqKQA zKP%5(vI>TLtFMs0())KSju3rC{f=CSvy zgYQhe?`rJ+2dl&(mgm4(-5rhirkk~Um?paM2JYJw&$Dv@Y5z3xkxY-;SE5-)@dIu}NPzIxJc#`*ngntZKsQw~3EO=$oJB{j2uk&KMyl*->2R@&@+q zHElrXmE5d72tBuZYd!nu&DI1~3`RF3J#~{BQmg2WJ8G|3>U)yM%7;$c%gI4kS*bYJJ1WfySeeSalUQ)@{S8`>t(pC3%vp}TA3n#|Tagwo9Bbm!+Q zlcq`0oMd7@-p(Lt&*AXZqU54;Ea`M((!e=Eb8kt4w5naC5hg5pW3YC07s+~=t~ck0 zgM2bn@B!nng4>IJcpaMmJy#p;^kGw0eKl!<1Ru4TD7fzzAOBjDcEq$Jk&KVAcx(-{ z`-GhI>>#)fM_%@q12UP|m}Nc47doTZjJ2mW_;omQaGd~ zVcID#Vyk`YMc!>@oRi5=%m`1a1y9sAc#}cowhghk3edAW?GGoi-m$Z2b8+Ko1GLTF zq(5n6YsJ+?4)|LivcW^{AN`(xRVD{=SPXc!p#XRwe|X3PiH9Q=dP+6~Y{A-!hs1&1 zJr+#9*7?JZXK9TBNMncj&!kALW2b|CS}Y2EYhd!g?!A$%I&nc<)&kL-1kqCzJpWh8 zA0Bvi^8M}R^ZX439OSAE6MX{KAV|D?5d|&^u&FEz(JKgJusnC0$fKtmk}Hv16bGc4hZc7WGHH-3SSk> zu?qYP!20~ThD`Nrc-htpOaZWg2i!IQ{rL+LxJBTf&jI#BQi=im z75;qqI~$qgX?%(TzXj%o{C$v2bwPst`5PRW5&-l=-4tkJ$5-vcBlfe*W?yj#oKn?O z+jf}rvgbv*p19dsL|Kt`MA{u5W!XR-VD+Mx_xN80T8(=GJ1?SaO)vNj$MdO$ca2+e z`C4tR@&#{Sy`ms9P$GKam^--x%pEAj0bx1D9gFS;l3rI+$f8}Uhtyyg^SdNY-*%^>|%cW4@q5>s&{o>sK!B3 zY&Hu9wEnqNWwZ_)lJ_yKa{U~0hXm$FB$zOD{pGsURhC8Qm&+j^u^#1d!6)?M$^%h( z(a;pO??Qw+)?)27?#pRa0 zLACwtW}&J}lA(%UbQn^PdJFJL+CmXB3nGevVwqa1%(ESjBY|R>WE!olj3vHK zFv+4YrZL)qSmIal`(A}m)|tnP{NPqUmv44lF(`ZOp#sXX2|CZB6N*O}aW@_`8fr;C zt2mTh_v9R7v{&MYi_^w)iW;vK#gWhq@2?a>8G7f!Qiz!K_9kCYWM%iAa}4CgeA|Nr z!Gp-b9vP_kGK6Km+=1`=$=cc)?+7$)J}Vo z7G!HH!!eQ#*m7ZM7yd196K7ZvgLzlrPf6sfqUP^*yBs!JmruP3w=Uf|XUePhmeuyn zXXMC1z3|Ov94k8|Ol!1G!c`cJ5JqL^(Oc7NzDXb>5>3ra?Df{&gl(EpEUQrO7L;vr zMk}%<;|_FmIq0ZpzwzV-Tb{)?^SpxvkQ5c=Ww$%lk$sXmGhTwL}Tp31$Q#ENco zWFIVwC$I>@pjB^~(-+rMx*~fEPhoZAqhQdB?PdsxxDX_dB|{0^vumryk`l{e_Tau> zUHm^p6L=^ab2~~q5k`XX5UtO7;^Tbw&haFfS-M4E8~qOOe-ljz0rVdZRT~8` z1K>oPc?)XR?U!$4~9Fn@+1!b$Dr zw)ybT(!Ak&d+X=|a23fchDxfw^7?do8z?TM^EB)jlJoRZQLy`_W(3JGnc+bSiQDxaBJ zK4s1EDU^F!6xVjS=RKiumRp^@7$x(SL)gWH9t-X8h=|Gs#J9tSo)dDb zde6DgZh;7)8h0mBsV9IK3!^$2DZultkt6*hn6|4-af|V&%u(G5IR;E+I%2w(y zMPMEw%5EfegM2}Hk|%A2d}zJTl3}pk+@*9oN_shFi!z6YY?#c3l*%i2lZ~c*e3X<% zypV;}U&@78skb2S_)~NrybZe+?Ax?p@WliX_t;E3zh@khPbTKTi3d-0R-cwN1ckS{ z+G)G_5_HWM4xCSr7*k!WZ9GBpaf;@Dk~9Ss?0k}R43{y};5?`2NV?KP56pP3d*l!3 zH(Si+6Y9*G+2=qLbE|&#(4Kz|5#W@h|2$T{qVOs`c~TR}cj_g4BLrVW`n|=1&(zmh z>-PmIHBQ?|!ScBCm=jvBbEF4ewaAC~X#$}8x$}h_(9i3OKZm=*_v6)L|c3IxoT(})B9FaFt-DqKc zRw9yXS#ExAL2g;egp8cbvP^Zw1=ouzesjqxDgqayq`a^&uW+Qg^1`%(UW47J+{_%H zsLL-*JL(jh;=Ho#QE&++U~IXtq$Nf9`AuyVIz?yq;+N+!{G~xgQAxwR+`_Vg%rd4w zm%&uUaSlZ#I&G0JHJiT|n@`na($TcgMZ-SCSIB^vOUFV{bmMGFPmQ+DcgAJ0X{4%8SOU=R8DEQD_zb|?S*&`9&qMAe9j_nP9>*6UYE zv5_S}77yjcm#5vWYoz6TBaK@(KMljfB=H}_OhG&TF!Ff(s(s)EFN=?Mncl>RFW2!8 zkB|9>5r0YIy$%F7PB7w=euJ=;KfQf;rcO^I;lwkYQ2C%}oslr?4@scrTWo{&qc`ng z8tbJc`p{DBz@OPwiidOBr@k~PXz1#$AMvts7q!Z`!*LR=?~A?Yj^#r zmzHHunhQ_2F@LaO+)Kvt=F#joYpxlG6{0N)q{VfWArw6DdE^;xHIMJS&)LEh8?TqA zNgw?_N%r}n)ic3yK$C-qOLurCsBQ6~am=r6f9%BmjBm&SmyV^u(?|5zDg$Ym>9YXs zSs*R7ae`9L{HCo?yB0*>Y&{EL;|SiW1#=JLh^g|vaXheVqXZSwxT>1Vomg$gs&01wlmP5=AzZE9)?*Nfbm4=4xE9ubxDS zTjFy~6eo&A1(%q(?@ElyRpZ5ofyCXQ#@BuNG@Rk(`*Pp=^ZwlPeNtW3)m7Ei-PKh+ zo;qN#|A4`AC)q^gAxP1{YzbPR-DGeCoZW*u^nJ{=G zerl9s=>6P5;-NAC6aa!;Z>r4SgBx%LOZ00WD&ZySQgCb;EBXYX}rr6IUaCaMO&AA{7(kw(Cc@R~s2 zg28~J1(il%j1g`o0pN*2WJr@SP?0g1;Elm+)3`pMiV%DO@WWu!k00~{#mTsZEhhfs zXPkCY#j|eN#9!lm}6iU^}{$2P84E-6$TGqb9q2T6k;C-VX)K2r5wyB1S}vJ zgO7GDoB`Go8Q4Zk3@*$wssee$@z`1YFmP%cPz63s#Vu^3(gDL6x`zvZia2->0FKB> zVefH`%)v|WIC!AyTmk-0y2AnJgF$0%*r#Egw;$Hj`02C?Bbn!R;w0>8tO4BV{Q5I3 zu@!%g+FrC_k7|D|*KM%*5l&i5Bu5Cs!J2I*>p&`zjF;L8gGYM<*8v@wurb8@41n5K z$JT+vqG`^U;1lA%1=Nt~#0jenmbXQJ38oM)E&(7Awe_kr8BY9xZ5@a-DqoXH#5gy+ zh-j~BVpxAN6i?F?gMT_~dk%Ks1{e|h5iv@fJ zmCZS~bvfat9Q+Oy4GH7+xZU^^Z5a}Tei~wHU<$!AQ}k+xJvSsuz6RMx`f;;8Jdx>-Byk-0CQjqKv8CPURGgQCK(VD=!#s5I8+(sXQ#lo zB;IA&<(e{WW=?U@l>DgzPgdL`W!F$VsRBB*G}j{*>!yQpiiA545$UhKo+Mxk7-p zM0x3g6P31hKJQRB`^1S5-<sE^$@9y%!%mT_NcVE8%nDalHQI5!T7>vpHC!hUHXv1xu+>?dPL#-6~Rwv3*TWE z`&}k-t?>pl1!3s0YHbiY`c(?U|11_(tB}6?&kVh5HpxDuP<111-^n_Grb$s`wBo z)ss-&cSLP1X7&;o@wF5KVEu%_KFI1`wk$XwD+tbDnscp}MKP(ZmvK zR9BLt95d2E&*YssB&?Zj>zt|S4vfQPS)bBXNXtzyBF+Ke5{l4-nY}RgvIAR;Gwge{ zq1l>jS=uyFXANF&cf*0^U7B$QM>X%<(+g`(L5s91Gq-;2F;vY19Dj%Fo@o~t$d*mS z`$dJ8)ow<+*17VTLtrJk_CY8oGuyQZDQAy`yl(pJG$?yviVn{C8y8~sZj8>aVlwQW zB~CdxRCOp2$>x^X98K~qk$TapH$)CqYHms6pe(*79QHx~eBavDwL;&Ek%bz$bkrr( zyDFOdGG^ygROQzzki|kahz+L|005__)b1n0iLVSn3R+(k%RSw3APcotX=IRGO|6DO z-Oj<;5_Qg``5w>!mAuyQw^T3%EnMJfwI_eZSSAXmmFU2NvcV?*h+j@r->9MnFtwK! z>f_-y;fJKNKr4`aRn)l0tseI%qxiR{`90-&`tLrPkTg_suyCNt=~x#4x!$4(c~)V` z4Fs!I5WA|{8IC}2Rri6T(CBIxn2Ba!%5Jo>+7&vY`f3lj65XtJf-2Nm-N)g>?V{0~ zn<&c=-x8VGp=~H=p#zLS$qOCA{;`)a;VmqDCN#ohzRG>9kJJt1Gb8~sJJbu+F6`|T za)c5g=Sv&FNCvM$rM9pVjzno6-S6H$I2)#`@!&fzyc*UsoKWER*>pudUgW9x zcJmJdq+VT1&ZXOloDmbd*J<+ts%xI8(p<9tZ7py;^2O~fbmxa4csE71Ifn#E5)mGw z%q5{#v)2^5bxTV|pDzjKPCXseru%Wp6%%Nw+r`&IrDgWN&Pxrc&mqYb=WuX`89MN3 zI$VmZRv)qqupNi50P@{8HGE5VbG018t2hOU=Gw+sR|t$((X84-KA!gju29CzQVQAM z!!ySQbX#*0Wqo#EF;AO$M5?@nWmQ~l`*R+xTbph&bsH6M2FF)u(90d4D;u7t1xd~QV;$P{@KM9J&$W(VL>tC~dbfv_#v!K1%kZObFVz=#!({ zxwa_fXGwbpEVk- zZ17ht-cP-19Tr9-RA2d{seiAlIS6$t_nXP#IJBYhW3Kv_Pd1^Trfm2V+Hk}UmF%9# zH$}nJ?)eMW-_9l0lb}v6kC?+Mn_2ICH%ht)Uu^`uYYbF#uOspXcSm-wCTaxx>(ybQ zcdJ&Q&co4MqoT7OsgGpiSg_%U#P_%?mZJ^NoVh;<(XMegM4$RrW#h1Cik`@pKK znVE@3QWGsV@pD69lzCkFb~$zERvDib49Cl^e@n_0eohpu>7KZtyzV0tfxy4Yem#na?!m-RnJGYIR31m0>eOhnElE~xY5MrCIIfM}`O z5C!iN1EZ|Ax7n)+o$jm)=+&u-35CaMy?{EH>7c4lyt_u2U*ie=S-u&Zw^9Gn!WlL^ zl4pU|owkRKsNr;9I2T>Re|w|fPrJbD$l*-iDfiD&DUcf=iZhy)mzA4eG(2CnN_6my1N;sBaK_ou z^FHGqBnmMim*9*njR8`eb%ajH|EvQ%j*`y0IL_=~yfps?z=+&;m^T8=KRZr#_qb3d z%=5lbnR)yTkorALC}_>Dymy}5uuB$An1dFbO_zn9 z5QL>(e91-U*%zS^m(pRCZo{S3&|~$nyk^l@3YhO0kgApIlkN)|UedQEu}ztD`?1_v@yeM%SBTxqg9j43X;(8WZ^)D)$7A(Z1+> zMJzJDHpB7h<+l$D^nt~V&C>a(X@Pz${`o51jcYHV$yb}`_#{;4A7X5TkpsJTHz21Q z=`dDTeB)!NtOlveC=dBNI7D|HS+qD<$Zt_%|B~hNG<7xCpw8l!sSF-Qz3+UZI8fBY zN!3>HUyOsLgY15wW7t-m^A2q>yuq%-PC9HXuFJ{SZCrJqX&vZrXCK?1qI}l}`U;iD zADeV(cbj2wWBXLGWT!d-8zB?%_Zm8mNtHd@gvPf{R(v+k;F6SVh$1_pIr-$JeT4d< zf6-so4=JeEeHAxupI0J^Di|gEx|J+5Eo-K>?dJ>|=8Y1{f;mjoUzY_T?J9-kxi9o0 z(O!~Zh25C_aP9xPx@$of+WV%AtIw?weepl)B?J9b7&tv(oD{alT3nNQNiBHE@LBd? zRMn9I6Zlep_%mw!^)UC9>Qp8U#KX+TDz%PfH24X1jm)zHJEjF)E#Hrt9vKc#pBh%A zADImCqtFZAGRTR=Dlo~@Xo@xDOjkXnb_n&2&@>e@U)*VdtRLIsu;BaH0cuhF<9^Tz zO?&K$U!xX1cE|bOPE6T|Ea63-32L;U?X_Xu}g{7>*7-=_MQSU&3qO-bvwir7j#Jkljyj zjF~D*5CDs7jP|zKjRUA)R=TX|iEwx5te*uzv*Xso-ca9`q4uggAsKlgL;IiqLM}jB z=1K~{6g2(!(F)Twcg>|1Xwik=qYWL-9ctA5#`{OWbeZ*2@qT)?6TSPv9MdwviVruc z4+u5eH^2Y8HCx#qp5A|qdsc3Q;0_1?S*h)Us(@BxpBr0XM~RM?hbyW42Iea^V2p+6-8r)s1CJX^7jf>V*&8x zFuIOR+cbB-!?Zmu@{()4Ob(|QSN>I@+p|*aS%I?2FjdE2QWXr>+R_%oZ$4jOO)RIk z{%hMslzpUVBq^C`QtU!gT32WMvyb=Hv!uVL?7(vGIrSChP-6^>`G>af-zxFS6d5;K z^Q6w>`CL0#I+V>+v;?r3GMs5=IGY*6ofj21*4y`n8OCrD54~VhJuddVq1p1~2fgWj z4knc#oI*Np*DrH}2GDHTxqgs#8Vj98B-%8Q+=)N!0dH_K)!B*s4o{c?XVl|i_aUb$ z95RRR@WzyiE?(rgWgCCm7jK!!w(6I(x0?34%y;1j^@soDVm}{umUr^UB3d9s%ObpVz$DE{ys;hNa7h+Gr=!60eo`sXDJe|sRz;hw&sx7 zVYX>(EM4|6$gAUFnIm2Ffyb%KR*0T67t-L;hQ7}GBVt!EcY>Mjs|}>fbd|3RfzgUb zFD^t%$@}>8nB2c}X%n# zXLM?`YkhPWG?zg;J|!HMRI!1UGAX{H?6?RN15owd zpkGN52hWm~(nfmxS{Z-4*D1s~IIVte1k8o@AAE2AME?#l#y|7!Sp0~8Jp}fS zaEqWSAU8tPYD(W!NQiGyv(8l3iC=%j_^;;EB4HnRgfEJO?r;{5BB2}nlCLKqk-tue z$*a`REut0wzCk|7jS;1oiJ3OhF>fU66(5~}iI8oRzFqtzHFSU%`S;YYuNJHy;MHc8kZ3eu zl@P;OCB$&%Er$23Uycl=GiCjQN2ejJ8i{{A5+7KN6h4Z zNrE{R`8%FF6QcRM5cJ}+!eI)(^=)Wf$&MNu%#Iqv**hY2A^_~|kl}1`89!TGhO@x2D2SvwwX6#Ge9c zNFi@xpF|aYjw7QlO3Pg$ly#0H;bgn7X6`iZ+Glh^b1%A%6iUFkGODv=w`12JTniph zmES4WIoaF}$AF)_}{3u=@*MD=x|GQxKr}_+zwfgwVwfeY9IOg3n z_O8w-bA=>{VF2{r)!ino>lY+A)(?7?^XvhHv9X5_hO-wBhW9K(l{Z)qg}?ozkFz%r zLM8eEyBFD-vC@p;$A2SS-CxfFf|X8;qh~(Ol2f)sEQQ0kpdYXtjxkudFx6CKYCnB^ zsk=VD(NiD4>#dKoa*P?M^k@B4KVXFzW9XT)CnmE2lq2~K#TUZ)ZU`vj6}9 delta 11029 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 diff --git a/examples/data/dolfyn/test_data/BenchFile01_rotate_beam2inst.nc b/examples/data/dolfyn/test_data/BenchFile01_rotate_beam2inst.nc index d36b432d1308f3501f24b4df8d9103f5d27f3c03..2004de5f4f25263e67aa07b5980b099c2b0e03e8 100644 GIT binary patch delta 15061 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 delta 14748 zcmeG@XLwb`wlimTdP4d+=?#()2&6zDh9r6w8JQyrhxFI z6EH$VL=Y7akfsz7q^QV^B66iDg1j{|dy;+5z25ut{dx0!IkQ)pzSf$xW+wYDc-%VW zu_S2lt!M?Y6wiMVQX8^1_wU(lpuz3aW`+okB)v4+h>qgp7cKWm7O=AcVfN)4gji*y z-LA6NnknSM$HSJohv2g`)IA!vqXVv_nU!2oLP1`iZgS6ju4gyohztmMLIrs(<9!4b zqjus0`QRf8=W=3PZi36tmTO{FYE1V*P#0QGe{hcrS`A+;Ws9sapE>i?92)Q8=M>}s zlo*5}=>QM^nlLATnj_Q(vgm_v*dwDOe5E7Q3bOuRr8%H$&^U6$$OPHkce4D^Vpai= zTSE3fm!?FZe*NJKnstRtoqDP{YUT`ISqLRUR@m-J8}xx4e5D{13fYTab&W@7*_?Gp zh`M+N)O2NifHi0fS)l*yzNigj2Wl{6vu}Jk7(K^2)fu7IkQH6IIT-!M<{Bytf~>kA z_IWgvjTe@NF`7DGd;w)M4&e8PtUmeVNL0Zp?SW7TWa(2jn~{-q8;aDpKz{d$l8-7_ zN0JbVgRIs&tO{LY2_DwX$R^a?osBx#!`Cwi zg+rE;J9{C@>kD6zh^-;&c&7QHR+m14j_Z=;tvI^#RmZwyuctUFi%o7cLQ#zGn^ZI$K~jK&*cJ*&1|{73qLb3&>*Hx7mb@S`j9Ei@&+G z742e(OoY4wAssGUc8kLK6LBEzwV`db+3Rx|gmHHK0$$irlvl{ezi}VU) zCB&$4Ay^r`G+XItj^o2E7E3OT4_ZQ(hj?HIY6&sXJ!$TAO^6eAVo*#oR)a3NYu{|$ zS=z=d2qX_)c3yBxCIUS9&;+RMVz7tP3mV2q2Lzk^nKZFvTZ2EV0SEBkK>i!Ve}maC zT@vC~0QzB-m|4)LSUmvWTGtw}pwU)|={rkIMOhlg^c_+>4TpYefH3obm7_>xqeuXA z!IKtxaP{OfR2tqB_oeH? zqv_fZN1V;)u0Q{Mmi^WYwka)FZio%u;y^fNs1087%XyF^m;A$Q_$7Y0z}GY`xFn4D zn*wGV{A9D}&}bXHtS8R~FZuJW@SxSQz!)n6LbAYE8@%K%_#6B<8@#I~6yt61cAvul zm{lqKcLcx{{SChOZ}265gD?FXd>Pj4v==c6zd-joBYp2k^vO2l54m-_|Zn3O(nV#eOg{Y;Qde=wk>Rk4YpieT4yE2$oxY4t1)}@#3N83&{hoif7b-MYT zU|FiJIYwI1i{0YgJ-D~LAe9$IW7U1$O8X$LR<`a7a3uV*T`sLji!!7n)7ms6-JjNd z*qFCOQH-9hoSV&~FFWQIxbWy}&v~s=C{3`T6^Z^`8y8RAGs zrgsP`_Y`uW?GG&0;A!~8tPZp`J>2a8fFZ~<1<@<%p%E2;F`?4ZPeYL}%zqeK zc=h!NEn*bUSw*LIVt$Hn5 zFiS1Ob-^uY?NV3kY0Cx8os~nGl@EnBWpcCT zWYT1FxSG(B6k=y))&}FmWk#iK7z=k1>N50Hw7$CQ%QzZ!LF%`zN;`Mv@twtI_?bh4k$n9dssBDJZU}WG!J=`d4xk^;|v0dr7CI*UX~}^V^2W z)K2}_TWRa)3RV8$(*9l8`dXM59vc&f6#&+ezQF- z*_cIN?iz3B+7}@Q_3l@s7oVFSS{=QhA58DD+yvJcQhMDH!xFz&4rMxVi}bCgA{d7G zkk257p4{ut*f5CgEm%$X+K$m6K3%v05Uj5t^*j;egt8KWAbHWft6svjFi#&=As%IV_>DFhIe4C=8R5~UNBbLY7|Fa6Og3ijFom8$%Pj( z7gm;=tEF>@6={+ji*p1?`jc4cDhk#h!^8yX3u2|4R-IvDqI3kY(p^K^<(tbZ$4YMr zD?KzkmnP{fVZ}{DJLKk6lvEYxDVYL+6>0R8%KW0z`Be-JW(zCQ`Z<=C=T}rzmFLf| zmPMo`Bt_;iGbTx_w19Tz+}!-awX-B%JJW-yHCI+9sS+(MqBCnZC%3R9cYN#$K{O!{ z0_dRZ8k@1i?VlFa9n5ycw0uZ--Ld}r4Rp&uFVhB}^yLJ0CRb*0i*umi!U~vex@%J; ztr%yDhHq|~nc9I7Lu`3<{0!v$Gkb)qr++8P(xG02mA4FTz4ST7=q4&2lwV$8F3hef zV%2#dx^8R;ojBAeIETi8;1k$Od{`ew7n+O5=j0Vsutleqp)p}AS3vSl=t7{R=w?Y# zL9w|oC%2@$oH4C?p@pB9C|z1oonM|)m_H%EFefilbR>>0c;NsYGB^}tT0AUQ%P=nwFNJF;LP{SX zLsELsljE`t${@%>lzu<|JiBhrnkBfd%lI%-=esVJU?a7wXl~^?T`J(l;l=Ckef>RG z-(=0H=IYB8f5`A_9XM(E9MKNGhmk9rcT}HS7RH?f?YI}gac}lp!Ynv}Q4neGlnsAa zEZ%hWv=Ha1n#?8_(Zy{?3~TEuOeaRq7~U6W!n&&FYkhG6Sr94xQLoQU| zu8~S58bL^ClY{`)G)Q6^bw@)q>Wl_UWs&?+KP3EQrTpS~4O*>vZW8UZR(^?m?`Dzn z6@ky3oniqbT)j?wN%$XE<(DY;J97kd$^Lsi3})jWKj;w%J-TBJU%P7!AO6i6UVh&i zKKLh>tHvhXo?63^{JudWM?s#3g_Ot=gALYjwxXz~I}ji0xv4Md)D6*uZ{q8gY%0T^ z!Ym&PjfxgqcLR$j`;e96IS*a3X(0{WddX8rYR>wkcLbXcb=^Ch3~$4^)a?^UCaCF| zE!lKpZF3x@dMZkwni@(xaJqWXs6^9`ehg9vq>z3A%($`Hk7vjCkc7QW_SqI&*YM#^ zrLJtlY)q$ZaIG77cpj#s4m&0@ZZ25 z-f2`KUEiyzYuJeuU$#Za0GpS6H9P7Wz5ZGnH{?t!>5APQFs?hj8{>9T*ElEqNL`a~ z??0_2uhxs~vS5RneTutLQMk2O%bXbEYrB?y%wcB}BiGU&zHg+SSXKW`4DqqEtj()0 z@FGianw}@orc46BtP8S^I2$#FOXvX~$1OI^S>GB%Pf4)(IUn$txmO)k=MVWkD#c`~p@S}v@}twA_0+&hwH3@gkA zvE(wbsGu?@zc{~q%p_^luo5kZ$Q~Rg@EE1cVI@W*IC9?!NFaV3g_Tx3AI#>GO+L^r z1X&wF&d|MQFKe$^$r5#rSuOG%p`w)A6Or$zOdmnloa$}Z$_0U@otUk*ZcnB;3s*Sm zZL`1nZ3ohuY+Z?|`B*hQeddOlB%U6)6-2LHj`BM8 z?0Ksbt)#{5H12t(L+`Y@`<1N)$zCf=Qcsj<@MaWUe5ZKTW?pNI4PYxSpB!{it>cf_B7C676;OU&m?3pnURNcX^p`q>rK;r zyAv9x4Kch>P=;<3IhqOUGaI6zgKZwLqbHaFo?uGE(GwVOW7*qcpl~y}adCh!y3q!@ zc4u3|^ljYg{?_&WUv(+pV#S`FOTk%D_7{EuPY?A^;RZuC&TGzoe9cL{eui+rh@QOB z4jbs{-FIp2SP#SA&ATi~23nix?6zgC=*btX5X8HqAQt?r;eHR>WmR7i)9cKyn;G-X zaWQWo^nCv4_hE|*ZTW=md!-|RwG!iTnVU5%XPX?)_LC!HZ5J#@Th^u;lea!yX4`Jg z7EwnN*ZJE!AJb8DI~W?ooK*Cj>{GXMuC^1&b5+(u6p2QmRU4iO$G~kM>{P$&Z~^IH0HN#VR$P=nt`2hPm_z8s109=~S9=GgZBIkF+4; zo*1~siyjX@-I7GQrl=W*NuWAmH;KkXeQP(FuG>0w`tHf1SvEy-^Xs8{xDsE-`YUgp zW6DA^R*r5Vr%rU1Po4ba`Q$lA@6?|?Y>D-HR?U{D|8#O~A z1Igz)rp_ORsTFqQtaES9l(KN(Ftxuu8E6P-2g~shICEj@Ob4>jOTH~ zXSk{MHj#zSBKF(4>fo#Dbrb1H?r@?nSp3oIdOy;VY3!5m5uD> zxd&n^AAlC5@CH!OhTnxignjv-?D<`mbL<5M9J9*(3+;!|1oC$?yud{cwiboKUcoqn z_<X@Pkcn0z^Pp%xD3Dmlp(>L0Q#}_ zCkTyYKSu#Jp|0$crQhMtm%Y*vNt~5uN$@H#N3qvMB1M2K`mq;0BBcOmLOmsD=fGC& zgCmafEEc1BJBYmH{w&ba2mkb$Py!JHb#Wjpn&L5#9n=7Z>H6N@|1Az`37(CW>JQ<~ z4ta3<7yUN*>IQX<+@QyNxpb4Qxvq6(YQ*j+Wc)wAxT1`2<0G{V8i!zXgG+B_^M`MW zAnV2AFw&XzRT`dG;9C#Rv4Lj;tnb(Se;DA(DBb&`UYjoK@c};xJgsf9Pmka7v%o+7 zqaad`Px(b$TJg&~?;p~S_tjG$gKd49mEysAwtZ5RyO{R2-1bDkm1h*45$l+=jh)Lw z3KLo5Ydmjs+)1G9wsQuw?}^Z2^bU9jo3XNF?VRVCcK|cQ6HHm-#ZQ-0TDW0+gUi@9B#c!}c4BOFPCsh+r0oBuvnCOlL`E>!a z9dS*g<13m&joz*X+j_VeA+igCVF$Z%(4|q$1*~-+eNt!*k1Mf)6*PB0M0c&luIsXQ z1eq84Z)CUhKgE0jL!o`Wq`biUb8FD2&#btPk$sV06egl0zDHFUC<;WiEs;L_3K;a9J`Xt9?3IrFHTA-Ebx|@IO={aW9pTu40p|v`818reLr}%fnqthrte4f%yk_J3F)n&5bHRr& zE7OZ{#%#Sj%hqvL^TcqPT;g9_Ozy*NY?emCD=~VB+ZNC7!{h7UPa!XLGPE(!aUFYd z6Q!1lD!4X4Gfht=>T(6;>qwFRtUWM5{{tp!6IOEem274v3L4-ET5NRqeLop13L*5S zcb8EjfLX~-jmT3_Y-XuW%==`_`pBh9rtP5iX?N^R8^_hiXJAkI@y}_WaqW>Et+}J_9KBsk z78s|pz2h}O$~oNvN79*H+c>i>{Mj%Bj&|zimBr)w0~3k4Q?Tw;OzjR){-Smx_n*Cd zE`WYxY&yt1mM0FVsD9QS66S6gAf|zvIdzR6iBdZsCM(?w_%Qm;KQ;IuT7yb0XJi(iB~&6vf@B86SF!dp(}iG*%l2$!I- z3E<9X*RlIfiJ=IC3TW7>wAAkEKM#q?OEjq0kCLWscD7cvqsA#^*c-jAd6cRqj$Jj` zLR&zFx8nrgn5Fk@9hZGyh%PkA-*5G2HwzIC;0K@WJMa*EZl9{@>rYHqoS4cs-Q&8C zhi>f59#qTq5bjEX8Un&SW8#?zTpc;$D=x#bW4y;B^NY}oA1(%fp} zL(?@yZEd$q_#ayf?5YhM6Bdd zkKN33?^h%7oMD0H@Q3eVF$|=@g-^;??XrB$BOZ0o7bMXTfz`QRz%zj?_1+hxH6E^p zog^7yB4Qd^;PM0sM|#A^vz}=F_%F)k9rZI$l6>q~1ant z#Pj+ag5}h|vXpR^=YMK>Td?H0s+M-ddY(!-&(T)5z15hnNN>Y#jVI_npUcVj$pGgv z&V!FvUl#S)yZviD<(%iwahqb*?dQp8!}D&c=LJ$>$JBu9mzrKpx~Zm%aJNwjIK4fp z;$VN#? zsIAui44A5IuUz~QD>A-vD=ewbnNU(#nNu;opj2KSVMPX9Zsy#|f(iL>^IH5;!5Dd& zgxM(yPt7Xx3-gQeE6XS47ww#8U~@Sh|W8#ZZ+2Mb*5T+OSCUNn7*y8j;OVfaH&oXCkUfB4x2 zPQ<;{yUE0)@`95%E&p6?sCxSW8HiUgS|Ym!Q=dL0S3DC^eS-Hp&TB|>S2G@xA5;%T*%mgH^OTvVjN@zEc+<+}<#OzA9_reQ zq_5i2PVxBvYu5kUYgQ(qQt2ER#Q#N zbLx_F#NJTc8MdPY;gdSSyf|~8l$t+mq2BaUyy3ksydi7DozIx*K-U|4cm@E;e`BfD z6WOz{1dsidlIGHeaW43=D>-I$&2vLz43vzlZkD|BmhBf^{guC}n*lg6*!Q%T#!H#A zr*8IDI?&dsk?eV3A~ie{LHn$lpxnNO&Z{*6N~QBf?G2tsGa7dyhM%&tGb2}a%C3GB zsJznA0-%0GOKo5Z@Wey(5B)lxup?@#U>M&P{*Pk7p{4Z|zbw@=ZVVEPK9W(6@9B~B M+WPImibu2m1=Dw{Hvj+t diff --git a/examples/data/dolfyn/test_data/BenchFile01_rotate_earth2principal.nc b/examples/data/dolfyn/test_data/BenchFile01_rotate_earth2principal.nc index 2a1ff37dfcc81d422bb43e2f3384b896e65d685b..a71cbbbddf15f69ff0533c5772061887db49c06f 100644 GIT binary patch delta 15113 zcmeHtXLwb`*6_^UlXlYUN$-Ukl28(9Ng%WU2??O+3xtyb1k*^N$t56BDWW-yu%(A0 zAPN%VCF;FYDJuE`Hk2z}xr!GlDqf|1Yi9N&`yBE4etv(x$&)>2m6=&Hv(}omX7Af4 zy$+x8Y78BFxHCblB=ILgS!b`{+@V?diu(}{C7L7Ep*pR;pZMY3U(~63ZBh_s82?bj zI#athPhaXmo(eg*V!3BH-bEuk6L5FBv8YwGXclrt3PRq{L2-v9 zKS9N)yZC|p@NvSqTo{+T;0m1*)>{=ei<=but8@D5z3W>r&}0ubBEsHWX}ESExdQ8b+WoOx$$d zIVwMQbu1dkCY*|3X%RA~fAu5^U_$}I6{=hJNeLRo`sj&JB2;hrM%E!`HZ(xVP%V1v zg&F8m)(1$(@TZ@3T7bqmz%N*$#BiuXuO%%&t6)o*bOBJ!Xt~vZE;7VpFeX$5MGedP zS6%LFwT^5_@^Mn)i$?%94zYbisi$LJO7emqP=bj6snQT6^#42DFrMfW*O2b?nh~J8EFF9Egxl1k{b!GIk=$R&g0Zo#_Vw z6)w-Qg##(N(R6bsmp)7c7%rYJG!KtQ5v(!v76#RY;2poA;|u{3MMCwD6@K?RPci)l z3qq3uV;v#_^SHdD3{6}2fabZn!YHZrvB8QZe9sj->aNCs+F z52ztR&unztW=(B$8W}eTwJike+oTF|H&Mh>>YAvEkp^a?Md10GqEY3)Ka~%%X>bGGp>`G6Eo3R@_eI1xxy3>teX6!-V z4T}i&G`51@x<MVdiQM)iI?#iEu{Mn5^llU{4J(Iz8 z@gjR(2lm5CFHyDid9jB;r1Dl^iM+^GS6>!zUpu^P)sMHzw4=&a1MKj!)j%$17Jm++ z%@GOoVyH77!r`IpNxdS6(yBjOuc)WuQh ztY~?TBwE{LQ4@P+JT000VFoL|>vcpo0i(^h*}4^z@}Ql+LELDX%T8%1 zgSydY%f>%-!dZxq(U;e~qKu&(ht58%R`H|? z0n1sV)mH2J__+(b8wZabJqXR2C&)M$qpK_sLf$#QXNhpTleW~N=BbOOBkh7kHHUos zyxB@$#9-}3 zi#n7~*A4yJC^%oRih8GB%jw3g7x*N3#H_JeH-zOoUW~~dICQudoQF;*ZnfaqBq|z` zG8#M3;LLdKovk>H&dcoQdVTDJ|A^sd({9P3THP9)Jdy=-QfXX}=Q+`Qqh24^YbnVG zy0&KsHiUj)KiaTXYntNLncmFtvo?h&ZsA&$i9{1OID)i|QL5S66yV?n$0e~^!(3yR zzH(hi(?L7nM@IUxW1h_g!c2WG-)j;6q)cl(r#jGY`zLFM{7D>WYnLVAABvWWXvxlZ z(ef?*v?$>%u`|8czXu$-bN*G9$2DV+uD=*o@*y{w37@`qgN{#)S0?nQ4XNXwsyxXg zV5j+zd5bUdJ#}Egv@2kWJB&Ud>Mk!w1{X8FlJO$btD4$!)`^QWfxwFMtxxh z{%Ek(JqFFCchgqkpXrLnrjnnoE`FQh-d5&xMe&Eq+@`3|DWo$zGUsH-G$1PX5UdD^ z(b-P$QVh?)_IPV9fFY1X>O}~~O_E`D!G8eq6+{^S2>c2}ega158~9-ck@b=Xas-P^ zfnlBo&MGs0EhF; zilZ(87=_~bZ+Ix6BN50YxO~3Vny(!^s}ik#D|Yn!XpP`FAu2HBC{kKcQ%gfLV|?>! z&nwL4r9D2k0ns_#`}=lnI&{>C_ut6zvGwZD1gMn5fal;`4@od#|6-Hvo7B0081cEswEe4bx~uz4 z_q9SYZ*X7(U1sqM1B70rmZmbgJfoMk%!@=ir@*+h_urnOQK=DWZU7AVqA;7A|9M@r zF<^hm1q#A2`=5Abfw zIkQ(4PHI2a%u-$mz0D{oV-2-tU$rwGHJtc)h)zeBRF_)Ha_Y)?x6PvxfWVuvu?lKR z=alef$*0wBUP+khv=}JJv4S`2YPO?nY!eeYA6RvY4<~&0w*bM|+6X-1N-0tZhe6sD!x|Z>b@?`KsO1o@EO(SxG}xC_Pgi#87otG-q(LR);M!TYyIb&_#%#$$Cgwnd2C78^aHhw2xx9vfup(6 zer6RS;nf&T=HsMJEE=a9EsHu$)-=HZ>Dk=mrylnGv@9tur&*nIpsg#7r|%2~E$O>w5pKNq%B`i{Tn-xXajW zqQ|}3t&K`2Ix1s;=H*0&YZrXgSlV}Bk63W~FDJZfE2O7_Zt*SN969+ev~S%|rKVth zUn9>Cb__8V3A2dO2(!?$Wpz;_ra4msTMDQ8sj)05F&nOm%1ug0+eGQw8Q=Pd z^th(Nud`U15Ps*FMMn*AHodkrbHPpI(pf1zixTK6^?>-WHxRtKA>gigL zq8;=jBRnFZrPPm}Tc}53V8~uw*1Dmh*O~ghqs7>2%`)C5MX}qYxJTb6Ik+XlX>PT4 zBW^uGg)MQZ4sIRbTFh$gNVXOCTSVv2E>t!QYMZT=664UH1r6Gz zQaF=6+Qm^j;G>RLZa#XEK>OqWv`7%| z@I?xwb6@i33l+seAfr}`9CydvZnZ*@R&g8K@S%{Q+Rnyqnn@%rd$rsVp* zwlqDno3vi?Q+Jb=iwhcYt2Ib%#BTje?`Dn*xSsi+ya$=usT)ctoxhN*ys0>7bGKp_ zugt-se`x^PEeDcg>dKK>cVD?flUL;{%Lj9HpjAzU+Nqn$U{L&yv~2Z@CQv0P%^72# z*Di&sLlx|zO?s#XXgkBz33Sqi1bTJNT<>n2>Dx68BG#MGUn|&z1M4?6ZW(5q{DbeR z@0{>CCc^eWO-rh)E30Mxg^92=gdA&XEwyzuG7rN&*2sg~Nmy5G%eG$Sx`;Pp&DcEs$3lGO@!%m=$O) zPp7$DGRwoHljs4t&MGghEhwodsh%=h=6sll*c(<6@_D%M;JV%&T417Ivmj@l(*vz7 zsw6@qz$fu70n)`}W)i+V2M--)dw;V-cTbg{2q~`g5E0LgTcTe3#=L2COnekTIlRwC7s& zkL6|&<3=l51f&f3ZQ%`j@0@b6b=-Q0^XSdR#9O|B;O)IX+&hi?JT0 z2I=xH(v$tYltX8mtw{>^V3oUZnKH;E%r()UglHbkcmQ@_YgGFu_XTmBzBrefaU5`+ z+N6YPF?;ai+P*z_WJ({9!RX8S+QI#}jDfW$0)%fPSew2Z_t7fCRTph{r5a*NKPuMu zRdG~GX(nz`x`Mv`IConU4%&vrtR9P7V_=af?Kd(fN@&l0KQY_xU7|K zQTFsv{w)^JCMsZudz{wklJdVkS$yQ|#HZ$oX)8Q7$X3!^`MTRSV;)`WaYgymB(C(x z*orSSuWyxnWwBtF?zdDM^qsQBM>y`E0!D5Wojd5ApZ&|unlGf8ICP`UTK6dBZ(`I>83J5ncLok9|{_u zKITz^%Td1rIZ26gs2Aq~<_-&Nyi;+jzS+@;_l z7@~fZIB3Opln(0cP*r>RjuO(XsPl?W1I7%?L5>N$3wT6^!`SC=qC5{^xPDCmm@NPX@$d0Cc^`lw{ChVJ ze#Jp?7y`l{G%gg0V-LV&0K@gq8R7B{OTTFj1xf+>w2aCJF^Gf_)n% z${r3fL+0f)fZ=Vg^7PkhU`nF&1XGM;U;K$O7QiT!A;B4(n#sT^?6DHyU^IaJWMwyi zf$S4EQ9c0B%s%uJB>@c3pM8TUivBWBf){`}o_#1Lih~nd`I!%Y)AHOwci>f#gVyqg zvdVcl&{HQibY{^luH>4P zc!V52ZmLJ!SrLBmv&rEg+JM$*kq6Y6*31LyRBUY$cEiF(eDMD=dSu(^Ndw&+Q`jqE zyU;Os6cZT!Wp*Qiw?EE#PZ04Ry+(^$Uq8r>$*U{JG&ebmsP*5W>Hx*tqdi0uH%!GF zVpyDJ%I9;*9o{jWAJ2u4U~KXp&6#%HI5Ud-V?CJD)_4<_`h{KI_?K-&2m1KU%{SvQ zk+$I?A~%R13P5WxW8NZf4b9US>A{O6c};|gv^@_)um=oqZzQ58Or$w_ z1IP3_OIc~{Y!)v9le|pABvm^fshVBoZ4xGFE2GrldU>0KiL}yyM?`_ple}iaMB1-g zMGadBF_641g9|mp?gym{U0mMZ9I&2~1L2)$i`^vE3C+)@-cj#OH-r1w| z^mNtI$B}<~K2EEQQM zpWFKGE;Z6q`AwfVI}7%vXUeiu}qYhVwdbRBcyLP~e z@NaDw4<>fshS1CA6)$R&{-x$Ct3(6#?s|gY*1RWip3^~3_mHPET*Z%6DnHaJk3*Kn zSYue&CL*oT`qgL?ZzZwUyolEHr5f6K-;M9m_=)YX;EQ)=%F|juLZU{0B1|x(qj^kI zv-N8T1HV#Jl_N;|{VTN$@6slJt#-!6+VZc}%t&Tw!Dw8m>wW!OiSdUP-bdo3c;fZ< z-L2M7nD2kp*#eCBmBs~PiZR~8^%OvgYMaId_jmwe=V0GJrj}TWODm?xmxY+f5Wu0Tw6unphLc?$zS;ZU0yERVXo7DE6oGnXd$Js};j9qwyw%VC^{r^|v|8HN7vnW;SJ>N8V zNNty})N%<+^_Fl$aBG=~oW>b#`^+diM52UF5Mc}t?aJWI3%eP=1N!wfL?`v^&28Lw z#)WMk8HpE;BO2O65Pg)P$`&K>(5L(hBA+FhcZ!Vo^lO^u8r&xUTm$-B?Jr*%X$Q{G zH{a}Ih71D)U`a*o%H1@%>$wvJ2 zGh8&^n6D=PXvE{MML${TXU-V$iEgTPU^VXVyJcl(n{UDPyX%An)mL{ITjY2Og3q*q zjpL1k^Io)|XCJ=Vw^=jEP>hmE8~dxhiu%7aet+MTyf3i!s&$nxJfCM$nyUUT{UOGZ*!4Ri^Gz~{FvgdS6@BDKUt%hDP1{t&w#a`8au4{P;hhNoV4ZqRhE1g zGSf(0oBvt5vE?&HVs_V0`WlJ#K>S}f(wlcX?Xv1(Breoj1~3ViWhB|GkN?+Nr90^p zz|P-JMO~4x;lX&#+k>S092oVwvk@PgAn?ZyJIyoV8xuL+cjBRZn=yV#RQczLi=yW@ z82P{XL*T#t{J71yW%oGV)%9p_wK#kJ#$LJNNi;me%p>-t{)B(!my2Dsb6zAP?AT}B zxtWP*?RJpxB+Fq^Q21LxH_qrfg!Y4%?C>Vpklp#XeUkqmTZ{y+xZd?9W7sP$mxd&v zI~nj{VJ8v$yFzzvBHV;4q3AzdCPFT#C$u-0xM<?s$Z8G-puof*=qJ;twFVm$hBOE*lxgZV-%ql2M8k Obz==}y&OcmJpK{Bu2mSTgf}gg$7THK%X4&I1fJzN%@6;Yi$Fqm5`OK7P{jTCD`^VL%G|3J622 zGTf|u;=-Dw@L8j-OT8nM*)-C-p3=-_t4DLa11)5Yyxg!HYzEDSI zeRC~ARZuhWf%@%#dYE$=8CMO#739n{<&A$|?lKVSLfzA1AJ( zW}hHqm>W>y5UNZ2`vjLn83C$~P-DpIRlV(s0^Q-OJwgp2>wC7H8@dLKBXv2{Tx9I~<--wsBDSnoO@)DW_q z%XbE&r)=J#qA7aB zAnPs^De;8-$#Y^wQ&>luBa{HyR{yACWM%_^ZZ&~y{?>Vu&>7YY43Xha&vu)Ima}Ps zS_2`QRPm$?wQ+^7)(AyImOi>{G0N-(Uv&{%Ow{sp?KeG7rKQ1Vb+Ww|QDOFTGsLE{ z39W}vEM)fspDi`}2&Paos~u9%%1Z}-Ew_EEP;;PL$rjcUNMvou_GPwMj&`&1u!eDv z-FUWRH7a5pFiSCz{k$__4Vua(1&V|~cF=q08s2TT$~6G7>hFVV&>dE!1wwToi)+?+ z6AILdFbRC?hr1u6a)wAj$S)M~w8{}%5oMFU7@FuIVYC7S3twC}K}4ui4x zign6CIvj+_K?v;9$b=E;I|_?f0w`@29i|cKXWe18kX^%Ab^5ie9y6op)y84T9CWb6 zLFTKcnAMOZ7H$pbGA2o+9q4Ohr6gN66!bEqr z^QLb_7!@Of;%W*u`@gsL#YBOnPnczaO;w1$vPWUp5=un0eUe=T8gqQqQ2Rx{=EHKuA02_?fz&Izo zmOYjo`Ka$bp8uK0jT)>c! zobg;8b@+aoSQm5d|Ky*<6;>0!C-Nfhj2 zES2*A>c3glpZsgK6FkQWF4JJ4orvxaHwM4%h_ic2bLsVP)6jWNHOzN{-*AE#IKc}w zSZfhUy;wy1N0_?9CdreeN-eW#csc%EwZyzEb;9rOAP|)L){Qh=a4ol;aHZx(u1ciZ zzbOt=`%S`bbWzLrX7$i8P23)6xt}Ix?tZKatTfORBuc0kcx*P?N8uZuZeX^YYe4DMv zZ}D99bA5aLN@?FN9sGQPCb!k2ugy}+jiiI$r`C(*di*`AdNX|Au4Msw{Ptz)D8PGO z@BB&~(d8(b)pB5y)*p(QKdR+Vv|J9O&w4SxX1PG`+H9*lKT9}Z6t#4w58vwKF>RwD zPeU|m$pnR{TLVaQ`bn$)Cb4Q)+wFGK_hu&T(xs5ttXhB=$cPR9eEg@L z7#*q}s7cyHFxuFEPo1Tw)rl2iF{+LF*poDkU|!*}8<%eCc}`EX^9=VOO-+o)-2Zg4 zo+o%e=Dl-gjXZ6&A|C=I&k${P3y)%YwNp6VUerFZ=P41TvLw3_+I> zz1%)BrVubDl%M}qBnp5@jzWvCE{j1mU={;V7h8?mXakMtuv}?GcXb#?NLI#On{6RA z`?-|3N_zq|L1@LjeBSH+P&NeFCnSJ3AYC{#0jMUV1s@2_1~3%DRV}RsFc5Jo!Z6IO z3P%1K5mf*+2nipLZF)y}U916~ZU7iAq7DWJz%&K%5R+3I0~iblEp-7flm~DeVF56Z zhb|mUh5taHYl%4;K|CJhh{?cQ638P+4vRUA(3sI;Ae&I61UCQ}iJ~OK zuF})q6*O;F4y6?RF0?6y*)?OdCYr<5t*$s*@nCjsu+l`e1(NtE7V#qF+TrH6`s(hy z!_g=isU5vYQ*Rdgoyi!wK~(z-+b8_z0ZwRTzhG@!qkG$C)Azf!0HYa6vT_THSQ|~L zp>>SXn)Y8AN}H@o_fK78`qGk-ZHd>RBo6)=vQ|atZW*4ts2)U_>EAGx`=XQf{&g+NM zV;r-|C5EK#kHomd|K%@JjNBw$(R4(CasJMyP=U_;(wDI*Ah&m6IpJ%hg4Xl-QtknQ zbr-~*Cyb2fT=2M$cx_3o1X2O&3&wF_!Tix(x^*r|LGt8@F^u@TO_4&RvXY{#9ILsJ zR+30Pbj{2v=-zv|C8-iEO`->DH+^(=-slO5%LUPdKnS3NvTJO{s@~t+yP~R(mqH8DVk?gH-DRL1 zZn)5`18SHy)N21OhV99VncduUXgs?RW}KF9s!I#Uo9e+gx6e%Xz>*=h#5#TkGJbVd zOI&y95I@Yy+6FgVI*8P&Au1hcEy%KD_bJX{RjJo6;3zs}NT6UGk^pj#VK1r0n$O6# z%qnDSPG|Lti(0W9QgC82h7CpZFfS)7*OHw+IxAfvAD`2H4G@m8AG%~hNmhqU3> zT5!_p*P$#V<&lDX*}9$Ws@Q4bh!NBVSXyNdQbKUYem=80Ai5-KMl zfHgIf7`uXL2)klwu+%8YFO^8bhL!S*=Ot*J=HE$l&9(AN3xf_nK&n-O+w&m$V?~MMrtA^dujOQTfV|{ABl{uC-(Py_NX%f zub7}QMt*$JSIBDm`sN!2Bh$mU-voG{C+YJJkE;?Wn|NX5hrWeT0K!K= zJS1AvG=UYQZ?nu&5kZG}TKzh`(rLu7M{h{J;0PJ9yU*xEdjku48@RZu3^ ze46$;y2wM!uKT=JN7QSHxEmhE8J5#S$EKHvZQ)VPd%yE7+wQi(B%QOp-dG`Q17XR< zT2PQzAdQhiOahHu3yUm8#f8!+DTGZCOYYX(LTe7VztTP_gv}O981_eDrMRb}ZA}PU za`L6IQiu$72-|0+%~FUAdAy`s!<;Z_!W0t48-QRp+sb!wMm1;#O(D@5wI)y}7F)8j zN{Xg34r$^P5~Fc=Sqh8n6-a}pklGsAn~`}9q5^3G6;emz@Mj!(W5yI(i_)bVDGj4S zuX(koIY#d9TQ*yG3(yh7Hg0WMj(Nsu1K}4?Ly@A^*1zI6-8o`}Aa6kgt z<|u_UkR-l}K)N2rNz`)E-)vuWU?Qo4GkcZDcSVXSOwUDrba$?Y+M_A%fj>?_6;DnE zDjvf1j(y(}Onp!Hz}r?RT>SRA;tV?ORFwL4OZ=sWSk3fZSGH6fIP;ED@#e$^N=5&( z+lbA!gvmMm`eHU#Rw~R^o7IM)B*lg4Ddsj-1VOR%jZ5BooeWb)9WwN$w=c!|joGkj zz21#Uddq(7H+=KsZ;G95(~z~o-c{SCM{^qsx5ia-SdX``h@F5yiq@>~$WJ(k&|Ib5_J%^%*X|Px$6U1WWp@m-0Y_ZJYfgJ#l!0s0k9%3^ z!^fhcFBa&AAu3_Kia+}#ex2Aa1*s+$+Dmu;z(|=mM3IXVbr%UHJFYmfBVv0TZJvU?S zc+UMMLa$geb|F;JvrZoUd~Qn&>&4IS+Su9;%lbqPaK#7V+nm=dOIz9M3O_ya#Zu>H zvpy1aRCA?G_`MB%bzY0GM=zPIZN{;;Dz?qjc075!%X*Cd?v-Y)xV|vlP+NL*vWIpf z$@vfh>sS%}=0|Yv42qO`CmX-_3Eu+XC?Qnn67!F^E~!IiN_@$JD`lNJvcCRoqwcJ`2_e8DQgXyz&El5ot9O9IjkmkL%Ft=0)mbq;`veAt_1($RmE3Cq(_EaO%t4man_|ZkA>ksU9p-N zgx46iJQISjk)8?*NLaHs6r#lLeUNK?-VOL;5)y(IB?&le%O8S2s$}ky+vT=%AlU3M zSnh9VFKBltf9b;uT;O0sQ5dET1SgCi>vBvYfDzS~<$VDR<}Y9uE0G33umoOr22RIV7k>F_nBT!ojiZgb3aF3dU;zqG& zH6jfLFq*wZ5@`~rCQI-g07L8-`e++~LF};%+(fXyZvZx-PVAGVKj0U@o)d{APV`eH zcompq*)u4SVn7dr*i$2sk^nRzp^t!Zabv5t=WF-*Hd~8Buw~Rk{nkn~i>2Kfb%!NG50>rg z8Tn4bd zf6IO}&~W6Tfq7@89H2x5;{f>puqi zA~lJ}RbF$Zj~yW9KAzj37`T~@rKOGBo9|>-2$1C6tGq4sN9T-!;-NVy2cHX7MjOGm z*sPEPuXR1juFzn1c!KGYS72I=ZyS28s$x~K3-Of= z^4j9A?!L#|3B`pzo;n$WTF~o9Omxd4Yeh(zyHaBJl||#o?jUQh<`ruY1kVw&M*_8r z-IeIlxY`2N27``gJHkbI4lqHsy%23S6dSeA?h$xt-Tx-exwwhf|0xU&83KLK%gs>U zr$4To$zg#goGM)X8cqQZoLQnY*0U{kDz?Z8UZ`G9#69sBd^0M5Nj!bJ4*lxbOpjr_ z{+QCtQNs7H(Tr_Z8iyL+440G)=PZ9ct?`*-APFkeaoXcW3X%5NC;~=^LFDWzq!BL+ z`XpmUpvt*cNMnua%8petn%gIxu*jJuS4&P@K2Yq_PoxERW5FX(>)mhDPUaz!HKhTxT32(hx(kCj&?S?&*BnL6^$YmzMY z3r`(2@&*Qb%N$4b#}>FVewD9mfC`StNGP3i(z-8Zj@lceJ3~ ztSj>0wD%I|12AHn6q0muaRoDC&;U=+qKqQ~j*k;%5E}F9wPWIi^9tI(I3diaJxP!@ zOy+mzwwSWM?o#6Lw3?k{AJpCtRPQpKckaB8(7*qhagZyS>?iNUS3kzCX10$mz0r~G zey0_N)nL}IV#}g_N^Pk$qQl5;sU!>vQf~W@8z!~(Bs>DcGh*g6@(_aWwQ_NL{BJ^t zNOjZ{+(XG!&F64{SLyqo{U&55Qc)Z2zdYV5(_ zA3nSXMyg`|M6*)))f8-L6RvyPvwMrE?8VT4Zg!NE%=XOJgfxX;~#K4XcEuU6rsj zq!N~vQ-cWtV+nC~J)tGHzQMilFKv-pei|1jZmREhSXEE`ie1T!9>dHqxauXZx+vy*tya5;fKxTEy*0fS7TEAd;M)eKj}oz9&c-QY^6?u)>k)vyb=cSsT7^vYk%Ysvj$ zZ=s$By~I(74DgJ31zFbIq8v-njzBo{y~SOQZBKDW!**@e*y`J-)I;2&7LLA{r+)Ml zcQxReV*MStrtmJ@FETt9T!a1_m+w^0_0IClal(>5tUk>!e4v%sM(`%}Crs+84gbJ9 zJznEQp8sq4pX&WT@BqVa|G`%08Q$VCjkDD|k<>}OWFP~Su4?Hdkabzvw^7qML*cb0 zzi@f5UoNvb^!@saE9!MZM*ROf<^Q*L%1n@@e(?2zKdBWGmf9d;sRk0>5m@=cm7G?B z>`Tlj3xpX%t=tJcIX=Me=6UY}dOvrSc+D2h_JuVazS^#*oqZ%iXTqO=nixyU@qM{^ zmJ9H9c9Rc0k7dL?pL`mtC(gd59`Yk?Ow0yOj{DK!S@dkU4cOyP+WX}e^>42)bbY_N z+aDezzpxe7*5gyFINrb6yA${5@tdnKf7ScAQ^MZ4*EW6(f7CR|Z*CAW05=D`h=DnGxG!to}JUXYlk++18zV7UF^-T5V(^ zMz1={F8nHV{Jf%eG?78-T376fb31TnmPIw%#$bd=@nGJV@Mh!1fq1){M)g4u@l$WQ z;du4UDL5={CNS!EixPn01o6ZCo%&&2f`w2*NM3De_&68*HQlr9l%DZ{#uzFYS=}r> z@Bi*UqUVqKqr+0biNWs+5(alEb0O6&0i*?O*tV{^G>|k_JmC@~miAjYiG;pAE>bNE zA#mFntUe1Ng)W{@gBQC$V?*57(gvV@AWNNK3UE@^A3y1P`<)9`6T)FwV6%Z-oz$Wp b3-yfUVS=%iWR$~dpVR&C%I)FAr{@0vt#Zo# diff --git a/examples/data/dolfyn/test_data/BenchFile01_rotate_inst2earth.nc b/examples/data/dolfyn/test_data/BenchFile01_rotate_inst2earth.nc index 194dee8a210eb3ca6a8a29428c04a9ee527f95e9..bbfeaf37e44140bcbfbde823a79956a87ac490d4 100644 GIT binary patch delta 14878 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 delta 14760 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 From 7fa66b027d2f61723613ce05fc9250e5acf230d3 Mon Sep 17 00:00:00 2001 From: jmcvey3 <53623232+jmcvey3@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:07:46 -0800 Subject: [PATCH 6/6] Black formatting --- mhkit/dolfyn/adv/turbulence.py | 3 +- mhkit/dolfyn/io/base.py | 230 +++++++++++++++++++-------------- mhkit/dolfyn/io/nortek.py | 8 +- mhkit/dolfyn/io/nortek2.py | 61 +++++---- mhkit/dolfyn/io/nortek2_lib.py | 2 +- 5 files changed, 173 insertions(+), 131 deletions(-) diff --git a/mhkit/dolfyn/adv/turbulence.py b/mhkit/dolfyn/adv/turbulence.py index 4e231cc33..bfc3e6d75 100644 --- a/mhkit/dolfyn/adv/turbulence.py +++ b/mhkit/dolfyn/adv/turbulence.py @@ -518,7 +518,8 @@ def _integral_TE01(self, I_tke, theta): out = np.empty_like(I_tke.flatten()) for i, (b, t) in enumerate(zip(I_tke.flatten(), theta.flatten())): out[i] = np.trapz( - cbrt(x**2 - 2 / b * np.cos(t) * x + b ** (-2)) * np.exp(-0.5 * x**2), + cbrt(x**2 - 2 / b * np.cos(t) * x + b ** (-2)) + * np.exp(-0.5 * x**2), x, ) diff --git a/mhkit/dolfyn/io/base.py b/mhkit/dolfyn/io/base.py index ba37a8afe..6d7a6a955 100644 --- a/mhkit/dolfyn/io/base.py +++ b/mhkit/dolfyn/io/base.py @@ -119,175 +119,209 @@ def _create_dataset(data): Direction 'dir' coordinates are set in `set_coords` """ - tag = ['_avg', '_b5', '_echo', '_bt', '_gps', '_altraw', '_altraw_avg', '_sl'] + tag = ["_avg", "_b5", "_echo", "_bt", "_gps", "_altraw", "_altraw_avg", "_sl"] ds_dict = {} - for key in data['coords']: - ds_dict[key] = {"dims": (key), "data": data['coords'][key]} + for key in data["coords"]: + ds_dict[key] = {"dims": (key), "data": data["coords"][key]} # Set various coordinate frames - if 'n_beams_avg' in data['attrs']: - beams = data['attrs']['n_beams_avg'] + if "n_beams_avg" in data["attrs"]: + beams = data["attrs"]["n_beams_avg"] else: - beams = data['attrs']['n_beams'] + beams = data["attrs"]["n_beams"] n_beams = max(min(beams, 4), 3) beams = np.arange(1, n_beams + 1, dtype=np.int32) - ds_dict['beam'] = {"dims": ('beam'), "data": beams} - ds_dict['dir'] = {"dims": ('dir'), "data": beams} - ds_dict['earth'] = {"dims": ('earth'), "data": ['E', 'N', 'U']} - ds_dict['inst'] = {"dims": ('inst'), "data": ['X', 'Y', 'Z']} - - data['units'].update({'beam': '1', - 'dir': '1', - 'earth': '1', - 'inst': '1'}) - data['long_name'].update({'beam': 'Beam Reference Frame', - 'dir': 'Reference Frame', - 'earth': 'Earth Reference Frame', - 'inst': 'Instrument Reference Frame'}) + ds_dict["beam"] = {"dims": ("beam"), "data": beams} + ds_dict["dir"] = {"dims": ("dir"), "data": beams} + ds_dict["earth"] = {"dims": ("earth"), "data": ["E", "N", "U"]} + ds_dict["inst"] = {"dims": ("inst"), "data": ["X", "Y", "Z"]} + + data["units"].update({"beam": "1", "dir": "1", "earth": "1", "inst": "1"}) + data["long_name"].update( + { + "beam": "Beam Reference Frame", + "dir": "Reference Frame", + "earth": "Earth Reference Frame", + "inst": "Instrument Reference Frame", + } + ) # Iterate through data variables and add them to new dictionary - for key in data['data_vars']: + for key in data["data_vars"]: # orientation matrices - if 'mat' in key: - if 'inst' in key: # beam2inst & inst2head orientation matrices - if 'x1' not in ds_dict: - ds_dict['x1'] = {"dims": ('x1'), "data": beams} - ds_dict['x2'] = {"dims": ('x2'), "data": beams} + if "mat" in key: + if "inst" in key: # beam2inst & inst2head orientation matrices + if "x1" not in ds_dict: + ds_dict["x1"] = {"dims": ("x1"), "data": beams} + ds_dict["x2"] = {"dims": ("x2"), "data": beams} - ds_dict[key] = {"dims": ('x1', 'x2'), "data": data['data_vars'][key]} - data['units'].update({key: '1'}) - data['long_name'].update({key: 'Rotation Matrix'}) + ds_dict[key] = {"dims": ("x1", "x2"), "data": data["data_vars"][key]} + data["units"].update({key: "1"}) + data["long_name"].update({key: "Rotation Matrix"}) - elif 'orientmat' in key: # earth2inst orientation matrix + elif "orientmat" in key: # earth2inst orientation matrix if any(val in key for val in tag): - tg = '_' + key.rsplit('_')[-1] + tg = "_" + key.rsplit("_")[-1] else: - tg = '' + tg = "" - ds_dict[key] = {"dims": ('earth', 'inst', 'time' + tg), "data": data['data_vars'][key]} - data['units'].update({key: data['units']['orientmat']}) - data['long_name'].update({key: data['long_name']['orientmat']}) + ds_dict[key] = { + "dims": ("earth", "inst", "time" + tg), + "data": data["data_vars"][key], + } + data["units"].update({key: data["units"]["orientmat"]}) + data["long_name"].update({key: data["long_name"]["orientmat"]}) # quaternion units never change - elif 'quaternions' in key: + elif "quaternions" in key: if any(val in key for val in tag): - tg = '_' + key.rsplit('_')[-1] + tg = "_" + key.rsplit("_")[-1] else: - tg = '' + tg = "" - if 'q' not in ds_dict: - ds_dict['q'] = {"dims": ("q"), "data": ['w', 'x', 'y', 'z']} - data['units'].update({'q': '1'}) - data['long_name'].update({'q': 'Quaternion Vector Components'}) + if "q" not in ds_dict: + ds_dict["q"] = {"dims": ("q"), "data": ["w", "x", "y", "z"]} + data["units"].update({"q": "1"}) + data["long_name"].update({"q": "Quaternion Vector Components"}) - ds_dict[key] = {"dims": ("q", "time" + tg), "data": data['data_vars'][key]} - data['units'].update({key: data['units']['quaternions']}) - data['long_name'].update({key: data['long_name']['quaternions']}) + ds_dict[key] = {"dims": ("q", "time" + tg), "data": data["data_vars"][key]} + data["units"].update({key: data["units"]["quaternions"]}) + data["long_name"].update({key: data["long_name"]["quaternions"]}) else: - shp = data['data_vars'][key].shape + shp = data["data_vars"][key].shape if len(shp) == 1: # 1D variables - if '_altraw_avg' in key: - tg = '_altraw_avg' + if "_altraw_avg" in key: + tg = "_altraw_avg" elif any(val in key for val in tag): - tg = '_' + key.rsplit('_')[-1] + tg = "_" + key.rsplit("_")[-1] else: - tg = '' - ds_dict[key] = {"dims": ("time" + tg), "data": data['data_vars'][key]} + tg = "" + ds_dict[key] = {"dims": ("time" + tg), "data": data["data_vars"][key]} elif len(shp) == 2: # 2D variables - if key == 'echo': - ds_dict[key] = {"dims": ("range_echo", "time_echo"), "data": data['data_vars'][key]} - elif key == 'samp_altraw': - ds_dict[key] = {"dims": ("n_altraw", "time_altraw"), "data": data['data_vars'][key]} - elif key == 'samp_altraw_avg': - ds_dict[key] = {"dims": ("n_altraw_avg", "time_altraw_avg"), "data": data['data_vars'][key]} + if key == "echo": + ds_dict[key] = { + "dims": ("range_echo", "time_echo"), + "data": data["data_vars"][key], + } + elif key == "samp_altraw": + ds_dict[key] = { + "dims": ("n_altraw", "time_altraw"), + "data": data["data_vars"][key], + } + elif key == "samp_altraw_avg": + ds_dict[key] = { + "dims": ("n_altraw_avg", "time_altraw_avg"), + "data": data["data_vars"][key], + } # ADV/ADCP instrument vector data, bottom tracking elif shp[0] == n_beams and not any(val in key for val in tag[:3]): - if 'bt' in key and 'time_bt' in data['coords']: - tg = '_bt' + if "bt" in key and "time_bt" in data["coords"]: + tg = "_bt" else: - tg = '' - if any(key.rsplit('_')[0] in s for s in ['amp', 'corr', 'dist', 'prcnt_gd']): - dim0 = 'beam' + tg = "" + if any( + key.rsplit("_")[0] in s + for s in ["amp", "corr", "dist", "prcnt_gd"] + ): + dim0 = "beam" else: - dim0 = 'dir' - ds_dict[key] = {"dims": (dim0, "time" + tg), "data": data['data_vars'][key]} + dim0 = "dir" + ds_dict[key] = { + "dims": (dim0, "time" + tg), + "data": data["data_vars"][key], + } # ADCP IMU data elif shp[0] == 3: if not any(val in key for val in tag): - tg = '' + tg = "" else: tg = [val for val in tag if val in key] tg = tg[0] - if 'dirIMU' not in ds_dict: - ds_dict['dirIMU'] = {"dims": ("dirIMU"), "data": [1, 2, 3]} - data['units'].update({'dirIMU': '1'}) - data['long_name'].update({'dirIMU': 'Reference Frame'}) + if "dirIMU" not in ds_dict: + ds_dict["dirIMU"] = {"dims": ("dirIMU"), "data": [1, 2, 3]} + data["units"].update({"dirIMU": "1"}) + data["long_name"].update({"dirIMU": "Reference Frame"}) - ds_dict[key] = {"dims": ("dirIMU", "time" + tg), "data": data['data_vars'][key]} + ds_dict[key] = { + "dims": ("dirIMU", "time" + tg), + "data": data["data_vars"][key], + } - elif 'b5' in tg: - ds_dict[key] = {"dims": ("range_b5", "time_b5"), "data": data['data_vars'][key]} + elif "b5" in tg: + ds_dict[key] = { + "dims": ("range_b5", "time_b5"), + "data": data["data_vars"][key], + } elif len(shp) == 3: # 3D variables - if 'vel' in key: - dim0 = 'dir' + if "vel" in key: + dim0 = "dir" else: # amp, corr, prcnt_gd, status - dim0 = 'beam' + dim0 = "beam" - if not any(val in key for val in tag) or ('_avg' in key): - if '_avg' in key: - tg = '_avg' + if not any(val in key for val in tag) or ("_avg" in key): + if "_avg" in key: + tg = "_avg" else: - tg = '' - ds_dict[key] = {"dims": (dim0, "range" + tg, "time" + tg), "data": data['data_vars'][key]} + tg = "" + ds_dict[key] = { + "dims": (dim0, "range" + tg, "time" + tg), + "data": data["data_vars"][key], + } - elif 'b5' in key: + elif "b5" in key: # "vel_b5" sometimes stored as (1, range_b5, time_b5) - ds_dict[key] = {"dims": ("range_b5", "time_b5"), "data": data['data_vars'][key][0]} - elif 'sl' in key: - ds_dict[key] = {"dims": (dim0, "range_sl", "time"), "data": data['data_vars'][key]} + ds_dict[key] = { + "dims": ("range_b5", "time_b5"), + "data": data["data_vars"][key][0], + } + elif "sl" in key: + ds_dict[key] = { + "dims": (dim0, "range_sl", "time"), + "data": data["data_vars"][key], + } else: - warnings.warn(f'Variable not included in dataset: {key}') + warnings.warn(f"Variable not included in dataset: {key}") # Create dataset ds = xr.Dataset.from_dict(ds_dict) # Assign data array attributes for key in ds.variables: - for md in ['units', 'long_name', 'standard_name']: + for md in ["units", "long_name", "standard_name"]: if key in data[md]: ds[key].attrs[md] = data[md][key] if len(ds[key].shape) > 1: - ds[key].attrs['coverage_content_type'] = 'physicalMeasurement' + ds[key].attrs["coverage_content_type"] = "physicalMeasurement" try: # make sure ones with tags get units - tg = '_' + key.rsplit('_')[-1] + tg = "_" + key.rsplit("_")[-1] if any(val in key for val in tag): - ds[key].attrs[md] = data[md][key[:-len(tg)]] + ds[key].attrs[md] = data[md][key[: -len(tg)]] except: pass # Assign coordinate attributes for ky in ds.dims: - ds[ky].attrs['coverage_content_type'] = 'coordinate' - r_list = [r for r in ds.coords if 'range' in r] + ds[ky].attrs["coverage_content_type"] = "coordinate" + r_list = [r for r in ds.coords if "range" in r] for ky in r_list: - ds[ky].attrs['units'] = 'm' - ds[ky].attrs['long_name'] = 'Profile Range' - ds[ky].attrs['description'] = 'Distance to the center of each depth bin' - time_list = [t for t in ds.coords if 'time' in t] + ds[ky].attrs["units"] = "m" + ds[ky].attrs["long_name"] = "Profile Range" + ds[ky].attrs["description"] = "Distance to the center of each depth bin" + time_list = [t for t in ds.coords if "time" in t] for ky in time_list: - ds[ky].attrs['units'] = 'seconds since 1970-01-01 00:00:00' - ds[ky].attrs['long_name'] = 'Time' - ds[ky].attrs['standard_name'] = 'time' + ds[ky].attrs["units"] = "seconds since 1970-01-01 00:00:00" + ds[ky].attrs["long_name"] = "Time" + ds[ky].attrs["standard_name"] = "time" # Set dataset metadata - ds.attrs = data['attrs'] + ds.attrs = data["attrs"] return ds diff --git a/mhkit/dolfyn/io/nortek.py b/mhkit/dolfyn/io/nortek.py index 1a829bbea..897fce3c2 100644 --- a/mhkit/dolfyn/io/nortek.py +++ b/mhkit/dolfyn/io/nortek.py @@ -273,10 +273,10 @@ def __init__( burst_seconds = self.config["n_burst"] / fs else: burst_seconds = round(1 / fs, 3) - da["duty_cycle_description"] = ( - "{} second bursts collected at {} Hz, with bursts taken every {} minutes".format( - burst_seconds, fs, self.config["burst_interval"] / 60 - ) + da[ + "duty_cycle_description" + ] = "{} second bursts collected at {} Hz, with bursts taken every {} minutes".format( + burst_seconds, fs, self.config["burst_interval"] / 60 ) self.burst_start = np.zeros(self.n_samp_guess, dtype="bool") da["fs"] = self.config["fs"] diff --git a/mhkit/dolfyn/io/nortek2.py b/mhkit/dolfyn/io/nortek2.py index 41fe8b09f..bd84a5f17 100644 --- a/mhkit/dolfyn/io/nortek2.py +++ b/mhkit/dolfyn/io/nortek2.py @@ -262,9 +262,12 @@ def init_data(self, ens_start, ens_stop): # ID 26 and 31 recorded infrequently def n_id(id): - return ((self._index['ID'] == id) & - (self._index['ens'] >= ens_start) & - (self._index['ens'] < ens_stop)).sum() + return ( + (self._index["ID"] == id) + & (self._index["ens"] >= ens_start) + & (self._index["ens"] < ens_stop) + ).sum() + n_altraw = {26: n_id(26), 31: n_id(31)} if not n_altraw[26] and 26 in self._burst_readers: self._burst_readers.pop(26) @@ -274,7 +277,7 @@ def n_id(id): for ky in self._burst_readers: if (ky == 26) or (ky == 31): n = n_altraw[ky] - ens = np.zeros(n, dtype='uint32') + ens = np.zeros(n, dtype="uint32") else: ens = np.arange(ens_start, ens_stop).astype("uint32") n = nens @@ -436,32 +439,36 @@ def sci_data(self, dat): ) -def _altraw_reorg(outdat, tag=''): - """Submethod for `_reorg` particular to raw altimeter pings (ID 26 and 31) - """ - for ky in list(outdat['data_vars']): - if ky.endswith('raw' + tag) and not ky.endswith('_altraw' + tag): - outdat['data_vars'].pop(ky) - outdat['coords']['time_altraw' + tag] = outdat['coords'].pop('timeraw' + tag) +def _altraw_reorg(outdat, tag=""): + """Submethod for `_reorg` particular to raw altimeter pings (ID 26 and 31)""" + for ky in list(outdat["data_vars"]): + if ky.endswith("raw" + tag) and not ky.endswith("_altraw" + tag): + outdat["data_vars"].pop(ky) + outdat["coords"]["time_altraw" + tag] = outdat["coords"].pop("timeraw" + tag) # convert "signed fractional" to float - outdat['data_vars']['samp_altraw' + tag] = outdat['data_vars']['samp_altraw' + tag].astype('float32') / 2**8 + outdat["data_vars"]["samp_altraw" + tag] = ( + outdat["data_vars"]["samp_altraw" + tag].astype("float32") / 2**8 + ) # Read altimeter status - outdat['data_vars'].pop('status_altraw' + tag) - status_alt = lib._alt_status2data(outdat['data_vars']['status_alt' + tag]) + outdat["data_vars"].pop("status_altraw" + tag) + status_alt = lib._alt_status2data(outdat["data_vars"]["status_alt" + tag]) for ky in status_alt: - outdat['attrs'][ky + tag] = lib._collapse( - status_alt[ky].astype('uint8'), name=ky) - outdat['data_vars'].pop('status_alt' + tag) + outdat["attrs"][ky + tag] = lib._collapse( + status_alt[ky].astype("uint8"), name=ky + ) + outdat["data_vars"].pop("status_alt" + tag) # Power level index - power = {0: 'high', 1: 'med-high', 2: 'med-low', 3: 'low'} - outdat['attrs']['power_level_alt' + tag] = power[outdat['attrs'].pop('power_level_idx_alt' + tag)] + power = {0: "high", 1: "med-high", 2: "med-low", 3: "low"} + outdat["attrs"]["power_level_alt" + tag] = power[ + outdat["attrs"].pop("power_level_idx_alt" + tag) + ] # Other attrs - for ky in list(outdat['attrs']): - if ky.endswith('raw' + tag): - outdat['attrs'][ky.split('raw')[0] + '_alt' + tag] = outdat['attrs'].pop(ky) + for ky in list(outdat["attrs"]): + if ky.endswith("raw" + tag): + outdat["attrs"][ky.split("raw")[0] + "_alt" + tag] = outdat["attrs"].pop(ky) def _reorg(dat): @@ -493,7 +500,7 @@ def _reorg(dat): (24, "_b5"), (26, "raw"), (28, "_echo"), - (31, 'raw_avg') + (31, "raw_avg"), ]: if id in [24, 26]: collapse_exclude = [0] @@ -598,7 +605,7 @@ def _reorg(dat): if 26 in dat: _altraw_reorg(outdat) if 31 in dat: - _altraw_reorg(outdat, tag='_avg') + _altraw_reorg(outdat, tag="_avg") # Read status data status0_vars = [x for x in outdat["data_vars"] if "status0" in x] @@ -769,13 +776,13 @@ def split_dp_datasets(ds): # Figure out which variables belong to which profile based on length of time variables t_dict = {} for t in ds.coords: - if 'time' in t: + if "time" in t: t_dict[t] = ds[t].size other_coords = [] for key, val in t_dict.items(): - if val != t_dict['time']: - if key.endswith('altraw'): + if val != t_dict["time"]: + if key.endswith("altraw"): # altraw goes with burst, altraw_avg goes with avg continue other_coords.append(key) diff --git a/mhkit/dolfyn/io/nortek2_lib.py b/mhkit/dolfyn/io/nortek2_lib.py index 65ab33059..8c7156a36 100644 --- a/mhkit/dolfyn/io/nortek2_lib.py +++ b/mhkit/dolfyn/io/nortek2_lib.py @@ -578,7 +578,7 @@ def _calc_config(index): # change in "n_cells" doesn't matter lob = np.unique(_beams_cy) beams = list(map(_beams_cy_int2dict, lob, 23 * np.ones(lob.size))) - if all([d['cy'] for d in beams]) and all([d['n_beams'] for d in beams]): + if all([d["cy"] for d in beams]) and all([d["n_beams"] for d in beams]): err = False if err: raise Exception("beams_cy are not identical for id: 0x{:X}.".format(id))