diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1a3914ee..e4bdb35fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,6 +31,7 @@ jobs: python -m pip install --upgrade pip wheel pip install h5pyd --upgrade pip install nose + pip install pytest pip install coveralls pip install . diff --git a/.gitignore b/.gitignore index 86b53c1e1..ce3e47416 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ $ cat .gitignore .DS_Store .DS_Store? .gitignore +.vscode # Exemptions !**/examples/data/wave/*.mat diff --git a/examples/adcp_example.ipynb b/examples/adcp_example.ipynb new file mode 100644 index 000000000..bb4ddb461 --- /dev/null +++ b/examples/adcp_example.ipynb @@ -0,0 +1,1575 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reading ADCP Data with MHKiT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following example demonstrates a simple workflow for analyzing Acoustic Doppler Current Profiler (ADCP) data using MHKiT. MHKiT has brought the DOLfYN codebase in as an MHKiT module for working with ADCP and Acoustic Doppler Velocimeter (ADV) data. The DOLfYN MHKiT module is transferring functionality in two parts. In phase 1 the basic IO and core functions of DOLfYN are brought in. These functions include reading instrument data and rotating thru coordinate systems. In phase 2 DOLfYN analysis functions are brought in. As phase 2 is still in progress we will import the analysis tools from the [DOLfYN](https://github.com/lkilcher/dolfyn) package.\n", + "\n", + "A typical ADCP data workflow is broken down into\n", + " 1. Review the raw data\n", + " - Check timestamps\n", + " - Calculate/check that the depth bin locations are correct\n", + " - Look at velocity, beam amplitude and/or beam correlation data quality\n", + " 2. Remove data located above the water surface or below the seafloor\n", + " 3. Check for spurious datapoints and remove if necessary\n", + " 4. If not already done within the instrument, average the data into time bins of a set time length (normally 5 to 10 min)\n", + " 5. Conduct further analysis as required\n", + "\n", + "Start by importing the necessary DOLfYN tools:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from mhkit import dolfyn as dlfn\n", + "from dolfyn.adp import api" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Raw Instrument Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The core benefit of DOLfYN is that it can read in raw data directly after transferring it off of the ADCP. The ADCP used here is a Nortek Signature1000, with the file extension '.ad2cp'. This specific dataset contains several hours worth of velocity data collected at 1 Hz from the ADCP mounted on a bottom lander in a tidal inlet. \n", + "The instruments that DOLfYN supports are listed in the [docs](https://dolfyn.readthedocs.io/en/latest/about.html).\n", + "\n", + "Start by reading in the raw datafile downloaded from the instrument. The `read` function reads the raw file and dumps the information into an xarray Dataset, which contains a few groups of variables:\n", + "\n", + "1. Velocity in the instrument-saved coordinate system (beam, XYZ, ENU)\n", + "2. Beam amplitude and correlation data\n", + "3. Measurements of the instrument's bearing and environment\n", + "4. Orientation matrices DOLfYN uses for rotating through coordinate frames.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Indexing data/dolfyn/Sig1000_tidal.ad2cp... Done.\n", + "Reading file data/dolfyn/Sig1000_tidal.ad2cp ...\n" + ] + } + ], + "source": [ + "ds = dlfn.read('data/dolfyn/Sig1000_tidal.ad2cp')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two ways to see what's in a Dataset. The first is to simply type the dataset's name to see the standard xarray output. To access a particular variable in a dataset, use dict-style (`ds['vel']`) or attribute-style syntax (`ds.vel`). See the [xarray docs](http://xarray.pydata.org/en/stable/getting-started-guide/quick-overview.html) for more details on how to use the xarray format." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:              (time: 55000, time_b5: 55000, dirIMU: 3, range_b5: 28, beam: 4, range: 28, dir: 4, earth: 3, inst: 3, q: 4, x: 4, x*: 4)\n",
+       "Coordinates:\n",
+       "  * time                 (time) datetime64[ns] 2020-08-15T00:20:00.500999 ......\n",
+       "  * time_b5              (time_b5) datetime64[ns] 2020-08-15T00:20:00.438499 ...\n",
+       "  * dirIMU               (dirIMU) <U1 'E' 'N' 'U'\n",
+       "  * range_b5             (range_b5) float64 0.6 1.1 1.6 2.1 ... 13.1 13.6 14.1\n",
+       "  * beam                 (beam) int32 1 2 3 4\n",
+       "  * range                (range) float64 0.6 1.1 1.6 2.1 ... 12.6 13.1 13.6 14.1\n",
+       "  * dir                  (dir) <U2 'E' 'N' 'U1' 'U2'\n",
+       "  * earth                (earth) <U1 'E' 'N' 'U'\n",
+       "  * inst                 (inst) <U1 'X' 'Y' 'Z'\n",
+       "  * q                    (q) <U1 'w' 'x' 'y' 'z'\n",
+       "  * x                    (x) int32 1 2 3 4\n",
+       "  * x*                   (x*) int32 1 2 3 4\n",
+       "Data variables: (12/38)\n",
+       "    c_sound              (time) float32 1.502e+03 1.502e+03 ... 1.498e+03\n",
+       "    temp                 (time) float32 14.55 14.55 14.55 ... 13.47 13.47 13.47\n",
+       "    pressure             (time) float32 9.713 9.718 9.718 ... 9.596 9.594 9.596\n",
+       "    mag                  (dirIMU, time) float32 72.5 72.7 72.6 ... -197.2 -195.7\n",
+       "    accel                (dirIMU, time) float32 -0.00479 -0.01437 ... 9.729\n",
+       "    batt                 (time) float32 16.6 16.6 16.6 16.6 ... 16.4 16.4 15.2\n",
+       "    ...                   ...\n",
+       "    telemetry_data       (time) uint8 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0\n",
+       "    boost_running        (time) uint8 0 0 0 0 0 0 0 0 1 0 ... 0 1 0 0 0 0 0 0 1\n",
+       "    heading              (time) float32 -12.52 -12.51 -12.51 ... -12.52 -12.5\n",
+       "    pitch                (time) float32 -0.065 -0.06 -0.06 ... -0.06 -0.05 -0.05\n",
+       "    roll                 (time) float32 -7.425 -7.42 -7.42 ... -6.45 -6.45 -6.45\n",
+       "    beam2inst_orientmat  (x, x*) float32 1.183 0.0 -1.183 ... 0.5518 0.0 0.5518\n",
+       "Attributes: (12/33)\n",
+       "    filehead_config:       {'CLOCKSTR': {'TIME': '"2020-08-13 13:56:21"'}, 'I...\n",
+       "    inst_model:            Signature1000\n",
+       "    inst_make:             Nortek\n",
+       "    inst_type:             ADCP\n",
+       "    rotate_vars:           ['vel', 'accel', 'accel_b5', 'angrt', 'angrt_b5', ...\n",
+       "    burst_config:          {'press_valid': True, 'temp_valid': True, 'compass...\n",
+       "    ...                    ...\n",
+       "    proc_idle_less_3pct:   0\n",
+       "    proc_idle_less_6pct:   0\n",
+       "    proc_idle_less_12pct:  0\n",
+       "    coord_sys:             earth\n",
+       "    has_imu:               1\n",
+       "    fs:                    1
" + ], + "text/plain": [ + "\n", + "Dimensions: (time: 55000, time_b5: 55000, dirIMU: 3, range_b5: 28, beam: 4, range: 28, dir: 4, earth: 3, inst: 3, q: 4, x: 4, x*: 4)\n", + "Coordinates:\n", + " * time (time) datetime64[ns] 2020-08-15T00:20:00.500999 ......\n", + " * time_b5 (time_b5) datetime64[ns] 2020-08-15T00:20:00.438499 ...\n", + " * dirIMU (dirIMU) : Nortek Signature1000\n", + " . 15.28 hours (started: Aug 15, 2020 00:20)\n", + " . earth-frame\n", + " . (55000 pings @ 1Hz)\n", + " Variables:\n", + " - time ('time',)\n", + " - time_b5 ('time_b5',)\n", + " - vel ('dir', 'range', 'time')\n", + " - vel_b5 ('range_b5', 'time_b5')\n", + " - range ('range',)\n", + " - orientmat ('earth', 'inst', 'time')\n", + " - heading ('time',)\n", + " - pitch ('time',)\n", + " - roll ('time',)\n", + " - temp ('time',)\n", + " - pressure ('time',)\n", + " - amp ('beam', 'range', 'time')\n", + " - amp_b5 ('range_b5', 'time_b5')\n", + " - corr ('beam', 'range', 'time')\n", + " - corr_b5 ('range_b5', 'time_b5')\n", + " - accel ('dirIMU', 'time')\n", + " - angrt ('dirIMU', 'time')\n", + " - mag ('dirIMU', 'time')\n", + " ... and others (see `.variables`)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds_dolfyn = ds.velds\n", + "ds_dolfyn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## First Steps and QC'ing Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.) Set deployment height" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because this is a Nortek instrument, the deployment software doesn't take into account the deployment height, aka where in the water column the ADCP is. The center of the first depth bin is located at a distance = deployment height + blanking distance + cell size, so the `range` coordinate needs to be corrected so that '0' corresponds to the seafloor. This can be done using the `set_range_offset` function. This same function can be used to account for the depth of a down-facing instrument below the water surface.\n", + "\n", + "Note, if using a Teledyne RDI ADCP, TRDI's deployment software asks the user to enter the deployment height/depth during configuration. If needed, this can be adjusted after-the-fact using `set_range_offset` as well. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# The ADCP transducers were measured to be 0.6 m from the feet of the lander\n", + "api.clean.set_range_offset(ds, 0.6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, the center of bin 1 is located at 1.2 m:" + ] + }, + { + "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.DataArray 'range' (range: 28)>\n",
+       "array([ 1.2,  1.7,  2.2,  2.7,  3.2,  3.7,  4.2,  4.7,  5.2,  5.7,  6.2,  6.7,\n",
+       "        7.2,  7.7,  8.2,  8.7,  9.2,  9.7, 10.2, 10.7, 11.2, 11.7, 12.2, 12.7,\n",
+       "       13.2, 13.7, 14.2, 14.7])\n",
+       "Coordinates:\n",
+       "  * range    (range) float64 1.2 1.7 2.2 2.7 3.2 ... 12.7 13.2 13.7 14.2 14.7\n",
+       "Attributes:\n",
+       "    units:    m
" + ], + "text/plain": [ + "\n", + "array([ 1.2, 1.7, 2.2, 2.7, 3.2, 3.7, 4.2, 4.7, 5.2, 5.7, 6.2, 6.7,\n", + " 7.2, 7.7, 8.2, 8.7, 9.2, 9.7, 10.2, 10.7, 11.2, 11.7, 12.2, 12.7,\n", + " 13.2, 13.7, 14.2, 14.7])\n", + "Coordinates:\n", + " * range (range) float64 1.2 1.7 2.2 2.7 3.2 ... 12.7 13.2 13.7 14.2 14.7\n", + "Attributes:\n", + " units: m" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds.range" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.) Remove data beyond surface level" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To reduce the amount of data the code must run through, we can remove all data at and above the water surface. Because the instrument was looking up, we can use the pressure sensor data and the function `find_surface_from_P`. This does require that the pressure sensor was 'zeroed' prior to deployment. If the instrument is looking down or lacks pressure data, use the function `find_surface` to detect the seabed or water surface.\n", + "\n", + "ADCPs don't measure water salinity, so it will need to be given to the function. The returned dataset contains the an additional variable \"depth\". If `find_surface_from_P` is run after `set_range_offset`, depth is the distance of the water surface away from the seafloor; otherwise it is the distance to the ADCP pressure sensor.\n", + "\n", + "After calculating depth, data in depth bins at and above the physical water surface can be removed using `nan_beyond_surface`. Note that this function returns a new dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "api.clean.find_surface_from_P(ds, salinity=31)\n", + "ds = api.clean.nan_beyond_surface(ds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.) Correlation filter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once beyond-surface bins have been removed, ADCP data is typically filtered by acoustic signal correlation to clear out spurious velocity datapoints (caused by bubbles, kelp, fish, etc moving through one or multiple beams).\n", + "\n", + "We can take a quick look at the data to see about where this value should be using xarray's built-in plotting.\n", + "In the following line of code, we use xarray's slicing capabilities to show data from beam 1 between a range of 0 to 10 m from the ADCP.\n", + "\n", + "Not all ADCPs return acoustic signal correlation, which in essence is a quantitative measure of signal quality. ADCPs with older hardware do not provide a correlation measurement, so this step will be skipped with these instruments." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "ds.corr.sel(beam=1, range=slice(0,10)).plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's a good idea to check the other beams as well. Much of this data is high quality, and to not lose data will low correlation caused by natural variation, we'll use the `correlation_filter` to set velocity values corresponding to correlations below 50% to NaN.\n", + "\n", + "Note that this threshold is dependent on the deployment environment and instrument, and it isn't uncommon to use a value as low as 30%, or to pass on this function completely." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "ds = api.clean.correlation_filter(ds, thresh=50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Review the Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the data has been cleaned, the next step is to rotate the velocity data into true East, North, Up coordinates.\n", + "\n", + "ADCPs use an internal compass or magnetometer to determine magnetic ENU directions. The `set_declination` function takes the user supplied magnetic declination (which can be looked up online for specific coordinates) and adjusts the velocity data accordingly.\n", + "\n", + "Instruments save vector data in the coordinate system specified in the deployment configuration file. To make the data useful, it must be rotated through coordinate systems (\"beam\"<->\"inst\"<->\"earth\"<->\"principal\"), done through the `rotate2` function. If the \"earth\" (ENU) coordinate system is specified, DOLfYN will automatically rotate the dataset through the necessary coordinate systems to get there. The `inplace` set as true will alter the input dataset \"in place\", a.k.a. it not create a new dataset.\n", + "\n", + "Because this ADCP data was already in the \"earth\" coordinate system, `rotate2` will return the input dataset. `set_declination` will run correctly no matter the coordinate system." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data is already in the earth coordinate system\n" + ] + } + ], + "source": [ + "dlfn.set_declination(ds, 15.8, inplace=True) # 15.8 deg East\n", + "dlfn.rotate2(ds, 'earth', inplace=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To rotate into the principal frame of reference (streamwise, cross-stream, vertical), if desired, we must first calculate the depth-averaged principal flow heading and add it to the dataset attributes. Then the dataset can be rotated using the same `rotate2` function. We use `inplace=False` because we do not want to alter the input dataset here." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "ds.attrs['principal_heading'] = dlfn.calc_principal_heading(ds.vel.mean('range'))\n", + "ds_streamwise = dlfn.rotate2(ds, 'principal', inplace=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because this deployment was set up in \"burst mode\", the next standard step in this analysis is to average the velocity data into time bins. \n", + "\n", + "If an instrument was set up to record velocity data in an \"averaging mode\" (a specific profile and/or average interval, e.g. take an average of 5 minutes of data every 30 minutes), this step was completed within the ADCP during deployment and can be skipped.\n", + "\n", + "To average the data into time bins (aka ensembles), start by initiating the binning tool `VelBinner`. \"n_bin\" is the number of data points in each ensemble, in this case 300 seconds worth of data, and \"fs\" is the sampling frequency, which is 1 Hz for this deployment. Once initiated, average the data into ensembles using the binning tool's `do_avg` function." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "avg_tool = api.VelBinner(n_bin=ds.fs*300, fs=ds.fs)\n", + "ds_avg = avg_tool.do_avg(ds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Two more variables not automatically provided that may be of interest are the horizontal velocity magnitude (speed) and its direction, respectively `U_mag` and `U_dir`. There are included as \"shortcut\" functions, and are accessed through the keyword `velds`, as shown in the code block below. The full list of \"shorcuts\" are listed [here](https://dolfyn.readthedocs.io/en/latest/apidoc/dolfyn.shortcuts.html).\n", + "\n", + "Because of the way ADCP coordinate systems were established, when in ENU, `U_dir` is in \"degrees CCW from East\" (aka \"Cartesian angle\"), as opposed to \"degrees CW from North\", typical for navigation. The `convert_degrees` function will transform from one to the other and vice versa, and is used below to save `U_dir` in place." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "ds_avg['U_mag'] = ds_avg.velds.U_mag\n", + "\n", + "ds_avg['U_dir'] = ds_avg.velds.U_dir\n", + "ds_avg['U_dir'].values = dlfn.tools.misc.convert_degrees(ds_avg['U_dir'].values, tidal_mode=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plotting can be accomplished through the user's preferred package. Matplotlib is shown here for simplicity, and flow speed and direction are plotted below with a blue line delineating the water surface level." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArUAAAHbCAYAAADVtmL2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAABbTklEQVR4nO3dd5xcV33///dn6jbtqtdV77IsF8ndBjeMjY1NMY5pocb5khBIwjeJ+ZJAfiT5hhDSQxL8JcZUG7ANGDAuFMe4W5J7l61erN61ZWbO748Z7f3MMqNdSbs7OzOvpx770Jkz5577ufeee+fs2TP3WghBAAAAQDWLVToAAAAA4HjRqQUAAEDVo1MLAACAqkenFgAAAFWPTi0AAACqHp1aAAAAVL1B69Sa2Y1mttXMnnV5f29mL5rZ02b2AzMbOVjrBwAAQP0YzJHamyRd2ivvXkmLQwhLJL0s6dODuH4AAADUiUHr1IYQ7pe0s1fePSGETOHlI5LaB2v9AAAAqB+VnFP7YUk/q+D6AQAAUCMSlVipmX1GUkbSt49Q5jpJ10lSc3Pz0gULFgxRdAAAAP2zYsWK7SGEcZWOI946PYTMoQGvNxzadncIofd00mFpyDu1ZvZBSVdIuiiEEMqVCyHcIOkGSVq2bFlYvnz50AQIAADQT2a2ttIxSFLIdCi94NoBr7fjiX8bO+CVDpIh7dSa2aWS/lTSG0MIB4dy3QAAAKhdg9apNbObJZ0vaayZbZD0OeXvdpCWdK+ZSdIjIYT/NVgxAAAA1AWTlO9b1a1B69SGEN5dIvu/B2t9AAAAqF8V+aIYAAAABpjV94Ni6dQCAADUgjqfflDfXXoAAADUBEZqAQAAqp7V/fSD+t56AAAA1ARGagEAAGpBnc+ppVMLAABQ7UxMP6h0AAAAAMDxYqQWAACg6lndTz9gpBYAAABVj5FaAACAWlDnc2rp1AIAANQCph8AAAAA1Y2RWgAAgKrHE8Xqe+sBAABQExipBQAAqHamup9TS6cWAACgFjD9AAAAAKhujNQCAABUPb4oVt9bDwAAgJrASC0AAEAtiPFFMQAAAFQzE9MPKh0AAAAAcLwYqQUAAKgFdX6fWkZqAQAAUPUYqQUAAKh63NKLTi0AAEAtYPoBAAAAUN0YqQUAAKgFdT79oL63HgAAADWBkVoAAIBqZ1b3c2rp1AIAANQCph8AAAAA1Y2RWgAAgFpQ59MPGKkFAADAMTGzG81sq5k9W+b995rZ02b2jJk9ZGYnDVYsdGoBAACqXuGJYgP907ebJF16hPdXS3pjCOFESX8l6Ybj39bSmH4AAABQCyow/SCEcL+ZzTjC+w+5l49Iah+sWOjUAgAAoJyxZrbcvb4hhHCso60fkfSzAYipJDq1AAAA1c40WLf02h5CWHa8lZjZBcp3as89/pBKo1MLAACAQWNmSyR9VdJlIYQdg7UeOrUAAABVz4blwxfMbJqk2yW9P4Tw8mCui04tAABALajAF8XM7GZJ5ys/93aDpM9JSkpSCOG/JH1W0hhJ/2H5+DIDMZ2hFDq1AAAAOCYhhHf38f5HJX10KGKhUwsAAFALhuH0g6FU31sPAACAmsBILQAAQC2owJza4YROLQAAQLWz4Xn3g6FU31sPAACAmsBILQAAQC2o8+kHjNQCAACg6jFSCwAAUAOszkdq6dQCAABUOROdWqYfAAAAoOoxUgsAAFDtrPBTx6pipDZI6sxkKx0GAAAAhqmqGKk90JnRwr+4S9NGN2n2uBbNHt+i2eOaNWtci2aPa9Ho5lSlQwQAAKggq/s5tVXRqU3GY7ruwrl6ddt+vbp1vx5YtV2dmVzP++NHpLVgUqsWThqhhRNbtXBSq2aNa1YyXhUD0QAAAMeNTm0VSCdi+uM3zet5nc0Fbdp9SKsKndwXNu/TC5v36muv7lBXNt/ZTcVjmjO+RQsPd3YntWrBxBEa05Ku1GYAAABgkFRFp7a3eMw0dXSTpo5u0gXzx/fkd2dzem3bAb24Za+e37xXL2zep1+/sk23rdzQU4ZRXQAAUIsYqa0hyXhM8yeO0PyJI3TVyVN68nfs79SLW/KjuYzqAgCAIwkhaPfBbm3e06Ft+zt1oDOjg11Z7e/o1o4DXdq+v0vb93fqb99xYqVDLUKntg6MaUnrnDlpnTNnbE/e4VHdFzbv1Qtbjjyqu2RKm06ZNlInTx1JRxcAgCrVnc1p275Obd3Xqdf3dmjbvk7tOdStXQe6tPNAlzbv6dCWvR3avOeQOrpzJeuImTS6Oa2xLSkd6MwM8RbgSOqiU1uKH9V9m6JR3e37O/Xi5n09Uxie37RX/7lqu7K5IEmaOrpRp0wdpZOnjtTJ00bqhMmtSifildoMAADqXghBuw52a8ueDr2+N98x9enX93Zq694O7TjQVXL5xmRco5tTmtTWoBMmt+riheM1sa1Rk9saNG5EWi0NCTWnEmpKxTWyKaV4bBiOiHKf2sHr1JrZjZKukLQ1hLC4kDda0nclzZC0RtI1IYRdgxXDsRjbkta5c9M6d240qnuwK6NnNuzRk+t368n1u/XY6p2646lNkqRk3LRocpuWThul02eO1mkzRjGaCwDAAMrmgrbt69TOA13adbBLG3cd0suv79NLr+/Tmh0H9PreTnVlikdWzaQxzWlNbEtrcluDTp46UhNa0xo/oqHn/3Ej0hrZlFRDksGpWjCYI7U3Sfp3Sd9weddL+kUI4Qtmdn3h9Z8NYgwDoimV0BmzxuiMWWN68rbs6dCT63fpiXW79cS63frWo2t144OrJUlzxrfotBmjdfrMUTpz1hhNamusVOgAAFSNXC5o4+6ow/rK6/v10pZ9enXb/qJbeUr5OyPNndCiU6aO0qS2Bk1obdBE9//4Eem6+hK4cZ/awevUhhDuN7MZvbKvknR+If11SfepCjq1pUxsa9ClbZN06eJJkvJPPHtmwx49tmanHlu9Uz95apNufmydJGnW2GadNXuM3jhvnM6dO1ZNqbqd9QEAgCSpozurtTsO6rVt+/X85r16cv1uPbV+t/Z2RPNUJ7U1aN6EETpnzhjNGNusMc0pjWpKaUJrg6aObhqe0wAqiE7t0JoQQthcSG+RNGGI1z9o0om4ls0YrWUzRuv3zs//qeSFzXv1yGs79NCrO/TDJzbq24+uUzoR0zlzxuqiheN10YIJmtjWUOnQAQAYVCEEvbrtgFau3aUVa3dpxbpdenXbfoX811UUM2n+xFZdvmSyTpzSpvkTWzR3wgi1NiQrGziqSsWGDEMIwcxCuffN7DpJ10nStGnThiyugRKPmRZPadPiKW366Hmz1JXJ6fE1O3Xv86/rFy++rl++uFWf0bM6cUqbLl44QRctHK8TJrfW/W9ZAIDq19Gd1dMb9mj52p1asSbfid19sFuSNLIpqaXTRunyEydp9vgWzRrbrFnjmvkr5gCo9z7EULeg181sUghhs5lNkrS1XMEQwg2SbpCkZcuWle38VotUYYT2nDlj9bm3LtIrW/fnO7gvvK5//sXL+qefv6xJbQ35EdyFE3TWrDFMXAcAVIXdB7v06OqdWr5mp5av3aVnN+5Rdzb/0T1rXLMuWTRBy6aP1qnTR2n2uOa673xhcAx1p/YOSR+Q9IXC/z8a4vUPC2ameRNGaN6EEfr9C+Zo+/5O/fLFrfrFC6/rthUb9a1H1qkpFdebFk3QtadN05mzRnMBAAAMKzsPdOnnz7+unzyzWQ8Wbn2Zise0pL1NHz53ppZNH62l00dpdHOq0qHWjXrvKwzmLb1uVv5LYWPNbIOkzynfmf2emX1E0lpJ1wzW+qvJ2Ja0rlk2Vdcsm6qO7qwefm2H7n3+df34qU360ZObNHNss9556hRduniS5oxvqXS4AIA6dKgrqwdXbdcDq7brkdd26MUt+yRJ00Y36bo3zNJFC8Zr8ZQ2/spYKdynVhbC8P/L/rJly8Ly5csrHcaQO9SV1Z3PbNZ3H1+vx9bslJS/XdiVJ03Wu5a1c6swAMCg2tvRrbue3aJ7nntdD6zapo7unBqSMS2bPlpnzhqtN84br8VT6vv7IGa2IoSwrNJxJMbOCm2X/82A17vzG+8ZFtvXH8zKHsYaU3G9c2m73rm0XVv2dOju57bozmc26x/vfVn//POX9YZ54/SOU9t14YLxaklzKAEAx6+jO6tfvbhVP3pyk3750lZ1ZXKaMrJR1542TRcvnKDTZo7iSZrDVD3/ciHRqa0aE9sa9IGzZ+gDZ8/Quh0H9f0V6/X95Rv0iZufUCoR03lzxuryJZN06eKJfIMUAHBUOrqzuv/lbfrJ05v18xde18GurMa2pPXeM6bpqpOn6KT2trrvMGH4o/dThaaNadKnLpmvP7x4nlas3aW7nt2iu5/bol+8uFV/8cNndfmSSbp66VSdNmMUFyEAQEldmZx+/co2/fTpzbr3+de1rzOjUU1JXXXyFF2xZJLOmDlaiTp6Ile144lidGqrWjxmOn3maJ0+c7T+4oqFenzNLn1/+Xr95OnN+t7yDZo+pklXn9qudyxt15SRzL8FgHrXnc3pwVXb9dOnN+vu57Zob0dGrQ0JXXbiRF2xZLLOmj2mrh4tW2vo1KImmEUd3L+88gTd9ewW3bpig/7h3pf1jz9/WWfPHqOrl7br0hMmqTHFXCgAqBe5XNDDr+3Qj5/apLue26LdB7s1Ip3Qm06YoLcumaxz5oxVKkFHFtWPTm0Nak4ner5gtn7nQd2+cqNuXblef/Tdp/QX6ed0xZJJunppu5ZOZ3oCANSq1dsP6LYVG3T7yg3atKdDzam4Ll40QVcsmazz5o7l1lu1qM4/0unU1ripo5v0yYvn6g8unKPH1+zUrSs26I6nNumWx9drxpgmXb20XW8/lekJAFAL9nZ066dPb9ZtKzZo+dpdipl03txx+vRbFupNiybQkUVNo1NbJ2Ix0xmzxuiMWWP0l1eeoJ89u0W3rlivL93zsv7h3pd1zuyxeteydl2yaCLTEwCgimRzQQ+9ul23rtigu57dos5MTnPGt+j6yxbo7adM0YTWhkqHiKFgzKmlU1uHmtMJXb20XVcXpifctnKDbl2xQZ+85UmNSCd0xUn56QmnTmN6AgAMV69t26/bVm7Q7Ss3avOeDrU2JPSuZe26eulUbsFVp+r9mNOprXNTRzfpDy+ep09cOFePFaYn/OjJTbr5sfWaObZZ1yybqvecPk1tTclKhwoAdW9vR7d+8tRm3bZyg1YUphe8cd44febyhbp4IdMLUN/o1EJSfnrCmbPG6MxZY/T/XXmC7nxms76/YoP+7q4X9W+/fEW/ddpUfficmZo6uqnSoQJAXcnmgh5clZ9ecPdz+ekFc8e36NOXLdDbmF4Ah5FaoJfmdELvWjZV71o2Vc9v2quv/vo1ffPhtbrpoTV647xxes/p03ThgvHclBsABtGm3Yf07UfX6rYVG7Vlb4faGpO6ZtlUXb20XUuYXgD8Bjq1OKJFk1v1j791sv73m+frlsfW6bvL1+u6b67QhNa0rlk2Vb912lS1j2L0FgAGQghBK9ft1tceXK2fPbtFIQSdP3+8PvvWRbpo4XilE0wvQGk8UYxOLfpp8shG/fEl8/WJi+bqly9u1XceW6d//9Uq/fuvVun8eeP0bkZvAeCYPbdpj37y9Gb99OnNWrfzoEakE/rwOTP0gbNnMHCA/qvvPi2dWhydRDymS06YqEtOmKgNuw7qu4+v13cfj0Zvrz1tmn77rOka05KudKgAMKxlsjnd9dwW3fjAaq1ct1vxmOmcOWP1+xfM1uVLJqslzUc0cDQ4Y3DM2kc16VOXzNcn3ejtv/ziFX3l/ld1zbKp+p3zZvHFMgDoZffBLt382Hp98+E12rSnQ9PHNOmzVyzS206ZotHNqUqHh2rFfWrp1OL4+dHbVVv364b7X9XNj63TNx9Zq7NmjdE7Tm3XpYsnMuoAoK6t2rpPX3twjW5buUEd3TmdPXuMPn/VYl2wYLzisfrujAADgV4GBtSc8S364tUn6Y/eNE/fe3yDbn9ig/7395/SZ3/0rK5e2q7fPmuG5oxvqXSYADAksrmg+1/Zpq89uEb3v7xNqURMbz95ij54zgwtnNRa6fBQYxipBQbBpLZGffLiufrERXO0Yu0u3fzYet3y2Hp94+G1Om/uWH3onBk6f954xRidAFCDej/ta/yItD71pnl6zxnT+M4BBg2dWmAQmZmWzRitZTNG69NvWaCbH81PS/jwTcs1Y0yT3n/WDF29tF1tjTyxDEB1y+aCfvniVn39oTV6YNX2oqd9XbJoolIJ7g4DDCY6tRgyY1vS+oOL5up33zhbdz23RTc9uFp/9ZPn9aW7X9LbTpms9585Q4sm8+c4ANVlX0e3vrd8g256aLXW7zykSW0N+pM3z9e7lrZrPE/7wlCq74FaOrUYeqlETFeeNFlXnjRZz2zYo28+ska3r9yomx9br2XTR+n9Z03XZYsnMaoBYFhbv/OgvvbgGn1v+Xrt78zotBmj9OnLFuqSRRO4ZzdQAXRqUVEntrfpi1efpP/zloW6dcUGfeuRtfrkLU/qr1qe1zXLpuo9Z0zjxuMAho0Qgpav3aX//vVq3fP8FsXMdPmSSfrIuTO1pH1kpcNDnWNOLTAMjGxK6aPnzdKHz5mpX6/arm8+vFb/9T+v6j//51VdtGC8Pnb+bC2dPrrSYQKoU93ZnH769Gbd+OBqPb1hj9oak/rdN87WB86aoYltTDFA5ZnxmFw6tRhWYjHTG+eN0xvnjdPG3Yd0y2Pr9O1H1+md//mwzp49Rh+/cI7OmjWm7k9cAENj98EuffvRdfrGw2v0+t5OzRrXrL9+22K949QpakrxEQoMJ5yRGLamjGzUpy6Zr4+dP1vfeXSdvnL/a3rP/3tUCye16gNnTddVJ09RYype6TAB1KBVW/fraw+u7nlQwrlzxuoL71iiN84bx60IMWzV+4APnVoMe02phD563iy978zp+sETG/X1h9bo+tuf0RfuelEfPXemPnD2DI1o4JZgAI5PCEEPrNqu/35gte57Kf+ghLedPFkfPnemFkzkzizAcEenFlWjIRnXu0+fpmtPm6rHVu/UDfe/pi/d87L+369X66PnztT7zpyuUTw3HcBR6ujO6kdPbtSND6zRS6/v09iWlP7w4rl635nTNZYHJaCKMFILVBkz0xmzxuiMWWP09Ibd+pefv6J/uPdlffm+VXr7KVP0wbNnav7EEZUOE8Awt21fp775yFp9+5G12nGgSwsmjtDfX71Ebz1pshqSTG1CFarvPi2dWlS3Je0j9d8fPE0vbtmrrz8U3e/20hMm6o8vmad5E+jcAij27MY9uumhNbrjyU3qyuZ00YLx+si5M3XWbL6EClQzOrWoCQsmtupv37FEf/rmBbrpoTX67wdW6+7nt+iqkybro+fN0uIpbZUOEUAFdWdzuvu5Lfr6Q2v0+JpdakzG9VunTdWHzpmhWeNaKh0eMCDq/ZcyOrWoKaOaU/qjN83TB8+eoa/c/5q+/tAa/fDJTTp56ki9/8zpuvLkyUrypB+gbuzY36mbH1unbz2yTlv2dmja6Cb9+eUL9a6lU9XWxBdMgVpSFZ3ag11ZrVizV5KUC6EnP5uN0t25KJ0JOZcfpd2iypQpH3MTUt564oTjjByVMqo5pesvW6CPnT9bt6/coG8+slaf+v5T+qefv6yPXzBH71zaTucWqGEvbN6rGx9YrR89tUldmZzOmztWf/22xbpgwXjFuSUX+mn19o6etG83qeH4+WGM1FZFpxY4Vm2NSX3onJn64NkzdN9L2/TPv3hF19/+jP79V6v0gbNm6F3L2jWyiTsmALUglwv61Utb9d8PrNZDr+5QYzKua5a164Nnz9Cc8cyvR20zSZXo05rZjZKukLQ1hLC4xPsm6V8kvUXSQUkfDCGsHIxY6NSiLpiZLlgwXufPH6f7Xt6m//jVKv3NnS/oS/e8pCtPmqyPnMd9KIFqdaAzo9tWbtDXHlyj1dsPaFJbg66/bIGuPW0qv7QCg+8mSf8u6Rtl3r9M0tzCzxmS/rPw/4CjU4u6Yma6YP54XTB/vF7YvFfffGStfrByo76/YoPeOG+cfvcNs/gGNFAlNu0+pK8/vEY3P7pOezsyOmnqSP3ru0/RZYsnMr0Idcgq8tkVQrjfzGYcochVkr4RQgiSHjGzkWY2KYSweaBjoVOLurVwUqv+79tP1J++eb6+9cha3fTQGr3nq49q3oQWvfeM6Xr7qVPUypPKgGEll8s/9es7j67TvS+8rhCCLls8SR8+d6aWTh9V6fCAWjTWzJa71zeEEG44iuWnSFrvXm8o5NGpBQbayKaUPn7hXH30vFm648lN+vaja/W5O57TF372oq48abLed+Z0ndjOLcGAStq6r0PfX75Btzy+Tut3HtLo5pQ+cu5M/fZZ09U+qqnS4QHDwiAN1G4PISwblJoHGJ1aoKAhGdc1p03VNadN1TMb9ujbj67Vj57cpO8uX68l7W167xnT9NaTJqspxWkDDIXDo7I3P7ZO9z7/ujK5oLNmjdGfvHmB3nzCBKUTPPUL8Ibp1LmNkqa61+2FvAHHpzNQwontbfpC+xL9n8sX6odPbNS3HlmrP7vtGf31T17QO06doveeOZ2nlQGDZOveDn1/RfGo7IfPnalrT5vKgxKA6nOHpI+b2S3Kf0Fsz2DMp5Xo1AJH1NqQ1G+fNUPvP3O6VqzdpW89slY3P7ZeX394rU6fMVrvPXOaLl08kREj4Dh1ZrL6xQtbdeuKDfqfl7cpmws6c9ZoRmWB/rKK3dLrZknnKz/3doOkz0lKSlII4b8k3an87bxWKX9Lrw8NVix0aoF+MDMtmzFay2aM1mff2qVbV6zXtx9dp0/e8qRGN6f0rqXtevfp0zRjbHOlQwWqRghBz2zco1tXbNCPntykPYe6NbG1Qb/7hlm6emk7o7JAFQghvLuP94Ok3x+KWOjUAkdpdHNK171htj567iw99OoOfeuRtfrqA6v1lftf03lzx+q9Z0zTRQsncEshoIz9nZmeaT0vbtmndCKmN58wUVcvbdc5c8byxC/gGJikWJ2fO3RqgWMUi5nOnTtW584dq9f3dui7j6/XzY+t0//61kpNaE3rg2fP1PvPmq6WNKcZIEnPb9qrbz+6Vj98YqMOdGW1aFKr/vpti/XWkyarrZHb5wHHa3h+T2zo8GkLDIAJrQ36xEVz9Xvnz9Z9L23T1x9eo7+760X91/+8qg+fk7/t0KhmnmyE+rO/M6M7n9msWx5bp5XrdiudiOmtJ03We8+YppOnjhyu39YGUIXo1AIDKBGP6eJFE3Txogl6cv1u/fsvV+mffv6yvnzfKr11yWS9/6zpOnnqyEqHCQyqbC7ooVe367YVG3TXc1vU0Z3TrLHN+vPLF+rqpe08uhYYJPX+SyKdWmCQnDx1pL76gWV6acs+ffORNfrByo26beUGLWlv0/vOnK63LpmsxhTf6EbtWLV1n25buVE/fGKjNu/p0IiGhN5xarveeWq7Tp3GqCyAwUWnFhhk8yeO0F+/7UT92aUL9MMnNuqbj6zVn976tP7mpy/oXUvb9d4zp2smd01Aldq6r0M/fmqzfvDEBj27ca/iMdMb543TZy5fqIsXTlBDkl/cgCFRoVt6DSd0aoEhMqIhqfefNUPvO3O6Hlu9U998ZK1uemiNvvrAap03d6w+dM4MnT9vfN1/exXD34HOjO55fot+8MQmPfDKNuWCdOKUNv3FFYv01pMmafyIhkqHCNQdE9MP6NQCQ8zMdMasMTpj1hht3duhWx5fr+88uk4fvmm55o5v0UfPm6mrTp7CCBeGlUw2pwdf3aEfPrFRdz+3RQe7spoyslG/d/4cve2UyZoznifsAaisqunU9vzuEUplFgvBF4kK5fwbZeTcCm5/KnqKW3cu58pEurM5VyaUzD/QFaX3dWZ70h2ZUDK/qztKZ1ydBzszPelDLn3ApQ8e7C7ankwmWndDQ3S4x7RFIyn7DkXLZLPR+u77w7OFwTW+cNeEj50/Wz99erO+cv9r+rPbntHnf/y8Ll40QZefOElvmDeODi4qIoSg5zbt1Q+e2Kg7ntqkbfs61dqQ0FUnT9HbT5miZdNH8ZeFChvzgZt70ql09AU8c8eloSm63qfcPP7u7ujzIZmM7qvd6G6vlkhE+f5Yt7Wmo/Kp6LOltSlatvdHrr8DzIh0FEeDW/eUtqhMi7vupeNRutE9Xa45mXBlonqy7rPT3/c44cr49SYTPj+qP+GW9Z/HId53f2LoGSO1lQ4AgJSMx/S2U6boqpMn6+HXdujHT23SXc9u0Y+e3KSWdEJvKnRwz5s3lseFYlB1dGf12Oqd+tVLW/WrF7dqzY6DSsVjunDBeL3tlCm6YME42iCAYYlOLTCMmJnOnj1WZ88eq89ftVgPv7pDP316s+56bot+8MRGjUgn9KYTJuiKJZN07pxxSiV4ahmO38bdh3RfoRP74KodOtSdVToR01mzx+h33jBLl584idtwAVWgzgdq6dQCw1UyHtMb5o3TG+aN01+/fbEeXLVdP316s+5+botuX7lRrQ0JXXLCRF2+ZJLOmT2WDi76rTub04q1u/Srl7bqvhe36aXX90mS2kc16l3L2nXB/PE6c9YYbjkHVBmmHwAY9pLxmM6fP17nzx+vv3n7iXpw1Xb9pNDBvXXFBrWkEzpv7lhduGC8Llo4QaN5ehl62bq3Q/e9vE33vbRVv355u/Z1ZpSMm06fOVrvWrZQ588fr9njmuv+QxFA9aJTC1SZVCKmCxaM1wULxqszs1gPvLJdP39hq3754uv62bNbFI+Zzp49RlcsmaQ3LZpIB7dOdWVyWrlulx54Zbvue3mrnt24V5I0sbVBV5w0SefPH69z5oxVS5qPAaAmcJ9aOrVANUsn4rpo4QRdtHCCQlis5zbt1Z3PbNZPnt6sP7vtGV1/+zM6ZepIXVjoBC+a1MpIXI0KIejVbfv161e269evbNcjr+3Qwa6s4jHTKVNH6k/ePF8XzB+vhZNG0AYA1CQ6tUCNMDMtntKmxVPa9Cdvnq9nN+7Vz194Xb96aau+dM/L+tI9L2tia4MuWDBOFxRG6ZoZpatq2VzQ8jU7dddzW3TPc69r4+5DkqQZY5r0jlOn6Ly543TW7DFqbUj2UROAasfDF+jUAjXJzHRie5tObG/TH71pnrbu69B9L23Tr17cqh8/tVk3P7ZeqXhMZ8warXPnjNWS9pE6sb2NP0UPcx3dWT21freWr92llWt3acW6Xdp9sFupRExvmDtOv3/BHJ03d6ymjm6qdKgAKqDO+7R0aoF6MH5Eg65ZNlXXLJuqrkxOy9fk70P6yxe36m9/9qKk/MVw9rgWLWlv00ntI3XS1JFaNKmVuypUUGcmq8dX79L/vLxVj63Zpec27um5Afyscc26ZNEEvWHeOJ0/fzy/kACoe1wFgTqTSsR09pyxOnvOWH3m8kXaeaBLT23YrafX79HTG3br/pe36/aVGyVJ6URMS9rbdOq0UTp1+iidOm2Uxo1I97EGHKsDnRk9tX63nli/WyvW7uqZF5uKx3Ty1JH6nTfM0tLCseALgAB6Y/oBgLo2ujmlC+aP1wXzx0vKf+Fo854OPVnoWK1ct0tfe3CNvnL/a5KkqaMbezpWp04bpQUTRxQ9ehJ9CyFo+/4urd1xQK9tO6AnN+zWE+t266Ute3X4SZyzxjXrnae26/z5+XmxTSku1wBwJBW5SprZH0n6qKQg6RlJHwohdFQiFgDFzEyTRzZq8shGveXESZLyczmf27RHK9fmO7oPvbpDP3xykySpMRnXSVPbdNLUkZo/YYTmTRih2eNauHG/8p3X1/d26qkNu/Xsxj16bdsBrdlxQGt3HNT+zkxPuRHphE6eNlJvunCuTpk2UqdMHckTvAActTofqB36Tq2ZTZH0CUmLQgiHzOx7kq6VdNNQxwKgfxqScS2dPlpLp4/W7yjfWdu4+5BWrN2lJ9blO7o3PrBa3dnQs8zYlpSmjGzUjLHNOnFKW88c3Vq840I2F7R6+349u3GvXti8Vxt2HdLG3Ye0YddBbd/fJUmKx0xTRzVq+phmnTZjtKaPadKMsc2aMaZZ00c3KRar808jAMfHmH5QqU+XhKRGM+uW1CRpU4XiAHAMzEzto5rUPqpJV508RVL+0atrdxzQS1v267Vt+7Vxd75j99jqnfrRk9EpPrIpqSmFkeAph39GRa/HtqSG7YW5K5PT5j2HtHHXIa3beVDPb96rZzfu0Qub9+lQd1aSlIrH1F7YngsXjNfCSa1a0j5SJ0xuVUOS0WsAGCxD3qkNIWw0sy9JWifpkKR7Qgj3DHUcAAZWMh7TnPEjNGf8iN94b+u+Dj29fo9e3rpPm3bnO4VrdxzQQ6u260BXtqhsKhErdHobNH5Eg+IxU9xMyYRpRENSrQ1JtTYm1NqQVFtjUs3puMxMJilmJjPJZD1/hjOTErGYmtNxjUgn1ZiKqzOT1aGurA51Z3Ww8P++jkxPZ3XT7kPac6hb+zq6tbcjo30d3drXkdHBXrG2pBNaNLlV154+VYsnt+mEKa2aPa5FSeYYAxhi+fvUVjqKyqrE9INRkq6SNFPSbknfN7P3hRC+1avcdZKuk6SJk6cOdZgABtD4EQ26eFGDLl40oSg/hKC9hzI9o7qbCv9vLHR8l+/cqWw2KBuCurNBew9199zSarAk4/k5xSMbk2ptTGpiW4NGpJMa0ZBQa2NSk9oaNGVUo6aOatKUkY1MGwCAYaIS0w8ulrQ6hLBNkszsdklnSyrq1IYQbpB0gyQtWnJKUInPjVDmsy2o9BtZt0C5Mr5O/yfQoj+HFtVTWn8+d7OuULlxneP9uPRh+23IuLmPmUyuJ33oUHdP+uS//EXJMt3dUbqrK/qyS6Y7Sge3bblcrmSZIm5/ZbPRaFgumyuZ9mWyrs5DP/ho6foxLJmZ2pqSamtKatHk1j7LhxDU0Z3T3o5u7T3Urb2FEdQgSSF/Xudy+eYUQij8nz/XDnRmtLejW4e6skonY2pMJdSUjKsxlf9pSSc0ZWSjJrTmR4dRHxrf+PnoRce+KJ1qjNIx91GZc9ewVEOU7nLfdU67h190d7mVtUTphPsiYNLVk42uwfGGKAZ/TU34O2G4a2ciGeXH3KdK8fXYfX6F0vmZTHR9jcfNlYnW5T9DsmXq7/0LX059y7mYij+DVTJdlpVJO/5j2n/2F+2XUK5MP2IYcjZsp24NlUp0atdJOtPMmpSffnCRpOUViANAlTGznk7ohNaGvhcAgDpS533asgOEgyaE8KikWyWtVP52XjEVRmQBAACAY1GRux+EED4n6XOVWDcAAEAtqvfpB3xFFwAAAFWv9u6CDgAAUG+MObV0agEAAKpc/j619d2rZfoBAAAAqh4jtQAAADWAkVoAAACgyjFSCwAAUAPqfKCWTi0AAEAtYPoBAAAAUOUYqQUAAKh23KeWkVoAAABUP0ZqAQAAqpzJ6n5OLZ1aAACAGlDnfVqmHwAAAKD6VclIrclU+PXDQp+lsyEq07PcEcRcmaz6rr8c/xuSC0G5Mumse5ErU+fRRhOL9d7e6PcWH182F60xm/XpUDK/qyvbk865uLOZbMl0LNb370vB1RNC6Tp9mUwm4xYuqqgn2fjW/yiZr+6OKB2LR+lc1pXpjNJ+Z/n1xt2yvp5sd+l8r8vFkG7qSR76+fWlywNV4ITP3NOTzmTctdedQh2HulVKzl2HDuw50JM+uP+gW3h/6XTG1enP42yZ/ExXlI4no3Sqse/yyViZMv6a5C5K3W7ZnC+TK53263L5uWyZTwZ//XP72Zf3f4b2+zmXi7bFX3d9+EWfX+4a7C/rRcu6gPznWu8/hefKfOblysThl/af02U/1122Bf+BXLq4X29xvl+09HYee09hcMXqfKiWkVoAAABUvSoZqQUAAMCR1PlALZ1aAACAamfGE8WYfgAAAICqx0gtAABADfiN74rXGUZqAQAAUPUYqQUAAKgB9T6nlk4tAABADajzPi3TDwAAAFD9GKkFAACocqb+PUW1ljFSCwAAgKrHSC0AAEANqPdbetGpBQAAqHZmdX/3A6YfAAAAoOoxUgsAAFAD6nyglpFaAAAAVD9GagEAAKqcSYrV+VBtVXRq8weqjzJl3g8Kx7zecuv0w9u+SLxMEHG3QDYXxZPJRfk5lx/KhJxzb7jiRcv+xrrjpWPKZKNlslm/brcOH6sLNpeN0qHMunO50uUVSpfx9RSV98osW7TDst0u3+/gbOk6uzvdsq6Mr6ds4/L1+xi6SpfJZqJ058GeZOO5f1G6/lg8SieSUTruTtu4y082uPIpVyQq779EkGqIyhzpvcbmxqjaRMylo/LJZBRrU1MUU3NTVE8qGS3b4Mo3N0TlWxqiWFtdenxLlB7dGKXHNKR70hObo+0vt660j9+dG6NbojgPdUXtwJ8n/vzzdW7Yeagn3eXOk52Honaw9WBHT/q1XVHaXzO27Iva3O6D0bIJdyHafSDKP9AZtafRLdF+ONQV5ftTI+POmQ63jeXO/y5Xxqel4utBIhF3+ZmSZYrOV8evLxSdx+5cCWWuB/6c9mXMX6F9mXIX1jJlfH65MkXnt7tmlKvfb5c/X3Mu38Uf3H6zWN9/WPXXzkSy9Md70ReJ/KaUOxb9UO4z6Ej9q/78mdifH7GitFuHX1/Rq1CykO8TmIviaPsKR/rcraRK9GnN7FJJ/yIpLumrIYQv9Hp/mqSvSxpZKHN9COHOwYiF6QcAAAA4amYWl/RlSZdJWiTp3Wa2qFexP5f0vRDCKZKulfQfgxVPVYzUAgAA4MgqcEuv0yWtCiG8Vlj/LZKukvS8KxMktRbSbZI2DVYwdGoBAABQzlgzW+5e3xBCuKGQniJpvXtvg6Qzei3/l5LuMbM/kNQs6eLBCpROLQAAQJUzG7Q5tdtDCMuOY/l3S7ophPAPZnaWpG+a2eIQyk2YP3ZH7NSa2eh+1JELIewemHAAAABwLCpw94ONkqa61+2FPO8jki6VpBDCw2bWIGmspK0DHUxfI7WbCj9H2ktxSdMGLCIAAABUg8clzTWzmcp3Zq+V9J5eZdZJukjSTWa2UFKDpG29KzKzf+3H+vaGEP683Jt9dWpfKHxbrSwze6IfQQAAAGAQDfU4bQghY2Yfl3S38oOcN4YQnjOzz0taHkK4Q9KnJP0/M/sj5b809sFQ+r5xV0n6bB+rvF75uymU1Fen9qw+3u9vGQAAANSYwj1n7+yV91mXfl7SOf2o6p9CCF8/UgEzG3Wk94/YqQ0h9NwlvFDRVL9MCGGlLwMAAIDKqMAtvQZMCOGfj7dMv+5+YGZ/JemDkl5V9JiOIOnC/iwPAACAwdOfp69WAzP7oqS/lnRI0l2Slkj6oxDCt/patr+39LpG0uwQQlefJQEAAIBjc0kI4U/N7O2S1kh6h6T7JQ1Yp/ZZ5Z/ZO+C3XwAAAMBxMqvq6QfO4b7p5ZK+H0LY09/t6m+n9m8lPWFmz0rqPJwZQrjyaKIEAAAAjuAnZvai8tMPPmZm4yT16/tb/e3Ufl3S30l6RtKAPwECAAAAx6eaB2rNbHIIYVMI4frCvNo9IYSsmR1U/nZffepvp/ZgCKE/N8UFAABABVT59IOvFp5ke5/yXxB7QJJCCAckHehPBf3t1P7azP5W0h0qnn6w8miiBQAAAHoLIbyl8Ajd8yW9XdKXzGyd8h3cu0II6/qqo7+d2sNPFTvTr1/c0gsAAKDiauGWXoVnH9xV+FHh8buXSfp3M5sYQjj9SMv3q1MbQrjgeAMdKOYeAmcKLl26TPBlXCH/gLacSj2tTYq5emJ+YZeO50ovW+4vAH69/ilxmWzfU5WLli0Tc+8nz8VisZLl/PqyRelo+e7uKD+47czlfPmsDyrSjxPL1+njNndW5vqxX4rkfDxlls24O9P5+HOZ0vXE4qXrdEWKypeLp9vNcw+p0uXNHS+/rriLofNQlE65nR53p3MuKp/NuHPGNcxMl9teSbF4tG6/37OZaBv8cQoh5tJRPYlElJ9Mlt4vcRdHZ3dUJuVi2GdRfnMqym9IRLGlE9E2dPq24tpf3LWncumSD2xUcZP2ZTLZ0teenGvTCVd/Mu7j9/utzIrLxBCPR3Um4qW3JeGPY9E5VrpM0Tns1xtKn59S/+bt5cqc32UuXeX3hT//fLooCH+d8+dNMkr7877cOe3rLLsup2hZF4M/78st62Pz14Yyxf2566+R/hpf7jpaXI9/ESX9edufP2EXxdOPdO91x4vOj9Lr85+7/pph5T6bi5Z161XpZYuU+WwuV6bMRz8GkJm1Stoj6ZbCz/6+lind44kqvKIfK+2zDAAAAAaXFW7rNZA/FdiG3zWzLZKelrSi8LO8P89K6Guk9u/NbKOOPO72fyX9pL/BAgAAYOBV+eyDw/63pMUhhO1Hu2BfndrXJf1jH2VeOdqVAgAAACW8KungsSx4xE5tCOH8Y6kUAAAAQ8es/BzjKvNpSQ+Z2aMqvuPWJ/pasL93PwAAAAAG21ck/VLH8MAvOrUAAAA1oDYGapUMIfzxsSxIpxYAAKAGVPkTxQ77mZldJ+nHKp5+sLOvBfvVqTWzJkmfkjQthPA7ZjZX0vwQAnc9AAAAwEB5d+H/T7u8IGlWXwv2d6T2a8rfJ+yswuuNkr4vbuUFAAAwLNTCQG0IYeaxLnvEhy84s0MIX5TUXVjhQdXM7dAAAABQSWZ26vGW6e9IbZeZNarwkDgzmy03zwEAAACVY7Jqv6XX18zsfB150PS/JZ1S7s3+dmo/J+kuSVPN7NuSzpH0wX4u+xvMbKSkr0parHxH+cMhhIePtT4AAIC6ZlU//aBN+amuR9qKbUeqoF+d2hDCvWa2UtKZhZV98lgeX+b8i6S7QghXm1lKUtNx1AUAAIAqFkKYcbx1HLFTW2LuwubC/9PMbFoIYeXRrtDM2iS9QYWR3hBCl6Suo60HAAAAkRq5pdcx62uk9h8K/zdIWibpKeVHapdIWq7obghHY6byw8dfM7OTlB9q/mQI4cAx1AUAAAAc+e4HIYQLQggXKD9Ce2oIYVkIYanyk3Q3HuM6E5JOlfSfIYRTJB2QdH3vQmZ2nZktN7Plu3Yez0wHAACA2hcbhJ9q0t8vis0PITxz+EUI4VkzW3iM69wgaUMI4dHC61tVolMbQrhB0g2SdMKSU8PhfD+y7ofZ+7PjY27ucU49VSru6sm6fP8twniZIf1ErHS+z/ZlfDWZXLSurEt7IYSS6UzW55dcNB9HmfiyZZbPZks/ZjmUW4nLzuWiZf2xKbtsGcHtC19nkYybseLr9zvY79NYPEpnMy7d7Vfsls1G6XiidHnPXAvMlJlNU7SjXQx+vT7Oojrdev2yyVTp+n38rv7g6s9kittGPETv5Vw7yGaiurJZV6+SUahuv3d3R8tmMm7d/nC4dpmIR2U6urMly3Rl3LniKupycXa62FLxaN/5JpTz55PK5Jdprr45dZc5T3xsvs60i6c5FaX9NSDhDnfO5fttSSeiY9Tl9nPR/nTpbhdD8T53+7aoGVjJ8r3/pBlzsfr3fLFyy/tzOua2LRYrcxWPJ13anYsJl9/dVbp8ufMymY7S/oD7dKofX/Xw9ftzt6iMlSxjLv7Q3RGVKYrfHQ+3r+LxeMl8f676fR537SbhGpr/HEgmo/ziY1q6Tfg25D8ry3329a4rlSj92RR3y8SL1h2ViZVpcz7tT+NyZY72L/W+zqP9XBsKpuqeftDX7br6M+W1v53ap83sq5K+VXj9XklP93PZ3kFtMbP1ZjY/hPCSpIskPX8sdQEAAKAmHPeU1/52aj8k6WOSPll4fb+k/zyaSHv5A0nfLtz54LVC/QAAADhGZf44WxUK011lZrcrP+X1mcLrxZL+sj919PeWXh2S/qnwc9xCCE8q3wsHAAAADjvmKa/96tSa2WoVTyc5vKJZ/Q4RAAAAg6aaR2qdY57y2t/pB35UtUHSuySN7nd4AAAAGDRm1f1FMeeYp7z2d/rBjl5Z/2xmKyR9tr8RAgAAAEcSQugws/+SdGfhhgL91t/pB/42CzHlR277O8oLAACAQVYL0w/M7EpJfy8pJWmmmZ0s6fMhhCv7Wra/HdN/cOmMpNWSrjnKOAEAAIAj+Zyk0yXdJ+VvLmBmM/uzYH87tR8JIbzmM/q7AgAAAAy+2phSq+4Qwp5e84P79bSL/j4B7dZ+5gEAAGCImfJPWxvonwp4zszeIyluZnPN7N8kPdSfBY84UmtmCySdIKnNzN7h3mpV/i4IAAAAwED5A0mfkdQp6TuS7pb01/1ZsK/pB/MlXSFppKS3uvx9kn7naKMEAADA4Ojvn9+HsxDCQUmfMbO/KaT77Yid2hDCjyT9yMzOCiE8fDxBAgAAAEdiZmdL+qqkFknTzOwkSb8bQvi9vpbta/rBn4YQvijpPWb27t7vhxA+cYwxAwAAYADVyBfF/knSmyXdIUkhhKfM7A39WbCv6QcvFP5ffuyxAQAAYDBZ5b7YNeBCCOt73f0g25/l+pp+8ONC8mAI4fv+PTN711FFCAAAABzZ+sIUhGBmSeUfl/tCH8tI6v+c4k/3Mw8AAAAVYDbwPxXwvyT9vqQpkjZJOrnwuk99zam9TNJbJE0xs391b7Uq/2SxoWOHYyr9th9y94+Jy+RcFS7fgpXMD+72vglXUS6UWXE8XjI7lYhGynMhCiIZL11PLpS+r3AmG+Vnc1G6221YzuX3Gq4veh3cOrLZ0sv3h6+nXLrcesvx5culsxnX5Mz9Ppbt9MGVC9qVd3/FCL6BlPkdzy/r0zF37HOuznINyq8rW2a9vnyuO0onUm5Zl58r8xeZbJll/fHqddxDzLWPTFRvNlt6HYlEdPnIuPaYccsWty1Xxm+/050tXabbt31/TrjtyZRpx7miNlqySL9u6+3bccaV923Ux2CK8pOx6Bg3JKO0P7/j/Xi+ZTIRK5lOu7SvJttZbsNc+Zi/drptyUTp+G9ct/z5rZLlQih9Pvn9FXfXT/OBxxMlywffrovOFZdOpd3KXAwZd51IN7l6XFtMunMl1eiW7XJ1ljkXy3wWFF0nklFs8USUn3FlYj6Gol0S7zPt22gs7tpKMl4ynXXtL52O9rk/b/0xTZRpf6ky+T4tSem4PydKf2b7y6c/b1JuH8V9G4qV/iyPq+/zqVyJXNH5HaX78VGGARBC2C7pvceybF8jtZskrZDUUfj/8M8dyk/iBQAAwDAQs4H/GWpmNsvMfmxm28xsq5n9yMxm9WfZvubUPiXpKTP7VghhaEdmAQAA0C+HnyhWA74j6cuS3l54fa2kmyWd0deCfU0/eEaFP8z1+rO2SQohhCXHECwAAABQSlMI4Zvu9bfM7E/6s2Bft/S64thjAgAAwFCpjYFa/czMrpd0i/IDq78l6U4zGy1JIYSd5Rbsa/rB2lL5ZnaupHern99GAwAAAPrhmsL/v9sr/1rlO7ll59f2NVLbw8xOkfQeSe+StFrS7UcXIwAAAAZFhb7YNdBCCDOPddm+5tTOU35E9t2Stkv6riQLIVxwrCsEAADAwLN+3MpsuDKz0yStDyFsKbz+bUnvlLRW0l8eadrBYX3d0utFSRdKuiKEcG4I4d/Uz0eVAQAAAP30FUldkmRmb5D0BUnfkLRH0g39qaCv6QfvUH4Ow6/M7C7lJ+1W768BAAAANSh/S69KR3Fc4m409rck3RBCuE3SbWb2ZH8qOOJIbQjhhyGEayUtkPQrSX8oabyZ/aeZXXLMYQMAAACRuJkdHmy9SNIv3Xv9+g5YvwqFEA4ofzPc75jZKOW/LPZnku7pf6wAAAAYLFU+UnuzpP8xs+2SDkn6tSSZ2RzlpyD0qd93PzgshLBL+bkN/ZrfAAAAgMFnVXyj2hDC35jZLyRNknRPCCEU3opJ+oP+1HHUnVoAAABgoIUQHimR93J/l6dTCwAAUOVq4Itix62vW3oBAAAAwx4jtQAAANXOpCqeUjsgGKkFAACoATGzAf/pi5ldamYvmdkqM7u+TJlrzOx5M3vOzL4z4BteUHUjtX4HxyxEaTeRxLpd2pXx4r4e17WPydcfpVPxqFBwVcbcC3/sU67SWDLKb0pG+QkXcyYb1RNcnV3ZXJ9lfLo3H5MvlsuVXj6TidZ3tPoVU1E8ft9Zyfwi2W5XjztouWzpdCwepbs73IrLbKPP98uWi6Hcunw6V+YBfD4/UWZdfj+Uizmb7buMryfbGWX32sYQon2ay5Wry5ePXuSyvnxUb7l25pW7ZvrS3dnSy/oq/bmSK7OuonPAv3Ax+PM+7vLdkS++Brgy5dbrz/V0zF9LophTcX9Nctced83Iuv2ZdNekZCJK+3Op253PGXctTCWiMoe6ojYU99ekZHQcs732v4/PH9d43McR5Xf6bfbXbVfepxPJ6KMp7s6PzlxTFESyIUr7tp9ujtKJVJT2527a1VPUKNz55Ovx1494snS+X9Y3al8+Fa031RDFls1E+cl0VN6fV7580f6J+/MtKp9IRGXS6ahMMunP8yjd0BCV98c7XtQuo/Jpd1xSrv2lXbtpThVfY3y5Bh+HOwYJt++Sbt3JmD8v+06HMp99xzOQaUXn+nFUVEPMLC7py5LeJGmDpMfN7I4QwvOuzFxJn5Z0Tghhl5mNH6x4qq5TCwAAgGIV+qLY6ZJWhRBekyQzu0XSVZKed2V+R9KXC7eEVQhh62AFw/QDAAAAlDPWzJa7n+vce1MkrXevNxTyvHmS5pnZg2b2iJldOliBMlILAABQAwbpi2LbQwjLjmP5hKS5ks6X1C7pfjM7MYSwewBi+40VAQAAoKpZ0feChshGSVPd6/ZCnrdB0qMhhG5Jq83sZeU7uY8PdDBMPwAAAMCxeFzSXDObaWYpSddKuqNXmR8qP0orMxur/HSE1wYjGEZqAQAAqpxp6O9TG0LImNnHJd2t/K1vbgwhPGdmn5e0PIRwR+G9S8zseUlZSX8SQtgxGPHQqQUAAMAxCSHcKenOXnmfdekg6Y8LP4OKTi0AAEC1s4rc0mtYoVMLAABQA/rzBLBaxhfFAAAAUPUYqQUAAKhylfii2HDDSC0AAACqHiO1AAAANaDe59TSqQUAAKgBdd6nZfoBAAAAqh8jtQAAAFXOxEhlvW8/AAAAakDVjdT6+SLmXsTdYzQSLh1yUdoUpTOxXFTeor59NhZK1xNKT1TJhqi8f5JHKh7VmXRvtKSj9TamojJ7DkVlOrujMt0Zl85G6Ww2Wq/Xez5NrGgbXNyuLp9fLl28jqjOXC6qR658UX4Zwa3A1xnKrTgW77POIn5n5LKl0+Xq92XKTVLy+aH0fpBrW0VplYnHtZuifL9Pysbpls12ly7v80PxMSp3XGOxqN5ctvRxLXcsy+26cl9msDLp/sj5HV+mCQX3hr8e+PJWJoii/FC6TK4ohNLrSiei45Fx+60hGe1nf+1pTEblu9z1IJmIyje4dLdFdXa5/JgLzsdwoCNqE/7YKR19PPS+3hQ1ffdWQ0O0TM6t79ChjEpJum1OppI96UwqKp9MR/nB1enbXHd3Y1S+pTWK0+3HrozLTzeVrMdvTMLHE4+2K+bO0VwiFS2b6ZIrFCWTUZm42+/phnRPOpuJzuNUOirvY0s3RuXj8ageX6eXTEX5jY3RtiQSpcey0u54l7sE+3ia3bFOuH3S7Nbr01Lx5a0hER2brPucTrpC/jrhP1M9/xlX7vPOn4uxco/cKroc+IWtVJHhyXqdw3Wo6jq1AAAA+E313aVl+gEAAABqACO1AAAAVc7EfWoZqQUAAEDVY6QWAACgBtT3OC2dWgAAgJpQ57MPmH4AAACA6sdILQAAQNWzur9PLSO1AAAAqHqM1AIAAFQ5EyOVdGoBAABqANMPKsTM4mb2hJn9pFIxAAAAoDZUcqT2k5JekNRawRgAAABqQn2P01ZopNbM2iVdLumrlVg/AAAAakulRmr/WdKfShpRofUDAADUDmNO7ZCP1JrZFZK2hhBW9FHuOjNbbmbLd+3cPkTRAQAAVJ/Ddz8Y6J9qUomR2nMkXWlmb5HUIKnVzL4VQnifLxRCuEHSDZJ0wpJTQ6zEbx+xWIjS7v1krB+HIReV8csWp6Pi/refILfeUDo/HXf1u1kumXRUZmRDtie9c393T/pgZyYKMxeV787kXH6UTiTiUQwhKt97e3JW/F6pZfxu9uv2ctlo3X7ZonSZZYv2oy/jDpnftiKxaDvlt9Pcwn4DypXx9ZRZVb+WjSddeVdRNuPKu3iK1pstnU42lAmoDOtH/b5MIlW6vKRYvPR5U/Sbf5lzwtx2JhKxkumMa7+JuFvW1ROPlU7Hygw+uGoULzpHS/OHtfe5cljS7YdcyJYsU3QNKHdt8Nvl0g2u/q6sy3f7Kp2Mjk1T0l+rohgOdEYvGl2ZRDyKoTNT+trQmIou/elUcTuI6onq99ee3u9ly5zrWXedSKejdfjmlHTb2dkRtU1/bUg1RPkxd23PZqJjk81Gf/RrbGksWd7XGS9zzfRl0o3pnnSmOzqn/Xniy2cy6ZL5yXR0nfDx+O3KZrMl831DbmqOrg2JhD/fom054PZtS0tUT8odY5+Ou20Z0eiuZ04mGwWRddfmlobS5Ztd/c2p8p/FTf7Yu7aScA0kFfPtunRdsTLXj6LPLyt9venPqGZxESuTj+FiyDvhIYRPhxDaQwgzJF0r6Ze9O7QAAAA4OmY24D/VpNpGlgEAAIDfUNGHL4QQ7pN0XyVjAAAAqAXVNa468HiiGAAAQA2ostkCA47pBwAAAKh6jNQCAABUufwtvep7qJaRWgAAAFQ9RmoBAABqQL3PqaVTCwAAUPVMxvQDAAAAoLoxUgsAAFAD6n36ASO1AAAAqHqM1AIAAFQ5bulFpxYAAKD6GdMPmH4AAACAqsdILQAAQA1gpBYAAACoclUxUmsmJWL5Xz9yIcpPxKI+ecz9dhJ3L7IhShf9AuO683H3q42rvqh8zHydPj9K+9gaE/HemyFJag7RLh/ZmOlJp5JRQD5+z/8GFvPbXlS+eFn/Xq47CjDngg0u7ng8Kt/V5cuUTueyORdf6bh9eR93UT25XMkyRfX4HeyPWczt61y25LJF4q58iNarWJnToah8ysXg4rRk6Rh8bMl06TLBSpe3Mvku5KKY/bYkojgtHpXx7Tveq43G3Xb64+GblC+jovZoJdMJt46sO3GS8dLt3S+bSsZLlkn48lY67Vuib99emeyic9rX6esp2j2uvL/xub+u+DL+2nAoE7WDJncNaHTpEWm/r6J69rv905SK3ujMBJfvjqmLv60hyt+dcu0jlL4uZLLFeyuZiNbnryX+OHVlop3U0BCtI+42Ium2s6ur9Lnb2BSdWzG3bDZTunxDU0NUvsy1xNfjr2F++9ON0fna3dXt4i99nvjrUzYbxZZMRfH7+ptbonPU19PQ6K4xTosr788Tvw+L6m+Oyjeko/3f6NqE3CWpOV36+pd129Xt9pVvo57Pb20ovsZ0uXaU9tcA8+d36WuDT1uZc9RfG6JP116fcWU+X8tdS4o/+6OKhuuAaL0/fKEqOrUAAAAoz1T8S3k9YvoBAAAAqh4jtQAAADWg3qcfMFILAACAqsdILQAAQA2o91t60akFAACoAUw/AAAAAKocI7UAAABVjlt6MVILAACAGsBILQAAQNWzup9TS6cWAACg2hl3P2D6AQAAAKoeI7UAAAA1oM4HahmpBQAAQPVjpBYAAKDK5W/pVd9jtYzUAgAA1AAbhJ8+12l2qZm9ZGarzOz6I5R7p5kFM1t2jJvXJzq1AAAAOGpmFpf0ZUmXSVok6d1mtqhEuRGSPinp0cGMp2qmH8QOPyYjF3rygpV4X8W3tEiUebxG3C98lKP1CbdANkTx+PvDNcTjPWkXsnJRtlrT0e5vTkVv7CnaliidiEe/g8SK8qN0VyZXFGtw8eV8IGX4/Vi8bK5kvo/P54cy6/JlYm574m5/FZV39cRSUflsNhulkw0ll+1VUZQ297tcrMMHF6WTKVcmfnTpXKZ0nSkXZ6ardJmixuti8PFkuqO0328xdzq7Zf0xiqWi/N77PJGKls9mov3rl48nomUSLh13bTDujmsq6Y9ZlE4mXNqVT7s6k64t+nTct/1yj8/pR7bf1aEov/TCoShd+rz3sfmY/XUi7fa7L9/s2ndLOirT2lD63BjVWPr6Ebeorfvd40/JEa7+EY3JnnRnlz/uUfnubPF1pcldu7rdNccv0+ia7KFDUZv17abBxdHREZ03/jrU1BTFl0xG5bu7S7fR5ha3Yifp9pE/5fw1yUu4Nlrc7qN8v6iPuasr2hYfsy/f0pIuuaw/f7wRI6Ly/lru96Gvv9Xth3SZc7XRnfOpoutxVMavq9Md6wa3H1IJ99mXjNItqeK221G0fPRep2tfRee9i8l/zpX7E7uP28sVHSf3hr/0umxfvU/Hq+FxXUMf4umSVoUQXpMkM7tF0lWSnu9V7q8k/Z2kPxnMYBipBQAAQDljzWy5+7nOvTdF0nr3ekMhr4eZnSppagjhp4MdaNWM1AIAAKC8QXqi2PYQwjHNgzWzmKR/lPTBAY2oDDq1AAAANaACNz/YKGmqe91eyDtshKTFku4rTBOaKOkOM7syhLB8oINh+gEAAACOxeOS5prZTDNLSbpW0h2H3wwh7AkhjA0hzAghzJD0iKRB6dBKdGoBAABqwlDf0iuEkJH0cUl3S3pB0vdCCM+Z2efN7MoB3bh+YPoBAAAAjkkI4U5Jd/bK+2yZsucPZix0agEAAGpBFdx1bDDRqQUAAKhy+ekC9d2rZU4tAAAAqh4jtQAAANXOKnJLr2GFkVoAAABUPUZqAQAAakCdD9TSqQUAAKgJdd6rZfoBAAAAqh4jtQAAAFXPuKVXpQMAAAAAjhcjtQAAADWg3m/pVRWdWjMpHssfqRCK8w/z+TH3hsWidM4VMjdGncu5ehSVKarHxeOKFw11+2UTVnoQ3MfQlIj3pEc1Rodi+/4oP9Gd7UmnQ1RnJue2xQWXiBevN+fK+bTfX8G/cGKxqC5zKwl+3W7/WojS2RDFbWWORzzutjMVbb9l+rGubleP248Zl/bLBn88YlGZop2Xi2JWsqFMftotW6ZOvz+z3aXLpJujdKbL5TeVLt/Q4urMlC6TSLkwo7Tf/zHXPmK92kq6Idq2nDspUulklE5F64vH3fFw60gmo3pT7nhkU6FkfioRlU+6dNwd70ZXZ4Ov37WhmPz56rbZn7wuXe789k3Cx+DPH/nrjduNRcv6/ZOz0mX8Nrp9MiIdpVvcuZErfaqqOeX2oavT7yvP75ORDdG6Drg3Uokonem14pZ0FFNnPGor/vhlslF+c1PUHhNuvzQ3RG3rUEfUrlOp6JxrbEyWzO/q8teYKLbW1ujcLb7mRenu7ii2cu3Y13noUHQep922+3oa3TX84MHS5X08rSOi882fM0WfZe54jHbl93dE9bc2lT7XR7dE5X078+2+xe1/v38aU6XjOeD2eYtro02uvG+LTcniLkbcouWbEomS+f488G0l4beh6KSWK+Pbe3RsYmX6Bxn/4e/PSyvdJvxn/HBkqvvviTH9AAAAANWvKkZqAQAA0Ic6H6plpBYAAABVj5FaAACAGlDvt/SiUwsAAFAD6v3uB0w/AAAAQNVjpBYAAKAG1PlALSO1AAAAqH6M1AIAAFQ7nr5ApxYAAKAW1PvdD5h+AAAAgKo35J1aM5tqZr8ys+fN7Dkz++RQxwAAAFBLTPlbeg30TzWpxPSDjKRPhRBWmtkISSvM7N4QwvMViAUAAAA1YMg7tSGEzZI2F9L7zOwFSVMk0akFAAA4RlU2sDrgKvpFMTObIekUSY9WMg4AAICqV+e92op9UczMWiTdJukPQwh7S7x/nZktN7PlO3dsH/oAAQAAUDUq0qk1s6TyHdpvhxBuL1UmhHBDCGFZCGHZ6DFjhzZAAACAKmOD8K+aDPn0AzMzSf8t6YUQwj/2cynFCl/BMwuurqhELlc6P+Ze5LJRmWQ86s93hZxbU+kDaK6emItBudLl47EoP7jijYl4T7o7F+3+cS1ReuPeKN3VnS0Zg1y+2/Ti2CR154pfRzFF+eX2XSIR7aNcNoo7Gy8Tk5PLRfs0lU5F8bn9nktEZRKpaJsz3RkXaJT0y2YzpWPo6uwqmd9xMF4yP5uMYvMxx91x8uvy+bls6fK5VEOU7jgQbUCqMUon90fpTFfpMrlovYkRI6Pibv/49XoNzQ0l8+Nxtx9ixceusTkdvef2UTodLZNMRmnfPnwzaGhIRnW6ZVOufHM6Ot7pVFSmwZVpSkbphmS0ggbXDnw65n5F9+dfzKXjLtCirS9z3fbb5fdJcA3TL5py8fgY4n74oMz1IOZqGpHu7kk3uTLlPl6a3XFpdFf1Lt+m/Xni2u5It0DanZPNKXeNzBRfR0a443qwO1qm211ju906Ro+I0jl37WlxbSXTFrXZg51RG29ybeVQV3ROpNPu+uHbVpM7p8tc5zKZaNnWlqh8h6vfX7f9sW9x5Q8ejI7TyNbo/Em561k87j6DXAzNDVEZv40d7tre6M6NMSOi+v12jWqO4vHtdUyLLx/lJ108/hhnXaEGd+75y4Q/P9saoth8e0jESp+rkpR07/m278+npkS0LxLupPb70X9O+/iKP3dduh9l/Lr8NSNW5kJR5qMVFVaJkdpzJL1f0oVm9mTh5y0ViAMAAKBmcEuvIRZCeEB1P5UZAABgYNV754onigEAAKDqVfSWXgAAABggdT5Uy0gtAAAAqh4jtQAAAFXOVP4OTvWCTi0AAEC1q8K7FQw0ph8AAACg6jFSCwAAUAPqfKCWkVoAAABUP0ZqAQAAakGdD9XSqQUAAKh6Vvd3P2D6AQAAAKoeI7UAAAA1gFt6AQAAAFWOkVoAAIAqZ6r774lVR6fWJCXj+UMV3CELISqTs+hFMh4NQOdcIctF5ROxqJ7ufrSCw+vvHUNGUf0xF4/5vwG4pB8ab00le9Ljm9I96Qkt3T3pXC6qdH9H6fyuTLRh1utvD8HvJCfu9lEiEVw6yk8mo/x0Ot6TPniw9AB/zO3TQwej8ql0qmQ9GRd3Y2O0Lzo7MyXj8fyyXmdnumR+Ihk1dXNxBrcfc7mozlgsWm82k+1JxxPxPvNjbt8e3Bdte6ohSme6m6L1ZqP1+np8bE2tUfnOg51l6oz2W0tbc0/at4lkMoqtszOKX5La2hqiOFx798c1lYr2Y6M7li5UtTZFx3JUc3Q8Orqj9TWlo3raGqJ6fPNtcrG2NSRcfpROx6NlG1w64eJPlNmWcn+m89eMmCvkDqvcoVfCtZWUa6++fl9P1u2sxrhrl4oqHdUQ7cNmt71Jty5/DWtw7cbz64q78gfdBiRd/r6uKL85FdXZ0et8S7md0eiOU9btuwNd0TL+mtzt2vvopmjb4m4fHUhFbTnt6m9IRvH5dR1y7bLZtRW/3zPu/O522zOyOTqH9ndE6/Ua3L5oddeqPe54j26J2nqji8e3g0w2irm1yV0b3P451OXOY7eusc1R2h/XqaOi89af65Nbo/Idmah8yp0P/nOt28XWnI7i8celIxntN39OjkiV7ko0Jcp3MRqTpdusP4cSRbFG+f5jzZ+Xvrz/tPWfg/48CG5hvy98Gd+GckX1lAy/8uq8VztcDwsAAADQb1UxUgsAAIAj45ZeAAAAQJVjpBYAAKAG1PstvejUAgAA1IA679My/QAAAADVj5FaAACAamdMP2CkFgAAAFWPkVoAAICaUN9DtXRqAQAAqpyJ6QdMPwAAAEDVY6QWAACgBtT5QC0jtQAAAKh+jNQCAADUAObUAgAAoOrZIPzrc51ml5rZS2a2ysyuL/H+H5vZ82b2tJn9wsymD8rGi04tAAAAjoGZxSV9WdJlkhZJereZLepV7AlJy0IISyTdKumLgxUPnVoAAIBaYIPwc2SnS1oVQngthNAl6RZJV/kCIYRfhRAOFl4+Iqn9eDbxSKpiTm0qYZo6Ol3pMIbMVSdWOgIAvTWn+r4GTR9TP9cpAHVjrJktd69vCCHcUEhPkbTevbdB0hlHqOsjkn42wPH1qIpOLQAAAI5skL4ntj2EsOx4KzGz90laJumNxx9SaXRqAQAAqpxZRe5+sFHSVPe6vZBXxMwulvQZSW8MIXQOVjDMqQUAAMCxeFzSXDObaWYpSddKusMXMLNTJH1F0pUhhK2DGQwjtQAAADWgP7fgGkghhIyZfVzS3ZLikm4MITxnZp+XtDyEcIekv5fUIun7lh9KXhdCuHIw4qFTCwAAUAsq8PCFEMKdku7slfdZl754qGJh+gEAAACqHiO1AAAANaDOn5LLSC0AAACqHyO1AAAANaACt/QaVujUAgAAVD0b8rsfDDdMPwAAAEDVY6QWAACgypmYfsBILQAAAKoenVoAAABUPaYfAAAA1ACmHwAAAABVjpFaAACAGsAtvQAAAIAqx0gtAABAtTPm1NKpBQAAqHJW+KlnTD8AAABA1WOkFgAAoBbU+VAtI7UAAACoeozUAgAA1IB6v6UXnVoAAIAaUO93P2D6AQAAAKoeI7UAAAA1oM4HahmpBQAAQPVjpBYAAKAW1PlQbUVGas3sUjN7ycxWmdn1lYgBAACgltgg/KsmQ96pNbO4pC9LukzSIknvNrNFQx0HAAAAakclph+cLmlVCOE1STKzWyRdJen5CsQCAABQ9Uzc0qsS0w+mSFrvXm8o5AEAAADHZNh+UczMrpN0XeHlfjN7qZLxDLGxkrZXOohhjn10ZOyfvrGPjoz90zf20ZHVy/6ZXukAJGnlyhV3NyZt7CBUXTXHsBKd2o2SprrX7YW8IiGEGyTdMFRBDSdmtjyEsKzScQxn7KMjY//0jX10ZOyfvrGPjoz9M7RCCJdWOoZKq8T0g8clzTWzmWaWknStpDsqEAcAAABqxJCP1IYQMmb2cUl3S4pLujGE8NxQxwEAAIDaUZE5tSGEOyXdWYl1V4m6nHZxlNhHR8b+6Rv76MjYP31jHx0Z+wdDykIIlY4BAAAAOC4VeaIYAAAAMJDo1A6BUo8FNrNvF/KeNbMbzSxZZtkPmNkrhZ8PuPylZvZMoc5/NaveWy4f6bHJhW3bf4RlP11Y7iUze3N/6qxGZdqQmdnfmNnLZvaCmX2izLJ12YbM7CIzW2lmT5rZA2Y2p8yyNd+GCteYrWb2rMsbbWb3FtrFvWY2qsyyNd9+pLL76O/N7EUze9rMfmBmI8ssW7KtFL4Q/Wgh/7uFL0dXpVL7x733KTMLZqVvJ1UvbQjDQAiBn0H8Uf7LcK9KmiUpJekp5R8P/BYVHgAi6WZJHyux7GhJrxX+H1VIjyq895ikMwvL/0zSZZXe1oHcP4X3lkn6pqT9ZZZdVCifljSzUE/8SHVW488R2tCHJH1DUqxQbjxtqGj/vCxpYaHM70m6qY7b0BsknSrpWZf3RUnXF9LXS/q7emw/feyjSyQlCum/K7OPjnQN+56kawvp/1KJ63y1/JTaP4X8qcp/8XutpLH13Ib4qfwPI7WDr+exwCGELkm3SLoqhHBnKFD+xG4vseybJd0bQtgZQtgl6V5Jl5rZJEmtIYRHCst/Q9LbhmRrBl7J/WNmcUl/L+lPj7DsVZJuCSF0hhBWS1pVqK9knYO6FYOr3PZ8TNLnQwg5SQohbC2xbN22IUlBUmuhTJukTSWWrYs2FEK4X9LOXtlXSfp6If11lT7+9dB+JJXeRyGEe0IImcLLR1T6Ol3uGmaSLpR0a6FcuX1cFcq0IUn6J+Wv0+W+oFM3bQiVR6d28B3xscCFaQfvl3RX4fUyM/tqH8tOKaRL1lllym3jxyXdEULY7Aub2ZVm9vk+lq21RzGX257Zkn7LzJab2c/MbK5EG1K0LR+VdKeZbVD+HPuCVLdtqJQJ7vzaImmCVJftp78+rPxoosxsspkdvoNPuX00RtJu1ymuuX1kZldJ2hhCeKpXPm0IFTFsH5NbR/5D0v0hhF9LUghhufIfxvWsSdK7JJ3f+40Qwh3iYR2HpSV1hBCWmdk7JN0o6TzaUI8/kvSWEMKjZvYnkv5R0kdpQ78phBDMLBTStJ9ezOwzkjKSvi1JIYRNyk8hq1tm1iTp/yg/RaMIbQiVwkjt4Cv7WGAz+5ykcZL++CiX3ajiP4OVfNRwlSi1ja9KmiNplZmtkdRkZqv6uezGI+RXq3Lbs0HS7YW8H0hachTL1nobel3SSSGERwt535V0dj+XrcU2VMrrhT8Bq/B/qekr9dB+jsjMPijpCknvLfyZvLdy+2iHpJFmluiVXytmKz8P/anCdbpd0kozm9irXN23IQyhSk/qrfUf5UfDX1P+5D/8JYITlP8t9iFJjUdYdrSk1cpPrh9VSI8uvNd7gv1bKr2tA7l/epUp90WxE1T8JZ/XlP/SRp91VtPPEdrQFyR9uFDmfEmP04Z69s9JkrZLmlco8xFJt9VrGyps6wwVfwnq71X8RbEv1mP76WMfXSrpeUnjjrL9nVB47/sq/qLY71V6Gwdy//R6b43Kf1GsbtoQP5X9qXgA9fCj/J+pXlZ+BPIzhbxM4fWThZ/PFvKXSfqqW/bDyn95ZZWkD7n8ZZKeLdTx7yo8SKMaf0rtn17v73fpK5X/ctTh158pLPeS3Ddn+6qz2n7KtKGRkn4q6RlJDys/MkkbivbP2wv75ilJ90maVa9tSPk7rGyW1K38CP9HlJ/z+QtJr0j6ueto1F37OcI+WqX8fNDD1+n/KpSdLOnOvtqK8ndEeKxQz/clpSu9nQO5f3q9v0aFTm29tiF+Kv/DE8UAAABQ9ZhTCwAAgKpHpxYAAABVj04tAAAAqh6dWgAAAFQ9OrUAAACoenRqAVQ1MxtjZk8WfraY2cZCer+Z/Uel4wMADA1u6QWgZpjZXyp/X+MvVToWAMDQYqQWQE0ys/PN7CeF9F+a2dfN7NdmttbM3mFmXzSzZ8zsLjNLFsotNbP/MbMVZnb34cfIAgCGPzq1AOrFbEkXKv9EsW9J+lUI4URJhyRdXujY/pukq0MISyXdKOlvKhUsAODoJCodAAAMkZ+FELrN7BlJcUl3FfKfUf6Z9vMlLZZ0r5mpUGZzBeIEABwDOrUA6kWnJIUQcmbWHaIvFOSUvxaapOdCCGdVKkAAwLFj+gEA5L0kaZyZnSVJZpY0sxMqHBMAoJ/o1AKApBBCl6SrJf2dmT0l6UlJZ1c0KABAv3FLLwAAAFQ9RmoBAABQ9ejUAgAAoOrRqQUAAEDVo1MLAACAqkenFgAAAFWPTi0AAACqHp1aAAAAVD06tQAAAKh6/z/3yJnyCYoPgwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline \n", + "from matplotlib import pyplot as plt\n", + "import matplotlib.dates as dt\n", + "\n", + "ax = plt.figure(figsize=(12,8)).add_axes([.14, .14, .8, .74])\n", + "# Plot flow speed\n", + "t = dlfn.time.dt642date(ds_avg.time)\n", + "plt.pcolormesh(t, ds_avg.range, ds_avg['U_mag'], cmap='Blues', shading='nearest')\n", + "# Plot the water surface\n", + "ax.plot(t, ds_avg.depth)\n", + "\n", + "# Set up time on x-axis\n", + "ax.set_xlabel('Time')\n", + "ax.xaxis.set_major_formatter(dt.DateFormatter('%H:%M'))\n", + "\n", + "ax.set_ylabel('Altitude [m]')\n", + "ax.set_ylim([0, 12])\n", + "plt.colorbar(label='Speed [m/s]')" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ax = plt.figure(figsize=(12,8)).add_axes([.14, .14, .8, .74])\n", + "# Plot flow direction\n", + "plt.pcolormesh(t, ds_avg.range, ds_avg['U_dir'], cmap='twilight', shading='nearest')\n", + "# Plot the water surface\n", + "ax.plot(t, ds_avg.depth)\n", + "\n", + "# set up time on x-axis\n", + "ax.set_xlabel('Time')\n", + "ax.xaxis.set_major_formatter(dt.DateFormatter('%H:%M'))\n", + "\n", + "ax.set_ylabel('Altitude [m]')\n", + "ax.set_ylim([0, 12]);\n", + "plt.colorbar(label='Horizontal Vel Dir [deg CW from true N]');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Saving and Loading DOLfYN datasets\n", + "Datasets can be saved and reloaded using the `save` and `load` functions. Xarray is saved natively in netCDF format, hence the \".nc\" extension.\n", + "\n", + "Note: DOLfYN datasets cannot be saved using xarray's native `ds.to_netcdf`; however, DOLfYN datasets can be opened using `xarray.open_dataset`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment these lines to save and load to your current working directory\n", + "#dlfn.save(ds, 'your_data.nc')\n", + "#ds_saved = dlfn.load('your_data.nc')" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "5cfd453a1a1cce2f32ea80f99ff7da863344217116d39185ac62b248c2577445" + }, + "kernelspec": { + "display_name": "Python 3.8.12 64-bit ('base': conda)", + "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.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/adv_example.ipynb b/examples/adv_example.ipynb new file mode 100644 index 000000000..69a2bceec --- /dev/null +++ b/examples/adv_example.ipynb @@ -0,0 +1,945 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reading ADV Data with MHKiT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following example demonstrates a simple workflow for analyzing Acoustic Doppler Velocimetry (ADV) data using MHKiT. MHKiT has brought the DOLfYN codebase in as an MHKiT module for working with ADV and Acoustic Doppler Current Profiler (ADCP) data. The DOLfYN MHKiT module is transferring functionality in two parts. In phase 1 the basic IO and core functions of DOLfYN are brought in. These functions include reading instrument data and rotating thru coordinate systems. In phase 2 DOLfYN analysis functions are brought in. As phase 2 is still in progress we will import the analysis tools from the [DOLfYN](https://github.com/lkilcher/dolfyn) package.\n", + " \n", + "A typical ADV data workflow is broken down into\n", + " 1. Review the raw data\n", + " - Check timestamps\n", + " - Look at velocity data quality, particularly for spiking\n", + " 2. Check for spurious datapoints and remove. Replace bad datapoints using interpolation if desired\n", + " 3. Rotate the data into principal flow coordinates (streamwise, cross-stream, vertical)\n", + " 4. Average the data into bins, or ensembles, of a set time length (normally 5 to 10 min)\n", + " 5. Calculate turbulence statistics (turbulence intensity, TKE, Reynolds stresses) of the measured flowfield\n", + "\n", + "Start by importing the necessary DOLfYN tools:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from mhkit import dolfyn as dlfn\n", + "from dolfyn.adv import api" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Raw Instrument Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "DOLfYN currently only carries support for the Nortek Vector ADV. The example loaded here is a short clip of data from a test deployment to show DOLfN's capabilities.\n", + "\n", + "Start by reading in the raw datafile downloaded from the instrument. The `dlfn.read` function reads the raw file and dumps the information into an xarray Dataset, which contains three groups of variables:\n", + "\n", + "1. Velocity, amplitude, and correlation of the Doppler velocimetry\n", + "2. Measurements of the instrument's bearing and environment\n", + "3. Orientation matrices DOLfYN uses for rotating through coordinate frames." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reading file data/dolfyn/vector_data01.VEC ...\n", + " end of file at 3000000 bytes.\n" + ] + } + ], + "source": [ + "ds = dlfn.read('data/dolfyn/vector_data01.VEC')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two ways to see what's in a Dataset. The first is to simply type the dataset's name to see the standard xarray output. To access a particular variable in a dataset, use dict-style (`ds['vel']`) or attribute-style syntax (`ds.vel`). See the [xarray docs](http://xarray.pydata.org/en/stable/getting-started-guide/quick-overview.html) for more details on how to use the xarray format." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:              (time: 122912, dir: 3, x: 3, x*: 3, earth: 3, inst: 3)\n",
+       "Coordinates:\n",
+       "  * time                 (time) datetime64[ns] 2012-06-12T12:00:02.968749 ......\n",
+       "  * dir                  (dir) <U1 'X' 'Y' 'Z'\n",
+       "  * x                    (x) int32 1 2 3\n",
+       "  * x*                   (x*) int32 1 2 3\n",
+       "  * earth                (earth) <U1 'E' 'N' 'U'\n",
+       "  * inst                 (inst) <U1 'X' 'Y' 'Z'\n",
+       "Data variables: (12/15)\n",
+       "    beam2inst_orientmat  (x, x*) float64 2.709 -1.34 -1.364 ... -0.3438 -0.3499\n",
+       "    batt                 (time) float32 13.2 13.2 13.2 13.2 ... nan nan nan nan\n",
+       "    c_sound              (time) float32 1.493e+03 1.493e+03 ... nan nan\n",
+       "    heading              (time) float32 5.6 10.5 10.51 10.52 ... nan nan nan nan\n",
+       "    pitch                (time) float32 -31.5 -31.7 -31.69 ... nan nan nan\n",
+       "    roll                 (time) float32 0.4 4.2 4.253 4.306 ... nan nan nan nan\n",
+       "    ...                   ...\n",
+       "    vel                  (dir, time) float32 -1.002 -1.008 -0.944 ... nan nan\n",
+       "    amp                  (dir, time) uint8 104 110 111 113 108 112 ... 0 0 0 0 0\n",
+       "    corr                 (dir, time) uint8 97 91 97 98 90 95 95 ... 0 0 0 0 0 0\n",
+       "    orientation_down     (time) bool True True True True ... True True True True\n",
+       "    pressure             (time) float64 5.448 5.436 5.484 5.448 ... 0.0 0.0 0.0\n",
+       "    orientmat            (earth, inst, time) float32 0.0832 0.155 ... -0.7065\n",
+       "Attributes:\n",
+       "    config:       {'ProLogID': 149, 'ProLogFWver': '4.08', 'config': 15412, '...\n",
+       "    inst_make:    Nortek\n",
+       "    inst_model:   Vector\n",
+       "    inst_type:    ADV\n",
+       "    rotate_vars:  ['vel']\n",
+       "    freq:         6000\n",
+       "    SerialNum:    VEC 9062\n",
+       "    Comments:     APL-UW vector on Tidal Turbulence Mooring in Admiralty, tim...\n",
+       "    fs:           32.0\n",
+       "    coord_sys:    inst\n",
+       "    has_imu:      0
" + ], + "text/plain": [ + "\n", + "Dimensions: (time: 122912, dir: 3, x: 3, x*: 3, earth: 3, inst: 3)\n", + "Coordinates:\n", + " * time (time) datetime64[ns] 2012-06-12T12:00:02.968749 ......\n", + " * dir (dir) : Nortek Vector\n", + " . 1.07 hours (started: Jun 12, 2012 12:00)\n", + " . inst-frame\n", + " . (122912 pings @ 32.0Hz)\n", + " Variables:\n", + " - time ('time',)\n", + " - vel ('dir', 'time')\n", + " - orientmat ('earth', 'inst', 'time')\n", + " - heading ('time',)\n", + " - pitch ('time',)\n", + " - roll ('time',)\n", + " - temp ('time',)\n", + " - pressure ('time',)\n", + " - amp ('dir', 'time')\n", + " - corr ('dir', 'time')\n", + " ... and others (see `.variables`)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds_dolfyn = ds.velds\n", + "ds_dolfyn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## QC'ing Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "ADV velocity data tends to have spikes due to Doppler noise, and the common way to \"despike\" the data is by using the phase-space algorithm by Goring and Nikora (2002). DOLfYN integrates this function using a 2-step approach: create a logical mask where True corresponds to a spike detection, and then utilize an interpolation function to replace the spikes." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Percent of data containing spikes: 0.73%\n" + ] + } + ], + "source": [ + "# Clean the file using the Goring+Nikora method:\n", + "mask = api.clean.GN2002(ds.vel, npt=5000)\n", + "# Replace bad datapoints via cubic spline interpolation\n", + "ds['vel'] = api.clean.clean_fill(ds['vel'], mask, npt=12, method='cubic')\n", + "\n", + "print('Percent of data containing spikes: {0:.2f}%'.format(100*mask.mean()))\n", + "\n", + "# If interpolation isn't desired:\n", + "ds_nan = ds.copy(deep=True)\n", + "ds_nan.coords['mask'] = (('dir','time'), ~mask)\n", + "ds_nan['vel'] = ds_nan.vel.where(ds_nan.mask)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Coordinate Rotations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the data has been cleaned, the next step is to rotate the velocity data into true East, North, Up (ENU) coordinates.\n", + "\n", + "ADVs use an internal compass or magnetometer to determine magnetic ENU directions. The `set_declination` function takes the user supplied magnetic declination (which can be looked up online for specific coordinates) and adjusts the orientation matrix saved within the dataset.\n", + "\n", + "Instruments save vector data in the coordinate system specified in the deployment configuration file. To make the data useful, it must be rotated through coordinate systems (\"beam\"<->\"inst\"<->\"earth\"<->\"principal\"), done through the `rotate2` function. If the \"earth\" (ENU) coordinate system is specified, DOLfYN will automatically rotate the dataset through the necessary coordinate systems to get there. The `inplace` set as true will alter the input dataset \"in place\", a.k.a. it not create a new dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# First set the magnetic declination\n", + "dlfn.set_declination(ds, declin=10, inplace=True) # declination points 10 degrees East\n", + "\n", + "# Rotate that data from the instrument to earth frame (ENU):\n", + "dlfn.rotate2(ds, 'earth', inplace=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once in the true ENU frame of reference, we can calculate the principal flow direction for the velocity data and rotate it into the principal frame of reference (streamwise, cross-stream, vertical). Principal flow directions are aligned with and orthogonal to the flow streamlines at the measurement location. \n", + "\n", + "First, the principal flow direction must be calculated through `calc_principal_heading`. As a standard for DOLfYN functions, those that begin with \"calc_*\" require the velocity data for input. This function is different from others in DOLfYN in that it requires place the output in an attribute called \"principal_heading\", as shown below.\n", + "\n", + "Again we use `rotate2` to change coordinate systems." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "ds.attrs['principal_heading'] = dlfn.calc_principal_heading(ds.vel)\n", + "dlfn.rotate2(ds, 'principal', inplace=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Averaging Data\n", + "The next step in ADV analysis is to average the velocity data into time bins (ensembles) and calculate turbulence statistics. There are a couple ways to do this, and both of these methods use the same variable inputs and return identical datasets.\n", + "\n", + "1. Define an averaging object, create a binned dataset and calculate basic turbulence statistics. This is done by initiating an object from the `ADVBinner` class, and subsequently supplying that object with our dataset.\n", + "\n", + "2. Alternatively, the functional version of ADVBinner, `calc_turbulence`.\n", + "\n", + "Function inputs shown here are the dataset itself; \"n_bin\", the number of elements in each bin; \"fs\", the ADV's sampling frequency in Hz; \"n_fft\", optional, the number of elements per FFT for spectral analysis; \"freq_units\", optional, either in Hz or rad/s, of the calculated spectral frequency vector.\n", + "\n", + "All of the variables in the returned dataset have been bin-averaged, where each average is computed using the number of elements specified in \"n_bins\". Additional variables in this dataset include the turbulent kinetic energy (TKE) vector (\"ds_binned.tke_vec\"), the Reynold's stresses (\"ds_binned.stress\"), and the power spectral densities (\"ds_binned.psd\"), calculated for each bin." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "binner = api.ADVBinner(n_bin=9600, fs=ds.fs, n_fft=2048)\n", + "ds_binned = binner(ds, freq_units=\"Hz\")\n", + "\n", + "ds_binned = api.calc_turbulence(ds, n_bin=9600, fs=ds.fs, n_fft=2048, freq_units=\"Hz\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The benefit to using `ADVBinner` is that one has access to all of the velocity and turbulence analysis functions that DOLfYN contains. If basic analysis will suffice, the `calc_turbulence` function is the most convienent. Either option can still utilize DOLfYN's shortcuts.\n", + "\n", + "See the [DOLfYN API](https://dolfyn.readthedocs.io/en/latest/apidoc/dolfyn.binners.html) for the full list of functions and shortcuts. A few examples are shown below.\n", + "\n", + "Some things to know:\n", + "- All functions operate bin-by-bin.\n", + "- `do_*` functions return a full dataset. The first two inputs are the original dataset and the dataset containing the variables calculated by the function. If an output dataset is not given, it will create one.\n", + "- `calc_*` functions return a data variable, which can be added to the dataset with a variable of your choosing. If inputs weren't specified in `ADVBinner`, they can be called here. Most of these functions can take both 3D and 1D velocity vectors as inputs.\n", + "- \"Shorcuts\", as referred to in DOLfYN, are functions accessible by the xarray accessor `velds`, as shown below. The list of \"shorcuts\" available through `velds` are listed [here](https://dolfyn.readthedocs.io/en/latest/apidoc/dolfyn.shortcuts.html). Some shorcut variables require the raw dataset, some an averaged dataset.\n", + "\n", + "For instance, \n", + "- `do_var` calculates the binned-variance of each variable in the raw dataset, the complementary to `do_avg`. Variables returned by this function contain a \"_var\" suffix to their name.\n", + "- `calc_csd` calculates the cross spectral power density between each direction of the supplied DataArray. Note that inputs specified in creating the `ADVBinner` object can be overridden or additionally specified for a particular function call.\n", + "- `velds.I` is the shortcut for turbulence intensity. This particular shortcut requires a dataset created by `do_avg`, because it requires bin-averaged data to calculate.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# Calculate the variance of each variable in the dataset and add to the averaged dataset\n", + "ds_binned = binner.do_var(ds, out_ds=ds_binned) \n", + "\n", + "# Calculate the cross power spectral density\n", + "ds_binned['csd'] = binner.calc_csd(ds['vel'], freq_units='Hz', n_fft_coh=2048)\n", + "\n", + "# Calculated the turbulence intensity (requires a binned dataset)\n", + "ds_binned['TI'] = ds_binned.velds.I" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plotting can be taken care of through matplotlib. As an example, the mean spectrum in the streamwise direction is plotted here. This spectrum shows the mean energy density in the flow at a particular flow frequency." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Streamwise Direction')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "plt.figure()\n", + "plt.loglog(ds_binned.f, ds_binned.psd.sel(S='Sxx').mean(dim='time'))\n", + "plt.xlabel('Frequency [Hz]')\n", + "plt.ylabel('Energy Density $\\mathrm{[m^2/s^s/Hz]}$')\n", + "plt.title('Streamwise Direction')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Saving and Loading DOLfYN datasets\n", + "Datasets can be saved and reloaded using the `save` and `load` functions. Xarray is saved natively in netCDF format, hence the \".nc\" extension.\n", + "\n", + "Note: DOLfYN datasets cannot be saved using xarray's native `ds.to_netcdf`; however, DOLfYN datasets can be opened using `xarray.open_dataset`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment these lines to save and load to your current working directory\n", + "#dlfn.save(ds, 'your_data.nc')\n", + "#ds_saved = dlfn.load('your_data.nc')" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "5cfd453a1a1cce2f32ea80f99ff7da863344217116d39185ac62b248c2577445" + }, + "kernelspec": { + "display_name": "Python 3.8.12 64-bit ('base': conda)", + "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.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/data/dolfyn/AWAC_test01.userdata.json b/examples/data/dolfyn/AWAC_test01.userdata.json new file mode 100644 index 000000000..7f53fe6ae --- /dev/null +++ b/examples/data/dolfyn/AWAC_test01.userdata.json @@ -0,0 +1,3 @@ +{"declination": 8.28, + "latlon": [39.9402, -105.2283] +} diff --git a/examples/data/dolfyn/AWAC_test01.wpr b/examples/data/dolfyn/AWAC_test01.wpr new file mode 100644 index 000000000..bd7728426 Binary files /dev/null and b/examples/data/dolfyn/AWAC_test01.wpr differ diff --git a/examples/data/dolfyn/BenchFile01.ad2cp b/examples/data/dolfyn/BenchFile01.ad2cp new file mode 100644 index 000000000..3a4a1464c Binary files /dev/null and b/examples/data/dolfyn/BenchFile01.ad2cp differ diff --git a/examples/data/dolfyn/H-AWAC_test01.wpr b/examples/data/dolfyn/H-AWAC_test01.wpr new file mode 100644 index 000000000..818063a5b Binary files /dev/null and b/examples/data/dolfyn/H-AWAC_test01.wpr differ diff --git a/examples/data/dolfyn/RDI_7f79.000 b/examples/data/dolfyn/RDI_7f79.000 new file mode 100644 index 000000000..da526b700 Binary files /dev/null and b/examples/data/dolfyn/RDI_7f79.000 differ diff --git a/examples/data/dolfyn/RDI_test01.000 b/examples/data/dolfyn/RDI_test01.000 new file mode 100644 index 000000000..86c30b0bb Binary files /dev/null and b/examples/data/dolfyn/RDI_test01.000 differ diff --git a/examples/data/dolfyn/RDI_withBT.000 b/examples/data/dolfyn/RDI_withBT.000 new file mode 100644 index 000000000..a8b5d201c Binary files /dev/null and b/examples/data/dolfyn/RDI_withBT.000 differ diff --git a/examples/data/dolfyn/Sig1000_IMU.ad2cp b/examples/data/dolfyn/Sig1000_IMU.ad2cp new file mode 100644 index 000000000..9f9935d33 Binary files /dev/null and b/examples/data/dolfyn/Sig1000_IMU.ad2cp differ diff --git a/examples/data/dolfyn/Sig1000_IMU.userdata.json b/examples/data/dolfyn/Sig1000_IMU.userdata.json new file mode 100644 index 000000000..7f53fe6ae --- /dev/null +++ b/examples/data/dolfyn/Sig1000_IMU.userdata.json @@ -0,0 +1,3 @@ +{"declination": 8.28, + "latlon": [39.9402, -105.2283] +} diff --git a/examples/data/dolfyn/Sig1000_tidal.ad2cp b/examples/data/dolfyn/Sig1000_tidal.ad2cp new file mode 100644 index 000000000..f5f6a3c9b Binary files /dev/null and b/examples/data/dolfyn/Sig1000_tidal.ad2cp differ diff --git a/examples/data/dolfyn/Sig500_Echo.ad2cp b/examples/data/dolfyn/Sig500_Echo.ad2cp new file mode 100644 index 000000000..33bebddc9 Binary files /dev/null and b/examples/data/dolfyn/Sig500_Echo.ad2cp differ diff --git a/examples/data/dolfyn/Sig500_last_ensemble_is_whole.ad2cp b/examples/data/dolfyn/Sig500_last_ensemble_is_whole.ad2cp new file mode 100644 index 000000000..0c66b1839 Binary files /dev/null and b/examples/data/dolfyn/Sig500_last_ensemble_is_whole.ad2cp differ diff --git a/examples/data/dolfyn/Sig_SkippedPings01.ad2cp b/examples/data/dolfyn/Sig_SkippedPings01.ad2cp new file mode 100644 index 000000000..7ceb8391f Binary files /dev/null and b/examples/data/dolfyn/Sig_SkippedPings01.ad2cp differ diff --git a/examples/data/dolfyn/VelEchoBT01.ad2cp b/examples/data/dolfyn/VelEchoBT01.ad2cp new file mode 100644 index 000000000..5b96badf4 Binary files /dev/null and b/examples/data/dolfyn/VelEchoBT01.ad2cp differ diff --git a/examples/data/dolfyn/burst_mode01.VEC b/examples/data/dolfyn/burst_mode01.VEC new file mode 100644 index 000000000..9e3020baf Binary files /dev/null and b/examples/data/dolfyn/burst_mode01.VEC differ diff --git a/examples/data/dolfyn/test_data/AWAC_test01.nc b/examples/data/dolfyn/test_data/AWAC_test01.nc new file mode 100644 index 000000000..1765c89c6 Binary files /dev/null and b/examples/data/dolfyn/test_data/AWAC_test01.nc differ diff --git a/examples/data/dolfyn/test_data/AWAC_test01_earth2inst.nc b/examples/data/dolfyn/test_data/AWAC_test01_earth2inst.nc new file mode 100644 index 000000000..3df557c79 Binary files /dev/null and b/examples/data/dolfyn/test_data/AWAC_test01_earth2inst.nc differ diff --git a/examples/data/dolfyn/test_data/AWAC_test01_earth2principal.nc b/examples/data/dolfyn/test_data/AWAC_test01_earth2principal.nc new file mode 100644 index 000000000..839635fcc Binary files /dev/null and b/examples/data/dolfyn/test_data/AWAC_test01_earth2principal.nc differ diff --git a/examples/data/dolfyn/test_data/AWAC_test01_inst2beam.nc b/examples/data/dolfyn/test_data/AWAC_test01_inst2beam.nc new file mode 100644 index 000000000..60dff7afa Binary files /dev/null and b/examples/data/dolfyn/test_data/AWAC_test01_inst2beam.nc differ diff --git a/examples/data/dolfyn/test_data/AWAC_test01_ud.nc b/examples/data/dolfyn/test_data/AWAC_test01_ud.nc new file mode 100644 index 000000000..b63c9038e Binary files /dev/null and b/examples/data/dolfyn/test_data/AWAC_test01_ud.nc differ diff --git a/examples/data/dolfyn/test_data/BenchFile01.mat b/examples/data/dolfyn/test_data/BenchFile01.mat new file mode 100644 index 000000000..e02f872c3 Binary files /dev/null and b/examples/data/dolfyn/test_data/BenchFile01.mat differ diff --git a/examples/data/dolfyn/test_data/BenchFile01.nc b/examples/data/dolfyn/test_data/BenchFile01.nc new file mode 100644 index 000000000..cedc12b0c Binary files /dev/null and b/examples/data/dolfyn/test_data/BenchFile01.nc differ diff --git a/examples/data/dolfyn/test_data/BenchFile01_avg.nc b/examples/data/dolfyn/test_data/BenchFile01_avg.nc new file mode 100644 index 000000000..386258a52 Binary files /dev/null and b/examples/data/dolfyn/test_data/BenchFile01_avg.nc differ diff --git a/examples/data/dolfyn/test_data/BenchFile01_rotate_beam2inst.nc b/examples/data/dolfyn/test_data/BenchFile01_rotate_beam2inst.nc new file mode 100644 index 000000000..d58c39770 Binary files /dev/null and b/examples/data/dolfyn/test_data/BenchFile01_rotate_beam2inst.nc differ diff --git a/examples/data/dolfyn/test_data/BenchFile01_rotate_earth2principal.nc b/examples/data/dolfyn/test_data/BenchFile01_rotate_earth2principal.nc new file mode 100644 index 000000000..9875152fd Binary files /dev/null and b/examples/data/dolfyn/test_data/BenchFile01_rotate_earth2principal.nc differ diff --git a/examples/data/dolfyn/test_data/BenchFile01_rotate_inst2earth.nc b/examples/data/dolfyn/test_data/BenchFile01_rotate_inst2earth.nc new file mode 100644 index 000000000..681752c34 Binary files /dev/null and b/examples/data/dolfyn/test_data/BenchFile01_rotate_inst2earth.nc differ diff --git a/examples/data/dolfyn/test_data/H-AWAC_test01.nc b/examples/data/dolfyn/test_data/H-AWAC_test01.nc new file mode 100644 index 000000000..2cef277df Binary files /dev/null and b/examples/data/dolfyn/test_data/H-AWAC_test01.nc differ diff --git a/examples/data/dolfyn/test_data/RDI_7f79.nc b/examples/data/dolfyn/test_data/RDI_7f79.nc new file mode 100644 index 000000000..6293e7242 Binary files /dev/null and b/examples/data/dolfyn/test_data/RDI_7f79.nc differ diff --git a/examples/data/dolfyn/test_data/RDI_test01.nc b/examples/data/dolfyn/test_data/RDI_test01.nc new file mode 100644 index 000000000..2683ee9d1 Binary files /dev/null and b/examples/data/dolfyn/test_data/RDI_test01.nc differ diff --git a/examples/data/dolfyn/test_data/RDI_test01_ofilt.nc b/examples/data/dolfyn/test_data/RDI_test01_ofilt.nc new file mode 100644 index 000000000..8546fdf43 Binary files /dev/null and b/examples/data/dolfyn/test_data/RDI_test01_ofilt.nc differ diff --git a/examples/data/dolfyn/test_data/RDI_test01_rotate_beam2inst.nc b/examples/data/dolfyn/test_data/RDI_test01_rotate_beam2inst.nc new file mode 100644 index 000000000..ee2948de2 Binary files /dev/null and b/examples/data/dolfyn/test_data/RDI_test01_rotate_beam2inst.nc differ diff --git a/examples/data/dolfyn/test_data/RDI_test01_rotate_earth2principal.nc b/examples/data/dolfyn/test_data/RDI_test01_rotate_earth2principal.nc new file mode 100644 index 000000000..e09b806ce Binary files /dev/null and b/examples/data/dolfyn/test_data/RDI_test01_rotate_earth2principal.nc differ diff --git a/examples/data/dolfyn/test_data/RDI_test01_rotate_inst2earth.nc b/examples/data/dolfyn/test_data/RDI_test01_rotate_inst2earth.nc new file mode 100644 index 000000000..fc7a22963 Binary files /dev/null and b/examples/data/dolfyn/test_data/RDI_test01_rotate_inst2earth.nc differ diff --git a/examples/data/dolfyn/test_data/RDI_withBT.nc b/examples/data/dolfyn/test_data/RDI_withBT.nc new file mode 100644 index 000000000..aff355482 Binary files /dev/null and b/examples/data/dolfyn/test_data/RDI_withBT.nc differ diff --git a/examples/data/dolfyn/test_data/Sig1000_BadTime01.ad2cp b/examples/data/dolfyn/test_data/Sig1000_BadTime01.ad2cp new file mode 100644 index 000000000..bf2e947dd Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_BadTime01.ad2cp differ diff --git a/examples/data/dolfyn/test_data/Sig1000_BadTime01.nc b/examples/data/dolfyn/test_data/Sig1000_BadTime01.nc new file mode 100644 index 000000000..39ca3be8d Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_BadTime01.nc differ diff --git a/examples/data/dolfyn/test_data/Sig1000_IMU.mat b/examples/data/dolfyn/test_data/Sig1000_IMU.mat new file mode 100644 index 000000000..e4b0fd10c Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_IMU.mat differ diff --git a/examples/data/dolfyn/test_data/Sig1000_IMU.nc b/examples/data/dolfyn/test_data/Sig1000_IMU.nc new file mode 100644 index 000000000..8c5c8e57d Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_IMU.nc differ diff --git a/examples/data/dolfyn/test_data/Sig1000_IMU_ofilt.nc b/examples/data/dolfyn/test_data/Sig1000_IMU_ofilt.nc new file mode 100644 index 000000000..618daaa26 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_IMU_ofilt.nc differ diff --git a/examples/data/dolfyn/test_data/Sig1000_IMU_rotate_beam2inst.nc b/examples/data/dolfyn/test_data/Sig1000_IMU_rotate_beam2inst.nc new file mode 100644 index 000000000..a3024a443 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_IMU_rotate_beam2inst.nc differ diff --git a/examples/data/dolfyn/test_data/Sig1000_IMU_rotate_inst2earth.nc b/examples/data/dolfyn/test_data/Sig1000_IMU_rotate_inst2earth.nc new file mode 100644 index 000000000..49466a99b Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_IMU_rotate_inst2earth.nc differ diff --git a/examples/data/dolfyn/test_data/Sig1000_IMU_ud.nc b/examples/data/dolfyn/test_data/Sig1000_IMU_ud.nc new file mode 100644 index 000000000..0265ed878 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_IMU_ud.nc differ diff --git a/examples/data/dolfyn/test_data/Sig1000_tidal.nc b/examples/data/dolfyn/test_data/Sig1000_tidal.nc new file mode 100644 index 000000000..174d770d6 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig1000_tidal.nc differ diff --git a/examples/data/dolfyn/test_data/Sig500_Echo.mat b/examples/data/dolfyn/test_data/Sig500_Echo.mat new file mode 100644 index 000000000..02e3a9872 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig500_Echo.mat differ diff --git a/examples/data/dolfyn/test_data/Sig500_Echo.nc b/examples/data/dolfyn/test_data/Sig500_Echo.nc new file mode 100644 index 000000000..20ac317d9 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig500_Echo.nc differ diff --git a/examples/data/dolfyn/test_data/Sig500_Echo_crop.nc b/examples/data/dolfyn/test_data/Sig500_Echo_crop.nc new file mode 100644 index 000000000..2363ac141 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig500_Echo_crop.nc differ diff --git a/examples/data/dolfyn/test_data/Sig500_Echo_earth2inst.nc b/examples/data/dolfyn/test_data/Sig500_Echo_earth2inst.nc new file mode 100644 index 000000000..9beae76a7 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig500_Echo_earth2inst.nc differ diff --git a/examples/data/dolfyn/test_data/Sig500_Echo_inst2beam.nc b/examples/data/dolfyn/test_data/Sig500_Echo_inst2beam.nc new file mode 100644 index 000000000..d8259c15d Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig500_Echo_inst2beam.nc differ diff --git a/examples/data/dolfyn/test_data/Sig500_last_ensemble_is_whole.nc b/examples/data/dolfyn/test_data/Sig500_last_ensemble_is_whole.nc new file mode 100644 index 000000000..a15f90e70 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig500_last_ensemble_is_whole.nc differ diff --git a/examples/data/dolfyn/test_data/Sig_SkippedPings01.nc b/examples/data/dolfyn/test_data/Sig_SkippedPings01.nc new file mode 100644 index 000000000..c8b787676 Binary files /dev/null and b/examples/data/dolfyn/test_data/Sig_SkippedPings01.nc differ diff --git a/examples/data/dolfyn/test_data/VelEchoBT01.mat b/examples/data/dolfyn/test_data/VelEchoBT01.mat new file mode 100644 index 000000000..b2ada9547 Binary files /dev/null and b/examples/data/dolfyn/test_data/VelEchoBT01.mat differ diff --git a/examples/data/dolfyn/test_data/VelEchoBT01.nc b/examples/data/dolfyn/test_data/VelEchoBT01.nc new file mode 100644 index 000000000..ad76ab2b8 Binary files /dev/null and b/examples/data/dolfyn/test_data/VelEchoBT01.nc differ diff --git a/examples/data/dolfyn/test_data/VelEchoBT01_rotate_beam2inst.nc b/examples/data/dolfyn/test_data/VelEchoBT01_rotate_beam2inst.nc new file mode 100644 index 000000000..f2d36b392 Binary files /dev/null and b/examples/data/dolfyn/test_data/VelEchoBT01_rotate_beam2inst.nc differ diff --git a/examples/data/dolfyn/test_data/awac_debug_out.txt b/examples/data/dolfyn/test_data/awac_debug_out.txt new file mode 100644 index 000000000..47b263824 --- /dev/null +++ b/examples/data/dolfyn/test_data/awac_debug_out.txt @@ -0,0 +1,261 @@ +Position: 2, codes: (165, 5) +Reading hardware configuration (0x05) ping #0 @ 2... +Position: 50, codes: (165, 4) +Reading head configuration (0x04) ping #0 @ 50... +Position: 274, codes: (165, 0) +Reading user configuration (0x00) ping #0 @ 274... +Position: 786, codes: (165, 32) +Position: 1086, codes: (165, 32) +Position: 1386, codes: (165, 32) +Position: 1686, codes: (165, 32) +Position: 1986, codes: (165, 32) +Position: 2286, codes: (165, 32) +Position: 2586, codes: (165, 32) +Position: 2886, codes: (165, 32) +Position: 3186, codes: (165, 32) +Position: 3486, codes: (165, 32) +Position: 3786, codes: (165, 32) +Position: 4086, codes: (165, 32) +Position: 4386, codes: (165, 32) +Position: 4686, codes: (165, 32) +Position: 4986, codes: (165, 32) +Position: 5286, codes: (165, 32) +Position: 5586, codes: (165, 32) +Position: 5886, codes: (165, 32) +Position: 6186, codes: (165, 32) +Position: 6486, codes: (165, 32) +Position: 6786, codes: (165, 32) +Position: 7086, codes: (165, 32) +Position: 7386, codes: (165, 32) +Position: 7686, codes: (165, 32) +Position: 7986, codes: (165, 32) +Position: 8286, codes: (165, 32) +Position: 8586, codes: (165, 32) +Position: 8886, codes: (165, 32) +Position: 9186, codes: (165, 32) +Position: 9486, codes: (165, 32) +Position: 9786, codes: (165, 32) +Position: 10086, codes: (165, 32) +Position: 10386, codes: (165, 32) +Position: 10686, codes: (165, 32) +Position: 10986, codes: (165, 32) +Position: 11286, codes: (165, 32) +Position: 11586, codes: (165, 32) +Position: 11886, codes: (165, 32) +Position: 12186, codes: (165, 32) +Position: 12486, codes: (165, 32) +Position: 12786, codes: (165, 32) +Position: 13086, codes: (165, 32) +Position: 13386, codes: (165, 32) +Position: 13686, codes: (165, 32) +Position: 13986, codes: (165, 32) +Position: 14286, codes: (165, 32) +Position: 14586, codes: (165, 32) +Position: 14886, codes: (165, 32) +Position: 15186, codes: (165, 32) +Position: 15486, codes: (165, 32) +Position: 15786, codes: (165, 32) +p0=1084, pos=16084, i=49 +Init completed +Reading file ... +Position: 786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #0 @ 786... +Position: 1086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #1 @ 1086... +Position: 1386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #2 @ 1386... +Position: 1686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #3 @ 1686... +Position: 1986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #4 @ 1986... +Position: 2286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #5 @ 2286... +Position: 2586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #6 @ 2586... +Position: 2886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #7 @ 2886... +Position: 3186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #8 @ 3186... +Position: 3486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #9 @ 3486... +Position: 3786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #10 @ 3786... +Position: 4086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #11 @ 4086... +Position: 4386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #12 @ 4386... +Position: 4686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #13 @ 4686... +Position: 4986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #14 @ 4986... +Position: 5286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #15 @ 5286... +Position: 5586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #16 @ 5586... +Position: 5886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #17 @ 5886... +Position: 6186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #18 @ 6186... +Position: 6486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #19 @ 6486... +Position: 6786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #20 @ 6786... +Position: 7086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #21 @ 7086... +Position: 7386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #22 @ 7386... +Position: 7686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #23 @ 7686... +Position: 7986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #24 @ 7986... +Position: 8286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #25 @ 8286... +Position: 8586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #26 @ 8586... +Position: 8886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #27 @ 8886... +Position: 9186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #28 @ 9186... +Position: 9486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #29 @ 9486... +Position: 9786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #30 @ 9786... +Position: 10086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #31 @ 10086... +Position: 10386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #32 @ 10386... +Position: 10686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #33 @ 10686... +Position: 10986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #34 @ 10986... +Position: 11286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #35 @ 11286... +Position: 11586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #36 @ 11586... +Position: 11886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #37 @ 11886... +Position: 12186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #38 @ 12186... +Position: 12486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #39 @ 12486... +Position: 12786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #40 @ 12786... +Position: 13086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #41 @ 13086... +Position: 13386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #42 @ 13386... +Position: 13686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #43 @ 13686... +Position: 13986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #44 @ 13986... +Position: 14286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #45 @ 14286... +Position: 14586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #46 @ 14586... +Position: 14886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #47 @ 14886... +Position: 15186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #48 @ 15186... +Position: 15486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #49 @ 15486... +Position: 15786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #50 @ 15786... +Position: 16086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #51 @ 16086... +Position: 16386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #52 @ 16386... +Position: 16686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #53 @ 16686... +Position: 16986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #54 @ 16986... +Position: 17286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #55 @ 17286... +Position: 17586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #56 @ 17586... +Position: 17886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #57 @ 17886... +Position: 18186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #58 @ 18186... +Position: 18486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #59 @ 18486... +Position: 18786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #60 @ 18786... +Position: 19086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #61 @ 19086... +Position: 19386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #62 @ 19386... +Position: 19686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #63 @ 19686... +Position: 19986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #64 @ 19986... +Position: 20286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #65 @ 20286... +Position: 20586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #66 @ 20586... +Position: 20886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #67 @ 20886... +Position: 21186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #68 @ 21186... +Position: 21486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #69 @ 21486... +Position: 21786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #70 @ 21786... +Position: 22086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #71 @ 22086... +Position: 22386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #72 @ 22386... +Position: 22686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #73 @ 22686... +Position: 22986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #74 @ 22986... +Position: 23286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #75 @ 23286... +Position: 23586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #76 @ 23586... +Position: 23886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #77 @ 23886... +Position: 24186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #78 @ 24186... +Position: 24486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #79 @ 24486... +Position: 24786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #80 @ 24786... +Position: 25086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #81 @ 25086... +Position: 25386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #82 @ 25386... +Position: 25686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #83 @ 25686... +Position: 25986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #84 @ 25986... +Position: 26286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #85 @ 26286... +Position: 26586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #86 @ 26586... +Position: 26886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #87 @ 26886... +Position: 27186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #88 @ 27186... +Position: 27486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #89 @ 27486... +Position: 27786, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #90 @ 27786... +Position: 28086, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #91 @ 28086... +Position: 28386, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #92 @ 28386... +Position: 28686, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #93 @ 28686... +Position: 28986, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #94 @ 28986... +Position: 29286, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #95 @ 29286... +Position: 29586, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #96 @ 29586... +Position: 29886, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #97 @ 29886... +Position: 30186, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #98 @ 30186... +Position: 30486, codes: (165, 32) +Reading AWAC velocity data (0x20) ping #99 @ 30486... + stopped at 30784 bytes. diff --git a/examples/data/dolfyn/test_data/burst_mode01.nc b/examples/data/dolfyn/test_data/burst_mode01.nc new file mode 100644 index 000000000..0e9b720b0 Binary files /dev/null and b/examples/data/dolfyn/test_data/burst_mode01.nc differ diff --git a/examples/data/dolfyn/test_data/dat_rdi_bt.mat b/examples/data/dolfyn/test_data/dat_rdi_bt.mat new file mode 100644 index 000000000..34d9e08c8 Binary files /dev/null and b/examples/data/dolfyn/test_data/dat_rdi_bt.mat differ diff --git a/examples/data/dolfyn/test_data/dat_vec.mat b/examples/data/dolfyn/test_data/dat_vec.mat new file mode 100644 index 000000000..12733b36d Binary files /dev/null and b/examples/data/dolfyn/test_data/dat_vec.mat differ diff --git a/examples/data/dolfyn/test_data/dat_vm.mat b/examples/data/dolfyn/test_data/dat_vm.mat new file mode 100644 index 000000000..edea34529 Binary files /dev/null and b/examples/data/dolfyn/test_data/dat_vm.mat differ diff --git a/examples/data/dolfyn/test_data/rdi_debug_out.txt b/examples/data/dolfyn/test_data/rdi_debug_out.txt new file mode 100644 index 000000000..55fd4fea0 --- /dev/null +++ b/examples/data/dolfyn/test_data/rdi_debug_out.txt @@ -0,0 +1,2811 @@ + +Reading file ... +2 + cfgid0: [7f, 7f] + ###In checkheader. + ###Leaving checkheader. +2 +4 + 1721 pings estimated in this file + taking data from pings 0 - 100 + 100 ensembles will be produced. + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +4 + pos: 22, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 81, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 146, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 284, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 354, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 424, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 494, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 581, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +585 + pos: 603, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 662, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 727, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 865, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 935, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 1005, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 1075, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 1162, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +1166 + pos: 1184, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 1243, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 1308, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 1446, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 1516, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 1586, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 1656, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 1743, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +1747 + pos: 1765, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 1824, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 1889, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 2027, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 2097, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 2167, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 2237, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 2324, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +2328 + pos: 2346, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 2405, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 2470, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 2608, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 2678, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 2748, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 2818, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 2905, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +2909 + pos: 2927, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 2986, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 3051, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 3189, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 3259, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 3329, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 3399, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 3486, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +3490 + pos: 3508, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 3567, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 3632, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 3770, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 3840, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 3910, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 3980, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 4067, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +4071 + pos: 4089, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 4148, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 4213, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 4351, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 4421, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 4491, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 4561, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 4648, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +4652 + pos: 4670, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 4729, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 4794, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 4932, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 5002, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 5072, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 5142, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 5229, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +5233 + pos: 5251, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 5310, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 5375, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 5513, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 5583, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 5653, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 5723, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 5810, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +5814 + pos: 5832, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 5891, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 5956, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 6094, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 6164, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 6234, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 6304, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 6391, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +6395 + pos: 6413, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 6472, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 6537, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 6675, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 6745, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 6815, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 6885, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 6972, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +6976 + pos: 6994, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 7053, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 7118, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 7256, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 7326, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 7396, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 7466, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 7553, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +7557 + pos: 7575, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 7634, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 7699, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 7837, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 7907, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 7977, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 8047, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 8134, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +8138 + pos: 8156, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 8215, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 8280, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 8418, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 8488, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 8558, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 8628, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 8715, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +8719 + pos: 8737, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 8796, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 8861, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 8999, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 9069, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 9139, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 9209, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 9296, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +9300 + pos: 9318, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 9377, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 9442, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 9580, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 9650, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 9720, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 9790, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 9877, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +9881 + pos: 9899, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 9958, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 10023, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 10161, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 10231, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 10301, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 10371, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 10458, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +10462 + pos: 10480, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 10539, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 10604, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 10742, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 10812, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 10882, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 10952, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 11039, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +11043 + pos: 11061, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 11120, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 11185, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 11323, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 11393, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 11463, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 11533, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 11620, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +11624 + pos: 11642, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 11701, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 11766, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 11904, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 11974, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 12044, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 12114, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 12201, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +12205 + pos: 12223, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 12282, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 12347, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 12485, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 12555, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 12625, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 12695, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 12782, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +12786 + pos: 12804, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 12863, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 12928, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 13066, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 13136, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 13206, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 13276, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 13363, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +13367 + pos: 13385, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 13444, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 13509, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 13647, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 13717, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 13787, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 13857, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 13944, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +13948 + pos: 13966, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 14025, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 14090, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 14228, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 14298, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 14368, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 14438, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 14525, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +14529 + pos: 14547, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 14606, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 14671, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 14809, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 14879, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 14949, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 15019, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 15106, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +15110 + pos: 15128, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 15187, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 15252, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 15390, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 15460, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 15530, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 15600, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 15687, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +15691 + pos: 15709, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 15768, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 15833, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 15971, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 16041, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 16111, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 16181, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 16268, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +16272 + pos: 16290, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 16349, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 16414, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 16552, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 16622, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 16692, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 16762, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 16849, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +16853 + pos: 16871, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 16930, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 16995, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 17133, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 17203, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 17273, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 17343, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 17430, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +17434 + pos: 17452, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 17511, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 17576, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 17714, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 17784, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 17854, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 17924, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 18011, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +18015 + pos: 18033, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 18092, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 18157, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 18295, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 18365, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 18435, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 18505, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 18592, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +18596 + pos: 18614, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 18673, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 18738, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 18876, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 18946, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 19016, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 19086, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 19173, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +19177 + pos: 19195, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 19254, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 19319, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 19457, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 19527, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 19597, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 19667, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 19754, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +19758 + pos: 19776, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 19835, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 19900, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 20038, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 20108, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 20178, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 20248, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 20335, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +20339 + pos: 20357, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 20416, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 20481, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 20619, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 20689, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 20759, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 20829, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 20916, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +20920 + pos: 20938, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 20997, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 21062, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 21200, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 21270, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 21340, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 21410, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 21497, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +21501 + pos: 21519, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 21578, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 21643, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 21781, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 21851, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 21921, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 21991, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 22078, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +22082 + pos: 22100, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 22159, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 22224, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 22362, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 22432, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 22502, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 22572, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 22659, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +22663 + pos: 22681, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 22740, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 22805, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 22943, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 23013, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 23083, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 23153, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 23240, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +23244 + pos: 23262, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 23321, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 23386, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 23524, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 23594, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 23664, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 23734, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 23821, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +23825 + pos: 23843, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 23902, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 23967, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 24105, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 24175, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 24245, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 24315, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 24402, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +24406 + pos: 24424, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 24483, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 24548, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 24686, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 24756, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 24826, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 24896, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 24983, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +24987 + pos: 25005, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 25064, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 25129, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 25267, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 25337, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 25407, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 25477, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 25564, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +25568 + pos: 25586, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 25645, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 25710, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 25848, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 25918, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 25988, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 26058, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 26145, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +26149 + pos: 26167, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 26226, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 26291, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 26429, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 26499, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 26569, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 26639, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 26726, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +26730 + pos: 26748, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 26807, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 26872, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 27010, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 27080, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 27150, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 27220, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 27307, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +27311 + pos: 27329, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 27388, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 27453, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 27591, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 27661, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 27731, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 27801, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 27888, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +27892 + pos: 27910, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 27969, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 28034, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 28172, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 28242, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 28312, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 28382, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 28469, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +28473 + pos: 28491, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 28550, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 28615, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 28753, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 28823, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 28893, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 28963, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 29050, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +29054 + pos: 29072, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 29131, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 29196, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 29334, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 29404, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 29474, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 29544, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 29631, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +29635 + pos: 29653, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 29712, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 29777, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 29915, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 29985, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 30055, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 30125, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 30212, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +30216 + pos: 30234, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 30293, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 30358, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 30496, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 30566, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 30636, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 30706, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 30793, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +30797 + pos: 30815, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 30874, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 30939, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 31077, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 31147, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 31217, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 31287, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 31374, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +31378 + pos: 31396, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 31455, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 31520, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 31658, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 31728, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 31798, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 31868, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 31955, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +31959 + pos: 31977, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 32036, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 32101, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 32239, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 32309, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 32379, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 32449, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 32536, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +32540 + pos: 32558, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 32617, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 32682, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 32820, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 32890, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 32960, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 33030, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 33117, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +33121 + pos: 33139, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 33198, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 33263, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 33401, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 33471, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 33541, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 33611, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 33698, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +33702 + pos: 33720, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 33779, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 33844, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 33982, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 34052, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 34122, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 34192, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 34279, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +34283 + pos: 34301, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 34360, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 34425, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 34563, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 34633, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 34703, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 34773, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 34860, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +34864 + pos: 34882, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 34941, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 35006, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 35144, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 35214, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 35284, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 35354, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 35441, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +35445 + pos: 35463, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 35522, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 35587, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 35725, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 35795, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 35865, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 35935, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 36022, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +36026 + pos: 36044, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 36103, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 36168, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 36306, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 36376, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 36446, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 36516, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 36603, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +36607 + pos: 36625, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 36684, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 36749, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 36887, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 36957, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 37027, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 37097, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 37184, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +37188 + pos: 37206, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 37265, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 37330, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 37468, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 37538, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 37608, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 37678, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 37765, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +37769 + pos: 37787, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 37846, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 37911, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 38049, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 38119, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 38189, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 38259, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 38346, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +38350 + pos: 38368, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 38427, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 38492, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 38630, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 38700, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 38770, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 38840, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 38927, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +38931 + pos: 38949, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 39008, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 39073, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 39211, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 39281, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 39351, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 39421, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 39508, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +39512 + pos: 39530, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 39589, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 39654, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 39792, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 39862, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 39932, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 40002, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 40089, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +40093 + pos: 40111, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 40170, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 40235, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 40373, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 40443, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 40513, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 40583, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 40670, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +40674 + pos: 40692, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 40751, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 40816, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 40954, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 41024, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 41094, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 41164, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 41251, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +41255 + pos: 41273, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 41332, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 41397, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 41535, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 41605, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 41675, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 41745, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 41832, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +41836 + pos: 41854, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 41913, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 41978, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 42116, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 42186, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 42256, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 42326, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 42413, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +42417 + pos: 42435, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 42494, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 42559, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 42697, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 42767, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 42837, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 42907, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 42994, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +42998 + pos: 43016, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 43075, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 43140, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 43278, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 43348, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 43418, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 43488, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 43575, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +43579 + pos: 43597, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 43656, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 43721, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 43859, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 43929, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 43999, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 44069, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 44156, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +44160 + pos: 44178, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 44237, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 44302, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 44440, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 44510, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 44580, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 44650, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 44737, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +44741 + pos: 44759, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 44818, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 44883, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 45021, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 45091, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 45161, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 45231, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 45318, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +45322 + pos: 45340, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 45399, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 45464, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 45602, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 45672, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 45742, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 45812, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 45899, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +45903 + pos: 45921, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 45980, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 46045, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 46183, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 46253, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 46323, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 46393, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 46480, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +46484 + pos: 46502, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 46561, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 46626, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 46764, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 46834, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 46904, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 46974, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 47061, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +47065 + pos: 47083, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 47142, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 47207, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 47345, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 47415, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 47485, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 47555, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 47642, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +47646 + pos: 47664, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 47723, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 47788, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 47926, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 47996, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 48066, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 48136, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 48223, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +48227 + pos: 48245, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 48304, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 48369, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 48507, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 48577, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 48647, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 48717, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 48804, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +48808 + pos: 48826, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 48885, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 48950, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 49088, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 49158, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 49228, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 49298, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 49385, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +49389 + pos: 49407, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 49466, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 49531, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 49669, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 49739, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 49809, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 49879, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 49966, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +49970 + pos: 49988, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 50047, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 50112, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 50250, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 50320, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 50390, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 50460, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 50547, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +50551 + pos: 50569, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 50628, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 50693, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 50831, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 50901, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 50971, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 51041, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 51128, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +51132 + pos: 51150, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 51209, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 51274, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 51412, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 51482, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 51552, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 51622, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 51709, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +51713 + pos: 51731, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 51790, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 51855, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 51993, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 52063, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 52133, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 52203, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 52290, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +52294 + pos: 52312, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 52371, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 52436, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 52574, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 52644, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 52714, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 52784, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 52871, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +52875 + pos: 52893, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 52952, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 53017, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 53155, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 53225, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 53295, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 53365, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 53452, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +53456 + pos: 53474, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 53533, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 53598, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 53736, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 53806, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 53876, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 53946, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 54033, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +54037 + pos: 54055, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 54114, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 54179, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 54317, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 54387, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 54457, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 54527, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 54614, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +54618 + pos: 54636, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 54695, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 54760, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 54898, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 54968, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 55038, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 55108, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 55195, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +55199 + pos: 55217, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 55276, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 55341, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 55479, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 55549, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 55619, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 55689, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 55776, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +55780 + pos: 55798, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 55857, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 55922, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 56060, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 56130, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 56200, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 56270, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 56357, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +56361 + pos: 56379, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 56438, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 56503, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 56641, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 56711, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 56781, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 56851, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 56938, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +56942 + pos: 56960, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 57019, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 57084, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 57222, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 57292, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 57362, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 57432, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 57519, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 + pos 0mb/1mb + + -->In search_buffer... + ###In checkheader. + ###Leaving checkheader. +57523 + pos: 57541, pos_: 0, nbyte: 18, k: -1, byte_offset: -1 + Reading code 0x0...0 + success! + 0: Adjust location by 17 + + pos: 57600, pos_: 0, nbyte: 42, k: -1, byte_offset: -1 + Reading code 0x80... success! + 128: Adjust location by 23 + + pos: 57665, pos_: 0, nbyte: 42, k: 0, byte_offset: -1 + Reading code 0x100... success! + pos: 57803, pos_: 0, nbyte: 138, k: 0, byte_offset: -1 + Reading code 0x200... success! + pos: 57873, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x300... success! + pos: 57943, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x400... success! + pos: 58013, pos_: 0, nbyte: 70, k: 0, byte_offset: -1 + Reading code 0x600... success! + 1536: Adjust location by 4 + + pos: 58100, pos_: 0, nbyte: 81, k: 0, byte_offset: 577 diff --git a/examples/data/dolfyn/test_data/sig_debug_out.txt b/examples/data/dolfyn/test_data/sig_debug_out.txt new file mode 100644 index 000000000..40a97479b --- /dev/null +++ b/examples/data/dolfyn/test_data/sig_debug_out.txt @@ -0,0 +1,32 @@ +Indexing 4464: A5, 10, 18, 188, 0, 1, 1 + + 4662: A5, 10, 15, 524, 0, 1, 1 + + 5196: A5, 10, 1C, 214, 0, 1, 1 + + 5420: A5, 10, 18, 188, 1, 2, 2 + + 5618: A5, 10, 15, 524, 1, 2, 2 + + 6152: A5, 10, 1C, 214, 1, 2, 2 + + 6376: A5, 10, 18, 188, 2, 3, 3 + + 6574: A5, 10, 15, 524, 2, 3, 3 + + 7108: A5, 10, 1C, 214, 2, 3, 3 + + 7332: A5, 10, 18, 188, 3, 4, 4 + + 7530: A5, 10, 15, 524, 3, 4, 4 + + 8064: A5, 10, 1C, 214, 3, 4, 4 + + 8288: A5, 10, 18, 188, 4, 5, 5 + + 8486: A5, 10, 15, 524, 4, 5, 5 + + 9020: A5, 10, 1C, 214, 4, 5, 5 + + Done. +Reading file ... diff --git a/examples/data/dolfyn/test_data/vec_debug_out.txt b/examples/data/dolfyn/test_data/vec_debug_out.txt new file mode 100644 index 000000000..f4c4bd4a6 --- /dev/null +++ b/examples/data/dolfyn/test_data/vec_debug_out.txt @@ -0,0 +1,3657 @@ +Position: 2, codes: (165, 5) +Reading hardware configuration (0x05) ping #0 @ 2... +Position: 50, codes: (165, 4) +Reading head configuration (0x04) ping #0 @ 50... +Position: 274, codes: (165, 0) +Reading user configuration (0x00) ping #0 @ 274... +Position: 786, codes: (165, 18) +Position: 828, codes: (165, 7) +Position: 1738, codes: (165, 17) +Position: 1766, codes: (165, 16) +Position: 1790, codes: (165, 113) +Position: 1876, codes: (165, 16) +Position: 1900, codes: (165, 113) +Position: 1986, codes: (165, 16) +Position: 2010, codes: (165, 113) +Position: 2096, codes: (165, 16) +Position: 2120, codes: (165, 113) +Position: 2206, codes: (165, 16) +Position: 2230, codes: (165, 113) +Position: 2316, codes: (165, 16) +Position: 2340, codes: (165, 113) +Position: 2426, codes: (165, 16) +Position: 2450, codes: (165, 113) +Position: 2536, codes: (165, 16) +Position: 2560, codes: (165, 113) +Position: 2646, codes: (165, 16) +Position: 2670, codes: (165, 113) +Position: 2756, codes: (165, 16) +Position: 2780, codes: (165, 113) +Position: 2866, codes: (165, 17) +Position: 2894, codes: (165, 16) +Position: 2918, codes: (165, 113) +Position: 3004, codes: (165, 16) +Position: 3028, codes: (165, 113) +Position: 3114, codes: (165, 16) +Position: 3138, codes: (165, 113) +Position: 3224, codes: (165, 16) +Position: 3248, codes: (165, 113) +Position: 3334, codes: (165, 16) +Position: 3358, codes: (165, 113) +Position: 3444, codes: (165, 16) +Position: 3468, codes: (165, 113) +Position: 3554, codes: (165, 16) +Position: 3578, codes: (165, 113) +Position: 3664, codes: (165, 16) +Position: 3688, codes: (165, 113) +Position: 3774, codes: (165, 16) +Position: 3798, codes: (165, 113) +Position: 3884, codes: (165, 16) +Position: 3908, codes: (165, 113) +Position: 3994, codes: (165, 16) +Position: 4018, codes: (165, 113) +Position: 4104, codes: (165, 16) +Position: 4128, codes: (165, 113) +Position: 4214, codes: (165, 16) +Position: 4238, codes: (165, 113) +Position: 4324, codes: (165, 16) +Position: 4348, codes: (165, 113) +Position: 4434, codes: (165, 16) +Position: 4458, codes: (165, 113) +Position: 4544, codes: (165, 16) +Position: 4568, codes: (165, 113) +Position: 4654, codes: (165, 16) +Position: 4678, codes: (165, 113) +Position: 4764, codes: (165, 16) +Position: 4788, codes: (165, 113) +Position: 4874, codes: (165, 16) +Position: 4898, codes: (165, 113) +Position: 4984, codes: (165, 16) +Position: 5008, codes: (165, 113) +Position: 5094, codes: (165, 16) +Position: 5118, codes: (165, 113) +Position: 5204, codes: (165, 16) +Position: 5228, codes: (165, 113) +Position: 5314, codes: (165, 16) +Position: 5338, codes: (165, 113) +Position: 5424, codes: (165, 16) +Position: 5448, codes: (165, 113) +Position: 5534, codes: (165, 16) +Position: 5558, codes: (165, 113) +Position: 5644, codes: (165, 16) +Position: 5668, codes: (165, 113) +Position: 5754, codes: (165, 16) +Position: 5778, codes: (165, 113) +Position: 5864, codes: (165, 16) +Position: 5888, codes: (165, 113) +Position: 5974, codes: (165, 16) +Position: 5998, codes: (165, 113) +Position: 6084, codes: (165, 16) +Position: 6108, codes: (165, 113) +Position: 6194, codes: (165, 16) +Position: 6218, codes: (165, 113) +Position: 6304, codes: (165, 16) +Position: 6328, codes: (165, 113) +Position: 6414, codes: (165, 16) +Position: 6438, codes: (165, 17) +Position: 6466, codes: (165, 113) +Position: 6552, codes: (165, 16) +Position: 6576, codes: (165, 113) +Position: 6662, codes: (165, 16) +Position: 6686, codes: (165, 113) +Position: 6772, codes: (165, 16) +Position: 6796, codes: (165, 113) +Position: 6882, codes: (165, 16) +Position: 6906, codes: (165, 113) +Position: 6992, codes: (165, 16) +Position: 7016, codes: (165, 113) +Position: 7102, codes: (165, 16) +Position: 7126, codes: (165, 113) +Position: 7212, codes: (165, 16) +Position: 7236, codes: (165, 113) +Position: 7322, codes: (165, 16) +Position: 7346, codes: (165, 113) +Position: 7432, codes: (165, 16) +Position: 7456, codes: (165, 113) +Position: 7542, codes: (165, 16) +Position: 7566, codes: (165, 113) +Position: 7652, codes: (165, 16) +Position: 7676, codes: (165, 113) +Position: 7762, codes: (165, 16) +Position: 7786, codes: (165, 113) +Position: 7872, codes: (165, 16) +Position: 7896, codes: (165, 113) +Position: 7982, codes: (165, 16) +Position: 8006, codes: (165, 113) +Position: 8092, codes: (165, 16) +Position: 8116, codes: (165, 113) +Position: 8202, codes: (165, 16) +Position: 8226, codes: (165, 113) +Position: 8312, codes: (165, 16) +Position: 8336, codes: (165, 113) +Position: 8422, codes: (165, 16) +Position: 8446, codes: (165, 113) +Position: 8532, codes: (165, 16) +Position: 8556, codes: (165, 113) +Position: 8642, codes: (165, 16) +Position: 8666, codes: (165, 113) +Position: 8752, codes: (165, 16) +Position: 8776, codes: (165, 113) +Position: 8862, codes: (165, 16) +Position: 8886, codes: (165, 113) +Position: 8972, codes: (165, 16) +Position: 8996, codes: (165, 113) +Position: 9082, codes: (165, 16) +Position: 9106, codes: (165, 113) +Position: 9192, codes: (165, 16) +Position: 9216, codes: (165, 113) +Position: 9302, codes: (165, 16) +Position: 9326, codes: (165, 113) +Position: 9412, codes: (165, 16) +Position: 9436, codes: (165, 113) +Position: 9522, codes: (165, 16) +Position: 9546, codes: (165, 113) +Position: 9632, codes: (165, 16) +Position: 9656, codes: (165, 113) +Position: 9742, codes: (165, 16) +Position: 9766, codes: (165, 113) +Position: 9852, codes: (165, 16) +Position: 9876, codes: (165, 113) +Position: 9962, codes: (165, 16) +Position: 9986, codes: (165, 113) +Position: 10072, codes: (165, 17) +Position: 10100, codes: (165, 16) +Position: 10124, codes: (165, 113) +Position: 10210, codes: (165, 16) +Position: 10234, codes: (165, 113) +Position: 10320, codes: (165, 16) +Position: 10344, codes: (165, 113) +Position: 10430, codes: (165, 16) +Position: 10454, codes: (165, 113) +Position: 10540, codes: (165, 16) +Position: 10564, codes: (165, 113) +Position: 10650, codes: (165, 16) +Position: 10674, codes: (165, 113) +Position: 10760, codes: (165, 16) +Position: 10784, codes: (165, 113) +Position: 10870, codes: (165, 16) +Position: 10894, codes: (165, 113) +Position: 10980, codes: (165, 16) +Position: 11004, codes: (165, 113) +Position: 11090, codes: (165, 16) +Position: 11114, codes: (165, 113) +Position: 11200, codes: (165, 16) +Position: 11224, codes: (165, 113) +Position: 11310, codes: (165, 16) +Position: 11334, codes: (165, 113) +Position: 11420, codes: (165, 16) +Position: 11444, codes: (165, 113) +Position: 11530, codes: (165, 16) +Position: 11554, codes: (165, 113) +Position: 11640, codes: (165, 16) +Position: 11664, codes: (165, 113) +Position: 11750, codes: (165, 16) +Position: 11774, codes: (165, 113) +Position: 11860, codes: (165, 16) +Position: 11884, codes: (165, 113) +Position: 11970, codes: (165, 16) +Position: 11994, codes: (165, 113) +Position: 12080, codes: (165, 16) +Position: 12104, codes: (165, 113) +Position: 12190, codes: (165, 16) +Position: 12214, codes: (165, 113) +Position: 12300, codes: (165, 16) +Position: 12324, codes: (165, 113) +Position: 12410, codes: (165, 16) +Position: 12434, codes: (165, 113) +Position: 12520, codes: (165, 16) +Position: 12544, codes: (165, 113) +Position: 12630, codes: (165, 16) +Position: 12654, codes: (165, 113) +Position: 12740, codes: (165, 16) +Position: 12764, codes: (165, 113) +Position: 12850, codes: (165, 16) +Position: 12874, codes: (165, 113) +Position: 12960, codes: (165, 16) +Position: 12984, codes: (165, 113) +Position: 13070, codes: (165, 16) +Position: 13094, codes: (165, 113) +Position: 13180, codes: (165, 16) +Position: 13204, codes: (165, 113) +Position: 13290, codes: (165, 16) +Position: 13314, codes: (165, 113) +Position: 13400, codes: (165, 16) +Position: 13424, codes: (165, 113) +Position: 13510, codes: (165, 16) +Position: 13534, codes: (165, 113) +Position: 13620, codes: (165, 17) +Position: 13648, codes: (165, 16) +Position: 13672, codes: (165, 113) +Position: 13758, codes: (165, 16) +Position: 13782, codes: (165, 113) +Position: 13868, codes: (165, 16) +Position: 13892, codes: (165, 113) +Position: 13978, codes: (165, 16) +Position: 14002, codes: (165, 113) +Position: 14088, codes: (165, 16) +Position: 14112, codes: (165, 113) +Position: 14198, codes: (165, 16) +Position: 14222, codes: (165, 113) +Position: 14308, codes: (165, 16) +Position: 14332, codes: (165, 113) +Position: 14418, codes: (165, 16) +Position: 14442, codes: (165, 113) +Position: 14528, codes: (165, 16) +Position: 14552, codes: (165, 113) +Position: 14638, codes: (165, 16) +Position: 14662, codes: (165, 113) +Position: 14748, codes: (165, 16) +Position: 14772, codes: (165, 113) +Position: 14858, codes: (165, 16) +Position: 14882, codes: (165, 113) +Position: 14968, codes: (165, 16) +Position: 14992, codes: (165, 113) +Position: 15078, codes: (165, 16) +Position: 15102, codes: (165, 113) +Position: 15188, codes: (165, 16) +Position: 15212, codes: (165, 113) +Position: 15298, codes: (165, 16) +Position: 15322, codes: (165, 113) +Position: 15408, codes: (165, 16) +Position: 15432, codes: (165, 113) +Position: 15518, codes: (165, 16) +Position: 15542, codes: (165, 113) +Position: 15628, codes: (165, 16) +Position: 15652, codes: (165, 113) +Position: 15738, codes: (165, 16) +Position: 15762, codes: (165, 113) +Position: 15848, codes: (165, 16) +Position: 15872, codes: (165, 113) +Position: 15958, codes: (165, 16) +Position: 15982, codes: (165, 113) +Position: 16068, codes: (165, 16) +Position: 16092, codes: (165, 113) +Position: 16178, codes: (165, 16) +Position: 16202, codes: (165, 113) +Position: 16288, codes: (165, 16) +Position: 16312, codes: (165, 113) +Position: 16398, codes: (165, 16) +Position: 16422, codes: (165, 113) +Position: 16508, codes: (165, 16) +Position: 16532, codes: (165, 113) +Position: 16618, codes: (165, 16) +Position: 16642, codes: (165, 113) +Position: 16728, codes: (165, 16) +Position: 16752, codes: (165, 113) +Position: 16838, codes: (165, 16) +Position: 16862, codes: (165, 113) +Position: 16948, codes: (165, 16) +Position: 16972, codes: (165, 113) +Position: 17058, codes: (165, 16) +Position: 17082, codes: (165, 113) +Position: 17168, codes: (165, 17) +Position: 17196, codes: (165, 16) +Position: 17220, codes: (165, 113) +Position: 17306, codes: (165, 16) +Position: 17330, codes: (165, 113) +Position: 17416, codes: (165, 16) +Position: 17440, codes: (165, 113) +Position: 17526, codes: (165, 16) +Position: 17550, codes: (165, 113) +Position: 17636, codes: (165, 16) +Position: 17660, codes: (165, 113) +Position: 17746, codes: (165, 16) +Position: 17770, codes: (165, 113) +Position: 17856, codes: (165, 16) +Position: 17880, codes: (165, 113) +Position: 17966, codes: (165, 16) +Position: 17990, codes: (165, 113) +Position: 18076, codes: (165, 16) +Position: 18100, codes: (165, 113) +Position: 18186, codes: (165, 16) +Position: 18210, codes: (165, 113) +Position: 18296, codes: (165, 16) +Position: 18320, codes: (165, 113) +Position: 18406, codes: (165, 16) +Position: 18430, codes: (165, 113) +Position: 18516, codes: (165, 16) +Position: 18540, codes: (165, 113) +Position: 18626, codes: (165, 16) +Position: 18650, codes: (165, 113) +Position: 18736, codes: (165, 16) +Position: 18760, codes: (165, 113) +Position: 18846, codes: (165, 16) +Position: 18870, codes: (165, 113) +Position: 18956, codes: (165, 16) +Position: 18980, codes: (165, 113) +Position: 19066, codes: (165, 16) +Position: 19090, codes: (165, 113) +Position: 19176, codes: (165, 16) +Position: 19200, codes: (165, 113) +Position: 19286, codes: (165, 16) +Position: 19310, codes: (165, 113) +Position: 19396, codes: (165, 16) +Position: 19420, codes: (165, 113) +Position: 19506, codes: (165, 16) +Position: 19530, codes: (165, 113) +Position: 19616, codes: (165, 16) +Position: 19640, codes: (165, 113) +Position: 19726, codes: (165, 16) +Position: 19750, codes: (165, 113) +Position: 19836, codes: (165, 16) +Position: 19860, codes: (165, 113) +Position: 19946, codes: (165, 16) +Position: 19970, codes: (165, 113) +Position: 20056, codes: (165, 16) +Position: 20080, codes: (165, 113) +Position: 20166, codes: (165, 16) +Position: 20190, codes: (165, 113) +Position: 20276, codes: (165, 16) +Position: 20300, codes: (165, 113) +Position: 20386, codes: (165, 16) +Position: 20410, codes: (165, 113) +Position: 20496, codes: (165, 16) +Position: 20520, codes: (165, 113) +Position: 20606, codes: (165, 16) +Position: 20630, codes: (165, 113) +Position: 20716, codes: (165, 16) +Position: 20740, codes: (165, 17) +Position: 20768, codes: (165, 113) +Position: 20854, codes: (165, 16) +Position: 20878, codes: (165, 113) +Position: 20964, codes: (165, 16) +Position: 20988, codes: (165, 113) +Position: 21074, codes: (165, 16) +Position: 21098, codes: (165, 113) +Position: 21184, codes: (165, 16) +Position: 21208, codes: (165, 113) +Position: 21294, codes: (165, 16) +Position: 21318, codes: (165, 113) +Position: 21404, codes: (165, 16) +Position: 21428, codes: (165, 113) +Position: 21514, codes: (165, 16) +Position: 21538, codes: (165, 113) +Position: 21624, codes: (165, 16) +Position: 21648, codes: (165, 113) +Position: 21734, codes: (165, 16) +Position: 21758, codes: (165, 113) +Position: 21844, codes: (165, 16) +Position: 21868, codes: (165, 113) +Position: 21954, codes: (165, 16) +Position: 21978, codes: (165, 113) +Position: 22064, codes: (165, 16) +Position: 22088, codes: (165, 113) +Position: 22174, codes: (165, 16) +Position: 22198, codes: (165, 113) +Position: 22284, codes: (165, 16) +Position: 22308, codes: (165, 113) +Position: 22394, codes: (165, 16) +Position: 22418, codes: (165, 113) +Position: 22504, codes: (165, 16) +Position: 22528, codes: (165, 113) +Position: 22614, codes: (165, 16) +Position: 22638, codes: (165, 113) +Position: 22724, codes: (165, 16) +Position: 22748, codes: (165, 113) +Position: 22834, codes: (165, 16) +Position: 22858, codes: (165, 113) +Position: 22944, codes: (165, 16) +Position: 22968, codes: (165, 113) +Position: 23054, codes: (165, 16) +Position: 23078, codes: (165, 113) +Position: 23164, codes: (165, 16) +Position: 23188, codes: (165, 113) +Position: 23274, codes: (165, 16) +Position: 23298, codes: (165, 113) +Position: 23384, codes: (165, 16) +Position: 23408, codes: (165, 113) +Position: 23494, codes: (165, 16) +Position: 23518, codes: (165, 113) +Position: 23604, codes: (165, 16) +Position: 23628, codes: (165, 113) +Position: 23714, codes: (165, 16) +Position: 23738, codes: (165, 113) +Position: 23824, codes: (165, 16) +Position: 23848, codes: (165, 113) +Position: 23934, codes: (165, 16) +Position: 23958, codes: (165, 113) +Position: 24044, codes: (165, 16) +Position: 24068, codes: (165, 113) +Position: 24154, codes: (165, 16) +Position: 24178, codes: (165, 113) +Position: 24264, codes: (165, 16) +Position: 24288, codes: (165, 113) +Position: 24374, codes: (165, 17) +Position: 24402, codes: (165, 16) +Position: 24426, codes: (165, 113) +Position: 24512, codes: (165, 16) +Position: 24536, codes: (165, 113) +Position: 24622, codes: (165, 16) +Position: 24646, codes: (165, 113) +Position: 24732, codes: (165, 16) +Position: 24756, codes: (165, 113) +Position: 24842, codes: (165, 16) +Position: 24866, codes: (165, 113) +Position: 24952, codes: (165, 16) +Position: 24976, codes: (165, 113) +Position: 25062, codes: (165, 16) +Position: 25086, codes: (165, 113) +Position: 25172, codes: (165, 16) +Position: 25196, codes: (165, 113) +Position: 25282, codes: (165, 16) +Position: 25306, codes: (165, 113) +Position: 25392, codes: (165, 16) +Position: 25416, codes: (165, 113) +Position: 25502, codes: (165, 16) +Position: 25526, codes: (165, 113) +Position: 25612, codes: (165, 16) +Position: 25636, codes: (165, 113) +Position: 25722, codes: (165, 16) +Position: 25746, codes: (165, 113) +Position: 25832, codes: (165, 16) +Position: 25856, codes: (165, 113) +Position: 25942, codes: (165, 16) +Position: 25966, codes: (165, 113) +Position: 26052, codes: (165, 16) +Position: 26076, codes: (165, 113) +Position: 26162, codes: (165, 16) +Position: 26186, codes: (165, 113) +Position: 26272, codes: (165, 16) +Position: 26296, codes: (165, 113) +Position: 26382, codes: (165, 16) +Position: 26406, codes: (165, 113) +Position: 26492, codes: (165, 16) +Position: 26516, codes: (165, 113) +Position: 26602, codes: (165, 16) +Position: 26626, codes: (165, 113) +Position: 26712, codes: (165, 16) +Position: 26736, codes: (165, 113) +Position: 26822, codes: (165, 16) +Position: 26846, codes: (165, 113) +Position: 26932, codes: (165, 16) +Position: 26956, codes: (165, 113) +Position: 27042, codes: (165, 16) +Position: 27066, codes: (165, 113) +Position: 27152, codes: (165, 16) +Position: 27176, codes: (165, 113) +Position: 27262, codes: (165, 16) +Position: 27286, codes: (165, 113) +Position: 27372, codes: (165, 16) +Position: 27396, codes: (165, 113) +Position: 27482, codes: (165, 16) +Position: 27506, codes: (165, 113) +Position: 27592, codes: (165, 16) +Position: 27616, codes: (165, 113) +Position: 27702, codes: (165, 16) +Position: 27726, codes: (165, 113) +Position: 27812, codes: (165, 16) +Position: 27836, codes: (165, 113) +Position: 27922, codes: (165, 17) +Position: 27950, codes: (165, 16) +Position: 27974, codes: (165, 113) +Position: 28060, codes: (165, 16) +Position: 28084, codes: (165, 113) +Position: 28170, codes: (165, 16) +Position: 28194, codes: (165, 113) +Position: 28280, codes: (165, 16) +Position: 28304, codes: (165, 113) +Position: 28390, codes: (165, 16) +Position: 28414, codes: (165, 113) +Position: 28500, codes: (165, 16) +Position: 28524, codes: (165, 113) +Position: 28610, codes: (165, 16) +Position: 28634, codes: (165, 113) +Position: 28720, codes: (165, 16) +Position: 28744, codes: (165, 113) +Position: 28830, codes: (165, 16) +Position: 28854, codes: (165, 113) +Position: 28940, codes: (165, 16) +Position: 28964, codes: (165, 113) +Position: 29050, codes: (165, 16) +Position: 29074, codes: (165, 113) +Position: 29160, codes: (165, 16) +Position: 29184, codes: (165, 113) +Position: 29270, codes: (165, 16) +Position: 29294, codes: (165, 113) +Position: 29380, codes: (165, 16) +Position: 29404, codes: (165, 113) +Position: 29490, codes: (165, 16) +Position: 29514, codes: (165, 113) +Position: 29600, codes: (165, 16) +Position: 29624, codes: (165, 113) +Position: 29710, codes: (165, 16) +Position: 29734, codes: (165, 113) +Position: 29820, codes: (165, 16) +Position: 29844, codes: (165, 113) +Position: 29930, codes: (165, 16) +Position: 29954, codes: (165, 113) +Position: 30040, codes: (165, 16) +Position: 30064, codes: (165, 113) +Position: 30150, codes: (165, 16) +Position: 30174, codes: (165, 113) +Position: 30260, codes: (165, 16) +Position: 30284, codes: (165, 113) +Position: 30370, codes: (165, 16) +Position: 30394, codes: (165, 113) +Position: 30480, codes: (165, 16) +Position: 30504, codes: (165, 113) +Position: 30590, codes: (165, 16) +Position: 30614, codes: (165, 113) +Position: 30700, codes: (165, 16) +Position: 30724, codes: (165, 113) +Position: 30810, codes: (165, 16) +Position: 30834, codes: (165, 113) +Position: 30920, codes: (165, 16) +Position: 30944, codes: (165, 113) +Position: 31030, codes: (165, 16) +Position: 31054, codes: (165, 113) +Position: 31140, codes: (165, 16) +Position: 31164, codes: (165, 113) +Position: 31250, codes: (165, 16) +Position: 31274, codes: (165, 113) +Position: 31360, codes: (165, 16) +Position: 31384, codes: (165, 113) +Position: 31470, codes: (165, 17) +Position: 31498, codes: (165, 16) +Position: 31522, codes: (165, 113) +Position: 31608, codes: (165, 16) +Position: 31632, codes: (165, 113) +Position: 31718, codes: (165, 16) +Position: 31742, codes: (165, 113) +Position: 31828, codes: (165, 16) +Position: 31852, codes: (165, 113) +Position: 31938, codes: (165, 16) +Position: 31962, codes: (165, 113) +Position: 32048, codes: (165, 16) +Position: 32072, codes: (165, 113) +Position: 32158, codes: (165, 16) +Position: 32182, codes: (165, 113) +Position: 32268, codes: (165, 16) +Position: 32292, codes: (165, 113) +Position: 32378, codes: (165, 16) +Position: 32402, codes: (165, 113) +Position: 32488, codes: (165, 16) +Position: 32512, codes: (165, 113) +Position: 32598, codes: (165, 16) +Position: 32622, codes: (165, 113) +Position: 32708, codes: (165, 16) +Position: 32732, codes: (165, 113) +Position: 32818, codes: (165, 16) +Position: 32842, codes: (165, 113) +Position: 32928, codes: (165, 16) +Position: 32952, codes: (165, 113) +Position: 33038, codes: (165, 16) +Position: 33062, codes: (165, 113) +Position: 33148, codes: (165, 16) +Position: 33172, codes: (165, 113) +Position: 33258, codes: (165, 16) +Position: 33282, codes: (165, 113) +Position: 33368, codes: (165, 16) +Position: 33392, codes: (165, 113) +Position: 33478, codes: (165, 16) +Position: 33502, codes: (165, 113) +Position: 33588, codes: (165, 16) +Position: 33612, codes: (165, 113) +Position: 33698, codes: (165, 16) +Position: 33722, codes: (165, 113) +Position: 33808, codes: (165, 16) +Position: 33832, codes: (165, 113) +Position: 33918, codes: (165, 16) +Position: 33942, codes: (165, 113) +Position: 34028, codes: (165, 16) +Position: 34052, codes: (165, 113) +Position: 34138, codes: (165, 16) +Position: 34162, codes: (165, 113) +Position: 34248, codes: (165, 16) +Position: 34272, codes: (165, 113) +Position: 34358, codes: (165, 16) +Position: 34382, codes: (165, 113) +Position: 34468, codes: (165, 16) +Position: 34492, codes: (165, 113) +Position: 34578, codes: (165, 16) +Position: 34602, codes: (165, 113) +Position: 34688, codes: (165, 16) +Position: 34712, codes: (165, 113) +Position: 34798, codes: (165, 16) +Position: 34822, codes: (165, 113) +Position: 34908, codes: (165, 16) +Position: 34932, codes: (165, 113) +Position: 35018, codes: (165, 16) +Position: 35042, codes: (165, 17) +Position: 35070, codes: (165, 113) +Position: 35156, codes: (165, 16) +Position: 35180, codes: (165, 113) +Position: 35266, codes: (165, 16) +Position: 35290, codes: (165, 113) +Position: 35376, codes: (165, 16) +Position: 35400, codes: (165, 113) +Position: 35486, codes: (165, 16) +Position: 35510, codes: (165, 113) +Position: 35596, codes: (165, 16) +Position: 35620, codes: (165, 113) +Position: 35706, codes: (165, 16) +Position: 35730, codes: (165, 113) +Position: 35816, codes: (165, 16) +Position: 35840, codes: (165, 113) +Position: 35926, codes: (165, 16) +Position: 35950, codes: (165, 113) +Position: 36036, codes: (165, 16) +Position: 36060, codes: (165, 113) +Position: 36146, codes: (165, 16) +Position: 36170, codes: (165, 113) +Position: 36256, codes: (165, 16) +Position: 36280, codes: (165, 113) +Position: 36366, codes: (165, 16) +Position: 36390, codes: (165, 113) +Position: 36476, codes: (165, 16) +Position: 36500, codes: (165, 113) +Position: 36586, codes: (165, 16) +Position: 36610, codes: (165, 113) +Position: 36696, codes: (165, 16) +Position: 36720, codes: (165, 113) +Position: 36806, codes: (165, 16) +Position: 36830, codes: (165, 113) +Position: 36916, codes: (165, 16) +Position: 36940, codes: (165, 113) +Position: 37026, codes: (165, 16) +Position: 37050, codes: (165, 113) +Position: 37136, codes: (165, 16) +Position: 37160, codes: (165, 113) +Position: 37246, codes: (165, 16) +Position: 37270, codes: (165, 113) +Position: 37356, codes: (165, 16) +Position: 37380, codes: (165, 113) +Position: 37466, codes: (165, 16) +Position: 37490, codes: (165, 113) +Position: 37576, codes: (165, 16) +Position: 37600, codes: (165, 113) +Position: 37686, codes: (165, 16) +Position: 37710, codes: (165, 113) +Position: 37796, codes: (165, 16) +Position: 37820, codes: (165, 113) +Position: 37906, codes: (165, 16) +Position: 37930, codes: (165, 113) +Position: 38016, codes: (165, 16) +Position: 38040, codes: (165, 113) +Position: 38126, codes: (165, 16) +Position: 38150, codes: (165, 113) +Position: 38236, codes: (165, 16) +Position: 38260, codes: (165, 113) +Position: 38346, codes: (165, 16) +Position: 38370, codes: (165, 113) +Position: 38456, codes: (165, 16) +Position: 38480, codes: (165, 113) +Position: 38566, codes: (165, 16) +Position: 38590, codes: (165, 113) +Position: 38676, codes: (165, 17) +Position: 38704, codes: (165, 16) +Position: 38728, codes: (165, 113) +Position: 38814, codes: (165, 16) +Position: 38838, codes: (165, 113) +Position: 38924, codes: (165, 16) +Position: 38948, codes: (165, 113) +Position: 39034, codes: (165, 16) +Position: 39058, codes: (165, 113) +Position: 39144, codes: (165, 16) +Position: 39168, codes: (165, 113) +Position: 39254, codes: (165, 16) +Position: 39278, codes: (165, 113) +Position: 39364, codes: (165, 16) +Position: 39388, codes: (165, 113) +Position: 39474, codes: (165, 16) +Position: 39498, codes: (165, 113) +Position: 39584, codes: (165, 16) +Position: 39608, codes: (165, 113) +Position: 39694, codes: (165, 16) +Position: 39718, codes: (165, 113) +Position: 39804, codes: (165, 16) +Position: 39828, codes: (165, 113) +Position: 39914, codes: (165, 16) +Position: 39938, codes: (165, 113) +Position: 40024, codes: (165, 16) +Position: 40048, codes: (165, 113) +Position: 40134, codes: (165, 16) +Position: 40158, codes: (165, 113) +Position: 40244, codes: (165, 16) +Position: 40268, codes: (165, 113) +Position: 40354, codes: (165, 16) +Position: 40378, codes: (165, 113) +Position: 40464, codes: (165, 16) +Position: 40488, codes: (165, 113) +Position: 40574, codes: (165, 16) +Position: 40598, codes: (165, 113) +Position: 40684, codes: (165, 16) +Position: 40708, codes: (165, 113) +Position: 40794, codes: (165, 16) +Position: 40818, codes: (165, 113) +Position: 40904, codes: (165, 16) +Position: 40928, codes: (165, 113) +Position: 41014, codes: (165, 16) +Position: 41038, codes: (165, 113) +Position: 41124, codes: (165, 16) +Position: 41148, codes: (165, 113) +Position: 41234, codes: (165, 16) +Position: 41258, codes: (165, 113) +Position: 41344, codes: (165, 16) +Position: 41368, codes: (165, 113) +Position: 41454, codes: (165, 16) +Position: 41478, codes: (165, 113) +Position: 41564, codes: (165, 16) +Position: 41588, codes: (165, 113) +Position: 41674, codes: (165, 16) +Position: 41698, codes: (165, 113) +Position: 41784, codes: (165, 16) +Position: 41808, codes: (165, 113) +Position: 41894, codes: (165, 16) +Position: 41918, codes: (165, 113) +Position: 42004, codes: (165, 16) +Position: 42028, codes: (165, 113) +Position: 42114, codes: (165, 16) +Position: 42138, codes: (165, 113) +Position: 42224, codes: (165, 17) +Position: 42252, codes: (165, 16) +Position: 42276, codes: (165, 113) +Position: 42362, codes: (165, 16) +Position: 42386, codes: (165, 113) +Position: 42472, codes: (165, 16) +Position: 42496, codes: (165, 113) +Position: 42582, codes: (165, 16) +Position: 42606, codes: (165, 113) +Position: 42692, codes: (165, 16) +Position: 42716, codes: (165, 113) +Position: 42802, codes: (165, 16) +Position: 42826, codes: (165, 113) +Position: 42912, codes: (165, 16) +Position: 42936, codes: (165, 113) +Position: 43022, codes: (165, 16) +Position: 43046, codes: (165, 113) +Position: 43132, codes: (165, 16) +Position: 43156, codes: (165, 113) +Position: 43242, codes: (165, 16) +Position: 43266, codes: (165, 113) +Position: 43352, codes: (165, 16) +Position: 43376, codes: (165, 113) +Position: 43462, codes: (165, 16) +Position: 43486, codes: (165, 113) +Position: 43572, codes: (165, 16) +Position: 43596, codes: (165, 113) +Position: 43682, codes: (165, 16) +Position: 43706, codes: (165, 113) +Position: 43792, codes: (165, 16) +Position: 43816, codes: (165, 113) +Position: 43902, codes: (165, 16) +Position: 43926, codes: (165, 113) +Position: 44012, codes: (165, 16) +Position: 44036, codes: (165, 113) +Position: 44122, codes: (165, 16) +Position: 44146, codes: (165, 113) +Position: 44232, codes: (165, 16) +Position: 44256, codes: (165, 113) +Position: 44342, codes: (165, 16) +Position: 44366, codes: (165, 113) +Position: 44452, codes: (165, 16) +Position: 44476, codes: (165, 113) +Position: 44562, codes: (165, 16) +Position: 44586, codes: (165, 113) +Position: 44672, codes: (165, 16) +Position: 44696, codes: (165, 113) +Position: 44782, codes: (165, 16) +Position: 44806, codes: (165, 113) +Position: 44892, codes: (165, 16) +Position: 44916, codes: (165, 113) +Position: 45002, codes: (165, 16) +Position: 45026, codes: (165, 113) +Position: 45112, codes: (165, 16) +Position: 45136, codes: (165, 113) +Position: 45222, codes: (165, 16) +Position: 45246, codes: (165, 113) +Position: 45332, codes: (165, 16) +Position: 45356, codes: (165, 113) +Position: 45442, codes: (165, 16) +Position: 45466, codes: (165, 113) +Position: 45552, codes: (165, 16) +Position: 45576, codes: (165, 113) +Position: 45662, codes: (165, 16) +Position: 45686, codes: (165, 113) +Position: 45772, codes: (165, 17) +Position: 45800, codes: (165, 16) +Position: 45824, codes: (165, 113) +Position: 45910, codes: (165, 16) +Position: 45934, codes: (165, 113) +Position: 46020, codes: (165, 16) +Position: 46044, codes: (165, 113) +Position: 46130, codes: (165, 16) +Position: 46154, codes: (165, 113) +Position: 46240, codes: (165, 16) +Position: 46264, codes: (165, 113) +Position: 46350, codes: (165, 16) +Position: 46374, codes: (165, 113) +Position: 46460, codes: (165, 16) +Position: 46484, codes: (165, 113) +Position: 46570, codes: (165, 16) +Position: 46594, codes: (165, 113) +Position: 46680, codes: (165, 16) +Position: 46704, codes: (165, 113) +Position: 46790, codes: (165, 16) +Position: 46814, codes: (165, 113) +Position: 46900, codes: (165, 16) +Position: 46924, codes: (165, 113) +Position: 47010, codes: (165, 16) +Position: 47034, codes: (165, 113) +Position: 47120, codes: (165, 16) +Position: 47144, codes: (165, 113) +Position: 47230, codes: (165, 16) +Position: 47254, codes: (165, 113) +Position: 47340, codes: (165, 16) +Position: 47364, codes: (165, 113) +Position: 47450, codes: (165, 16) +Position: 47474, codes: (165, 113) +Position: 47560, codes: (165, 16) +Position: 47584, codes: (165, 113) +Position: 47670, codes: (165, 16) +Position: 47694, codes: (165, 113) +Position: 47780, codes: (165, 16) +Position: 47804, codes: (165, 113) +Position: 47890, codes: (165, 16) +Position: 47914, codes: (165, 113) +Position: 48000, codes: (165, 16) +Position: 48024, codes: (165, 113) +Position: 48110, codes: (165, 16) +Position: 48134, codes: (165, 113) +Position: 48220, codes: (165, 16) +Position: 48244, codes: (165, 113) +Position: 48330, codes: (165, 16) +Position: 48354, codes: (165, 113) +Position: 48440, codes: (165, 16) +Position: 48464, codes: (165, 113) +Position: 48550, codes: (165, 16) +Position: 48574, codes: (165, 113) +Position: 48660, codes: (165, 16) +Position: 48684, codes: (165, 113) +Position: 48770, codes: (165, 16) +Position: 48794, codes: (165, 113) +Position: 48880, codes: (165, 16) +Position: 48904, codes: (165, 113) +Position: 48990, codes: (165, 16) +Position: 49014, codes: (165, 113) +Position: 49100, codes: (165, 16) +Position: 49124, codes: (165, 113) +Position: 49210, codes: (165, 16) +Position: 49234, codes: (165, 113) +Position: 49320, codes: (165, 16) +Position: 49344, codes: (165, 17) +Position: 49372, codes: (165, 113) +Position: 49458, codes: (165, 16) +Position: 49482, codes: (165, 113) +Position: 49568, codes: (165, 16) +Position: 49592, codes: (165, 113) +Position: 49678, codes: (165, 16) +Position: 49702, codes: (165, 113) +Position: 49788, codes: (165, 16) +Position: 49812, codes: (165, 113) +Position: 49898, codes: (165, 16) +Position: 49922, codes: (165, 113) +Position: 50008, codes: (165, 16) +Position: 50032, codes: (165, 113) +Position: 50118, codes: (165, 16) +Position: 50142, codes: (165, 113) +Position: 50228, codes: (165, 16) +Position: 50252, codes: (165, 113) +Position: 50338, codes: (165, 16) +Position: 50362, codes: (165, 113) +Position: 50448, codes: (165, 16) +Position: 50472, codes: (165, 113) +Position: 50558, codes: (165, 16) +Position: 50582, codes: (165, 113) +Position: 50668, codes: (165, 16) +Position: 50692, codes: (165, 113) +Position: 50778, codes: (165, 16) +Position: 50802, codes: (165, 113) +Position: 50888, codes: (165, 16) +Position: 50912, codes: (165, 113) +Position: 50998, codes: (165, 16) +Position: 51022, codes: (165, 113) +Position: 51108, codes: (165, 16) +Position: 51132, codes: (165, 113) +Position: 51218, codes: (165, 16) +Position: 51242, codes: (165, 113) +Position: 51328, codes: (165, 16) +Position: 51352, codes: (165, 113) +Position: 51438, codes: (165, 16) +Position: 51462, codes: (165, 113) +Position: 51548, codes: (165, 16) +Position: 51572, codes: (165, 113) +Position: 51658, codes: (165, 16) +Position: 51682, codes: (165, 113) +Position: 51768, codes: (165, 16) +Position: 51792, codes: (165, 113) +Position: 51878, codes: (165, 16) +Position: 51902, codes: (165, 113) +Position: 51988, codes: (165, 16) +Position: 52012, codes: (165, 113) +Position: 52098, codes: (165, 16) +Position: 52122, codes: (165, 113) +Position: 52208, codes: (165, 16) +Position: 52232, codes: (165, 113) +Position: 52318, codes: (165, 16) +Position: 52342, codes: (165, 113) +Position: 52428, codes: (165, 16) +Position: 52452, codes: (165, 113) +Position: 52538, codes: (165, 16) +Position: 52562, codes: (165, 113) +Position: 52648, codes: (165, 16) +Position: 52672, codes: (165, 113) +Position: 52758, codes: (165, 16) +Position: 52782, codes: (165, 113) +Position: 52868, codes: (165, 16) +Position: 52892, codes: (165, 113) +Position: 52978, codes: (165, 17) +Position: 53006, codes: (165, 16) +Position: 53030, codes: (165, 113) +Position: 53116, codes: (165, 16) +Position: 53140, codes: (165, 113) +Position: 53226, codes: (165, 16) +Position: 53250, codes: (165, 113) +Position: 53336, codes: (165, 16) +Position: 53360, codes: (165, 113) +Position: 53446, codes: (165, 16) +Position: 53470, codes: (165, 113) +Position: 53556, codes: (165, 16) +Position: 53580, codes: (165, 113) +Position: 53666, codes: (165, 16) +Position: 53690, codes: (165, 113) +Position: 53776, codes: (165, 16) +Position: 53800, codes: (165, 113) +Position: 53886, codes: (165, 16) +Position: 53910, codes: (165, 113) +Position: 53996, codes: (165, 16) +Position: 54020, codes: (165, 113) +Position: 54106, codes: (165, 16) +Position: 54130, codes: (165, 113) +Position: 54216, codes: (165, 16) +Position: 54240, codes: (165, 113) +Position: 54326, codes: (165, 16) +Position: 54350, codes: (165, 113) +Position: 54436, codes: (165, 16) +Position: 54460, codes: (165, 113) +Position: 54546, codes: (165, 16) +Position: 54570, codes: (165, 113) +Position: 54656, codes: (165, 16) +Position: 54680, codes: (165, 113) +Position: 54766, codes: (165, 16) +Position: 54790, codes: (165, 113) +Position: 54876, codes: (165, 16) +Position: 54900, codes: (165, 113) +Position: 54986, codes: (165, 16) +Position: 55010, codes: (165, 113) +Position: 55096, codes: (165, 16) +Position: 55120, codes: (165, 113) +Position: 55206, codes: (165, 16) +Position: 55230, codes: (165, 113) +Position: 55316, codes: (165, 16) +Position: 55340, codes: (165, 113) +Position: 55426, codes: (165, 16) +Position: 55450, codes: (165, 113) +Position: 55536, codes: (165, 16) +Position: 55560, codes: (165, 113) +Position: 55646, codes: (165, 16) +Position: 55670, codes: (165, 113) +Position: 55756, codes: (165, 16) +Position: 55780, codes: (165, 113) +Position: 55866, codes: (165, 16) +Position: 55890, codes: (165, 113) +Position: 55976, codes: (165, 16) +Position: 56000, codes: (165, 113) +Position: 56086, codes: (165, 16) +Position: 56110, codes: (165, 113) +Position: 56196, codes: (165, 16) +Position: 56220, codes: (165, 113) +Position: 56306, codes: (165, 16) +Position: 56330, codes: (165, 113) +Position: 56416, codes: (165, 16) +Position: 56440, codes: (165, 113) +Position: 56526, codes: (165, 17) +Position: 56554, codes: (165, 16) +Position: 56578, codes: (165, 113) +Position: 56664, codes: (165, 16) +Position: 56688, codes: (165, 113) +Position: 56774, codes: (165, 16) +Position: 56798, codes: (165, 113) +Position: 56884, codes: (165, 16) +Position: 56908, codes: (165, 113) +Position: 56994, codes: (165, 16) +Position: 57018, codes: (165, 113) +Position: 57104, codes: (165, 16) +Position: 57128, codes: (165, 113) +Position: 57214, codes: (165, 16) +Position: 57238, codes: (165, 113) +Position: 57324, codes: (165, 16) +Position: 57348, codes: (165, 113) +Position: 57434, codes: (165, 16) +Position: 57458, codes: (165, 113) +Position: 57544, codes: (165, 16) +Position: 57568, codes: (165, 113) +Position: 57654, codes: (165, 16) +Position: 57678, codes: (165, 113) +Position: 57764, codes: (165, 16) +Position: 57788, codes: (165, 113) +Position: 57874, codes: (165, 16) +Position: 57898, codes: (165, 113) +Position: 57984, codes: (165, 16) +Position: 58008, codes: (165, 113) +Position: 58094, codes: (165, 16) +Position: 58118, codes: (165, 113) +Position: 58204, codes: (165, 16) +Position: 58228, codes: (165, 113) +Position: 58314, codes: (165, 16) +Position: 58338, codes: (165, 113) +Position: 58424, codes: (165, 16) +Position: 58448, codes: (165, 113) +Position: 58534, codes: (165, 16) +Position: 58558, codes: (165, 113) +Position: 58644, codes: (165, 16) +Position: 58668, codes: (165, 113) +Position: 58754, codes: (165, 16) +Position: 58778, codes: (165, 113) +Position: 58864, codes: (165, 16) +Position: 58888, codes: (165, 113) +Position: 58974, codes: (165, 16) +Position: 58998, codes: (165, 113) +Position: 59084, codes: (165, 16) +Position: 59108, codes: (165, 113) +Position: 59194, codes: (165, 16) +Position: 59218, codes: (165, 113) +Position: 59304, codes: (165, 16) +Position: 59328, codes: (165, 113) +Position: 59414, codes: (165, 16) +Position: 59438, codes: (165, 113) +Position: 59524, codes: (165, 16) +Position: 59548, codes: (165, 113) +Position: 59634, codes: (165, 16) +Position: 59658, codes: (165, 113) +Position: 59744, codes: (165, 16) +Position: 59768, codes: (165, 113) +Position: 59854, codes: (165, 16) +Position: 59878, codes: (165, 113) +Position: 59964, codes: (165, 16) +Position: 59988, codes: (165, 113) +Position: 60074, codes: (165, 17) +Position: 60102, codes: (165, 16) +Position: 60126, codes: (165, 113) +Position: 60212, codes: (165, 16) +Position: 60236, codes: (165, 113) +Position: 60322, codes: (165, 16) +Position: 60346, codes: (165, 113) +Position: 60432, codes: (165, 16) +Position: 60456, codes: (165, 113) +Position: 60542, codes: (165, 16) +Position: 60566, codes: (165, 113) +Position: 60652, codes: (165, 16) +Position: 60676, codes: (165, 113) +Position: 60762, codes: (165, 16) +Position: 60786, codes: (165, 113) +Position: 60872, codes: (165, 16) +Position: 60896, codes: (165, 113) +Position: 60982, codes: (165, 16) +Position: 61006, codes: (165, 113) +Position: 61092, codes: (165, 16) +Position: 61116, codes: (165, 113) +Position: 61202, codes: (165, 16) +Position: 61226, codes: (165, 113) +Position: 61312, codes: (165, 16) +Position: 61336, codes: (165, 113) +Position: 61422, codes: (165, 16) +Position: 61446, codes: (165, 113) +Position: 61532, codes: (165, 16) +Position: 61556, codes: (165, 113) +Position: 61642, codes: (165, 16) +Position: 61666, codes: (165, 113) +Position: 61752, codes: (165, 16) +Position: 61776, codes: (165, 113) +Position: 61862, codes: (165, 16) +Position: 61886, codes: (165, 113) +Position: 61972, codes: (165, 16) +Position: 61996, codes: (165, 113) +Position: 62082, codes: (165, 16) +Position: 62106, codes: (165, 113) +Position: 62192, codes: (165, 16) +Position: 62216, codes: (165, 113) +Position: 62302, codes: (165, 16) +Position: 62326, codes: (165, 113) +Position: 62412, codes: (165, 16) +Position: 62436, codes: (165, 113) +Position: 62522, codes: (165, 16) +Position: 62546, codes: (165, 113) +Position: 62632, codes: (165, 16) +Position: 62656, codes: (165, 113) +Position: 62742, codes: (165, 16) +Position: 62766, codes: (165, 113) +Position: 62852, codes: (165, 16) +Position: 62876, codes: (165, 113) +Position: 62962, codes: (165, 16) +Position: 62986, codes: (165, 113) +Position: 63072, codes: (165, 16) +Position: 63096, codes: (165, 113) +Position: 63182, codes: (165, 16) +Position: 63206, codes: (165, 113) +Position: 63292, codes: (165, 16) +Position: 63316, codes: (165, 113) +Position: 63402, codes: (165, 16) +Position: 63426, codes: (165, 113) +Position: 63512, codes: (165, 16) +Position: 63536, codes: (165, 113) +Position: 63622, codes: (165, 16) +Position: 63646, codes: (165, 17) +Position: 63674, codes: (165, 113) +Position: 63760, codes: (165, 16) +Position: 63784, codes: (165, 113) +Position: 63870, codes: (165, 16) +Position: 63894, codes: (165, 113) +Position: 63980, codes: (165, 16) +Position: 64004, codes: (165, 113) +Position: 64090, codes: (165, 16) +Position: 64114, codes: (165, 113) +Position: 64200, codes: (165, 16) +Position: 64224, codes: (165, 113) +Position: 64310, codes: (165, 16) +Position: 64334, codes: (165, 113) +Position: 64420, codes: (165, 16) +Position: 64444, codes: (165, 113) +Position: 64530, codes: (165, 16) +Position: 64554, codes: (165, 113) +Position: 64640, codes: (165, 16) +Position: 64664, codes: (165, 113) +Position: 64750, codes: (165, 16) +Position: 64774, codes: (165, 113) +Position: 64860, codes: (165, 16) +Position: 64884, codes: (165, 113) +Position: 64970, codes: (165, 16) +Position: 64994, codes: (165, 113) +Position: 65080, codes: (165, 16) +Position: 65104, codes: (165, 113) +Position: 65190, codes: (165, 16) +Position: 65214, codes: (165, 113) +Position: 65300, codes: (165, 16) +Position: 65324, codes: (165, 113) +Position: 65410, codes: (165, 16) +Position: 65434, codes: (165, 113) +Position: 65520, codes: (165, 16) +Position: 65544, codes: (165, 113) +Position: 65630, codes: (165, 16) +Position: 65654, codes: (165, 113) +Position: 65740, codes: (165, 16) +Position: 65764, codes: (165, 113) +Position: 65850, codes: (165, 16) +Position: 65874, codes: (165, 113) +Position: 65960, codes: (165, 16) +Position: 65984, codes: (165, 113) +Position: 66070, codes: (165, 16) +Position: 66094, codes: (165, 113) +Position: 66180, codes: (165, 16) +Position: 66204, codes: (165, 113) +Position: 66290, codes: (165, 16) +Position: 66314, codes: (165, 113) +Position: 66400, codes: (165, 16) +Position: 66424, codes: (165, 113) +Position: 66510, codes: (165, 16) +Position: 66534, codes: (165, 113) +Position: 66620, codes: (165, 16) +Position: 66644, codes: (165, 113) +Position: 66730, codes: (165, 16) +Position: 66754, codes: (165, 113) +Position: 66840, codes: (165, 16) +Position: 66864, codes: (165, 113) +Position: 66950, codes: (165, 16) +Position: 66974, codes: (165, 113) +Position: 67060, codes: (165, 16) +Position: 67084, codes: (165, 113) +Position: 67170, codes: (165, 16) +Position: 67194, codes: (165, 113) +Position: 67280, codes: (165, 17) +Position: 67308, codes: (165, 16) +Position: 67332, codes: (165, 113) +Position: 67418, codes: (165, 16) +Position: 67442, codes: (165, 113) +Position: 67528, codes: (165, 16) +Position: 67552, codes: (165, 113) +Position: 67638, codes: (165, 16) +Position: 67662, codes: (165, 113) +Position: 67748, codes: (165, 16) +Position: 67772, codes: (165, 113) +Position: 67858, codes: (165, 16) +Position: 67882, codes: (165, 113) +Position: 67968, codes: (165, 16) +Position: 67992, codes: (165, 113) +Position: 68078, codes: (165, 16) +Position: 68102, codes: (165, 113) +Position: 68188, codes: (165, 16) +Position: 68212, codes: (165, 113) +Position: 68298, codes: (165, 16) +Position: 68322, codes: (165, 113) +Position: 68408, codes: (165, 16) +Position: 68432, codes: (165, 113) +Position: 68518, codes: (165, 16) +Position: 68542, codes: (165, 113) +Position: 68628, codes: (165, 16) +Position: 68652, codes: (165, 113) +Position: 68738, codes: (165, 16) +Position: 68762, codes: (165, 113) +Position: 68848, codes: (165, 16) +Position: 68872, codes: (165, 113) +Position: 68958, codes: (165, 16) +Position: 68982, codes: (165, 113) +Position: 69068, codes: (165, 16) +Position: 69092, codes: (165, 113) +Position: 69178, codes: (165, 16) +Position: 69202, codes: (165, 113) +Position: 69288, codes: (165, 16) +Position: 69312, codes: (165, 113) +Position: 69398, codes: (165, 16) +Position: 69422, codes: (165, 113) +Position: 69508, codes: (165, 16) +Position: 69532, codes: (165, 113) +Position: 69618, codes: (165, 16) +Position: 69642, codes: (165, 113) +Position: 69728, codes: (165, 16) +Position: 69752, codes: (165, 113) +Position: 69838, codes: (165, 16) +Position: 69862, codes: (165, 113) +Position: 69948, codes: (165, 16) +Position: 69972, codes: (165, 113) +Position: 70058, codes: (165, 16) +Position: 70082, codes: (165, 113) +Position: 70168, codes: (165, 16) +Position: 70192, codes: (165, 113) +Position: 70278, codes: (165, 16) +Position: 70302, codes: (165, 113) +Position: 70388, codes: (165, 16) +Position: 70412, codes: (165, 113) +Position: 70498, codes: (165, 16) +Position: 70522, codes: (165, 113) +Position: 70608, codes: (165, 16) +Position: 70632, codes: (165, 113) +Position: 70718, codes: (165, 16) +Position: 70742, codes: (165, 113) +Position: 70828, codes: (165, 17) +Position: 70856, codes: (165, 16) +Position: 70880, codes: (165, 113) +Position: 70966, codes: (165, 16) +Position: 70990, codes: (165, 113) +Position: 71076, codes: (165, 16) +Position: 71100, codes: (165, 113) +Position: 71186, codes: (165, 16) +Position: 71210, codes: (165, 113) +Position: 71296, codes: (165, 16) +Position: 71320, codes: (165, 113) +Position: 71406, codes: (165, 16) +Position: 71430, codes: (165, 113) +Position: 71516, codes: (165, 16) +Position: 71540, codes: (165, 113) +Position: 71626, codes: (165, 16) +Position: 71650, codes: (165, 113) +Position: 71736, codes: (165, 16) +Position: 71760, codes: (165, 113) +Position: 71846, codes: (165, 16) +Position: 71870, codes: (165, 113) +Position: 71956, codes: (165, 16) +Position: 71980, codes: (165, 113) +Position: 72066, codes: (165, 16) +Position: 72090, codes: (165, 113) +Position: 72176, codes: (165, 16) +Position: 72200, codes: (165, 113) +Position: 72286, codes: (165, 16) +Position: 72310, codes: (165, 113) +Position: 72396, codes: (165, 16) +Position: 72420, codes: (165, 113) +Position: 72506, codes: (165, 16) +Position: 72530, codes: (165, 113) +Position: 72616, codes: (165, 16) +Position: 72640, codes: (165, 113) +Position: 72726, codes: (165, 16) +Position: 72750, codes: (165, 113) +Position: 72836, codes: (165, 16) +Position: 72860, codes: (165, 113) +Position: 72946, codes: (165, 16) +Position: 72970, codes: (165, 113) +Position: 73056, codes: (165, 16) +Position: 73080, codes: (165, 113) +Position: 73166, codes: (165, 16) +Position: 73190, codes: (165, 113) +Position: 73276, codes: (165, 16) +Position: 73300, codes: (165, 113) +Position: 73386, codes: (165, 16) +Position: 73410, codes: (165, 113) +Position: 73496, codes: (165, 16) +Position: 73520, codes: (165, 113) +Position: 73606, codes: (165, 16) +Position: 73630, codes: (165, 113) +Position: 73716, codes: (165, 16) +Position: 73740, codes: (165, 113) +Position: 73826, codes: (165, 16) +Position: 73850, codes: (165, 113) +Position: 73936, codes: (165, 16) +Position: 73960, codes: (165, 113) +Position: 74046, codes: (165, 16) +Position: 74070, codes: (165, 113) +Position: 74156, codes: (165, 16) +Position: 74180, codes: (165, 113) +Position: 74266, codes: (165, 16) +Position: 74290, codes: (165, 113) +Position: 74376, codes: (165, 17) +Position: 74404, codes: (165, 16) +Position: 74428, codes: (165, 113) +Position: 74514, codes: (165, 16) +Position: 74538, codes: (165, 113) +Position: 74624, codes: (165, 16) +Position: 74648, codes: (165, 113) +Position: 74734, codes: (165, 16) +Position: 74758, codes: (165, 113) +Position: 74844, codes: (165, 16) +Position: 74868, codes: (165, 113) +Position: 74954, codes: (165, 16) +Position: 74978, codes: (165, 113) +Position: 75064, codes: (165, 16) +Position: 75088, codes: (165, 113) +Position: 75174, codes: (165, 16) +Position: 75198, codes: (165, 113) +Position: 75284, codes: (165, 16) +Position: 75308, codes: (165, 113) +Position: 75394, codes: (165, 16) +Position: 75418, codes: (165, 113) +Position: 75504, codes: (165, 16) +Position: 75528, codes: (165, 113) +Position: 75614, codes: (165, 16) +Position: 75638, codes: (165, 113) +Position: 75724, codes: (165, 16) +Position: 75748, codes: (165, 113) +Position: 75834, codes: (165, 16) +Position: 75858, codes: (165, 113) +Position: 75944, codes: (165, 16) +Position: 75968, codes: (165, 113) +Position: 76054, codes: (165, 16) +Position: 76078, codes: (165, 113) +Position: 76164, codes: (165, 16) +Position: 76188, codes: (165, 113) +Position: 76274, codes: (165, 16) +Position: 76298, codes: (165, 113) +Position: 76384, codes: (165, 16) +Position: 76408, codes: (165, 113) +Position: 76494, codes: (165, 16) +Position: 76518, codes: (165, 113) +Position: 76604, codes: (165, 16) +Position: 76628, codes: (165, 113) +Position: 76714, codes: (165, 16) +Position: 76738, codes: (165, 113) +Position: 76824, codes: (165, 16) +Position: 76848, codes: (165, 113) +Position: 76934, codes: (165, 16) +Position: 76958, codes: (165, 113) +Position: 77044, codes: (165, 16) +Position: 77068, codes: (165, 113) +Position: 77154, codes: (165, 16) +Position: 77178, codes: (165, 113) +Position: 77264, codes: (165, 16) +Position: 77288, codes: (165, 113) +Position: 77374, codes: (165, 16) +Position: 77398, codes: (165, 113) +Position: 77484, codes: (165, 16) +Position: 77508, codes: (165, 113) +Position: 77594, codes: (165, 16) +Position: 77618, codes: (165, 113) +Position: 77704, codes: (165, 16) +Position: 77728, codes: (165, 113) +Position: 77814, codes: (165, 16) +Position: 77838, codes: (165, 113) +Position: 77924, codes: (165, 16) +Position: 77948, codes: (165, 17) +Position: 77976, codes: (165, 113) +Position: 78062, codes: (165, 16) +Position: 78086, codes: (165, 113) +Position: 78172, codes: (165, 16) +Position: 78196, codes: (165, 113) +Position: 78282, codes: (165, 16) +Position: 78306, codes: (165, 113) +Position: 78392, codes: (165, 16) +Position: 78416, codes: (165, 113) +Position: 78502, codes: (165, 16) +Position: 78526, codes: (165, 113) +Position: 78612, codes: (165, 16) +Position: 78636, codes: (165, 113) +Position: 78722, codes: (165, 16) +Position: 78746, codes: (165, 113) +Position: 78832, codes: (165, 16) +Position: 78856, codes: (165, 113) +Position: 78942, codes: (165, 16) +Position: 78966, codes: (165, 113) +Position: 79052, codes: (165, 16) +Position: 79076, codes: (165, 113) +Position: 79162, codes: (165, 16) +Position: 79186, codes: (165, 113) +Position: 79272, codes: (165, 16) +Position: 79296, codes: (165, 113) +Position: 79382, codes: (165, 16) +Position: 79406, codes: (165, 113) +Position: 79492, codes: (165, 16) +Position: 79516, codes: (165, 113) +Position: 79602, codes: (165, 16) +Position: 79626, codes: (165, 113) +Position: 79712, codes: (165, 16) +Position: 79736, codes: (165, 113) +Position: 79822, codes: (165, 16) +Position: 79846, codes: (165, 113) +Position: 79932, codes: (165, 16) +Position: 79956, codes: (165, 113) +Position: 80042, codes: (165, 16) +Position: 80066, codes: (165, 113) +Position: 80152, codes: (165, 16) +Position: 80176, codes: (165, 113) +Position: 80262, codes: (165, 16) +Position: 80286, codes: (165, 113) +Position: 80372, codes: (165, 16) +Position: 80396, codes: (165, 113) +Position: 80482, codes: (165, 16) +Position: 80506, codes: (165, 113) +Position: 80592, codes: (165, 16) +Position: 80616, codes: (165, 113) +Position: 80702, codes: (165, 16) +Position: 80726, codes: (165, 113) +Position: 80812, codes: (165, 16) +Position: 80836, codes: (165, 113) +Position: 80922, codes: (165, 16) +Position: 80946, codes: (165, 113) +Position: 81032, codes: (165, 16) +Position: 81056, codes: (165, 113) +Position: 81142, codes: (165, 16) +Position: 81166, codes: (165, 113) +Position: 81252, codes: (165, 16) +Position: 81276, codes: (165, 113) +Position: 81362, codes: (165, 16) +Position: 81386, codes: (165, 113) +Position: 81472, codes: (165, 16) +Position: 81496, codes: (165, 113) +Position: 81582, codes: (165, 17) +Position: 81610, codes: (165, 16) +Position: 81634, codes: (165, 113) +Position: 81720, codes: (165, 16) +Position: 81744, codes: (165, 113) +Position: 81830, codes: (165, 16) +Position: 81854, codes: (165, 113) +Position: 81940, codes: (165, 16) +Position: 81964, codes: (165, 113) +Position: 82050, codes: (165, 16) +Position: 82074, codes: (165, 113) +Position: 82160, codes: (165, 16) +Position: 82184, codes: (165, 113) +Position: 82270, codes: (165, 16) +Position: 82294, codes: (165, 113) +Position: 82380, codes: (165, 16) +Position: 82404, codes: (165, 113) +Position: 82490, codes: (165, 16) +Position: 82514, codes: (165, 113) +Position: 82600, codes: (165, 16) +Position: 82624, codes: (165, 113) +Position: 82710, codes: (165, 16) +Position: 82734, codes: (165, 113) +Position: 82820, codes: (165, 16) +Position: 82844, codes: (165, 113) +Position: 82930, codes: (165, 16) +Position: 82954, codes: (165, 113) +Position: 83040, codes: (165, 16) +Position: 83064, codes: (165, 113) +Position: 83150, codes: (165, 16) +Position: 83174, codes: (165, 113) +Position: 83260, codes: (165, 16) +Position: 83284, codes: (165, 113) +Position: 83370, codes: (165, 16) +Position: 83394, codes: (165, 113) +Position: 83480, codes: (165, 16) +Position: 83504, codes: (165, 113) +Position: 83590, codes: (165, 16) +Position: 83614, codes: (165, 113) +Position: 83700, codes: (165, 16) +Position: 83724, codes: (165, 113) +Position: 83810, codes: (165, 16) +Position: 83834, codes: (165, 113) +Position: 83920, codes: (165, 16) +Position: 83944, codes: (165, 113) +Position: 84030, codes: (165, 16) +Position: 84054, codes: (165, 113) +Position: 84140, codes: (165, 16) +Position: 84164, codes: (165, 113) +Position: 84250, codes: (165, 16) +Position: 84274, codes: (165, 113) +Position: 84360, codes: (165, 16) +Position: 84384, codes: (165, 113) +Position: 84470, codes: (165, 16) +Position: 84494, codes: (165, 113) +Position: 84580, codes: (165, 16) +Position: 84604, codes: (165, 113) +Position: 84690, codes: (165, 16) +Position: 84714, codes: (165, 113) +Position: 84800, codes: (165, 16) +Position: 84824, codes: (165, 113) +Position: 84910, codes: (165, 16) +Position: 84934, codes: (165, 113) +Position: 85020, codes: (165, 16) +Position: 85044, codes: (165, 113) +Position: 85130, codes: (165, 17) +Position: 85158, codes: (165, 16) +Position: 85182, codes: (165, 113) +Position: 85268, codes: (165, 16) +Position: 85292, codes: (165, 113) +Position: 85378, codes: (165, 16) +Position: 85402, codes: (165, 113) +Position: 85488, codes: (165, 16) +Position: 85512, codes: (165, 113) +Position: 85598, codes: (165, 16) +Position: 85622, codes: (165, 113) +Position: 85708, codes: (165, 16) +Position: 85732, codes: (165, 113) +Position: 85818, codes: (165, 16) +Position: 85842, codes: (165, 113) +Position: 85928, codes: (165, 16) +Position: 85952, codes: (165, 113) +Position: 86038, codes: (165, 16) +Position: 86062, codes: (165, 113) +Position: 86148, codes: (165, 16) +Position: 86172, codes: (165, 113) +Position: 86258, codes: (165, 16) +Position: 86282, codes: (165, 113) +Position: 86368, codes: (165, 16) +Position: 86392, codes: (165, 113) +Position: 86478, codes: (165, 16) +Position: 86502, codes: (165, 113) +Position: 86588, codes: (165, 16) +Position: 86612, codes: (165, 113) +Position: 86698, codes: (165, 16) +Position: 86722, codes: (165, 113) +Position: 86808, codes: (165, 16) +Position: 86832, codes: (165, 113) +Position: 86918, codes: (165, 16) +Position: 86942, codes: (165, 113) +Position: 87028, codes: (165, 16) +Position: 87052, codes: (165, 113) +Position: 87138, codes: (165, 16) +Position: 87162, codes: (165, 113) +Position: 87248, codes: (165, 16) +Position: 87272, codes: (165, 113) +Position: 87358, codes: (165, 16) +Position: 87382, codes: (165, 113) +Position: 87468, codes: (165, 16) +Position: 87492, codes: (165, 113) +Position: 87578, codes: (165, 16) +Position: 87602, codes: (165, 113) +Position: 87688, codes: (165, 16) +Position: 87712, codes: (165, 113) +Position: 87798, codes: (165, 16) +Position: 87822, codes: (165, 113) +Position: 87908, codes: (165, 16) +Position: 87932, codes: (165, 113) +Position: 88018, codes: (165, 16) +Position: 88042, codes: (165, 113) +Position: 88128, codes: (165, 16) +Position: 88152, codes: (165, 113) +Position: 88238, codes: (165, 16) +Position: 88262, codes: (165, 113) +Position: 88348, codes: (165, 16) +Position: 88372, codes: (165, 113) +Position: 88458, codes: (165, 16) +Position: 88482, codes: (165, 113) +Position: 88568, codes: (165, 16) +Position: 88592, codes: (165, 113) +Position: 88678, codes: (165, 17) +Position: 88706, codes: (165, 16) +Position: 88730, codes: (165, 113) +Position: 88816, codes: (165, 16) +Position: 88840, codes: (165, 113) +Position: 88926, codes: (165, 16) +Position: 88950, codes: (165, 113) +Position: 89036, codes: (165, 16) +Position: 89060, codes: (165, 113) +Position: 89146, codes: (165, 16) +Position: 89170, codes: (165, 113) +Position: 89256, codes: (165, 16) +Position: 89280, codes: (165, 113) +Position: 89366, codes: (165, 16) +Position: 89390, codes: (165, 113) +Position: 89476, codes: (165, 16) +Position: 89500, codes: (165, 113) +Position: 89586, codes: (165, 16) +Position: 89610, codes: (165, 113) +Position: 89696, codes: (165, 16) +Position: 89720, codes: (165, 113) +Position: 89806, codes: (165, 16) +Position: 89830, codes: (165, 113) +Position: 89916, codes: (165, 16) +Position: 89940, codes: (165, 113) +Position: 90026, codes: (165, 16) +Position: 90050, codes: (165, 113) +Position: 90136, codes: (165, 16) +Position: 90160, codes: (165, 113) +Position: 90246, codes: (165, 16) +Position: 90270, codes: (165, 113) +Position: 90356, codes: (165, 16) +Position: 90380, codes: (165, 113) +Position: 90466, codes: (165, 16) +Position: 90490, codes: (165, 113) +Position: 90576, codes: (165, 16) +Position: 90600, codes: (165, 113) +Position: 90686, codes: (165, 16) +Position: 90710, codes: (165, 113) +Position: 90796, codes: (165, 16) +Position: 90820, codes: (165, 113) +Position: 90906, codes: (165, 16) +Position: 90930, codes: (165, 113) +Position: 91016, codes: (165, 16) +Position: 91040, codes: (165, 113) +Position: 91126, codes: (165, 16) +Position: 91150, codes: (165, 113) +Position: 91236, codes: (165, 16) +Position: 91260, codes: (165, 113) +Position: 91346, codes: (165, 16) +Position: 91370, codes: (165, 113) +Position: 91456, codes: (165, 16) +Position: 91480, codes: (165, 113) +Position: 91566, codes: (165, 16) +Position: 91590, codes: (165, 113) +Position: 91676, codes: (165, 16) +Position: 91700, codes: (165, 113) +Position: 91786, codes: (165, 16) +Position: 91810, codes: (165, 113) +Position: 91896, codes: (165, 16) +Position: 91920, codes: (165, 113) +Position: 92006, codes: (165, 16) +Position: 92030, codes: (165, 113) +Position: 92116, codes: (165, 16) +Position: 92140, codes: (165, 113) +Position: 92226, codes: (165, 16) +Position: 92250, codes: (165, 17) +Position: 92278, codes: (165, 113) +Position: 92364, codes: (165, 16) +Position: 92388, codes: (165, 113) +Position: 92474, codes: (165, 16) +Position: 92498, codes: (165, 113) +Position: 92584, codes: (165, 16) +Position: 92608, codes: (165, 113) +Position: 92694, codes: (165, 16) +Position: 92718, codes: (165, 113) +Position: 92804, codes: (165, 16) +Position: 92828, codes: (165, 113) +Position: 92914, codes: (165, 16) +Position: 92938, codes: (165, 113) +Position: 93024, codes: (165, 16) +Position: 93048, codes: (165, 113) +Position: 93134, codes: (165, 16) +Position: 93158, codes: (165, 113) +Position: 93244, codes: (165, 16) +Position: 93268, codes: (165, 113) +Position: 93354, codes: (165, 16) +Position: 93378, codes: (165, 113) +Position: 93464, codes: (165, 16) +Position: 93488, codes: (165, 113) +Position: 93574, codes: (165, 16) +Position: 93598, codes: (165, 113) +Position: 93684, codes: (165, 16) +Position: 93708, codes: (165, 113) +Position: 93794, codes: (165, 16) +Position: 93818, codes: (165, 113) +Position: 93904, codes: (165, 16) +Position: 93928, codes: (165, 113) +Position: 94014, codes: (165, 16) +Position: 94038, codes: (165, 113) +Position: 94124, codes: (165, 16) +Position: 94148, codes: (165, 113) +Position: 94234, codes: (165, 16) +Position: 94258, codes: (165, 113) +Position: 94344, codes: (165, 16) +Position: 94368, codes: (165, 113) +Position: 94454, codes: (165, 16) +Position: 94478, codes: (165, 113) +Position: 94564, codes: (165, 16) +Position: 94588, codes: (165, 113) +Position: 94674, codes: (165, 16) +Position: 94698, codes: (165, 113) +Position: 94784, codes: (165, 16) +Position: 94808, codes: (165, 113) +Position: 94894, codes: (165, 16) +Position: 94918, codes: (165, 113) +Position: 95004, codes: (165, 16) +Position: 95028, codes: (165, 113) +Position: 95114, codes: (165, 16) +Position: 95138, codes: (165, 113) +Position: 95224, codes: (165, 16) +Position: 95248, codes: (165, 113) +Position: 95334, codes: (165, 16) +Position: 95358, codes: (165, 113) +Position: 95444, codes: (165, 16) +Position: 95468, codes: (165, 113) +Position: 95554, codes: (165, 16) +Position: 95578, codes: (165, 113) +Position: 95664, codes: (165, 16) +Position: 95688, codes: (165, 113) +Position: 95774, codes: (165, 16) +Position: 95798, codes: (165, 113) +Position: 95884, codes: (165, 17) +Position: 95912, codes: (165, 16) +Position: 95936, codes: (165, 113) +Position: 96022, codes: (165, 16) +Position: 96046, codes: (165, 113) +Position: 96132, codes: (165, 16) +Position: 96156, codes: (165, 113) +Position: 96242, codes: (165, 16) +Position: 96266, codes: (165, 113) +Position: 96352, codes: (165, 16) +Position: 96376, codes: (165, 113) +Position: 96462, codes: (165, 16) +Position: 96486, codes: (165, 113) +Position: 96572, codes: (165, 16) +Position: 96596, codes: (165, 113) +Position: 96682, codes: (165, 16) +Position: 96706, codes: (165, 113) +Position: 96792, codes: (165, 16) +Position: 96816, codes: (165, 113) +Position: 96902, codes: (165, 16) +Position: 96926, codes: (165, 113) +Position: 97012, codes: (165, 16) +Position: 97036, codes: (165, 113) +Position: 97122, codes: (165, 16) +Position: 97146, codes: (165, 113) +Position: 97232, codes: (165, 16) +Position: 97256, codes: (165, 113) +Position: 97342, codes: (165, 16) +Position: 97366, codes: (165, 113) +Position: 97452, codes: (165, 16) +Position: 97476, codes: (165, 113) +Position: 97562, codes: (165, 16) +Position: 97586, codes: (165, 113) +Position: 97672, codes: (165, 16) +Position: 97696, codes: (165, 113) +Position: 97782, codes: (165, 16) +Position: 97806, codes: (165, 113) +Position: 97892, codes: (165, 16) +Position: 97916, codes: (165, 113) +Position: 98002, codes: (165, 16) +Position: 98026, codes: (165, 113) +Position: 98112, codes: (165, 16) +Position: 98136, codes: (165, 113) +Position: 98222, codes: (165, 16) +Position: 98246, codes: (165, 113) +Position: 98332, codes: (165, 16) +Position: 98356, codes: (165, 113) +Position: 98442, codes: (165, 16) +Position: 98466, codes: (165, 113) +Position: 98552, codes: (165, 16) +Position: 98576, codes: (165, 113) +Position: 98662, codes: (165, 16) +Position: 98686, codes: (165, 113) +Position: 98772, codes: (165, 16) +Position: 98796, codes: (165, 113) +Position: 98882, codes: (165, 16) +Position: 98906, codes: (165, 113) +Position: 98992, codes: (165, 16) +Position: 99016, codes: (165, 113) +Position: 99102, codes: (165, 16) +Position: 99126, codes: (165, 113) +Position: 99212, codes: (165, 16) +Position: 99236, codes: (165, 113) +Position: 99322, codes: (165, 16) +Position: 99346, codes: (165, 113) +Position: 99432, codes: (165, 17) +Position: 99460, codes: (165, 16) +Position: 99484, codes: (165, 113) +Position: 99570, codes: (165, 16) +Position: 99594, codes: (165, 113) +Position: 99680, codes: (165, 16) +Position: 99704, codes: (165, 113) +Position: 99790, codes: (165, 16) +Position: 99814, codes: (165, 113) +Position: 99900, codes: (165, 16) +Position: 99924, codes: (165, 113) +Position: 100010, codes: (165, 16) +Position: 100034, codes: (165, 113) +Position: 100120, codes: (165, 16) +Position: 100144, codes: (165, 113) +Position: 100230, codes: (165, 16) +Position: 100254, codes: (165, 113) +Position: 100340, codes: (165, 16) +Position: 100364, codes: (165, 113) +Position: 100450, codes: (165, 16) +Position: 100474, codes: (165, 113) +Position: 100560, codes: (165, 16) +Position: 100584, codes: (165, 113) +Position: 100670, codes: (165, 16) +Position: 100694, codes: (165, 113) +Position: 100780, codes: (165, 16) +Position: 100804, codes: (165, 113) +Position: 100890, codes: (165, 16) +Position: 100914, codes: (165, 113) +Position: 101000, codes: (165, 16) +Position: 101024, codes: (165, 113) +Position: 101110, codes: (165, 16) +Position: 101134, codes: (165, 113) +Position: 101220, codes: (165, 16) +Position: 101244, codes: (165, 113) +Position: 101330, codes: (165, 16) +Position: 101354, codes: (165, 113) +Position: 101440, codes: (165, 16) +Position: 101464, codes: (165, 113) +Position: 101550, codes: (165, 16) +Position: 101574, codes: (165, 113) +Position: 101660, codes: (165, 16) +Position: 101684, codes: (165, 113) +Position: 101770, codes: (165, 16) +Position: 101794, codes: (165, 113) +Position: 101880, codes: (165, 16) +Position: 101904, codes: (165, 113) +Position: 101990, codes: (165, 16) +Position: 102014, codes: (165, 113) +Position: 102100, codes: (165, 16) +Position: 102124, codes: (165, 113) +Position: 102210, codes: (165, 16) +Position: 102234, codes: (165, 113) +Position: 102320, codes: (165, 16) +Position: 102344, codes: (165, 113) +Position: 102430, codes: (165, 16) +Position: 102454, codes: (165, 113) +Position: 102540, codes: (165, 16) +Position: 102564, codes: (165, 113) +Position: 102650, codes: (165, 16) +Position: 102674, codes: (165, 113) +Position: 102760, codes: (165, 16) +Position: 102784, codes: (165, 113) +Position: 102870, codes: (165, 16) +Position: 102894, codes: (165, 113) +Position: 102980, codes: (165, 16) +Position: 103004, codes: (165, 17) +Position: 103032, codes: (165, 113) +Position: 103118, codes: (165, 16) +Position: 103142, codes: (165, 113) +Position: 103228, codes: (165, 16) +Position: 103252, codes: (165, 113) +Position: 103338, codes: (165, 16) +Position: 103362, codes: (165, 113) +Position: 103448, codes: (165, 16) +Position: 103472, codes: (165, 113) +Position: 103558, codes: (165, 16) +Position: 103582, codes: (165, 113) +Position: 103668, codes: (165, 16) +Position: 103692, codes: (165, 113) +Position: 103778, codes: (165, 16) +Position: 103802, codes: (165, 113) +Position: 103888, codes: (165, 16) +Position: 103912, codes: (165, 113) +Position: 103998, codes: (165, 16) +Position: 104022, codes: (165, 113) +Position: 104108, codes: (165, 16) +Position: 104132, codes: (165, 113) +Position: 104218, codes: (165, 16) +Position: 104242, codes: (165, 113) +Position: 104328, codes: (165, 16) +Position: 104352, codes: (165, 113) +Position: 104438, codes: (165, 16) +Position: 104462, codes: (165, 113) +Position: 104548, codes: (165, 16) +Position: 104572, codes: (165, 113) +Position: 104658, codes: (165, 16) +Position: 104682, codes: (165, 113) +Position: 104768, codes: (165, 16) +Position: 104792, codes: (165, 113) +Position: 104878, codes: (165, 16) +Position: 104902, codes: (165, 113) +Position: 104988, codes: (165, 16) +Position: 105012, codes: (165, 113) +Position: 105098, codes: (165, 16) +Position: 105122, codes: (165, 113) +Position: 105208, codes: (165, 16) +Position: 105232, codes: (165, 113) +Position: 105318, codes: (165, 16) +Position: 105342, codes: (165, 113) +Position: 105428, codes: (165, 16) +Position: 105452, codes: (165, 113) +Position: 105538, codes: (165, 16) +Position: 105562, codes: (165, 113) +Position: 105648, codes: (165, 16) +Position: 105672, codes: (165, 113) +Position: 105758, codes: (165, 16) +Position: 105782, codes: (165, 113) +Position: 105868, codes: (165, 16) +Position: 105892, codes: (165, 113) +Position: 105978, codes: (165, 16) +Position: 106002, codes: (165, 113) +Position: 106088, codes: (165, 16) +Position: 106112, codes: (165, 113) +Position: 106198, codes: (165, 16) +Position: 106222, codes: (165, 113) +Position: 106308, codes: (165, 16) +Position: 106332, codes: (165, 113) +Position: 106418, codes: (165, 16) +Position: 106442, codes: (165, 113) +Position: 106528, codes: (165, 16) +Position: 106552, codes: (165, 113) +Position: 106638, codes: (165, 17) +Position: 106666, codes: (165, 16) +Position: 106690, codes: (165, 113) +Position: 106776, codes: (165, 16) +Position: 106800, codes: (165, 113) +Position: 106886, codes: (165, 16) +Position: 106910, codes: (165, 113) +Position: 106996, codes: (165, 16) +Position: 107020, codes: (165, 113) +Position: 107106, codes: (165, 16) +Position: 107130, codes: (165, 113) +Position: 107216, codes: (165, 16) +Position: 107240, codes: (165, 113) +Position: 107326, codes: (165, 16) +Position: 107350, codes: (165, 113) +Position: 107436, codes: (165, 16) +Position: 107460, codes: (165, 113) +Position: 107546, codes: (165, 16) +Position: 107570, codes: (165, 113) +Position: 107656, codes: (165, 16) +Position: 107680, codes: (165, 113) +Position: 107766, codes: (165, 16) +Position: 107790, codes: (165, 113) +Position: 107876, codes: (165, 16) +Position: 107900, codes: (165, 113) +Position: 107986, codes: (165, 16) +Position: 108010, codes: (165, 113) +Position: 108096, codes: (165, 16) +Position: 108120, codes: (165, 113) +Position: 108206, codes: (165, 16) +Position: 108230, codes: (165, 113) +Position: 108316, codes: (165, 16) +Position: 108340, codes: (165, 113) +Position: 108426, codes: (165, 16) +Position: 108450, codes: (165, 113) +Position: 108536, codes: (165, 16) +Position: 108560, codes: (165, 113) +Position: 108646, codes: (165, 16) +Position: 108670, codes: (165, 113) +Position: 108756, codes: (165, 16) +Position: 108780, codes: (165, 113) +Position: 108866, codes: (165, 16) +Position: 108890, codes: (165, 113) +Position: 108976, codes: (165, 16) +Position: 109000, codes: (165, 113) +Position: 109086, codes: (165, 16) +Position: 109110, codes: (165, 113) +Position: 109196, codes: (165, 16) +Position: 109220, codes: (165, 113) +Position: 109306, codes: (165, 16) +Position: 109330, codes: (165, 113) +Position: 109416, codes: (165, 16) +Position: 109440, codes: (165, 113) +Position: 109526, codes: (165, 16) +Position: 109550, codes: (165, 113) +Position: 109636, codes: (165, 16) +Position: 109660, codes: (165, 113) +Position: 109746, codes: (165, 16) +Position: 109770, codes: (165, 113) +Position: 109856, codes: (165, 16) +Position: 109880, codes: (165, 113) +Position: 109966, codes: (165, 16) +Position: 109990, codes: (165, 113) +Position: 110076, codes: (165, 16) +Position: 110100, codes: (165, 113) +Position: 110186, codes: (165, 17) +Position: 110214, codes: (165, 16) +Position: 110238, codes: (165, 113) +Position: 110324, codes: (165, 16) +Position: 110348, codes: (165, 113) +Position: 110434, codes: (165, 16) +Position: 110458, codes: (165, 113) +Position: 110544, codes: (165, 16) +Position: 110568, codes: (165, 113) +Position: 110654, codes: (165, 16) +Position: 110678, codes: (165, 113) +Position: 110764, codes: (165, 16) +Position: 110788, codes: (165, 113) +Position: 110874, codes: (165, 16) +Position: 110898, codes: (165, 113) +Position: 110984, codes: (165, 16) +Position: 111008, codes: (165, 113) +Position: 111094, codes: (165, 16) +Position: 111118, codes: (165, 113) +Position: 111204, codes: (165, 16) +Position: 111228, codes: (165, 113) +Position: 111314, codes: (165, 16) +Position: 111338, codes: (165, 113) +Position: 111424, codes: (165, 16) +Position: 111448, codes: (165, 113) +Position: 111534, codes: (165, 16) +Position: 111558, codes: (165, 113) +Position: 111644, codes: (165, 16) +Position: 111668, codes: (165, 113) +Position: 111754, codes: (165, 16) +Position: 111778, codes: (165, 113) +Position: 111864, codes: (165, 16) +Position: 111888, codes: (165, 113) +Position: 111974, codes: (165, 16) +Position: 111998, codes: (165, 113) +Position: 112084, codes: (165, 16) +Position: 112108, codes: (165, 113) +Position: 112194, codes: (165, 16) +Position: 112218, codes: (165, 113) +Position: 112304, codes: (165, 16) +Position: 112328, codes: (165, 113) +Position: 112414, codes: (165, 16) +Position: 112438, codes: (165, 113) +Position: 112524, codes: (165, 16) +Position: 112548, codes: (165, 113) +Position: 112634, codes: (165, 16) +Position: 112658, codes: (165, 113) +Position: 112744, codes: (165, 16) +Position: 112768, codes: (165, 113) +Position: 112854, codes: (165, 16) +Position: 112878, codes: (165, 113) +Position: 112964, codes: (165, 16) +Position: 112988, codes: (165, 113) +Position: 113074, codes: (165, 16) +Position: 113098, codes: (165, 113) +Position: 113184, codes: (165, 16) +Position: 113208, codes: (165, 113) +Position: 113294, codes: (165, 16) +Position: 113318, codes: (165, 113) +Position: 113404, codes: (165, 16) +Position: 113428, codes: (165, 113) +Position: 113514, codes: (165, 16) +Position: 113538, codes: (165, 113) +Position: 113624, codes: (165, 16) +Position: 113648, codes: (165, 113) +Position: 113734, codes: (165, 17) +Position: 113762, codes: (165, 16) +Position: 113786, codes: (165, 113) +Position: 113872, codes: (165, 16) +Position: 113896, codes: (165, 113) +Position: 113982, codes: (165, 16) +Position: 114006, codes: (165, 113) +Position: 114092, codes: (165, 16) +Position: 114116, codes: (165, 113) +Position: 114202, codes: (165, 16) +Position: 114226, codes: (165, 113) +Position: 114312, codes: (165, 16) +Position: 114336, codes: (165, 113) +Position: 114422, codes: (165, 16) +Position: 114446, codes: (165, 113) +Position: 114532, codes: (165, 16) +Position: 114556, codes: (165, 113) +Position: 114642, codes: (165, 16) +Position: 114666, codes: (165, 113) +Position: 114752, codes: (165, 16) +Position: 114776, codes: (165, 113) +Position: 114862, codes: (165, 16) +Position: 114886, codes: (165, 113) +Position: 114972, codes: (165, 16) +Position: 114996, codes: (165, 113) +Position: 115082, codes: (165, 16) +Position: 115106, codes: (165, 113) +Position: 115192, codes: (165, 16) +Position: 115216, codes: (165, 113) +Position: 115302, codes: (165, 16) +Position: 115326, codes: (165, 113) +Position: 115412, codes: (165, 16) +Position: 115436, codes: (165, 113) +Position: 115522, codes: (165, 16) +Position: 115546, codes: (165, 113) +Position: 115632, codes: (165, 16) +Position: 115656, codes: (165, 113) +Position: 115742, codes: (165, 16) +Position: 115766, codes: (165, 113) +Position: 115852, codes: (165, 16) +Position: 115876, codes: (165, 113) +Position: 115962, codes: (165, 16) +Position: 115986, codes: (165, 113) +Position: 116072, codes: (165, 16) +Position: 116096, codes: (165, 113) +Position: 116182, codes: (165, 16) +Position: 116206, codes: (165, 113) +Position: 116292, codes: (165, 16) +Position: 116316, codes: (165, 113) +Position: 116402, codes: (165, 16) +Position: 116426, codes: (165, 113) +Position: 116512, codes: (165, 16) +Position: 116536, codes: (165, 113) +Position: 116622, codes: (165, 16) +Position: 116646, codes: (165, 113) +Position: 116732, codes: (165, 16) +Position: 116756, codes: (165, 113) +Position: 116842, codes: (165, 16) +Position: 116866, codes: (165, 113) +Position: 116952, codes: (165, 16) +Position: 116976, codes: (165, 113) +Position: 117062, codes: (165, 16) +Position: 117086, codes: (165, 113) +Position: 117172, codes: (165, 16) +Position: 117196, codes: (165, 113) +Position: 117282, codes: (165, 16) +Position: 117306, codes: (165, 17) +Position: 117334, codes: (165, 113) +Position: 117420, codes: (165, 16) +Position: 117444, codes: (165, 113) +Position: 117530, codes: (165, 16) +Position: 117554, codes: (165, 113) +Position: 117640, codes: (165, 16) +Position: 117664, codes: (165, 113) +Position: 117750, codes: (165, 16) +Position: 117774, codes: (165, 113) +Position: 117860, codes: (165, 16) +Position: 117884, codes: (165, 113) +Position: 117970, codes: (165, 16) +Position: 117994, codes: (165, 113) +Position: 118080, codes: (165, 16) +Position: 118104, codes: (165, 113) +Position: 118190, codes: (165, 16) +Position: 118214, codes: (165, 113) +Position: 118300, codes: (165, 16) +Position: 118324, codes: (165, 113) +Position: 118410, codes: (165, 16) +Position: 118434, codes: (165, 113) +Position: 118520, codes: (165, 16) +Position: 118544, codes: (165, 113) +Position: 118630, codes: (165, 16) +Position: 118654, codes: (165, 113) +Position: 118740, codes: (165, 16) +Position: 118764, codes: (165, 113) +Position: 118850, codes: (165, 16) +Position: 118874, codes: (165, 113) +Position: 118960, codes: (165, 16) +Position: 118984, codes: (165, 113) +Position: 119070, codes: (165, 16) +Position: 119094, codes: (165, 113) +Position: 119180, codes: (165, 16) +Position: 119204, codes: (165, 113) +Position: 119290, codes: (165, 16) +Position: 119314, codes: (165, 113) +Position: 119400, codes: (165, 16) +Position: 119424, codes: (165, 113) +Position: 119510, codes: (165, 16) +Position: 119534, codes: (165, 113) +Position: 119620, codes: (165, 16) +Position: 119644, codes: (165, 113) +Position: 119730, codes: (165, 16) +Position: 119754, codes: (165, 113) +Position: 119840, codes: (165, 16) +Position: 119864, codes: (165, 113) +Position: 119950, codes: (165, 16) +Position: 119974, codes: (165, 113) +Position: 120060, codes: (165, 16) +Position: 120084, codes: (165, 113) +Position: 120170, codes: (165, 16) +Position: 120194, codes: (165, 113) +Position: 120280, codes: (165, 16) +Position: 120304, codes: (165, 113) +Position: 120390, codes: (165, 16) +Position: 120414, codes: (165, 113) +Position: 120500, codes: (165, 16) +Position: 120524, codes: (165, 113) +Position: 120610, codes: (165, 16) +Position: 120634, codes: (165, 113) +Position: 120720, codes: (165, 16) +Position: 120744, codes: (165, 113) +Position: 120830, codes: (165, 16) +Position: 120854, codes: (165, 113) +Position: 120940, codes: (165, 17) +Position: 120968, codes: (165, 16) +Position: 120992, codes: (165, 113) +Position: 121078, codes: (165, 16) +Position: 121102, codes: (165, 113) +Position: 121188, codes: (165, 16) +Position: 121212, codes: (165, 113) +Position: 121298, codes: (165, 16) +Position: 121322, codes: (165, 113) +Position: 121408, codes: (165, 16) +Position: 121432, codes: (165, 113) +Position: 121518, codes: (165, 16) +Position: 121542, codes: (165, 113) +Position: 121628, codes: (165, 16) +Position: 121652, codes: (165, 113) +Position: 121738, codes: (165, 16) +Position: 121762, codes: (165, 113) +Position: 121848, codes: (165, 16) +Position: 121872, codes: (165, 113) +Position: 121958, codes: (165, 16) +Position: 121982, codes: (165, 113) +Position: 122068, codes: (165, 16) +Position: 122092, codes: (165, 113) +Position: 122178, codes: (165, 16) +Position: 122202, codes: (165, 113) +Position: 122288, codes: (165, 16) +Position: 122312, codes: (165, 113) +Position: 122398, codes: (165, 16) +Position: 122422, codes: (165, 113) +Position: 122508, codes: (165, 16) +Position: 122532, codes: (165, 113) +Position: 122618, codes: (165, 16) +Position: 122642, codes: (165, 113) +Position: 122728, codes: (165, 16) +Position: 122752, codes: (165, 113) +Position: 122838, codes: (165, 16) +Position: 122862, codes: (165, 113) +Position: 122948, codes: (165, 16) +Position: 122972, codes: (165, 113) +Position: 123058, codes: (165, 16) +Position: 123082, codes: (165, 113) +Position: 123168, codes: (165, 16) +Position: 123192, codes: (165, 113) +Position: 123278, codes: (165, 16) +Position: 123302, codes: (165, 113) +Position: 123388, codes: (165, 16) +Position: 123412, codes: (165, 113) +Position: 123498, codes: (165, 16) +Position: 123522, codes: (165, 113) +Position: 123608, codes: (165, 16) +Position: 123632, codes: (165, 113) +Position: 123718, codes: (165, 16) +Position: 123742, codes: (165, 113) +Position: 123828, codes: (165, 16) +Position: 123852, codes: (165, 113) +Position: 123938, codes: (165, 16) +Position: 123962, codes: (165, 113) +Position: 124048, codes: (165, 16) +Position: 124072, codes: (165, 113) +Position: 124158, codes: (165, 16) +Position: 124182, codes: (165, 113) +Position: 124268, codes: (165, 16) +Position: 124292, codes: (165, 113) +Position: 124378, codes: (165, 16) +Position: 124402, codes: (165, 113) +Position: 124488, codes: (165, 17) +Position: 124516, codes: (165, 16) +Position: 124540, codes: (165, 113) +Position: 124626, codes: (165, 16) +Position: 124650, codes: (165, 113) +Position: 124736, codes: (165, 16) +Position: 124760, codes: (165, 113) +Position: 124846, codes: (165, 16) +Position: 124870, codes: (165, 113) +Position: 124956, codes: (165, 16) +Position: 124980, codes: (165, 113) +Position: 125066, codes: (165, 16) +Position: 125090, codes: (165, 113) +Position: 125176, codes: (165, 16) +Position: 125200, codes: (165, 113) +Position: 125286, codes: (165, 16) +Position: 125310, codes: (165, 113) +Position: 125396, codes: (165, 16) +Position: 125420, codes: (165, 113) +Position: 125506, codes: (165, 16) +Position: 125530, codes: (165, 113) +Position: 125616, codes: (165, 16) +Position: 125640, codes: (165, 113) +Position: 125726, codes: (165, 16) +Position: 125750, codes: (165, 113) +Position: 125836, codes: (165, 16) +Position: 125860, codes: (165, 113) +Position: 125946, codes: (165, 16) +Position: 125970, codes: (165, 113) +Position: 126056, codes: (165, 16) +Position: 126080, codes: (165, 113) +Position: 126166, codes: (165, 16) +Position: 126190, codes: (165, 113) +Position: 126276, codes: (165, 16) +Position: 126300, codes: (165, 113) +Position: 126386, codes: (165, 16) +Position: 126410, codes: (165, 113) +Position: 126496, codes: (165, 16) +Position: 126520, codes: (165, 113) +Position: 126606, codes: (165, 16) +Position: 126630, codes: (165, 113) +Position: 126716, codes: (165, 16) +Position: 126740, codes: (165, 113) +Position: 126826, codes: (165, 16) +Position: 126850, codes: (165, 113) +Position: 126936, codes: (165, 16) +Position: 126960, codes: (165, 113) +Position: 127046, codes: (165, 16) +Position: 127070, codes: (165, 113) +Position: 127156, codes: (165, 16) +Position: 127180, codes: (165, 113) +Position: 127266, codes: (165, 16) +Position: 127290, codes: (165, 113) +Position: 127376, codes: (165, 16) +Position: 127400, codes: (165, 113) +Position: 127486, codes: (165, 16) +Position: 127510, codes: (165, 113) +Position: 127596, codes: (165, 16) +Position: 127620, codes: (165, 113) +Position: 127706, codes: (165, 16) +Position: 127730, codes: (165, 113) +Position: 127816, codes: (165, 16) +Position: 127840, codes: (165, 113) +Position: 127926, codes: (165, 16) +Position: 127950, codes: (165, 113) +Position: 128036, codes: (165, 17) +Position: 128064, codes: (165, 16) +Position: 128088, codes: (165, 113) +Position: 128174, codes: (165, 16) +Position: 128198, codes: (165, 113) +Position: 128284, codes: (165, 16) +Position: 128308, codes: (165, 113) +Position: 128394, codes: (165, 16) +Position: 128418, codes: (165, 113) +Position: 128504, codes: (165, 16) +Position: 128528, codes: (165, 113) +Position: 128614, codes: (165, 16) +Position: 128638, codes: (165, 113) +Position: 128724, codes: (165, 16) +Position: 128748, codes: (165, 113) +Position: 128834, codes: (165, 16) +Position: 128858, codes: (165, 113) +Position: 128944, codes: (165, 16) +Position: 128968, codes: (165, 113) +Position: 129054, codes: (165, 16) +Position: 129078, codes: (165, 113) +Position: 129164, codes: (165, 16) +Position: 129188, codes: (165, 113) +Position: 129274, codes: (165, 16) +Position: 129298, codes: (165, 113) +Position: 129384, codes: (165, 16) +Position: 129408, codes: (165, 113) +Position: 129494, codes: (165, 16) +Position: 129518, codes: (165, 113) +Position: 129604, codes: (165, 16) +Position: 129628, codes: (165, 113) +Position: 129714, codes: (165, 16) +Position: 129738, codes: (165, 113) +Position: 129824, codes: (165, 16) +Position: 129848, codes: (165, 113) +Position: 129934, codes: (165, 16) +Position: 129958, codes: (165, 113) +Position: 130044, codes: (165, 16) +Position: 130068, codes: (165, 113) +Position: 130154, codes: (165, 16) +Position: 130178, codes: (165, 113) +Position: 130264, codes: (165, 16) +Position: 130288, codes: (165, 113) +Position: 130374, codes: (165, 16) +Position: 130398, codes: (165, 113) +Position: 130484, codes: (165, 16) +Position: 130508, codes: (165, 113) +Position: 130594, codes: (165, 16) +Position: 130618, codes: (165, 113) +Position: 130704, codes: (165, 16) +Position: 130728, codes: (165, 113) +Position: 130814, codes: (165, 16) +Position: 130838, codes: (165, 113) +Position: 130924, codes: (165, 16) +Position: 130948, codes: (165, 113) +Position: 131034, codes: (165, 16) +Position: 131058, codes: (165, 113) +Position: 131144, codes: (165, 16) +Position: 131168, codes: (165, 113) +Position: 131254, codes: (165, 16) +Position: 131278, codes: (165, 113) +Position: 131364, codes: (165, 16) +Position: 131388, codes: (165, 113) +Position: 131474, codes: (165, 16) +Position: 131498, codes: (165, 113) +Position: 131584, codes: (165, 16) +Position: 131608, codes: (165, 17) +Position: 131636, codes: (165, 113) +Position: 131722, codes: (165, 16) +Position: 131746, codes: (165, 113) +Position: 131832, codes: (165, 16) +Position: 131856, codes: (165, 113) +Position: 131942, codes: (165, 16) +Position: 131966, codes: (165, 113) +Position: 132052, codes: (165, 16) +Position: 132076, codes: (165, 113) +Position: 132162, codes: (165, 16) +Position: 132186, codes: (165, 113) +Position: 132272, codes: (165, 16) +Position: 132296, codes: (165, 113) +Position: 132382, codes: (165, 16) +Position: 132406, codes: (165, 113) +Position: 132492, codes: (165, 16) +Position: 132516, codes: (165, 113) +Position: 132602, codes: (165, 16) +Position: 132626, codes: (165, 113) +Position: 132712, codes: (165, 16) +Position: 132736, codes: (165, 113) +Position: 132822, codes: (165, 16) +Position: 132846, codes: (165, 113) +Position: 132932, codes: (165, 16) +Position: 132956, codes: (165, 113) +Position: 133042, codes: (165, 16) +Position: 133066, codes: (165, 113) +Position: 133152, codes: (165, 16) +Position: 133176, codes: (165, 113) +Position: 133262, codes: (165, 16) +Position: 133286, codes: (165, 113) +Position: 133372, codes: (165, 16) +Position: 133396, codes: (165, 113) +Position: 133482, codes: (165, 16) +Position: 133506, codes: (165, 113) +Position: 133592, codes: (165, 16) +Position: 133616, codes: (165, 113) +Position: 133702, codes: (165, 16) +Position: 133726, codes: (165, 113) +Position: 133812, codes: (165, 16) +Position: 133836, codes: (165, 113) +Position: 133922, codes: (165, 16) +Position: 133946, codes: (165, 113) +Position: 134032, codes: (165, 16) +Position: 134056, codes: (165, 113) +Position: 134142, codes: (165, 16) +Position: 134166, codes: (165, 113) +Position: 134252, codes: (165, 16) +Position: 134276, codes: (165, 113) +Position: 134362, codes: (165, 16) +Position: 134386, codes: (165, 113) +Position: 134472, codes: (165, 16) +Position: 134496, codes: (165, 113) +Position: 134582, codes: (165, 16) +Position: 134606, codes: (165, 113) +Position: 134692, codes: (165, 16) +Position: 134716, codes: (165, 113) +Position: 134802, codes: (165, 16) +Position: 134826, codes: (165, 113) +Position: 134912, codes: (165, 16) +Position: 134936, codes: (165, 113) +Position: 135022, codes: (165, 16) +Position: 135046, codes: (165, 113) +Position: 135132, codes: (165, 16) +Position: 135156, codes: (165, 113) +Position: 135242, codes: (165, 17) +Position: 135270, codes: (165, 16) +Position: 135294, codes: (165, 113) +Position: 135380, codes: (165, 16) +Position: 135404, codes: (165, 113) +Position: 135490, codes: (165, 16) +Position: 135514, codes: (165, 113) +Position: 135600, codes: (165, 16) +Position: 135624, codes: (165, 113) +Position: 135710, codes: (165, 16) +Position: 135734, codes: (165, 113) +Position: 135820, codes: (165, 16) +Position: 135844, codes: (165, 113) +Position: 135930, codes: (165, 16) +Position: 135954, codes: (165, 113) +Position: 136040, codes: (165, 16) +Position: 136064, codes: (165, 113) +Position: 136150, codes: (165, 16) +Position: 136174, codes: (165, 113) +Position: 136260, codes: (165, 16) +Position: 136284, codes: (165, 113) +Position: 136370, codes: (165, 16) +Position: 136394, codes: (165, 113) +Position: 136480, codes: (165, 16) +Position: 136504, codes: (165, 113) +Position: 136590, codes: (165, 16) +Position: 136614, codes: (165, 113) +Position: 136700, codes: (165, 16) +Position: 136724, codes: (165, 113) +Position: 136810, codes: (165, 16) +Position: 136834, codes: (165, 113) +Position: 136920, codes: (165, 16) +Position: 136944, codes: (165, 113) +Position: 137030, codes: (165, 16) +Position: 137054, codes: (165, 113) +Position: 137140, codes: (165, 16) +Position: 137164, codes: (165, 113) +Position: 137250, codes: (165, 16) +Position: 137274, codes: (165, 113) +Position: 137360, codes: (165, 16) +Position: 137384, codes: (165, 113) +Position: 137470, codes: (165, 16) +Position: 137494, codes: (165, 113) +Position: 137580, codes: (165, 16) +Position: 137604, codes: (165, 113) +Position: 137690, codes: (165, 16) +Position: 137714, codes: (165, 113) +Position: 137800, codes: (165, 16) +Position: 137824, codes: (165, 113) +Position: 137910, codes: (165, 16) +Position: 137934, codes: (165, 113) +Position: 138020, codes: (165, 16) +Position: 138044, codes: (165, 113) +Position: 138130, codes: (165, 16) +Position: 138154, codes: (165, 113) +Position: 138240, codes: (165, 16) +Position: 138264, codes: (165, 113) +Position: 138350, codes: (165, 16) +Position: 138374, codes: (165, 113) +Position: 138460, codes: (165, 16) +Position: 138484, codes: (165, 113) +Position: 138570, codes: (165, 16) +Position: 138594, codes: (165, 113) +Position: 138680, codes: (165, 16) +Position: 138704, codes: (165, 113) +Position: 138790, codes: (165, 17) +Position: 138818, codes: (165, 16) +Position: 138842, codes: (165, 113) +Position: 138928, codes: (165, 16) +Position: 138952, codes: (165, 113) +Position: 139038, codes: (165, 16) +Position: 139062, codes: (165, 113) +Position: 139148, codes: (165, 16) +Position: 139172, codes: (165, 113) +Position: 139258, codes: (165, 16) +Position: 139282, codes: (165, 113) +Position: 139368, codes: (165, 16) +Position: 139392, codes: (165, 113) +Position: 139478, codes: (165, 16) +Position: 139502, codes: (165, 113) +Position: 139588, codes: (165, 16) +Position: 139612, codes: (165, 113) +Position: 139698, codes: (165, 16) +Position: 139722, codes: (165, 113) +Position: 139808, codes: (165, 16) +Position: 139832, codes: (165, 113) +Position: 139918, codes: (165, 16) +Position: 139942, codes: (165, 113) +Position: 140028, codes: (165, 16) +Position: 140052, codes: (165, 113) +Position: 140138, codes: (165, 16) +Position: 140162, codes: (165, 113) +Position: 140248, codes: (165, 16) +Position: 140272, codes: (165, 113) +Position: 140358, codes: (165, 16) +Position: 140382, codes: (165, 113) +Position: 140468, codes: (165, 16) +Position: 140492, codes: (165, 113) +Position: 140578, codes: (165, 16) +Position: 140602, codes: (165, 113) +Position: 140688, codes: (165, 16) +Position: 140712, codes: (165, 113) +Position: 140798, codes: (165, 16) +Position: 140822, codes: (165, 113) +Position: 140908, codes: (165, 16) +Position: 140932, codes: (165, 113) +Position: 141018, codes: (165, 16) +Position: 141042, codes: (165, 113) +Position: 141128, codes: (165, 16) +Position: 141152, codes: (165, 113) +Position: 141238, codes: (165, 16) +Position: 141262, codes: (165, 113) +Position: 141348, codes: (165, 16) +Position: 141372, codes: (165, 113) +Position: 141458, codes: (165, 16) +Position: 141482, codes: (165, 113) +Position: 141568, codes: (165, 16) +Position: 141592, codes: (165, 113) +Position: 141678, codes: (165, 16) +Position: 141702, codes: (165, 113) +Position: 141788, codes: (165, 16) +Position: 141812, codes: (165, 113) +Position: 141898, codes: (165, 16) +Position: 141922, codes: (165, 113) +Position: 142008, codes: (165, 16) +Position: 142032, codes: (165, 113) +Position: 142118, codes: (165, 16) +Position: 142142, codes: (165, 113) +Position: 142228, codes: (165, 16) +Position: 142252, codes: (165, 113) +Position: 142338, codes: (165, 17) +Position: 142366, codes: (165, 16) +Position: 142390, codes: (165, 113) +Position: 142476, codes: (165, 16) +Position: 142500, codes: (165, 113) +Position: 142586, codes: (165, 16) +Position: 142610, codes: (165, 113) +Position: 142696, codes: (165, 16) +Position: 142720, codes: (165, 113) +Position: 142806, codes: (165, 16) +Position: 142830, codes: (165, 113) +Position: 142916, codes: (165, 16) +Position: 142940, codes: (165, 113) +Position: 143026, codes: (165, 16) +Position: 143050, codes: (165, 113) +Position: 143136, codes: (165, 16) +Position: 143160, codes: (165, 113) +Position: 143246, codes: (165, 16) +Position: 143270, codes: (165, 113) +Position: 143356, codes: (165, 16) +Position: 143380, codes: (165, 113) +Position: 143466, codes: (165, 16) +Position: 143490, codes: (165, 113) +Position: 143576, codes: (165, 16) +Position: 143600, codes: (165, 113) +Position: 143686, codes: (165, 16) +Position: 143710, codes: (165, 113) +Position: 143796, codes: (165, 16) +Position: 143820, codes: (165, 113) +Position: 143906, codes: (165, 16) +Position: 143930, codes: (165, 113) +Position: 144016, codes: (165, 16) +Position: 144040, codes: (165, 113) +Position: 144126, codes: (165, 16) +Position: 144150, codes: (165, 113) +Position: 144236, codes: (165, 16) +Position: 144260, codes: (165, 113) +Position: 144346, codes: (165, 16) +Position: 144370, codes: (165, 113) +Position: 144456, codes: (165, 16) +Position: 144480, codes: (165, 113) +Position: 144566, codes: (165, 16) +Position: 144590, codes: (165, 113) +Position: 144676, codes: (165, 16) +Position: 144700, codes: (165, 113) +Position: 144786, codes: (165, 16) +Position: 144810, codes: (165, 113) +Position: 144896, codes: (165, 16) +Position: 144920, codes: (165, 113) +Position: 145006, codes: (165, 16) +Position: 145030, codes: (165, 113) +Position: 145116, codes: (165, 16) +Position: 145140, codes: (165, 113) +Position: 145226, codes: (165, 16) +Position: 145250, codes: (165, 113) +Position: 145336, codes: (165, 16) +Position: 145360, codes: (165, 113) +Position: 145446, codes: (165, 16) +Position: 145470, codes: (165, 113) +Position: 145556, codes: (165, 16) +Position: 145580, codes: (165, 113) +Position: 145666, codes: (165, 16) +Position: 145690, codes: (165, 113) +Position: 145776, codes: (165, 16) +Position: 145800, codes: (165, 113) +Position: 145886, codes: (165, 16) +Position: 145910, codes: (165, 17) +Position: 145938, codes: (165, 113) +Position: 146024, codes: (165, 16) +Position: 146048, codes: (165, 113) +Position: 146134, codes: (165, 16) +Position: 146158, codes: (165, 113) +Position: 146244, codes: (165, 16) +Position: 146268, codes: (165, 113) +Position: 146354, codes: (165, 16) +Position: 146378, codes: (165, 113) +Position: 146464, codes: (165, 16) +Position: 146488, codes: (165, 113) +Position: 146574, codes: (165, 16) +Position: 146598, codes: (165, 113) +Position: 146684, codes: (165, 16) +Position: 146708, codes: (165, 113) +Position: 146794, codes: (165, 16) +Position: 146818, codes: (165, 113) +Position: 146904, codes: (165, 16) +Position: 146928, codes: (165, 113) +Position: 147014, codes: (165, 16) +Position: 147038, codes: (165, 113) +Position: 147124, codes: (165, 16) +Position: 147148, codes: (165, 113) +Position: 147234, codes: (165, 16) +Position: 147258, codes: (165, 113) +Position: 147344, codes: (165, 16) +Position: 147368, codes: (165, 113) +Position: 147454, codes: (165, 16) +Position: 147478, codes: (165, 113) +Position: 147564, codes: (165, 16) +Position: 147588, codes: (165, 113) +Position: 147674, codes: (165, 16) +Position: 147698, codes: (165, 113) +Position: 147784, codes: (165, 16) +Position: 147808, codes: (165, 113) +Position: 147894, codes: (165, 16) +Position: 147918, codes: (165, 113) +Position: 148004, codes: (165, 16) +Position: 148028, codes: (165, 113) +Position: 148114, codes: (165, 16) +Position: 148138, codes: (165, 113) +Position: 148224, codes: (165, 16) +Position: 148248, codes: (165, 113) +Position: 148334, codes: (165, 16) +Position: 148358, codes: (165, 113) +Position: 148444, codes: (165, 16) +Position: 148468, codes: (165, 113) +Position: 148554, codes: (165, 16) +Position: 148578, codes: (165, 113) +Position: 148664, codes: (165, 16) +Position: 148688, codes: (165, 113) +Position: 148774, codes: (165, 16) +Position: 148798, codes: (165, 113) +Position: 148884, codes: (165, 16) +Position: 148908, codes: (165, 113) +Position: 148994, codes: (165, 16) +Position: 149018, codes: (165, 113) +Position: 149104, codes: (165, 16) +Position: 149128, codes: (165, 113) +Position: 149214, codes: (165, 16) +Position: 149238, codes: (165, 113) +Position: 149324, codes: (165, 16) +Position: 149348, codes: (165, 113) +Position: 149434, codes: (165, 16) +Position: 149458, codes: (165, 113) +Position: 149544, codes: (165, 17) +Position: 149572, codes: (165, 16) +Position: 149596, codes: (165, 113) +Position: 149682, codes: (165, 16) +Position: 149706, codes: (165, 113) +Position: 149792, codes: (165, 16) +Position: 149816, codes: (165, 113) +Position: 149902, codes: (165, 16) +Position: 149926, codes: (165, 113) +Position: 150012, codes: (165, 16) +Position: 150036, codes: (165, 113) +Position: 150122, codes: (165, 16) +Position: 150146, codes: (165, 113) +Position: 150232, codes: (165, 16) +Position: 150256, codes: (165, 113) +Position: 150342, codes: (165, 16) +Position: 150366, codes: (165, 113) +Position: 150452, codes: (165, 16) +Position: 150476, codes: (165, 113) +Position: 150562, codes: (165, 16) +Position: 150586, codes: (165, 113) +Position: 150672, codes: (165, 16) +Position: 150696, codes: (165, 113) +Position: 150782, codes: (165, 16) +Position: 150806, codes: (165, 113) +Position: 150892, codes: (165, 16) +Position: 150916, codes: (165, 113) +Position: 151002, codes: (165, 16) +Position: 151026, codes: (165, 113) +Position: 151112, codes: (165, 16) +Position: 151136, codes: (165, 113) +Position: 151222, codes: (165, 16) +Position: 151246, codes: (165, 113) +Position: 151332, codes: (165, 16) +Position: 151356, codes: (165, 113) +Position: 151442, codes: (165, 16) +Position: 151466, codes: (165, 113) +Position: 151552, codes: (165, 16) +Position: 151576, codes: (165, 113) +Position: 151662, codes: (165, 16) +Position: 151686, codes: (165, 113) +Position: 151772, codes: (165, 16) +Position: 151796, codes: (165, 113) +Position: 151882, codes: (165, 16) +Position: 151906, codes: (165, 113) +Position: 151992, codes: (165, 16) +Position: 152016, codes: (165, 113) +Position: 152102, codes: (165, 16) +Position: 152126, codes: (165, 113) +Position: 152212, codes: (165, 16) +Position: 152236, codes: (165, 113) +Position: 152322, codes: (165, 16) +Position: 152346, codes: (165, 113) +Position: 152432, codes: (165, 16) +Position: 152456, codes: (165, 113) +Position: 152542, codes: (165, 16) +Position: 152566, codes: (165, 113) +Position: 152652, codes: (165, 16) +Position: 152676, codes: (165, 113) +Position: 152762, codes: (165, 16) +Position: 152786, codes: (165, 113) +Position: 152872, codes: (165, 16) +Position: 152896, codes: (165, 113) +Position: 152982, codes: (165, 16) +Position: 153006, codes: (165, 113) +Position: 153092, codes: (165, 17) +Position: 153120, codes: (165, 16) +Position: 153144, codes: (165, 113) +Position: 153230, codes: (165, 16) +Position: 153254, codes: (165, 113) +Position: 153340, codes: (165, 16) +Position: 153364, codes: (165, 113) +Position: 153450, codes: (165, 16) +Position: 153474, codes: (165, 113) +Position: 153560, codes: (165, 16) +Position: 153584, codes: (165, 113) +Position: 153670, codes: (165, 16) +Position: 153694, codes: (165, 113) +Position: 153780, codes: (165, 16) +Position: 153804, codes: (165, 113) +Position: 153890, codes: (165, 16) +Position: 153914, codes: (165, 113) +Position: 154000, codes: (165, 16) +Position: 154024, codes: (165, 113) +Position: 154110, codes: (165, 16) +Position: 154134, codes: (165, 113) +Position: 154220, codes: (165, 16) +Position: 154244, codes: (165, 113) +Position: 154330, codes: (165, 16) +Position: 154354, codes: (165, 113) +Position: 154440, codes: (165, 16) +Position: 154464, codes: (165, 113) +Position: 154550, codes: (165, 16) +Position: 154574, codes: (165, 113) +Position: 154660, codes: (165, 16) +Position: 154684, codes: (165, 113) +Position: 154770, codes: (165, 16) +Position: 154794, codes: (165, 113) +Position: 154880, codes: (165, 16) +Position: 154904, codes: (165, 113) +Position: 154990, codes: (165, 16) +Position: 155014, codes: (165, 113) +Position: 155100, codes: (165, 16) +Position: 155124, codes: (165, 113) +Position: 155210, codes: (165, 16) +Position: 155234, codes: (165, 113) +Position: 155320, codes: (165, 16) +Position: 155344, codes: (165, 113) +Position: 155430, codes: (165, 16) +Position: 155454, codes: (165, 113) +Position: 155540, codes: (165, 16) +Position: 155564, codes: (165, 113) +Position: 155650, codes: (165, 16) +Position: 155674, codes: (165, 113) +Position: 155760, codes: (165, 16) +Position: 155784, codes: (165, 113) +Position: 155870, codes: (165, 16) +Position: 155894, codes: (165, 113) +Position: 155980, codes: (165, 16) +Position: 156004, codes: (165, 113) +Position: 156090, codes: (165, 16) +Position: 156114, codes: (165, 113) +Position: 156200, codes: (165, 16) +Position: 156224, codes: (165, 113) +Position: 156310, codes: (165, 16) +Position: 156334, codes: (165, 113) +Position: 156420, codes: (165, 16) +Position: 156444, codes: (165, 113) +Position: 156530, codes: (165, 16) +Position: 156554, codes: (165, 113) +Position: 156640, codes: (165, 17) +Position: 156668, codes: (165, 16) +Position: 156692, codes: (165, 113) +Position: 156778, codes: (165, 16) +Position: 156802, codes: (165, 113) +Position: 156888, codes: (165, 16) +Position: 156912, codes: (165, 113) +Position: 156998, codes: (165, 16) +Position: 157022, codes: (165, 113) +Position: 157108, codes: (165, 16) +Position: 157132, codes: (165, 113) +Position: 157218, codes: (165, 16) +Position: 157242, codes: (165, 113) +Position: 157328, codes: (165, 16) +Position: 157352, codes: (165, 113) +Position: 157438, codes: (165, 16) +Position: 157462, codes: (165, 113) +Position: 157548, codes: (165, 16) +Position: 157572, codes: (165, 113) +Position: 157658, codes: (165, 16) +Position: 157682, codes: (165, 113) +Position: 157768, codes: (165, 16) +Position: 157792, codes: (165, 113) +Position: 157878, codes: (165, 16) +Position: 157902, codes: (165, 113) +Position: 157988, codes: (165, 16) +Position: 158012, codes: (165, 113) +Position: 158098, codes: (165, 16) +Position: 158122, codes: (165, 113) +Position: 158208, codes: (165, 16) +Position: 158232, codes: (165, 113) +Position: 158318, codes: (165, 16) +Position: 158342, codes: (165, 113) +Position: 158428, codes: (165, 16) +Position: 158452, codes: (165, 113) +Position: 158538, codes: (165, 16) +Position: 158562, codes: (165, 113) +Position: 158648, codes: (165, 16) +Position: 158672, codes: (165, 113) +Position: 158758, codes: (165, 16) +Position: 158782, codes: (165, 113) +Position: 158868, codes: (165, 16) +Position: 158892, codes: (165, 113) +Position: 158978, codes: (165, 16) +Position: 159002, codes: (165, 113) +Position: 159088, codes: (165, 16) +Position: 159112, codes: (165, 113) +Position: 159198, codes: (165, 16) +Position: 159222, codes: (165, 113) +Position: 159308, codes: (165, 16) +Position: 159332, codes: (165, 113) +Position: 159418, codes: (165, 16) +Position: 159442, codes: (165, 113) +Position: 159528, codes: (165, 16) +Position: 159552, codes: (165, 113) +Position: 159638, codes: (165, 16) +Position: 159662, codes: (165, 113) +Position: 159748, codes: (165, 16) +Position: 159772, codes: (165, 113) +Position: 159858, codes: (165, 16) +Position: 159882, codes: (165, 113) +Position: 159968, codes: (165, 16) +Position: 159992, codes: (165, 113) +Position: 160078, codes: (165, 16) +Position: 160102, codes: (165, 113) +Position: 160188, codes: (165, 16) +Position: 160212, codes: (165, 17) +Position: 160240, codes: (165, 113) +Position: 160326, codes: (165, 16) +Position: 160350, codes: (165, 113) +Position: 160436, codes: (165, 16) +Position: 160460, codes: (165, 113) +Position: 160546, codes: (165, 16) +Position: 160570, codes: (165, 113) +Position: 160656, codes: (165, 16) +Position: 160680, codes: (165, 113) +Position: 160766, codes: (165, 16) +Position: 160790, codes: (165, 113) +Position: 160876, codes: (165, 16) +Position: 160900, codes: (165, 113) +Position: 160986, codes: (165, 16) +Position: 161010, codes: (165, 113) +Position: 161096, codes: (165, 16) +Position: 161120, codes: (165, 113) +Position: 161206, codes: (165, 16) +Position: 161230, codes: (165, 113) +Position: 161316, codes: (165, 16) +Position: 161340, codes: (165, 113) +Position: 161426, codes: (165, 16) +Position: 161450, codes: (165, 113) +Position: 161536, codes: (165, 16) +Position: 161560, codes: (165, 113) +Position: 161646, codes: (165, 16) +Position: 161670, codes: (165, 113) +Position: 161756, codes: (165, 16) +Position: 161780, codes: (165, 113) +Position: 161866, codes: (165, 16) +Position: 161890, codes: (165, 113) +Position: 161976, codes: (165, 16) +Position: 162000, codes: (165, 113) +Position: 162086, codes: (165, 16) +Position: 162110, codes: (165, 113) +Position: 162196, codes: (165, 16) +Position: 162220, codes: (165, 113) +Position: 162306, codes: (165, 16) +Position: 162330, codes: (165, 113) +Position: 162416, codes: (165, 16) +Position: 162440, codes: (165, 113) +Position: 162526, codes: (165, 16) +Position: 162550, codes: (165, 113) +Position: 162636, codes: (165, 16) +Position: 162660, codes: (165, 113) +Position: 162746, codes: (165, 16) +Position: 162770, codes: (165, 113) +Position: 162856, codes: (165, 16) +Position: 162880, codes: (165, 113) +Position: 162966, codes: (165, 16) +Position: 162990, codes: (165, 113) +Position: 163076, codes: (165, 16) +Position: 163100, codes: (165, 113) +Position: 163186, codes: (165, 16) +Position: 163210, codes: (165, 113) +Position: 163296, codes: (165, 16) +Position: 163320, codes: (165, 113) +Position: 163406, codes: (165, 16) +Position: 163430, codes: (165, 113) +Position: 163516, codes: (165, 16) +Position: 163540, codes: (165, 113) +Position: 163626, codes: (165, 16) +Position: 163650, codes: (165, 113) +Position: 163736, codes: (165, 16) +Position: 163760, codes: (165, 113) +Position: 163846, codes: (165, 17) +Position: 163874, codes: (165, 16) +Position: 163898, codes: (165, 113) +Position: 163984, codes: (165, 16) +Position: 164008, codes: (165, 113) +Position: 164094, codes: (165, 16) +Position: 164118, codes: (165, 113) +Position: 164204, codes: (165, 16) +Position: 164228, codes: (165, 113) +Position: 164314, codes: (165, 16) +Position: 164338, codes: (165, 113) +Position: 164424, codes: (165, 16) +Position: 164448, codes: (165, 113) +Position: 164534, codes: (165, 16) +Position: 164558, codes: (165, 113) +Position: 164644, codes: (165, 16) +Position: 164668, codes: (165, 113) +Position: 164754, codes: (165, 16) +Position: 164778, codes: (165, 113) +Position: 164864, codes: (165, 16) +Position: 164888, codes: (165, 113) +Position: 164974, codes: (165, 16) +Position: 164998, codes: (165, 113) +Position: 165084, codes: (165, 16) +Position: 165108, codes: (165, 113) +Position: 165194, codes: (165, 16) +Position: 165218, codes: (165, 113) +Position: 165304, codes: (165, 16) +Position: 165328, codes: (165, 113) +Position: 165414, codes: (165, 16) +Position: 165438, codes: (165, 113) +Position: 165524, codes: (165, 16) +Position: 165548, codes: (165, 113) +Position: 165634, codes: (165, 16) +Position: 165658, codes: (165, 113) +Position: 165744, codes: (165, 16) +Position: 165768, codes: (165, 113) +Position: 165854, codes: (165, 16) +Position: 165878, codes: (165, 113) +Position: 165964, codes: (165, 16) +Position: 165988, codes: (165, 113) +Position: 166074, codes: (165, 16) +Position: 166098, codes: (165, 113) +Position: 166184, codes: (165, 16) +Position: 166208, codes: (165, 113) +Position: 166294, codes: (165, 16) +Position: 166318, codes: (165, 113) +Position: 166404, codes: (165, 16) +Position: 166428, codes: (165, 113) +Position: 166514, codes: (165, 16) +Position: 166538, codes: (165, 113) +Position: 166624, codes: (165, 16) +Position: 166648, codes: (165, 113) +Position: 166734, codes: (165, 16) +Position: 166758, codes: (165, 113) +Position: 166844, codes: (165, 16) +Position: 166868, codes: (165, 113) +Position: 166954, codes: (165, 16) +Position: 166978, codes: (165, 113) +Position: 167064, codes: (165, 16) +Position: 167088, codes: (165, 113) +Position: 167174, codes: (165, 16) +Position: 167198, codes: (165, 113) +Position: 167284, codes: (165, 16) +Position: 167308, codes: (165, 113) +Position: 167394, codes: (165, 17) +Position: 167422, codes: (165, 16) +Position: 167446, codes: (165, 113) +Position: 167532, codes: (165, 16) +Position: 167556, codes: (165, 113) +Position: 167642, codes: (165, 16) +Position: 167666, codes: (165, 113) +Position: 167752, codes: (165, 16) +Position: 167776, codes: (165, 113) +Position: 167862, codes: (165, 16) +Position: 167886, codes: (165, 113) +Position: 167972, codes: (165, 16) +Position: 167996, codes: (165, 113) +Position: 168082, codes: (165, 16) +Position: 168106, codes: (165, 113) +Position: 168192, codes: (165, 16) +Position: 168216, codes: (165, 113) +Position: 168302, codes: (165, 16) +Position: 168326, codes: (165, 113) +Position: 168412, codes: (165, 16) +Position: 168436, codes: (165, 113) +Position: 168522, codes: (165, 16) +Position: 168546, codes: (165, 113) +Position: 168632, codes: (165, 16) +Position: 168656, codes: (165, 113) +Position: 168742, codes: (165, 16) +Position: 168766, codes: (165, 113) +Position: 168852, codes: (165, 16) +Position: 168876, codes: (165, 113) +Position: 168962, codes: (165, 16) +Position: 168986, codes: (165, 113) +Position: 169072, codes: (165, 16) +Position: 169096, codes: (165, 113) +Position: 169182, codes: (165, 16) +Position: 169206, codes: (165, 113) +Position: 169292, codes: (165, 16) +Position: 169316, codes: (165, 113) +Position: 169402, codes: (165, 16) +Position: 169426, codes: (165, 113) +Position: 169512, codes: (165, 16) +Position: 169536, codes: (165, 113) +Position: 169622, codes: (165, 16) +Position: 169646, codes: (165, 113) +Position: 169732, codes: (165, 16) +Position: 169756, codes: (165, 113) +Position: 169842, codes: (165, 16) +Position: 169866, codes: (165, 113) +Position: 169952, codes: (165, 16) +Position: 169976, codes: (165, 113) +Position: 170062, codes: (165, 16) +Position: 170086, codes: (165, 113) +Position: 170172, codes: (165, 16) +Position: 170196, codes: (165, 113) +Position: 170282, codes: (165, 16) +Position: 170306, codes: (165, 113) +Position: 170392, codes: (165, 16) +Position: 170416, codes: (165, 113) +Position: 170502, codes: (165, 16) +Position: 170526, codes: (165, 113) +Position: 170612, codes: (165, 16) +Position: 170636, codes: (165, 113) +Position: 170722, codes: (165, 16) +Position: 170746, codes: (165, 113) +Position: 170832, codes: (165, 16) +Position: 170856, codes: (165, 113) +Position: 170942, codes: (165, 17) +Position: 170970, codes: (165, 16) +Position: 170994, codes: (165, 113) +Position: 171080, codes: (165, 16) +Position: 171104, codes: (165, 113) +Position: 171190, codes: (165, 16) +Position: 171214, codes: (165, 113) +Position: 171300, codes: (165, 16) +Position: 171324, codes: (165, 113) +Position: 171410, codes: (165, 16) +Position: 171434, codes: (165, 113) +Position: 171520, codes: (165, 16) +Position: 171544, codes: (165, 113) +Position: 171630, codes: (165, 16) +Position: 171654, codes: (165, 113) +Position: 171740, codes: (165, 16) +Position: 171764, codes: (165, 113) +Position: 171850, codes: (165, 16) +Position: 171874, codes: (165, 113) +Position: 171960, codes: (165, 16) +Position: 171984, codes: (165, 113) +Position: 172070, codes: (165, 16) +Position: 172094, codes: (165, 113) +Position: 172180, codes: (165, 16) +Position: 172204, codes: (165, 113) +Position: 172290, codes: (165, 16) +Position: 172314, codes: (165, 113) +Position: 172400, codes: (165, 16) +Position: 172424, codes: (165, 113) +Position: 172510, codes: (165, 16) +Position: 172534, codes: (165, 113) +Position: 172620, codes: (165, 16) +Position: 172644, codes: (165, 113) +Position: 172730, codes: (165, 16) +Position: 172754, codes: (165, 113) +Position: 172840, codes: (165, 16) +Position: 172864, codes: (165, 113) +Position: 172950, codes: (165, 16) +Position: 172974, codes: (165, 113) +Position: 173060, codes: (165, 16) +Position: 173084, codes: (165, 113) +Position: 173170, codes: (165, 16) +Position: 173194, codes: (165, 113) +Position: 173280, codes: (165, 16) +Position: 173304, codes: (165, 113) +Position: 173390, codes: (165, 16) +Position: 173414, codes: (165, 113) +Position: 173500, codes: (165, 16) +Position: 173524, codes: (165, 113) +Position: 173610, codes: (165, 16) +Position: 173634, codes: (165, 113) +Position: 173720, codes: (165, 16) +Position: 173744, codes: (165, 113) +Position: 173830, codes: (165, 16) +Position: 173854, codes: (165, 113) +Position: 173940, codes: (165, 16) +Position: 173964, codes: (165, 113) +Position: 174050, codes: (165, 16) +Position: 174074, codes: (165, 113) +Position: 174160, codes: (165, 16) +Position: 174184, codes: (165, 113) +Position: 174270, codes: (165, 16) +Position: 174294, codes: (165, 113) +Position: 174380, codes: (165, 16) +Position: 174404, codes: (165, 113) +Position: 174490, codes: (165, 16) +Position: 174514, codes: (165, 17) +Position: 174542, codes: (165, 113) +Position: 174628, codes: (165, 16) +Position: 174652, codes: (165, 113) +Position: 174738, codes: (165, 16) +Position: 174762, codes: (165, 113) +Position: 174848, codes: (165, 16) +Position: 174872, codes: (165, 113) +Position: 174958, codes: (165, 16) +Position: 174982, codes: (165, 113) +Position: 175068, codes: (165, 16) +Position: 175092, codes: (165, 113) +Position: 175178, codes: (165, 16) +Position: 175202, codes: (165, 113) +Position: 175288, codes: (165, 16) +Position: 175312, codes: (165, 113) +Position: 175398, codes: (165, 16) +Position: 175422, codes: (165, 113) +Position: 175508, codes: (165, 16) +Position: 175532, codes: (165, 113) +Position: 175618, codes: (165, 16) +Position: 175642, codes: (165, 113) +Position: 175728, codes: (165, 16) +Position: 175752, codes: (165, 113) +Position: 175838, codes: (165, 16) +Position: 175862, codes: (165, 113) +Position: 175948, codes: (165, 16) +Position: 175972, codes: (165, 113) +Position: 176058, codes: (165, 16) +Position: 176082, codes: (165, 113) +Position: 176168, codes: (165, 16) +Position: 176192, codes: (165, 113) +Position: 176278, codes: (165, 16) +Position: 176302, codes: (165, 113) +Position: 176388, codes: (165, 16) +Position: 176412, codes: (165, 113) +Position: 176498, codes: (165, 16) +Position: 176522, codes: (165, 113) +Position: 176608, codes: (165, 16) +Position: 176632, codes: (165, 113) +Position: 176718, codes: (165, 16) +Position: 176742, codes: (165, 113) +Position: 176828, codes: (165, 16) +Position: 176852, codes: (165, 113) +Position: 176938, codes: (165, 16) +Position: 176962, codes: (165, 113) +Position: 177048, codes: (165, 16) +Position: 177072, codes: (165, 113) +Position: 177158, codes: (165, 16) +Position: 177182, codes: (165, 113) +Position: 177268, codes: (165, 16) +Position: 177292, codes: (165, 113) +Position: 177378, codes: (165, 16) +Position: 177402, codes: (165, 113) +Position: 177488, codes: (165, 16) +Position: 177512, codes: (165, 113) +Position: 177598, codes: (165, 16) +Position: 177622, codes: (165, 113) +Position: 177708, codes: (165, 16) +Position: 177732, codes: (165, 113) +Position: 177818, codes: (165, 16) +Position: 177842, codes: (165, 113) +Position: 177928, codes: (165, 16) +Position: 177952, codes: (165, 113) +Position: 178038, codes: (165, 16) +Position: 178062, codes: (165, 113) +Position: 178148, codes: (165, 17) +p0=1764, pos=178174, i=49 +Init completed +Reading file ... +Position: 786, codes: (165, 18) +Reading vector header data (0x12) ping #0 @ 786... +Position: 828, codes: (165, 7) +Reading vector check data (0x07) ping #0 @ 828... +Position: 1738, codes: (165, 17) +Reading vector system data (0x11) ping #0 @ 1738... +Position: 1766, codes: (165, 16) +Reading vector velocity data (0x10) ping #0 @ 1766... +Position: 1790, codes: (165, 113) +Reading vector microstrain data (0x71) ping #0 @ 1790... +Position: 1876, codes: (165, 16) +Reading vector velocity data (0x10) ping #1 @ 1876... +Position: 1900, codes: (165, 113) +Reading vector microstrain data (0x71) ping #1 @ 1900... +Position: 1986, codes: (165, 16) +Reading vector velocity data (0x10) ping #2 @ 1986... +Position: 2010, codes: (165, 113) +Reading vector microstrain data (0x71) ping #2 @ 2010... +Position: 2096, codes: (165, 16) +Reading vector velocity data (0x10) ping #3 @ 2096... +Position: 2120, codes: (165, 113) +Reading vector microstrain data (0x71) ping #3 @ 2120... +Position: 2206, codes: (165, 16) +Reading vector velocity data (0x10) ping #4 @ 2206... +Position: 2230, codes: (165, 113) +Reading vector microstrain data (0x71) ping #4 @ 2230... +Position: 2316, codes: (165, 16) +Reading vector velocity data (0x10) ping #5 @ 2316... +Position: 2340, codes: (165, 113) +Reading vector microstrain data (0x71) ping #5 @ 2340... +Position: 2426, codes: (165, 16) +Reading vector velocity data (0x10) ping #6 @ 2426... +Position: 2450, codes: (165, 113) +Reading vector microstrain data (0x71) ping #6 @ 2450... +Position: 2536, codes: (165, 16) +Reading vector velocity data (0x10) ping #7 @ 2536... +Position: 2560, codes: (165, 113) +Reading vector microstrain data (0x71) ping #7 @ 2560... +Position: 2646, codes: (165, 16) +Reading vector velocity data (0x10) ping #8 @ 2646... +Position: 2670, codes: (165, 113) +Reading vector microstrain data (0x71) ping #8 @ 2670... +Position: 2756, codes: (165, 16) +Reading vector velocity data (0x10) ping #9 @ 2756... +Position: 2780, codes: (165, 113) +Reading vector microstrain data (0x71) ping #9 @ 2780... +Position: 2866, codes: (165, 17) +Reading vector system data (0x11) ping #10 @ 2866... +Position: 2894, codes: (165, 16) +Reading vector velocity data (0x10) ping #10 @ 2894... +Position: 2918, codes: (165, 113) +Reading vector microstrain data (0x71) ping #10 @ 2918... +Position: 3004, codes: (165, 16) +Reading vector velocity data (0x10) ping #11 @ 3004... +Position: 3028, codes: (165, 113) +Reading vector microstrain data (0x71) ping #11 @ 3028... +Position: 3114, codes: (165, 16) +Reading vector velocity data (0x10) ping #12 @ 3114... +Position: 3138, codes: (165, 113) +Reading vector microstrain data (0x71) ping #12 @ 3138... +Position: 3224, codes: (165, 16) +Reading vector velocity data (0x10) ping #13 @ 3224... +Position: 3248, codes: (165, 113) +Reading vector microstrain data (0x71) ping #13 @ 3248... +Position: 3334, codes: (165, 16) +Reading vector velocity data (0x10) ping #14 @ 3334... +Position: 3358, codes: (165, 113) +Reading vector microstrain data (0x71) ping #14 @ 3358... +Position: 3444, codes: (165, 16) +Reading vector velocity data (0x10) ping #15 @ 3444... +Position: 3468, codes: (165, 113) +Reading vector microstrain data (0x71) ping #15 @ 3468... +Position: 3554, codes: (165, 16) +Reading vector velocity data (0x10) ping #16 @ 3554... +Position: 3578, codes: (165, 113) +Reading vector microstrain data (0x71) ping #16 @ 3578... +Position: 3664, codes: (165, 16) +Reading vector velocity data (0x10) ping #17 @ 3664... +Position: 3688, codes: (165, 113) +Reading vector microstrain data (0x71) ping #17 @ 3688... +Position: 3774, codes: (165, 16) +Reading vector velocity data (0x10) ping #18 @ 3774... +Position: 3798, codes: (165, 113) +Reading vector microstrain data (0x71) ping #18 @ 3798... +Position: 3884, codes: (165, 16) +Reading vector velocity data (0x10) ping #19 @ 3884... +Position: 3908, codes: (165, 113) +Reading vector microstrain data (0x71) ping #19 @ 3908... +Position: 3994, codes: (165, 16) +Reading vector velocity data (0x10) ping #20 @ 3994... +Position: 4018, codes: (165, 113) +Reading vector microstrain data (0x71) ping #20 @ 4018... +Position: 4104, codes: (165, 16) +Reading vector velocity data (0x10) ping #21 @ 4104... +Position: 4128, codes: (165, 113) +Reading vector microstrain data (0x71) ping #21 @ 4128... +Position: 4214, codes: (165, 16) +Reading vector velocity data (0x10) ping #22 @ 4214... +Position: 4238, codes: (165, 113) +Reading vector microstrain data (0x71) ping #22 @ 4238... +Position: 4324, codes: (165, 16) +Reading vector velocity data (0x10) ping #23 @ 4324... +Position: 4348, codes: (165, 113) +Reading vector microstrain data (0x71) ping #23 @ 4348... +Position: 4434, codes: (165, 16) +Reading vector velocity data (0x10) ping #24 @ 4434... +Position: 4458, codes: (165, 113) +Reading vector microstrain data (0x71) ping #24 @ 4458... +Position: 4544, codes: (165, 16) +Reading vector velocity data (0x10) ping #25 @ 4544... +Position: 4568, codes: (165, 113) +Reading vector microstrain data (0x71) ping #25 @ 4568... +Position: 4654, codes: (165, 16) +Reading vector velocity data (0x10) ping #26 @ 4654... +Position: 4678, codes: (165, 113) +Reading vector microstrain data (0x71) ping #26 @ 4678... +Position: 4764, codes: (165, 16) +Reading vector velocity data (0x10) ping #27 @ 4764... +Position: 4788, codes: (165, 113) +Reading vector microstrain data (0x71) ping #27 @ 4788... +Position: 4874, codes: (165, 16) +Reading vector velocity data (0x10) ping #28 @ 4874... +Position: 4898, codes: (165, 113) +Reading vector microstrain data (0x71) ping #28 @ 4898... +Position: 4984, codes: (165, 16) +Reading vector velocity data (0x10) ping #29 @ 4984... +Position: 5008, codes: (165, 113) +Reading vector microstrain data (0x71) ping #29 @ 5008... +Position: 5094, codes: (165, 16) +Reading vector velocity data (0x10) ping #30 @ 5094... +Position: 5118, codes: (165, 113) +Reading vector microstrain data (0x71) ping #30 @ 5118... +Position: 5204, codes: (165, 16) +Reading vector velocity data (0x10) ping #31 @ 5204... +Position: 5228, codes: (165, 113) +Reading vector microstrain data (0x71) ping #31 @ 5228... +Position: 5314, codes: (165, 16) +Reading vector velocity data (0x10) ping #32 @ 5314... +Position: 5338, codes: (165, 113) +Reading vector microstrain data (0x71) ping #32 @ 5338... +Position: 5424, codes: (165, 16) +Reading vector velocity data (0x10) ping #33 @ 5424... +Position: 5448, codes: (165, 113) +Reading vector microstrain data (0x71) ping #33 @ 5448... +Position: 5534, codes: (165, 16) +Reading vector velocity data (0x10) ping #34 @ 5534... +Position: 5558, codes: (165, 113) +Reading vector microstrain data (0x71) ping #34 @ 5558... +Position: 5644, codes: (165, 16) +Reading vector velocity data (0x10) ping #35 @ 5644... +Position: 5668, codes: (165, 113) +Reading vector microstrain data (0x71) ping #35 @ 5668... +Position: 5754, codes: (165, 16) +Reading vector velocity data (0x10) ping #36 @ 5754... +Position: 5778, codes: (165, 113) +Reading vector microstrain data (0x71) ping #36 @ 5778... +Position: 5864, codes: (165, 16) +Reading vector velocity data (0x10) ping #37 @ 5864... +Position: 5888, codes: (165, 113) +Reading vector microstrain data (0x71) ping #37 @ 5888... +Position: 5974, codes: (165, 16) +Reading vector velocity data (0x10) ping #38 @ 5974... +Position: 5998, codes: (165, 113) +Reading vector microstrain data (0x71) ping #38 @ 5998... +Position: 6084, codes: (165, 16) +Reading vector velocity data (0x10) ping #39 @ 6084... +Position: 6108, codes: (165, 113) +Reading vector microstrain data (0x71) ping #39 @ 6108... +Position: 6194, codes: (165, 16) +Reading vector velocity data (0x10) ping #40 @ 6194... +Position: 6218, codes: (165, 113) +Reading vector microstrain data (0x71) ping #40 @ 6218... +Position: 6304, codes: (165, 16) +Reading vector velocity data (0x10) ping #41 @ 6304... +Position: 6328, codes: (165, 113) +Reading vector microstrain data (0x71) ping #41 @ 6328... +Position: 6414, codes: (165, 16) +Reading vector velocity data (0x10) ping #42 @ 6414... +Position: 6438, codes: (165, 17) +Reading vector system data (0x11) ping #43 @ 6438... +Position: 6466, codes: (165, 113) +Reading vector microstrain data (0x71) ping #42 @ 6466... +Position: 6552, codes: (165, 16) +Reading vector velocity data (0x10) ping #43 @ 6552... +Position: 6576, codes: (165, 113) +Reading vector microstrain data (0x71) ping #43 @ 6576... +Position: 6662, codes: (165, 16) +Reading vector velocity data (0x10) ping #44 @ 6662... +Position: 6686, codes: (165, 113) +Reading vector microstrain data (0x71) ping #44 @ 6686... +Position: 6772, codes: (165, 16) +Reading vector velocity data (0x10) ping #45 @ 6772... +Position: 6796, codes: (165, 113) +Reading vector microstrain data (0x71) ping #45 @ 6796... +Position: 6882, codes: (165, 16) +Reading vector velocity data (0x10) ping #46 @ 6882... +Position: 6906, codes: (165, 113) +Reading vector microstrain data (0x71) ping #46 @ 6906... +Position: 6992, codes: (165, 16) +Reading vector velocity data (0x10) ping #47 @ 6992... +Position: 7016, codes: (165, 113) +Reading vector microstrain data (0x71) ping #47 @ 7016... +Position: 7102, codes: (165, 16) +Reading vector velocity data (0x10) ping #48 @ 7102... +Position: 7126, codes: (165, 113) +Reading vector microstrain data (0x71) ping #48 @ 7126... +Position: 7212, codes: (165, 16) +Reading vector velocity data (0x10) ping #49 @ 7212... +Position: 7236, codes: (165, 113) +Reading vector microstrain data (0x71) ping #49 @ 7236... +Position: 7322, codes: (165, 16) +Reading vector velocity data (0x10) ping #50 @ 7322... +Position: 7346, codes: (165, 113) +Reading vector microstrain data (0x71) ping #50 @ 7346... +Position: 7432, codes: (165, 16) +Reading vector velocity data (0x10) ping #51 @ 7432... +Position: 7456, codes: (165, 113) +Reading vector microstrain data (0x71) ping #51 @ 7456... +Position: 7542, codes: (165, 16) +Reading vector velocity data (0x10) ping #52 @ 7542... +Position: 7566, codes: (165, 113) +Reading vector microstrain data (0x71) ping #52 @ 7566... +Position: 7652, codes: (165, 16) +Reading vector velocity data (0x10) ping #53 @ 7652... +Position: 7676, codes: (165, 113) +Reading vector microstrain data (0x71) ping #53 @ 7676... +Position: 7762, codes: (165, 16) +Reading vector velocity data (0x10) ping #54 @ 7762... +Position: 7786, codes: (165, 113) +Reading vector microstrain data (0x71) ping #54 @ 7786... +Position: 7872, codes: (165, 16) +Reading vector velocity data (0x10) ping #55 @ 7872... +Position: 7896, codes: (165, 113) +Reading vector microstrain data (0x71) ping #55 @ 7896... +Position: 7982, codes: (165, 16) +Reading vector velocity data (0x10) ping #56 @ 7982... +Position: 8006, codes: (165, 113) +Reading vector microstrain data (0x71) ping #56 @ 8006... +Position: 8092, codes: (165, 16) +Reading vector velocity data (0x10) ping #57 @ 8092... +Position: 8116, codes: (165, 113) +Reading vector microstrain data (0x71) ping #57 @ 8116... +Position: 8202, codes: (165, 16) +Reading vector velocity data (0x10) ping #58 @ 8202... +Position: 8226, codes: (165, 113) +Reading vector microstrain data (0x71) ping #58 @ 8226... +Position: 8312, codes: (165, 16) +Reading vector velocity data (0x10) ping #59 @ 8312... +Position: 8336, codes: (165, 113) +Reading vector microstrain data (0x71) ping #59 @ 8336... +Position: 8422, codes: (165, 16) +Reading vector velocity data (0x10) ping #60 @ 8422... +Position: 8446, codes: (165, 113) +Reading vector microstrain data (0x71) ping #60 @ 8446... +Position: 8532, codes: (165, 16) +Reading vector velocity data (0x10) ping #61 @ 8532... +Position: 8556, codes: (165, 113) +Reading vector microstrain data (0x71) ping #61 @ 8556... +Position: 8642, codes: (165, 16) +Reading vector velocity data (0x10) ping #62 @ 8642... +Position: 8666, codes: (165, 113) +Reading vector microstrain data (0x71) ping #62 @ 8666... +Position: 8752, codes: (165, 16) +Reading vector velocity data (0x10) ping #63 @ 8752... +Position: 8776, codes: (165, 113) +Reading vector microstrain data (0x71) ping #63 @ 8776... +Position: 8862, codes: (165, 16) +Reading vector velocity data (0x10) ping #64 @ 8862... +Position: 8886, codes: (165, 113) +Reading vector microstrain data (0x71) ping #64 @ 8886... +Position: 8972, codes: (165, 16) +Reading vector velocity data (0x10) ping #65 @ 8972... +Position: 8996, codes: (165, 113) +Reading vector microstrain data (0x71) ping #65 @ 8996... +Position: 9082, codes: (165, 16) +Reading vector velocity data (0x10) ping #66 @ 9082... +Position: 9106, codes: (165, 113) +Reading vector microstrain data (0x71) ping #66 @ 9106... +Position: 9192, codes: (165, 16) +Reading vector velocity data (0x10) ping #67 @ 9192... +Position: 9216, codes: (165, 113) +Reading vector microstrain data (0x71) ping #67 @ 9216... +Position: 9302, codes: (165, 16) +Reading vector velocity data (0x10) ping #68 @ 9302... +Position: 9326, codes: (165, 113) +Reading vector microstrain data (0x71) ping #68 @ 9326... +Position: 9412, codes: (165, 16) +Reading vector velocity data (0x10) ping #69 @ 9412... +Position: 9436, codes: (165, 113) +Reading vector microstrain data (0x71) ping #69 @ 9436... +Position: 9522, codes: (165, 16) +Reading vector velocity data (0x10) ping #70 @ 9522... +Position: 9546, codes: (165, 113) +Reading vector microstrain data (0x71) ping #70 @ 9546... +Position: 9632, codes: (165, 16) +Reading vector velocity data (0x10) ping #71 @ 9632... +Position: 9656, codes: (165, 113) +Reading vector microstrain data (0x71) ping #71 @ 9656... +Position: 9742, codes: (165, 16) +Reading vector velocity data (0x10) ping #72 @ 9742... +Position: 9766, codes: (165, 113) +Reading vector microstrain data (0x71) ping #72 @ 9766... +Position: 9852, codes: (165, 16) +Reading vector velocity data (0x10) ping #73 @ 9852... +Position: 9876, codes: (165, 113) +Reading vector microstrain data (0x71) ping #73 @ 9876... +Position: 9962, codes: (165, 16) +Reading vector velocity data (0x10) ping #74 @ 9962... +Position: 9986, codes: (165, 113) +Reading vector microstrain data (0x71) ping #74 @ 9986... +Position: 10072, codes: (165, 17) +Reading vector system data (0x11) ping #75 @ 10072... +Position: 10100, codes: (165, 16) +Reading vector velocity data (0x10) ping #75 @ 10100... +Position: 10124, codes: (165, 113) +Reading vector microstrain data (0x71) ping #75 @ 10124... +Position: 10210, codes: (165, 16) +Reading vector velocity data (0x10) ping #76 @ 10210... +Position: 10234, codes: (165, 113) +Reading vector microstrain data (0x71) ping #76 @ 10234... +Position: 10320, codes: (165, 16) +Reading vector velocity data (0x10) ping #77 @ 10320... +Position: 10344, codes: (165, 113) +Reading vector microstrain data (0x71) ping #77 @ 10344... +Position: 10430, codes: (165, 16) +Reading vector velocity data (0x10) ping #78 @ 10430... +Position: 10454, codes: (165, 113) +Reading vector microstrain data (0x71) ping #78 @ 10454... +Position: 10540, codes: (165, 16) +Reading vector velocity data (0x10) ping #79 @ 10540... +Position: 10564, codes: (165, 113) +Reading vector microstrain data (0x71) ping #79 @ 10564... +Position: 10650, codes: (165, 16) +Reading vector velocity data (0x10) ping #80 @ 10650... +Position: 10674, codes: (165, 113) +Reading vector microstrain data (0x71) ping #80 @ 10674... +Position: 10760, codes: (165, 16) +Reading vector velocity data (0x10) ping #81 @ 10760... +Position: 10784, codes: (165, 113) +Reading vector microstrain data (0x71) ping #81 @ 10784... +Position: 10870, codes: (165, 16) +Reading vector velocity data (0x10) ping #82 @ 10870... +Position: 10894, codes: (165, 113) +Reading vector microstrain data (0x71) ping #82 @ 10894... +Position: 10980, codes: (165, 16) +Reading vector velocity data (0x10) ping #83 @ 10980... +Position: 11004, codes: (165, 113) +Reading vector microstrain data (0x71) ping #83 @ 11004... +Position: 11090, codes: (165, 16) +Reading vector velocity data (0x10) ping #84 @ 11090... +Position: 11114, codes: (165, 113) +Reading vector microstrain data (0x71) ping #84 @ 11114... +Position: 11200, codes: (165, 16) +Reading vector velocity data (0x10) ping #85 @ 11200... +Position: 11224, codes: (165, 113) +Reading vector microstrain data (0x71) ping #85 @ 11224... +Position: 11310, codes: (165, 16) +Reading vector velocity data (0x10) ping #86 @ 11310... +Position: 11334, codes: (165, 113) +Reading vector microstrain data (0x71) ping #86 @ 11334... +Position: 11420, codes: (165, 16) +Reading vector velocity data (0x10) ping #87 @ 11420... +Position: 11444, codes: (165, 113) +Reading vector microstrain data (0x71) ping #87 @ 11444... +Position: 11530, codes: (165, 16) +Reading vector velocity data (0x10) ping #88 @ 11530... +Position: 11554, codes: (165, 113) +Reading vector microstrain data (0x71) ping #88 @ 11554... +Position: 11640, codes: (165, 16) +Reading vector velocity data (0x10) ping #89 @ 11640... +Position: 11664, codes: (165, 113) +Reading vector microstrain data (0x71) ping #89 @ 11664... +Position: 11750, codes: (165, 16) +Reading vector velocity data (0x10) ping #90 @ 11750... +Position: 11774, codes: (165, 113) +Reading vector microstrain data (0x71) ping #90 @ 11774... +Position: 11860, codes: (165, 16) +Reading vector velocity data (0x10) ping #91 @ 11860... +Position: 11884, codes: (165, 113) +Reading vector microstrain data (0x71) ping #91 @ 11884... +Position: 11970, codes: (165, 16) +Reading vector velocity data (0x10) ping #92 @ 11970... +Position: 11994, codes: (165, 113) +Reading vector microstrain data (0x71) ping #92 @ 11994... +Position: 12080, codes: (165, 16) +Reading vector velocity data (0x10) ping #93 @ 12080... +Position: 12104, codes: (165, 113) +Reading vector microstrain data (0x71) ping #93 @ 12104... +Position: 12190, codes: (165, 16) +Reading vector velocity data (0x10) ping #94 @ 12190... +Position: 12214, codes: (165, 113) +Reading vector microstrain data (0x71) ping #94 @ 12214... +Position: 12300, codes: (165, 16) +Reading vector velocity data (0x10) ping #95 @ 12300... +Position: 12324, codes: (165, 113) +Reading vector microstrain data (0x71) ping #95 @ 12324... +Position: 12410, codes: (165, 16) +Reading vector velocity data (0x10) ping #96 @ 12410... +Position: 12434, codes: (165, 113) +Reading vector microstrain data (0x71) ping #96 @ 12434... +Position: 12520, codes: (165, 16) +Reading vector velocity data (0x10) ping #97 @ 12520... +Position: 12544, codes: (165, 113) +Reading vector microstrain data (0x71) ping #97 @ 12544... +Position: 12630, codes: (165, 16) +Reading vector velocity data (0x10) ping #98 @ 12630... +Position: 12654, codes: (165, 113) +Reading vector microstrain data (0x71) ping #98 @ 12654... +Position: 12740, codes: (165, 16) +Reading vector velocity data (0x10) ping #99 @ 12740... +Position: 12764, codes: (165, 113) +Reading vector microstrain data (0x71) ping #99 @ 12764... + stopped at 12848 bytes. diff --git a/examples/data/dolfyn/test_data/vector_data01.nc b/examples/data/dolfyn/test_data/vector_data01.nc new file mode 100644 index 000000000..cf258d6ff Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data01.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data01_rotate_earth2principal.nc b/examples/data/dolfyn/test_data/vector_data01_rotate_earth2principal.nc new file mode 100644 index 000000000..1e6797197 Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data01_rotate_earth2principal.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data01_rotate_inst2beam.nc b/examples/data/dolfyn/test_data/vector_data01_rotate_inst2beam.nc new file mode 100644 index 000000000..3d5a8d008 Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data01_rotate_inst2beam.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data01_rotate_inst2earth.nc b/examples/data/dolfyn/test_data/vector_data01_rotate_inst2earth.nc new file mode 100644 index 000000000..a9ae5567f Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data01_rotate_inst2earth.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data_imu01-json.nc b/examples/data/dolfyn/test_data/vector_data_imu01-json.nc new file mode 100644 index 000000000..879bc45e6 Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data_imu01-json.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data_imu01.nc b/examples/data/dolfyn/test_data/vector_data_imu01.nc new file mode 100644 index 000000000..649475b42 Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data_imu01.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data_imu01_head_pitch_roll.nc b/examples/data/dolfyn/test_data/vector_data_imu01_head_pitch_roll.nc new file mode 100644 index 000000000..154558ba0 Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data_imu01_head_pitch_roll.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data_imu01_rotate_earth2principal.nc b/examples/data/dolfyn/test_data/vector_data_imu01_rotate_earth2principal.nc new file mode 100644 index 000000000..ee1e22e26 Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data_imu01_rotate_earth2principal.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data_imu01_rotate_inst2beam.nc b/examples/data/dolfyn/test_data/vector_data_imu01_rotate_inst2beam.nc new file mode 100644 index 000000000..54ebc89cc Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data_imu01_rotate_inst2beam.nc differ diff --git a/examples/data/dolfyn/test_data/vector_data_imu01_rotate_inst2earth.nc b/examples/data/dolfyn/test_data/vector_data_imu01_rotate_inst2earth.nc new file mode 100644 index 000000000..fd82d68c2 Binary files /dev/null and b/examples/data/dolfyn/test_data/vector_data_imu01_rotate_inst2earth.nc differ diff --git a/examples/data/dolfyn/test_data/vmdas01.nc b/examples/data/dolfyn/test_data/vmdas01.nc new file mode 100644 index 000000000..4314ea4d9 Binary files /dev/null and b/examples/data/dolfyn/test_data/vmdas01.nc differ diff --git a/examples/data/dolfyn/test_data/winriver01.nc b/examples/data/dolfyn/test_data/winriver01.nc new file mode 100644 index 000000000..804a5376a Binary files /dev/null and b/examples/data/dolfyn/test_data/winriver01.nc differ diff --git a/examples/data/dolfyn/test_data/winriver02.nc b/examples/data/dolfyn/test_data/winriver02.nc new file mode 100644 index 000000000..998b1149f Binary files /dev/null and b/examples/data/dolfyn/test_data/winriver02.nc differ diff --git a/examples/data/dolfyn/test_data/winriver02_rotate_ship2earth.nc b/examples/data/dolfyn/test_data/winriver02_rotate_ship2earth.nc new file mode 100644 index 000000000..0007005fc Binary files /dev/null and b/examples/data/dolfyn/test_data/winriver02_rotate_ship2earth.nc differ diff --git a/examples/data/dolfyn/vector_data01.VEC b/examples/data/dolfyn/vector_data01.VEC new file mode 100644 index 000000000..bbb3e5675 Binary files /dev/null and b/examples/data/dolfyn/vector_data01.VEC differ diff --git a/examples/data/dolfyn/vector_data_imu01.VEC b/examples/data/dolfyn/vector_data_imu01.VEC new file mode 100644 index 000000000..d5e4b0d3f Binary files /dev/null and b/examples/data/dolfyn/vector_data_imu01.VEC differ diff --git a/examples/data/dolfyn/vector_data_imu01.userdata.json b/examples/data/dolfyn/vector_data_imu01.userdata.json new file mode 100644 index 000000000..67f024a9c --- /dev/null +++ b/examples/data/dolfyn/vector_data_imu01.userdata.json @@ -0,0 +1,5 @@ +{"inst2head_rotmat": "identity", + "inst2head_vec": [-1.0, 0.5, 0.2], + "motion accel_filtfreq Hz": 0.03, + "declination": 10.0 +} diff --git a/examples/data/dolfyn/vmdas01.ENX b/examples/data/dolfyn/vmdas01.ENX new file mode 100644 index 000000000..e739904ca Binary files /dev/null and b/examples/data/dolfyn/vmdas01.ENX differ diff --git a/examples/data/dolfyn/vmdas01.userdata.json b/examples/data/dolfyn/vmdas01.userdata.json new file mode 100644 index 000000000..1eee64d98 --- /dev/null +++ b/examples/data/dolfyn/vmdas01.userdata.json @@ -0,0 +1,2 @@ +{"declination": 15.83 +} \ No newline at end of file diff --git a/examples/data/dolfyn/winriver01.PD0 b/examples/data/dolfyn/winriver01.PD0 new file mode 100644 index 000000000..2210ff5f1 Binary files /dev/null and b/examples/data/dolfyn/winriver01.PD0 differ diff --git a/examples/data/dolfyn/winriver01.userdata.json b/examples/data/dolfyn/winriver01.userdata.json new file mode 100644 index 000000000..7f53fe6ae --- /dev/null +++ b/examples/data/dolfyn/winriver01.userdata.json @@ -0,0 +1,3 @@ +{"declination": 8.28, + "latlon": [39.9402, -105.2283] +} diff --git a/examples/data/dolfyn/winriver02.PD0 b/examples/data/dolfyn/winriver02.PD0 new file mode 100644 index 000000000..b9f826926 Binary files /dev/null and b/examples/data/dolfyn/winriver02.PD0 differ diff --git a/mhkit/__init__.py b/mhkit/__init__.py index ba48b24ad..1900dbb91 100644 --- a/mhkit/__init__.py +++ b/mhkit/__init__.py @@ -5,6 +5,7 @@ from mhkit import utils from mhkit import power from mhkit import loads +from mhkit import dolfyn # Register datetime converter for a matplotlib plotting methods from pandas.plotting import register_matplotlib_converters as _rmc diff --git a/mhkit/dolfyn/__init__.py b/mhkit/dolfyn/__init__.py new file mode 100644 index 000000000..bc8b7a76f --- /dev/null +++ b/mhkit/dolfyn/__init__.py @@ -0,0 +1,3 @@ +from mhkit.dolfyn.io.api import read, read_example, save, load, save_mat, load_mat +from mhkit.dolfyn.rotate.api import rotate2, calc_principal_heading, set_declination +from mhkit.dolfyn import time diff --git a/mhkit/dolfyn/io/_read_bin.py b/mhkit/dolfyn/io/_read_bin.py new file mode 100644 index 000000000..a6b847b9f --- /dev/null +++ b/mhkit/dolfyn/io/_read_bin.py @@ -0,0 +1,181 @@ +import numpy as np +from struct import unpack +from os.path import expanduser + + +class bin_reader(): + """ + Reads binary data files. It is mostly for development purposes, to + simplify learning a data file's format. Reading binary data files should + minimize the number of calls to struct.unpack and file.read because many + calls to these functions (i.e. using the code in this module) are slow. + """ + _size_factor = {'B': 1, 'b': 1, 'H': 2, + 'h': 2, 'L': 4, 'l': 4, 'f': 4, 'd': 8} + _frmt = {np.uint8: 'B', np.int8: 'b', + np.uint16: 'H', np.int16: 'h', + np.uint32: 'L', np.int32: 'l', + float: 'f', np.float32: 'f', + np.double: 'd', np.float64: 'd', + } + + @property + def pos(self,): + return self.f.tell() + + def __enter__(self,): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def __init__(self, fname, endian='<', checksum_size=None, debug_level=0): + """ + Default to little-endian '<'... + *checksum_size* is in bytes, if it is None or False, this + function does not perform checksums. + """ + self.endian = endian + self.f = open(expanduser(fname), 'rb') + self.f.seek(0, 2) + self.fsize = self.tell() + self.f.seek(0, 0) + self.close = self.f.close + if checksum_size: + pass + #self.cs = checksum(self, 0, checksum_size) + else: + self.cs = checksum_size + self.debug_level = debug_level + + # def checksum(self,): + # """ + # The next byte(s) are the expected checksum. Perform the checksum. + # """ + # if self.cs: + # cs = self.read(1, self.cs._frmt) + # self.cs(cs, True) + # else: + # raise Exception('CheckSum not requested for this file') + + def tell(self,): + return self.f.tell() + + def seek(self, pos, rel=1): + return self.f.seek(pos, rel) + + def reads(self, n): + """ + Read a string of n characters. + """ + val = self.f.read(n) + self.cs and self.cs.add(val) + try: + val = val.decode('utf-8') + except: + if self.debug_level > 5: + print("ERROR DECODING: {}".format(val)) + pass + return val + + def read(self, n, frmt): + val = self.f.read(n * self._size_factor[frmt]) + if not val: # If val is empty we are at the end of the file. + raise Exception('End of file') + self.cs and self.cs.add(val) + if n == 1: + return unpack(self.endian + frmt * n, val)[0] + else: + return np.array(unpack(self.endian + frmt * n, val)) + + def read_ui8(self, n): + return self.read(n, 'B') + + def read_float(self, n): + return self.read(n, 'f') + + def read_double(self, n): + return self.read(n, 'd') + + read_f32 = read_float + read_f64 = read_double + + def read_i8(self, n): + return self.read(n, 'b') + + def read_ui16(self, n): + return self.read(n, 'H') + + def read_i16(self, n): + return self.read(n, 'h') + + def read_ui32(self, n): + return self.read(n, 'L') + + def read_i32(self, n): + return self.read(n, 'l') + +#ics = 0 # This is a holder for the checksum index +# class checksum(): +# # Checksum for TRDI +# def __init__(self, file, val, size, error_behavior='exception'): +# """ +# Value *val* to initialize the checksum with, +# and the *size* of the checksum (in bytes, currently this can only be 1,2,4 or 8). +# """ +# self.file = file +# self.init(val, size, error_behavior) + +# def init(self, val, size, error_behavior='exception'): +# self._cs = val +# self._size = size +# self._rem = '' +# self._frmt = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}[size] +# self._mask = (2 ** (8 * size) - 1) +# ## I'd like to add different behavior for the checksum +# self._error_behavior = error_behavior + +# def error(self, val): +# if val == 'rem': +# message = 'A remainder exists in the checksum.' +# else: +# message = 'Checksum failed at %d, with a difference of %d.' % ( +# self.file.tell(), val) +# if self._error_behavior == 'warning': +# print('Warning: ' + message) +# elif self._error_behavior == 'silent': +# pass +# else: +# raise Exception(message) + +# def __call__(self, val, remove_val=False): +# """ +# Compare the checksum to *val*. +# *remove_val* specifies whether *val* should be removed from +# self._cs because it was already added to it. +# """ +# if self._rem: +# self.error('rem') +# retval = (self._cs - val * remove_val & self._mask) - val +# if retval: +# self.error(retval) +# return retval # returns zero if there is no remainder + +# def add(self, valcs): +# """ +# Add the data in *valcs* to the checksum. +# """ +# if self._rem: # If the cs remainder is not empty: +# lr = self._rem.__len__() +# ics = self._size - lr +# self._rem, valcs = self._rem + valcs[:ics], valcs[ics:] +# if lr == self._size: +# self._cs += unpack(self.file.endian + self._frmt, self._rem)[0] +# self._rem = '' +# if valcs: # If valcs is not empty: +# ics = (valcs.__len__() / self._size) * self._size +# for v in unpack(self.file.endian + self._frmt * (ics / self._size), valcs[:ics]): +# self._cs += v +# self._rem += valcs[ics:] + +# __iadd__ = add diff --git a/mhkit/dolfyn/io/api.py b/mhkit/dolfyn/io/api.py new file mode 100644 index 000000000..a06859f09 --- /dev/null +++ b/mhkit/dolfyn/io/api.py @@ -0,0 +1,315 @@ +import numpy as np +import scipy.io as sio +import xarray as xr +from os.path import abspath, dirname, join, normpath, relpath +from .nortek import read_nortek +from .nortek2 import read_signature +from .rdi import read_rdi +from .base import _create_dataset, _get_filetype +from ..rotate.base import _set_coords +from ..time import date2matlab, matlab2date, date2dt64, dt642date, date2epoch, epoch2date + + +def _check_file_ext(path, ext): + filename = path.replace("\\", "/").rsplit("/")[-1] # windows/linux + # for a filename like mcrl.water_velocity-1s.b1.20200813.150000.nc + file_ext = filename.rsplit(".")[-1] + if '.' in filename: + if file_ext != ext: + raise IOError("File extension must be of the type {}".format(ext)) + if file_ext == ext: + return path + + return path + '.' + ext + + +def read(fname, userdata=True, nens=None): + """Read a binary Nortek (e.g., .VEC, .wpr, .ad2cp, etc.) or RDI + (.000, .PD0, .ENX, etc.) data file. + + Parameters + ---------- + filename : string + Filename of instrument file to read. + userdata : True, False, or string of userdata.json filename (default ``True``) + Whether to read the '.userdata.json' file. + nens : None (default: read entire file), int, or 2-element tuple (start, stop) + Number of pings or ensembles to read from the file + + Returns + ------- + ds : xarray.Dataset + An xarray dataset from instrument datafile. + + """ + file_type = _get_filetype(fname) + if file_type == '': + raise IOError("File '{}' looks like a git-lfs pointer. You may need to " + "install and initialize git-lfs. See https://git-lfs.github.com" + " for details.".format(fname)) + elif file_type is None: + raise IOError("File '{}' is not recognized as a file-type that is readable by " + "DOLfYN. If you think it should be readable, try using the " + "appropriate read function (`read_rdi`, `read_nortek`, or " + "`read_signature`) found in dolfyn.io.api.".format(fname)) + else: + func_map = dict(RDI=read_rdi, + nortek=read_nortek, + signature=read_signature) + func = func_map[file_type] + return func(fname, userdata=userdata, nens=nens) + + +def read_example(name, **kwargs): + """Read an ADCP or ADV datafile from the examples directory. + + Parameters + ---------- + name : str + A few available files: + + AWAC_test01.wpr + BenchFile01.ad2cp + RDI_test01.000 + burst_mode01.VEC + vector_data01.VEC + vector_data_imu01.VEC + winriver01.PD0 + winriver02.PD0 + + Returns + ------- + ds : xarray.Dataset + An xarray dataset from the binary instrument data. + + """ + testdir = dirname(abspath(__file__)) + exdir = normpath(join(testdir, relpath('../../../examples/data/dolfyn/'))) + filename = exdir + '/' + name + + return read(filename, **kwargs) + + +def save(ds, filename, + format='NETCDF4', engine='netcdf4', + compression=False, + **kwargs): + """Save xarray dataset as netCDF (.nc). + + Parameters + ---------- + ds : xarray.Dataset + filename : str + Filename and/or path with the '.nc' extension + compression : bool (default: False) + When true, compress all variables with zlib complevel=1. + **kwargs : these are passed directly to :func:`xarray.Dataset.to_netcdf` + + Notes + ----- + Drops 'config' lines. + + More detailed compression options can be specified by specifying + 'encoding' in kwargs. The values in encoding will take precedence + over whatever is set according to the compression option above. + See the xarray.to_netcdf documentation for more details. + + """ + filename = _check_file_ext(filename, 'nc') + + # Dropping the detailed configuration stats because netcdf can't save it + for key in list(ds.attrs.keys()): + if 'config' in key: + ds.attrs.pop(key) + + # Handling complex values for netCDF4 + ds.attrs['complex_vars'] = [] + for var in ds.data_vars: + if np.iscomplexobj(ds[var]): + ds[var+'_real'] = ds[var].real + ds[var+'_imag'] = ds[var].imag + + ds = ds.drop_vars(var) + ds.attrs['complex_vars'].append(var) + + if compression: + enc = dict() + for ky in ds.variables: + enc[ky] = dict(zlib=True, complevel=1) + if 'encoding' in kwargs: + # Overwrite ('update') values in enc with whatever is in kwargs['encoding'] + enc.update(kwargs['encoding']) + else: + kwargs['encoding'] = enc + + ds.to_netcdf(filename, format=format, engine=engine, **kwargs) + + +def load(filename): + """Load xarray dataset from netCDF (.nc) + + Parameters + ---------- + filename : str + Filename and/or path with the '.nc' extension + + Returns + ------- + ds : xarray.Dataset + An xarray dataset from the binary instrument data. + + """ + filename = _check_file_ext(filename, 'nc') + + ds = xr.load_dataset(filename, engine='netcdf4') + + # Convert numpy arrays and strings back to lists + for nm in ds.attrs: + if type(ds.attrs[nm]) == np.ndarray and ds.attrs[nm].size > 1: + ds.attrs[nm] = list(ds.attrs[nm]) + elif type(ds.attrs[nm]) == str and nm in ['rotate_vars']: + ds.attrs[nm] = [ds.attrs[nm]] + + # Rejoin complex numbers + if hasattr(ds, 'complex_vars') and len(ds.complex_vars): + if len(ds.complex_vars[0]) == 1: + ds.attrs['complex_vars'] = [ds.complex_vars] + for var in ds.complex_vars: + ds[var] = ds[var+'_real'] + ds[var+'_imag'] * 1j + ds = ds.drop_vars([var+'_real', var+'_imag']) + ds.attrs.pop('complex_vars') + + return ds + + +def save_mat(ds, filename, datenum=True): + """Save xarray dataset as a MATLAB (.mat) file + + Parameters + ---------- + ds : xarray.Dataset + Data to save + filename : str + Filename and/or path with the '.mat' extension + datenum : bool + If true, converts time to datenum. If false, time will be saved + in "epoch time". + + Notes + ----- + The xarray data format is saved as a MATLAB structure with the fields + 'vars, coords, config, units'. + + See Also + -------- + scipy.io.savemat() + + """ + filename = _check_file_ext(filename, 'mat') + + # Convert time to datenum + t_coords = [t for t in ds.coords if np.issubdtype( + ds[t].dtype, np.datetime64)] + t_data = [t for t in ds.data_vars if np.issubdtype( + ds[t].dtype, np.datetime64)] + + if datenum: + func = date2matlab + else: + func = date2epoch + + for ky in t_coords: + dt = func(dt642date(ds[ky])) + ds = ds.assign_coords({ky: dt}) + for ky in t_data: + dt = func(dt642date(ds[ky])) + ds[ky].data = dt + + ds.attrs['time_coords'] = t_coords + ds.attrs['time_data_vars'] = t_data + + # Save xarray structure with more descriptive structure names + matfile = {'vars': {}, 'coords': {}, 'config': {}, 'units': {}} + for key in ds.data_vars: + matfile['vars'][key] = ds[key].values + if hasattr(ds[key], 'units'): + matfile['units'][key] = ds[key].units + for key in ds.coords: + matfile['coords'][key] = ds[key].values + matfile['config'] = ds.attrs + + sio.savemat(filename, matfile) + + +def load_mat(filename, datenum=True): + """Load xarray dataset from MATLAB (.mat) file, complimentary to `save_mat()` + + A .mat file must contain the fields: {vars, coords, config, units}, + where 'coords' contain the dimensions of all variables in 'vars'. + + Parameters + ---------- + filename : str + Filename and/or path with the '.mat' extension + datenum : bool + If true, converts time from datenum. If false, converts time from + "epoch time". + + Returns + ------- + ds : xarray.Dataset + An xarray dataset from the binary instrument data. + + See Also + -------- + scipy.io.loadmat() + + """ + filename = _check_file_ext(filename, 'mat') + + data = sio.loadmat(filename, struct_as_record=False, squeeze_me=True) + + ds_dict = {'vars': {}, 'coords': {}, 'config': {}, 'units': {}} + for nm in ds_dict: + key_list = data[nm]._fieldnames + for ky in key_list: + ds_dict[nm][ky] = getattr(data[nm], ky) + + ds_dict['data_vars'] = ds_dict.pop('vars') + ds_dict['attrs'] = ds_dict.pop('config') + + # Recreate dataset + ds = _create_dataset(ds_dict) + ds = _set_coords(ds, ds.coord_sys) + + # Convert numpy arrays and strings back to lists + for nm in ds.attrs: + if type(ds.attrs[nm]) == np.ndarray and ds.attrs[nm].size > 1: + try: + ds.attrs[nm] = [x.strip(' ') for x in list(ds.attrs[nm])] + except: + ds.attrs[nm] = list(ds.attrs[nm]) + elif type(ds.attrs[nm]) == str and nm in ['time_coords', 'time_data_vars', 'rotate_vars']: + ds.attrs[nm] = [ds.attrs[nm]] + + if hasattr(ds, 'orientation_down'): + ds['orientation_down'] = ds['orientation_down'].astype(bool) + + if datenum: + func = matlab2date + else: + func = epoch2date + + # Restore datnum to np.dt64 + if hasattr(ds, 'time_coords'): + for ky in ds.attrs['time_coords']: + dt = date2dt64(func(ds[ky].values)) + ds = ds.assign_coords({ky: dt}) + ds.attrs.pop('time_coords') + if hasattr(ds, 'time_data_vars'): + for ky in ds.attrs['time_data_vars']: + dt = date2dt64(func(ds[ky].values)) + ds[ky].data = dt + ds.attrs.pop('time_data_vars') + + return ds diff --git a/mhkit/dolfyn/io/base.py b/mhkit/dolfyn/io/base.py new file mode 100644 index 000000000..058dbc314 --- /dev/null +++ b/mhkit/dolfyn/io/base.py @@ -0,0 +1,230 @@ +import numpy as np +import xarray as xr +import json +import os +import warnings + + +def _abspath(fname): + return os.path.abspath(os.path.expanduser(fname)) + + +def _get_filetype(fname): + """Detects whether the file is a Nortek, Signature (Nortek), or RDI + file by reading the first few bytes of the file. + + Returns + ======= + None - Doesn't match any known pattern + 'signature' - for Nortek signature files + 'nortek' - for Nortek (Vec, AWAC) files + 'RDI' - for RDI files + ' - if the file looks like a GIT-LFS pointer. + """ + + with open(fname, 'rb') as rdr: + bytes = rdr.read(40) + code = bytes[:2].hex() + if code in ['7f79', '7f7f']: + return 'RDI' + elif code in ['a50a']: + return 'signature' + elif code in ['a505']: + # AWAC + return 'nortek' + elif bytes == b'version https://git-lfs.github.com/spec/': + return '' + else: + return None + + +def _find_userdata(filename, userdata=True): + # This function finds the file to read + if userdata: + for basefile in [filename.rsplit('.', 1)[0], + filename]: + jsonfile = basefile + '.userdata.json' + if os.path.isfile(jsonfile): + return _read_userdata(jsonfile) + + elif isinstance(userdata, (str, )) or hasattr(userdata, 'read'): + return _read_userdata(userdata) + return {} + + +def _read_userdata(fname): + """Reads a userdata.json file and returns the data it contains as a + dictionary. + """ + with open(fname) as data_file: + data = json.load(data_file) + for nm in ['body2head_rotmat', 'body2head_vec']: + if nm in data: + new_name = 'inst' + nm[4:] + warnings.warn( + f'{nm} has been deprecated, please change this to {new_name} \ + in {fname}.') + data[new_name] = data.pop(nm) + if 'inst2head_rotmat' in data: + if data['inst2head_rotmat'] in ['identity', 'eye', 1, 1.]: + data['inst2head_rotmat'] = np.eye(3) + else: + data['inst2head_rotmat'] = np.array(data['inst2head_rotmat']) + if 'inst2head_vec' in data and type(data['inst2head_vec']) != list: + data['inst2head_vec'] = list(data['inst2head_vec']) + + return data + + +def _handle_nan(data): + """Finds nan's that cause issues in running the rotation algorithms + and deletes them. + """ + nan = np.zeros(data['coords']['time'].shape, dtype=bool) + l = data['coords']['time'].size + + if any(np.isnan(data['coords']['time'])): + nan += np.isnan(data['coords']['time']) + + var = ['accel', 'angrt', 'mag'] + for key in data['data_vars']: + if any(val in key for val in var): + shp = data['data_vars'][key].shape + if shp[-1] == l: + if len(shp) == 1: + if any(np.isnan(data['data_vars'][key])): + nan += np.isnan(data['data_vars'][key]) + elif len(shp) == 2: + if any(np.isnan(data['data_vars'][key][-1])): + nan += np.isnan(data['data_vars'][key][-1]) + + if nan.sum() > 0: + data['coords']['time'] = data['coords']['time'][~nan] + for key in data['data_vars']: + if data['data_vars'][key].shape[-1] == l: + data['data_vars'][key] = data['data_vars'][key][..., ~nan] + return data + + +def _create_dataset(data): + """Creates an xarray dataset from dictionary created from binary + readers. + Direction 'dir' coordinates get reset in `set_coords` + """ + ds = xr.Dataset() + inst = ['X', 'Y', 'Z'] + earth = ['E', 'N', 'U'] + beam = list(range(1, data['data_vars']['vel'].shape[0]+1)) + tag = ['_b5', '_echo', '_bt', '_gps', '_ast'] + + 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={'x': beam, + 'x*': beam}, + dims=['x', 'x*']) + else: # earth2inst orientation matrix + if any(val in key for val in tag): + tg = '_' + key.rsplit('_')[-1] + else: + tg = '' + time = data['coords']['time'+tg] + coords = {'earth': earth, 'inst': inst, 'time'+tg: time} + dims = ['earth', 'inst', 'time'+tg] + ds[key] = xr.DataArray(data['data_vars'][key], coords, dims) + + # quaternion units never change + elif 'quaternion' in key: + if any(val in key for val in tag): + tg = '_' + key.rsplit('_')[-1] + else: + tg = '' + ds[key] = xr.DataArray(data['data_vars'][key], + coords={'q': ['w', 'x', 'y', 'z'], + 'time'+tg: data['coords']['time'+tg]}, + dims=['q', 'time'+tg]) + else: + ds[key] = xr.DataArray(data['data_vars'][key]) + if key in data['units']: # not all variables have units + ds[key].attrs['units'] = data['units'][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['units'] = data['units'][key[:-len(tg)]] + except: + pass + + shp = data['data_vars'][key].shape + vshp = data['data_vars']['vel'].shape + l = len(shp) + if l == 1: # 1D variables + if any(val in key for val in tag): + 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']}) + # 3- & 4-beam instrument vector data, bottom tracking + elif shp[0] == vshp[0] and not any(val in key for val in tag[:2]): + # b/c rdi time + if 'bt' in key and 'time_bt' in data['coords']: + tg = '_bt' + else: + tg = '' + ds[key] = ds[key].rename({'dim_0': 'dir', + 'dim_1': 'time'+tg}) + ds[key] = ds[key].assign_coords({'dir': beam, + 'time'+tg: data['coords']['time'+tg]}) + # 4-beam instrument IMU data + elif shp[0] == vshp[0]-1: + if not any(val in key for val in tag): + tg = '' + else: + tg = [val for val in tag if val in key] + tg = tg[0] + + ds[key] = ds[key].rename({'dim_0': 'dirIMU', + 'dim_1': 'time'+tg}) + ds[key] = ds[key].assign_coords({'dirIMU': [1, 2, 3], + 'time'+tg: data['coords']['time'+tg]}) + + elif l == 3: # 3D variables + if not any(val in key for val in tag): + if 'vel' in key: + dim0 = 'dir' + else: # amp, corr + dim0 = 'beam' + ds[key] = ds[key].rename({'dim_0': dim0, + 'dim_1': 'range', + 'dim_2': 'time'}) + ds[key] = ds[key].assign_coords({dim0: beam, + 'range': data['coords']['range'], + 'time': data['coords']['time']}) + 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']}) + else: + warnings.warn(f'Variable not included in dataset: {key}') + + # coordinate units + r_list = [r for r in ds.coords if 'range' in r] + for ky in r_list: + ds[ky].attrs['units'] = 'm' + + ds.attrs = data['attrs'] + + return ds diff --git a/mhkit/dolfyn/io/nortek.py b/mhkit/dolfyn/io/nortek.py new file mode 100644 index 000000000..446ca4b21 --- /dev/null +++ b/mhkit/dolfyn/io/nortek.py @@ -0,0 +1,986 @@ +import numpy as np +import xarray as xr +from struct import unpack +import warnings +from . import nortek_defs +from .base import _find_userdata, _create_dataset, _handle_nan, _abspath +from .. import time +from datetime import datetime +from ..tools import misc as tbx +from ..rotate.vector import _calc_omat +from ..rotate.base import _set_coords +from ..rotate import api as rot + + +def read_nortek(filename, userdata=True, debug=False, do_checksum=False, + nens=None): + """Read a classic Nortek (AWAC and Vector) datafile + + Parameters + ---------- + filename : string + Filename of Nortek file to read. + userdata : True, False, or string of userdata.json filename + (default ``True``) Whether to read the '.userdata.json' + file. + do_checksum : bool (default False) + Whether to perform the checksum of each data block. + nens : None (default: read entire file), int, or 2-element tuple (start, stop) + Number of pings to read from the file + + Returns + ------- + ds : xarray.Dataset + An xarray dataset from the binary instrument data + + """ + userdata = _find_userdata(filename, userdata) + + with _NortekReader(filename, debug=debug, do_checksum=do_checksum, + nens=nens) as rdr: + rdr.readfile() + rdr.dat2sci() + dat = rdr.data + + rotmat = None + declin = None + for nm in userdata: + if 'rotmat' in nm: + rotmat = userdata[nm] + elif 'dec' in nm: + declin = userdata[nm] + else: + dat['attrs'][nm] = userdata[nm] + + # NaN in time and orientation data + dat = _handle_nan(dat) + + # Create xarray dataset from upper level dictionary + ds = _create_dataset(dat) + ds = _set_coords(ds, ref_frame=ds.coord_sys) + + if 'orientmat' not in ds: + omat = _calc_omat(ds['heading'].values, + ds['pitch'].values, + ds['roll'].values, + ds.get('orientation_down', None)) + ds['orientmat'] = xr.DataArray(omat, + coords={'earth': ['E', 'N', 'U'], + 'inst': ['X', 'Y', 'Z'], + 'time': ds['time']}, + dims=['earth', 'inst', 'time']) + if rotmat is not None: + rot.set_inst2head_rotmat(ds, rotmat, inplace=True) + if declin is not None: + rot.set_declination(ds, declin, inplace=True) + + ds['time'] = time.epoch2dt64(ds['time']).astype('datetime64[us]') + + return ds + + +def _bcd2char(cBCD): + """ + Taken from the Nortek System Integrator + Manual "Example Program" Chapter. + """ + cBCD = min(cBCD, 153) + c = (cBCD & 15) + c += 10 * (cBCD >> 4) + return c + + +def _bitshift8(val): + return val >> 8 + + +def _int2binarray(val, n): + out = np.zeros(n, dtype='bool') + for idx, n in enumerate(range(n)): + out[idx] = val & (2 ** n) + return out + + +class _NortekReader(): + """A class for reading reading nortek binary files. + This reader currently only supports AWAC and Vector data formats. + + Parameters + ---------- + fname : string + Nortek file filename to read. + endian : {'<','>'} (optional) + Specifies if the file is in 'little' or 'big' endian format. By + default the reader will attempt to determine this. + debug : {True, False*} (optional) + Print debug/progress information? + do_checksum : {True*, False} (optional) + Specifies whether to perform the checksum. + bufsize : int (default 100000) + The size of the read buffer to use. + nens : None (default: None, read all files), int, or 2-element tuple (start, stop). + The number of pings to read from the file. By default, the entire file + is read. + + """ + _lastread = [None, None, None, None, None] + fun_map = {'0x00': 'read_user_cfg', + '0x04': 'read_head_cfg', + '0x05': 'read_hw_cfg', + '0x07': 'read_vec_checkdata', + '0x10': 'read_vec_data', + '0x11': 'read_vec_sysdata', + '0x12': 'read_vec_hdr', + '0x71': 'read_microstrain', + '0x20': 'read_awac_profile', + } + + def __init__(self, fname, endian=None, debug=False, + do_checksum=True, bufsize=100000, nens=None): + self.fname = fname + self._bufsize = bufsize + self.f = open(_abspath(fname), 'rb', 1000) + self.do_checksum = do_checksum + self.filesize # initialize the filesize. + self.debug = debug + self.c = 0 + self._dtypes = [] + self._n_start = 0 + try: + len(nens) + except TypeError: + # not a tuple, so we assume None or int + self._npings = nens + else: + if len(nens) != 2: + raise TypeError('nens must be: None (), int, or len 2') + warnings.warn("A 'start ensemble' is not yet supported " + "for the Nortek reader. This function will read " + "the entire file, then crop the beginning at " + "nens[0].") + self._npings = nens[1] + self._n_start = nens[0] + if endian is None: + if unpack('HH', self.read(4)) == (1445, 24): + endian = '>' + else: + raise Exception("I/O error: could not determine the " + "'endianness' of the file. Are you sure this is a Nortek " + "file?") + self.endian = endian + self.f.seek(0, 0) + + # This is the configuration data: + self.config = {} + err_msg = ("I/O error: The file does not " + "appear to be a Nortek data file.") + # Read the header: + if self.read_id() == 5: + self.read_hw_cfg() + else: + raise Exception() + if self.read_id() == 4: + self.read_head_cfg() + else: + raise Exception(err_msg) + if self.read_id() == 0: + self.read_user_cfg() + else: + raise Exception(err_msg) + if self.config['serialNum'][0:3].upper() == 'WPR': + self.config['config_type'] = 'AWAC' + elif self.config['serialNum'][0:3].upper() == 'VEC': + self.config['config_type'] = 'ADV' + # Initialize the instrument type: + self._inst = self.config.pop('config_type') + # This is the position after reading the 'hardware', + # 'head', and 'user' configuration. + pnow = self.pos + + # Run the appropriate initialization routine (e.g. init_ADV). + getattr(self, 'init_' + self._inst)() + self.f.close() # This has a small buffer, so close it. + # This has a large buffer... + self.f = open(_abspath(fname), 'rb', bufsize) + self.close = self.f.close + if self._npings is not None: + self.n_samp_guess = self._npings + 1 + self.f.seek(pnow, 0) # Seek to the previous position. + + props = self.data['attrs'] + if self.config['NBurst'] > 0: + props['DutyCycle_NBurst'] = self.config['NBurst'] + props['DutyCycle_NCycle'] = (self.config['MeasInterval'] * + self.config['fs']) + self.burst_start = np.zeros(self.n_samp_guess, dtype='bool') + props['fs'] = self.config['fs'] + props['coord_sys'] = {'XYZ': 'inst', + 'ENU': 'earth', + 'beam': 'beam'}[self.config['coord_sys_axes']] + # This just initializes it; this gets overwritten in read_microstrain + props['has_imu'] = 0 + if self.debug: + print('Init completed') + + @property + def filesize(self,): + if not hasattr(self, '_filesz'): + pos = self.pos + self.f.seek(0, 2) + # Seek to the end of the file to determine the filesize. + self._filesz = self.pos + self.f.seek(pos, 0) # Return to the initial position. + return self._filesz + + @property + def pos(self,): + return self.f.tell() + + def init_ADV(self,): + dat = self.data = {'data_vars': {}, 'coords': {}, 'attrs': {}, + 'units': {}, 'sys': {}} + da = dat['attrs'] + dv = dat['data_vars'] + da['config'] = self.config + da['inst_make'] = 'Nortek' + da['inst_model'] = 'Vector' + da['inst_type'] = 'ADV' + da['rotate_vars'] = ['vel'] + da['freq'] = self.config['freq'] + da['SerialNum'] = self.config.pop('serialNum') + dv['beam2inst_orientmat'] = self.config.pop('beam2inst_orientmat') + da['Comments'] = self.config.pop('Comments') + # No apparent way to determine how many samples are in a file + dlta = self.code_spacing('0x11') + self.config['fs'] = 512 / self.config['AvgInterval'] + self.n_samp_guess = int(self.filesize / dlta + 1) + self.n_samp_guess *= int(self.config['fs']) + + def init_AWAC(self,): + dat = self.data = {'data_vars': {}, 'coords': {}, 'attrs': {}, + 'units': {}, 'sys': {}} + da = dat['attrs'] + dv = dat['data_vars'] + da['config'] = self.config + da['inst_make'] = 'Nortek' + da['inst_model'] = 'AWAC' + da['inst_type'] = 'ADCP' + da['SerialNum'] = self.config.pop('serialNum') + dv['beam2inst_orientmat'] = self.config.pop('beam2inst_orientmat') + da['Comments'] = self.config.pop('Comments') + da['freq'] = self.config['freq'] + da['n_beams'] = self.config['NBeams'] + da['avg_interval'] = self.config['AvgInterval'] + da['rotate_vars'] = ['vel'] + space = self.code_spacing('0x20') + if space == 0: + # code spacing is zero if there's only 1 profile + self.n_samp_guess = 1 + else: + self.n_samp_guess = int(self.filesize / space + 1) + self.config['fs'] = 1. / self.config['AvgInterval'] + + def read(self, nbyte): + byts = self.f.read(nbyte) + if not (len(byts) == nbyte): + raise EOFError('Reached the end of the file') + return byts + + def findnext(self, do_cs=True): + """Find the next data block by checking the checksum and the + sync byte(0xa5) + """ + sum = np.uint16(int('0xb58c', 0)) # Initialize the sum + cs = 0 + func = _bitshift8 + func2 = np.uint8 + if self.endian == '<': + func = np.uint8 + func2 = _bitshift8 + while True: + val = unpack(self.endian + 'H', self.read(2))[0] + if func(val) == 165 and (not do_cs or cs == np.uint16(sum)): + self.f.seek(-2, 1) + return hex(func2(val)) + sum += cs + cs = val + + def read_id(self,): + """Read the next 'ID' from the file. + """ + self._thisid_bytes = bts = self.read(2) + tmp = unpack(self.endian + 'BB', bts) + if self.debug: + print('Position: {}, codes: {}'.format(self.f.tell(), tmp)) + if tmp[0] != 165: # This catches a corrupted data block. + if self.debug: + print("Corrupted data block sync code (%d, %d) found " + "in ping %d. Searching for next valid code..." % + (tmp[0], tmp[1], self.c)) + val = int(self.findnext(do_cs=False), 0) + self.f.seek(2, 1) + if self.debug: + print(' ...FOUND {} at position: {}.'.format(val, self.pos)) + return val + return tmp[1] + + def readnext(self,): + id = '0x%02x' % self.read_id() + if id in self.fun_map: + func_name = self.fun_map[id] + out = getattr(self, func_name)() # Should return None + self._lastread = [func_name[5:]] + self._lastread[:-1] + return out + else: + print('Unrecognized identifier: ' + id) + self.f.seek(-2, 1) + return 10 + + def readfile(self, nlines=None): + print('Reading file %s ...' % self.fname) + retval = None + try: + while not retval: + if self.c == nlines: + break + retval = self.readnext() + if retval == 10: + self.findnext() + retval = None + if self._npings is not None and self.c >= self._npings: + if 'microstrain' in self._dtypes: + try: + self.readnext() + except: + pass + break + except EOFError: + print(' end of file at {} bytes.'.format(self.pos)) + else: + print(' stopped at {} bytes.'.format(self.pos)) + self.c -= 1 + _crop_data(self.data, slice(0, self.c), self.n_samp_guess) + + def findnextid(self, id): + if id.__class__ is str: + id = int(id, 0) + nowid = None + while nowid != id: + nowid = self.read_id() + if nowid == 16: + shift = 22 + else: + sz = 2 * unpack(self.endian + 'H', self.read(2))[0] + shift = sz - 4 + self.f.seek(shift, 1) + return self.pos + + def code_spacing(self, searchcode, iternum=50): + """ + Find the spacing, in bytes, between a specific hardware code. + Repeat this * iternum * times(default 50). + Returns the average spacing, in bytes, between the code. + """ + p0 = self.findnextid(searchcode) + for i in range(iternum): + try: + self.findnextid(searchcode) + except EOFError: + break + if self.debug: + print('p0={}, pos={}, i={}'.format(p0, self.pos, i)) + # Compute the average of the data size: + return (self.pos - p0) / (i + 1) + + def checksum(self, byts): + """Perform a checksum on `byts` and read the checksum value. + """ + if self.do_checksum: + if not np.sum(unpack(self.endian + str(int(1 + len(byts) / 2)) + 'H', + self._thisid_bytes + byts)) + \ + 46476 - unpack(self.endian + 'H', self.read(2)): + + raise Exception("CheckSum Failed at {}".format(self.pos)) + else: + self.f.seek(2, 1) + + def read_user_cfg(self,): + # ID: '0x00 = 00 + if self.debug: + print('Reading user configuration (0x00) ping #{} @ {}...' + .format(self.c, self.pos)) + cfg_u = self.config + byts = self.read(508) + tmp = unpack(self.endian + + '2x5H13H6s4HI8H2x90H180s6H4xH2x2H2xH30x8H', + byts) + # the first two are the size. + cfg_u['Transmit'] = { + 'pulse_length': tmp[0], + 'blank_distance': tmp[1], + 'receive_length': tmp[2], + 'time_between_pings': tmp[3], + 'time_between_bursts': tmp[4], + } + cfg_u['Npings'] = tmp[5] + cfg_u['AvgInterval'] = tmp[6] + cfg_u['NBeams'] = tmp[7] + cfg_u['TimCtrlReg'] = _int2binarray(tmp[8], 16) + # From the nortek system integrator manual + # (note: bit numbering is zero-based) + treg = cfg_u['TimCtrlReg'].astype(int) + cfg_u['Profile_Timing'] = ['single', 'continuous'][treg[1]] + cfg_u['Burst_Mode'] = bool(~treg[2]) + cfg_u['Power Level'] = treg[5] + 2 * treg[6] + 1 + cfg_u['sync-out'] = ['middle', 'end', ][treg[7]] + cfg_u['Sample_on_Sync'] = bool(treg[8]) + cfg_u['Start_on_Sync'] = bool(treg[9]) + cfg_u['PwrCtrlReg'] = _int2binarray(tmp[9], 16) + cfg_u['A1'] = tmp[10] + cfg_u['B0'] = tmp[11] + cfg_u['B1'] = tmp[12] + cfg_u['CompassUpdRate'] = tmp[13] + cfg_u['coord_sys_axes'] = ['ENU', 'XYZ', 'beam'][tmp[14]] + cfg_u['NBins'] = tmp[15] + cfg_u['BinLength'] = tmp[16] + cfg_u['MeasInterval'] = tmp[17] + cfg_u['DeployName'] = tmp[18].partition(b'\x00')[0].decode('utf-8') + cfg_u['WrapMode'] = tmp[19] + cfg_u['ClockDeploy'] = np.array(tmp[20:23]) + cfg_u['DiagInterval'] = tmp[23] + cfg_u['Mode0'] = _int2binarray(tmp[24], 16) + cfg_u['AdjSoundSpeed'] = tmp[25] + cfg_u['NSampDiag'] = tmp[26] + cfg_u['NBeamsCellDiag'] = tmp[27] + cfg_u['NPingsDiag'] = tmp[28] + cfg_u['ModeTest'] = _int2binarray(tmp[29], 16) + cfg_u['AnaInAddr'] = tmp[30] + cfg_u['SWVersion'] = tmp[31] + cfg_u['VelAdjTable'] = np.array(tmp[32:122]) + cfg_u['Comments'] = tmp[122].partition(b'\x00')[0].decode('utf-8') + cfg_u['Mode1'] = _int2binarray(tmp[123], 16) + cfg_u['DynPercPos'] = tmp[124] + cfg_u['T1w'] = tmp[125] + cfg_u['T2w'] = tmp[126] + cfg_u['T3w'] = tmp[127] + cfg_u['NSamp'] = tmp[128] + cfg_u['NBurst'] = tmp[129] + cfg_u['AnaOutScale'] = tmp[130] + cfg_u['CorrThresh'] = tmp[131] + cfg_u['TiLag2'] = tmp[132] + cfg_u['QualConst'] = np.array(tmp[133:141]) + self.checksum(byts) + cfg_u['mode'] = {} + cfg_u['mode']['user_sound'] = cfg_u['Mode0'][0] + cfg_u['mode']['diagnostics_mode'] = cfg_u['Mode0'][1] + cfg_u['mode']['analog_output_mode'] = cfg_u['Mode0'][2] + cfg_u['mode']['output_format'] = ['Vector', 'ADV'][int(cfg_u['Mode0'][3])] # noqa + cfg_u['mode']['vel_scale'] = [1, 0.1][int(cfg_u['Mode0'][4])] + cfg_u['mode']['serial_output'] = cfg_u['Mode0'][5] + cfg_u['mode']['reserved_EasyQ'] = cfg_u['Mode0'][6] + cfg_u['mode']['stage'] = cfg_u['Mode0'][7] + cfg_u['mode']['output_power'] = cfg_u['Mode0'][8] + cfg_u['mode']['mode_test_use_DSP'] = cfg_u['ModeTest'][0] + cfg_u['mode']['mode_test_filter_output'] = ['total', 'correction_only'][int(cfg_u['ModeTest'][1])] # noqa + cfg_u['mode']['rate'] = ['1hz', '2hz'][int(cfg_u['Mode1'][0])] + cfg_u['mode']['cell_position'] = ['fixed', 'dynamic'][int(cfg_u['Mode1'][1])] # noqa + cfg_u['mode']['dynamic_pos_type'] = ['pct of mean press', 'pct of min re'][int(cfg_u['Mode1'][2])] # noqa + + def read_head_cfg(self,): + # ID: '0x04 = 04 + cfg = self.config + if self.debug: + print('Reading head configuration (0x04) ping #{} @ {}...' + .format(self.c, self.pos)) + byts = self.read(220) + tmp = unpack(self.endian + '2x3H12s176s22sH', byts) + cfg['freq'] = tmp[1] + cfg['beam2inst_orientmat'] = np.array( + unpack(self.endian + '9h', tmp[4][8:26])).reshape(3, 3) / 4096. + self.checksum(byts) + + def read_hw_cfg(self,): + # ID 0x05 = 05 + cfg = self.config + if self.debug: + print('Reading hardware configuration (0x05) ping #{} @ {}...' + .format(self.c, self.pos)) + cfg_hw = cfg + byts = self.read(44) + tmp = unpack(self.endian + '2x14s6H12xI', byts) + cfg_hw['serialNum'] = tmp[0][:8].decode('utf-8') + cfg_hw['ProLogID'] = unpack('B', tmp[0][8:9])[0] + cfg_hw['ProLogFWver'] = tmp[0][10:].decode('utf-8') + cfg_hw['config'] = tmp[1] + cfg_hw['freq'] = tmp[2] + cfg_hw['PICversion'] = tmp[3] + cfg_hw['HWrevision'] = tmp[4] + cfg_hw['recSize'] = tmp[5] * 65536 + cfg_hw['status'] = tmp[6] + cfg_hw['FWversion'] = tmp[7] + self.checksum(byts) + + def rd_time(self, strng): + """Read the time from the first 6bytes of the input string. + """ + min, sec, day, hour, year, month = unpack('BBBBBB', strng[:6]) + return time.date2epoch(datetime(time._fullyear(_bcd2char(year)), + _bcd2char(month), + _bcd2char(day), + _bcd2char(hour), + _bcd2char(min), + _bcd2char(sec)))[0] + + def _init_data(self, vardict): + """Initialize the data object according to vardict. + + Parameters + ---------- + vardict : (dict of :class:``) + The variable definitions in the :class:`` specify + how to initialize each data variable. + + """ + shape_args = {'n': self.n_samp_guess} + try: + shape_args['nbins'] = self.config['NBins'] + except KeyError: + pass + for nm, va in list(vardict.items()): + if va.group is None: + # These have to stay separated. + if nm not in self.data: + self.data[nm] = va._empty_array(**shape_args) + else: + if nm not in self.data[va.group]: + self.data[va.group][nm] = va._empty_array(**shape_args) + self.data['units'][nm] = va.units + + def read_vec_data(self,): + # ID: 0x10 = 16 + c = self.c + dat = self.data + if self.debug: + print('Reading vector velocity data (0x10) ping #{} @ {}...' + .format(self.c, self.pos)) + + if 'vel' not in dat['data_vars']: + self._init_data(nortek_defs.vec_data) + self._dtypes += ['vec_data'] + + byts = self.read(20) + ds = dat['sys'] + dv = dat['data_vars'] + (ds['AnaIn2LSB'][c], + ds['Count'][c], + dv['PressureMSB'][c], + ds['AnaIn2MSB'][c], + dv['PressureLSW'][c], + ds['AnaIn1'][c], + dv['vel'][0, c], + dv['vel'][1, c], + dv['vel'][2, c], + dv['amp'][0, c], + dv['amp'][1, c], + dv['amp'][2, c], + dv['corr'][0, c], + dv['corr'][1, c], + dv['corr'][2, c]) = unpack(self.endian + '4B2H3h6B', byts) + + self.checksum(byts) + self.c += 1 + + def read_vec_checkdata(self,): + # ID: 0x07 = 07 + if self.debug: + print('Reading vector check data (0x07) ping #{} @ {}...' + .format(self.c, self.pos)) + byts0 = self.read(6) + checknow = {} + tmp = unpack(self.endian + '2x2H', byts0) # The first two are size. + checknow['Samples'] = tmp[0] + n = checknow['Samples'] + checknow['First_samp'] = tmp[1] + checknow['Amp1'] = tbx._nans(n, dtype=np.uint8) + 8 + checknow['Amp2'] = tbx._nans(n, dtype=np.uint8) + 8 + checknow['Amp3'] = tbx._nans(n, dtype=np.uint8) + 8 + byts1 = self.read(3 * n) + tmp = unpack(self.endian + (3 * n * 'B'), byts1) + for idx, nm in enumerate(['Amp1', 'Amp2', 'Amp3']): + checknow[nm] = np.array(tmp[idx * n:(idx + 1) * n], dtype=np.uint8) + self.checksum(byts0 + byts1) + if 'checkdata' not in self.config: + self.config['checkdata'] = checknow + else: + if not isinstance(self.config['checkdata'], list): + self.config['checkdata'] = [self.config['checkdata']] + self.config['checkdata'] += [checknow] + + def _sci_data(self, vardict): + """Convert the data to scientific units accordint to vardict. + + Parameters + ---------- + vardict : (dict of :class:``) + The variable definitions in the :class:`` specify + how to scale each data variable. + + """ + for nm, vd in list(vardict.items()): + if vd.group is None: + dat = self.data + else: + dat = self.data[vd.group] + retval = vd.sci_func(dat[nm]) + # This checks whether a new data object was created: + # sci_func returns None if it modifies the existing data. + if retval is not None: + dat[nm] = retval + + def sci_vec_data(self,): + self._sci_data(nortek_defs.vec_data) + dat = self.data + + dat['data_vars']['pressure'] = ( + dat['data_vars']['PressureMSB'].astype('float32') * 65536 + + dat['data_vars']['PressureLSW'].astype('float32')) / 1000. + dat['units']['pressure'] = 'dbar' + + dat['data_vars'].pop('PressureMSB') + dat['data_vars'].pop('PressureLSW') + + # Apply velocity scaling (1 or 0.1) + dat['data_vars']['vel'] *= self.config['mode']['vel_scale'] + + def read_vec_hdr(self,): + # ID: '0x12 = 18 + if self.debug: + print('Reading vector header data (0x12) ping #{} @ {}...' + .format(self.c, self.pos)) + byts = self.read(38) + # The first two are size, the next 6 are time. + tmp = unpack(self.endian + '8xH7B21x', byts) + hdrnow = {} + hdrnow['time'] = self.rd_time(byts[2:8]) + hdrnow['NRecords'] = tmp[0] + hdrnow['Noise1'] = tmp[1] + hdrnow['Noise2'] = tmp[2] + hdrnow['Noise3'] = tmp[3] + hdrnow['Spare0'] = byts[13:14].decode('utf-8') + hdrnow['Corr1'] = tmp[5] + hdrnow['Corr2'] = tmp[6] + hdrnow['Corr3'] = tmp[7] + hdrnow['Spare1'] = byts[17:].decode('utf-8') + self.checksum(byts) + if 'data_header' not in self.config: + self.config['data_header'] = hdrnow + else: + if not isinstance(self.config['data_header'], list): + self.config['data_header'] = [self.config['data_header']] + self.config['data_header'] += [hdrnow] + + def read_vec_sysdata(self,): + # ID: 0x11 = 17 + c = self.c + if self.debug: + print('Reading vector system data (0x11) ping #{} @ {}...' + .format(self.c, self.pos)) + dat = self.data + if self._lastread[:2] == ['vec_checkdata', 'vec_hdr', ]: + self.burst_start[c] = True + if 'time' not in dat['coords']: + self._init_data(nortek_defs.vec_sysdata) + self._dtypes += ['vec_sysdata'] + byts = self.read(24) + # The first two are size (skip them). + dat['coords']['time'][c] = self.rd_time(byts[2:8]) + ds = dat['sys'] + dv = dat['data_vars'] + (dv['batt'][c], + dv['c_sound'][c], + dv['heading'][c], + dv['pitch'][c], + dv['roll'][c], + dv['temp'][c], + dv['error'][c], + dv['status'][c], + ds['AnaIn'][c]) = unpack(self.endian + '2H3hH2BH', byts[8:]) + self.checksum(byts) + + def sci_vec_sysdata(self,): + """Translate the data in the vec_sysdata structure into + scientific units. + """ + dat = self.data + fs = dat['attrs']['fs'] + self._sci_data(nortek_defs.vec_sysdata) + t = dat['coords']['time'] + dv = dat['data_vars'] + dat['sys']['_sysi'] = ~np.isnan(t) + # These are the indices in the sysdata variables + # that are not interpolated. + nburst = self.config['NBurst'] + dv['orientation_down'] = tbx._nans(len(t), dtype='bool') + if nburst == 0: + num_bursts = 1 + nburst = len(t) + else: + num_bursts = int(len(t) // nburst + 1) + for nb in range(num_bursts): + iburst = slice(nb * nburst, (nb + 1) * nburst) + sysi = dat['sys']['_sysi'][iburst] + if len(sysi) == 0: + break + # Skip the first entry for the interpolation process + inds = np.nonzero(sysi)[0][1:] + arng = np.arange(len(t[iburst]), dtype=np.float64) + if len(inds) >= 2: + p = np.poly1d(np.polyfit(inds, t[iburst][inds], 1)) + t[iburst] = p(arng) + elif len(inds) == 1: + t[iburst] = ((arng - inds[0]) / (fs * 3600 * 24) + + t[iburst][inds[0]]) + else: + t[iburst] = (t[iburst][0] + arng / (fs * 24 * 3600)) + + tmpd = tbx._nans_like(dv['heading'][iburst]) + # The first status bit should be the orientation. + tmpd[sysi] = dv['status'][iburst][sysi] & 1 + tbx.fillgaps(tmpd, extrapFlg=True) + tmpd = np.nan_to_num(tmpd, nan=0) # nans in pitch roll heading + slope = np.diff(tmpd) + tmpd[1:][slope < 0] = 1 + tmpd[:-1][slope > 0] = 0 + dv['orientation_down'][iburst] = tmpd.astype('bool') + tbx.interpgaps(dv['batt'], t) + tbx.interpgaps(dv['c_sound'], t) + tbx.interpgaps(dv['heading'], t) + tbx.interpgaps(dv['pitch'], t) + tbx.interpgaps(dv['roll'], t) + tbx.interpgaps(dv['temp'], t) + + def read_microstrain(self,): + """Read ADV microstrain sensor (IMU) data + """ + # 0x71 = 113 + if self.c == 0: + print('Warning: First "microstrain data" block ' + 'is before first "vector system data" block.') + else: + self.c -= 1 + if self.debug: + print('Reading vector microstrain data (0x71) ping #{} @ {}...' + .format(self.c, self.pos)) + byts0 = self.read(4) + # The first 2 are the size, 3rd is count, 4th is the id. + ahrsid = unpack(self.endian + '3xB', byts0)[0] + if hasattr(self, '_ahrsid') and self._ahrsid != ahrsid: + warnings.warn('AHRS_ID changes mid-file!') + + if ahrsid in [195, 204, 210, 211]: + self._ahrsid = ahrsid + + c = self.c + dat = self.data + dv = dat['data_vars'] + da = dat['attrs'] + da['has_imu'] = 1 # logical + if 'accel' not in dv: + self._dtypes += ['microstrain'] + if ahrsid == 195: + self._orient_dnames = ['accel', 'angrt', 'orientmat'] + dv['accel'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + dv['angrt'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + dv['orientmat'] = tbx._nans((3, 3, self.n_samp_guess), + dtype=np.float32) + rv = ['accel', 'angrt'] + if not all(x in da['rotate_vars'] for x in rv): + da['rotate_vars'].extend(rv) + dat['units'].update({'accel': 'm/s^2', + 'angrt': 'rad/s'}) + + if ahrsid in [204, 210]: + self._orient_dnames = ['accel', 'angrt', 'mag', 'orientmat'] + dv['accel'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + dv['angrt'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + dv['mag'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + rv = ['accel', 'angrt', 'mag'] + if not all(x in da['rotate_vars'] for x in rv): + da['rotate_vars'].extend(rv) + if ahrsid == 204: + dv['orientmat'] = tbx._nans((3, 3, self.n_samp_guess), + dtype=np.float32) + dat['units'].update({'accel': 'm/s^2', + 'angrt': 'rad/s', + 'mag': 'gauss'}) + + elif ahrsid == 211: + self._orient_dnames = ['angrt', 'accel', 'mag'] + dv['angrt'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + dv['accel'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + dv['mag'] = tbx._nans((3, self.n_samp_guess), + dtype=np.float32) + rv = ['angrt', 'accel', 'mag'] + if not all(x in da['rotate_vars'] for x in rv): + da['rotate_vars'].extend(rv) + dat['units'].update({'accel': 'm/s^2', + 'angrt': 'rad/s', + 'mag': 'gauss'}) + byts = '' + if ahrsid == 195: # 0xc3 + byts = self.read(64) + dt = unpack(self.endian + '6f9f4x', byts) + (dv['angrt'][:, c], + dv['accel'][:, c]) = (dt[0:3], dt[3:6],) + dv['orientmat'][:, :, c] = ((dt[6:9], dt[9:12], dt[12:15])) + elif ahrsid == 204: # 0xcc + byts = self.read(78) + # This skips the "DWORD" (4 bytes) and the AHRS checksum + # (2 bytes) + dt = unpack(self.endian + '18f6x', byts) + (dv['accel'][:, c], + dv['angrt'][:, c], + dv['mag'][:, c]) = (dt[0:3], dt[3:6], dt[6:9],) + dv['orientmat'][:, :, c] = ((dt[9:12], dt[12:15], dt[15:18])) + elif ahrsid == 211: + byts = self.read(42) + dt = unpack(self.endian + '9f6x', byts) + (dv['angrt'][:, c], + dv['accel'][:, c], + dv['mag'][:, c]) = (dt[0:3], dt[3:6], dt[6:9],) + else: + print('Unrecognized IMU identifier: ' + str(ahrsid)) + self.f.seek(-2, 1) + return 10 + self.checksum(byts0 + byts) + self.c += 1 # reset the increment + + def sci_microstrain(self,): + """Rotate orientation data into ADV coordinate system. + """ + # MS = MicroStrain + dv = self.data['data_vars'] + for nm in self._orient_dnames: + # Rotate the MS orientation data (in MS coordinate system) + # to be consistent with the ADV coordinate system. + # (x,y,-z)_ms = (z,y,x)_adv + (dv[nm][2], + dv[nm][0]) = (dv[nm][0], + -dv[nm][2].copy()) + if 'orientmat' in self._orient_dnames: + # MS coordinate system is in North-East-Down (NED), + # we want East-North-Up (ENU) + dv['orientmat'][:, 2] *= -1 + (dv['orientmat'][:, 0], + dv['orientmat'][:, 1]) = (dv['orientmat'][:, 1], + dv['orientmat'][:, 0].copy()) + if 'accel' in dv: + # This value comes from the MS 3DM-GX3 MIP manual + dv['accel'] *= 9.80665 + if self._ahrsid in [195, 211]: + # These are DAng and DVel, so we convert them to angrt, accel here + dv['angrt'] *= self.config['fs'] + dv['accel'] *= self.config['fs'] + + def read_awac_profile(self,): + # ID: '0x20' = 32 + dat = self.data + if self.debug: + print('Reading AWAC velocity data (0x20) ping #{} @ {}...' + .format(self.c, self.pos)) + nbins = self.config['NBins'] + if 'temp' not in dat['data_vars']: + self._init_data(nortek_defs.awac_profile) + self._dtypes += ['awac_profile'] + + # Note: docs state there is 'fill' byte at the end, if nbins is odd, + # but doesn't appear to be the case + n = self.config['NBeams'] + byts = self.read(116 + n*3 * nbins) + c = self.c + dat['coords']['time'][c] = self.rd_time(byts[2:8]) + ds = dat['sys'] + dv = dat['data_vars'] + (dv['error'][c], + ds['AnaIn1'][c], + dv['batt'][c], + dv['c_sound'][c], + dv['heading'][c], + dv['pitch'][c], + dv['roll'][c], + p_msb, + dv['status'][c], + p_lsw, + dv['temp'][c],) = unpack(self.endian + '7HBB2H', byts[8:28]) + dv['pressure'][c] = (65536 * p_msb + p_lsw) + # The nortek system integrator manual specifies an 88byte 'spare' + # field, therefore we start at 116. + tmp = unpack(self.endian + str(n * nbins) + 'h' + + str(n * nbins) + 'B', byts[116:116 + n*3 * nbins]) + for idx in range(n): + dv['vel'][idx, :, c] = tmp[idx * nbins: (idx + 1) * nbins] + dv['amp'][idx, :, c] = tmp[(idx + n) * nbins: (idx + n+1) * nbins] + self.checksum(byts) + self.c += 1 + + def sci_awac_profile(self,): + self._sci_data(nortek_defs.awac_profile) + # Calculate the ranges. + cs_coefs = {2000: 0.0239, + 1000: 0.0478, + 600: 0.0797, + 400: 0.1195} + h_ang = 25 * (np.pi / 180) # Head angle is 25 degrees for all awacs. + # Cell size + cs = round(float(self.config['BinLength']) / 256. * + cs_coefs[self.config['freq']] * np.cos(h_ang), ndigits=2) + # Blanking distance + bd = round(self.config['Transmit']['blank_distance'] * + 0.0229 * np.cos(h_ang) - cs, ndigits=2) + + r = (np.float32(np.arange(self.config['NBins']))+1)*cs + bd + self.data['coords']['range'] = r + self.data['attrs']['cell_size'] = cs + self.data['attrs']['blank_dist'] = bd + + def dat2sci(self,): + for nm in self._dtypes: + getattr(self, 'sci_' + nm)() + for nm in ['data_header', 'checkdata']: + if nm in self.config and isinstance(self.config[nm], list): + self.config[nm] = _recatenate(self.config[nm]) + + def __exit__(self, type, value, trace): + self.close() + + def __enter__(self): + return self + + +def _crop_data(obj, range, n_lastdim): + for nm, dat in obj.items(): + if isinstance(dat, np.ndarray) and (dat.shape[-1] == n_lastdim): + obj[nm] = dat[..., range] + + +def _recatenate(obj): + out = type(obj[0])() + for ky in list(obj[0].keys()): + if ky in ['__data_groups__', '_type']: + continue + val0 = obj[0][ky] + if isinstance(val0, np.ndarray) and val0.size > 1: + out[ky] = np.concatenate([val[ky][..., None] for val in obj], + axis=-1) + else: + out[ky] = np.array([val[ky] for val in obj]) + return out diff --git a/mhkit/dolfyn/io/nortek2.py b/mhkit/dolfyn/io/nortek2.py new file mode 100644 index 000000000..5da4b2690 --- /dev/null +++ b/mhkit/dolfyn/io/nortek2.py @@ -0,0 +1,547 @@ +import numpy as np +import xarray as xr +from struct import unpack, calcsize +import warnings +from . import nortek2_defs as defs +from . import nortek2_lib as lib +from .base import _find_userdata, _create_dataset, _abspath +from ..rotate.vector import _euler2orient +from ..rotate.base import _set_coords +from ..rotate.api import set_declination +from ..time import epoch2dt64, _fill_time_gaps + + +def read_signature(filename, userdata=True, nens=None, rebuild_index=False, + debug=False): + """Read a Nortek Signature (.ad2cp) datafile + + Parameters + ---------- + filename : string + The filename of the file to load. + userdata : bool + To search for and use a .userdata.json or not + nens : int, or tuple of 2 ints + The number of ensembles to read, if int (starting at the + beginning); or the range of ensembles to read, if tuple. + + Returns + ------- + ds : xarray.Dataset + An xarray dataset from the binary instrument data + """ + if nens is None: + nens = [0, None] + else: + try: + n = len(nens) + except TypeError: + nens = [0, nens] + else: + # passes: it's a list/tuple/array + if n != 2: + raise TypeError('nens must be: None (), int, or len 2') + + userdata = _find_userdata(filename, userdata) + + rdr = _Ad2cpReader(filename, rebuild_index=rebuild_index, debug=debug) + d = rdr.readfile(nens[0], nens[1]) + rdr.sci_data(d) + out = _reorg(d) + _reduce(out) + + # Convert time to dt64 and fill gaps + coords = out['coords'] + t_list = [t for t in coords if 'time' in t] + for ky in t_list: + tdat = coords[ky] + tdat[tdat == 0] = np.NaN + if np.isnan(tdat).any(): + tag = ky.lstrip('time') + warnings.warn("Zero/NaN values found in '{}'. Interpolating and " + "extrapolating them. To identify which values were filled later, " + "look for 0 values in 'status{}'".format(ky, tag)) + tdat = _fill_time_gaps(tdat, sample_rate_hz=out['attrs']['fs']) + coords[ky] = epoch2dt64(tdat).astype('datetime64[us]') + + declin = None + for nm in userdata: + if 'dec' in nm: + declin = userdata[nm] + else: + out['attrs'][nm] = userdata[nm] + + # Create xarray dataset from upper level dictionary + ds = _create_dataset(out) + ds = _set_coords(ds, ref_frame=ds.coord_sys) + + if 'orientmat' not in ds: + omat = _euler2orient(ds['heading'], ds['pitch'], ds['roll']) + ds['orientmat'] = xr.DataArray(omat, + coords={'earth': ['E', 'N', 'U'], + 'inst': ['X', 'Y', 'Z'], + 'time': ds['time']}, + dims=['earth', 'inst', 'time']) + if declin is not None: + set_declination(ds, declin, inplace=True) + + return ds + + +class _Ad2cpReader(): + def __init__(self, fname, endian=None, bufsize=None, rebuild_index=False, + debug=False): + self.fname = fname + 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._reopen(bufsize) + self.filehead_config = self._read_filehead_config_string() + self._ens_pos = self._index['pos'][lib._boolarray_firstensemble_ping( + self._index)] + self._lastblock_iswhole = self._calc_lastblock_iswhole() + self._config = lib._calc_config(self._index) + self._init_burst_readers() + self.unknown_ID_count = {} + + def _calc_lastblock_iswhole(self, ): + blocksize, blocksize_count = np.unique(np.diff(self._ens_pos), + return_counts=True) + standard_blocksize = blocksize[blocksize_count.argmax()] + return (self._eof - self._ens_pos[-1]) == standard_blocksize + + def _check_nortek(self, endian): + self._reopen(10) + byts = self.f.read(2) + if endian is None: + if unpack('<' + 'BB', byts) == (165, 10): + endian = '<' + elif unpack('>' + 'BB', byts) == (165, 10): + endian = '>' + else: + raise Exception( + "I/O error: could not determine the 'endianness' " + "of the file. Are you sure this is a Nortek " + "AD2CP file?") + self.endian = endian + + def _reopen(self, bufsize=None): + if bufsize is None: + bufsize = 1000000 + try: + self.f.close() + except AttributeError: + pass + self.f = open(_abspath(self.fname), 'rb', bufsize) + + def _read_filehead_config_string(self, ): + hdr = self._read_hdr() + out = {} + s_id, string = self._read_str(hdr['sz']) + string = string.decode('utf-8') + for ln in string.splitlines(): + ky, val = ln.split(',', 1) + if ky in out: + # There are more than one of this key + if not isinstance(out[ky], list): + tmp = out[ky] + out[ky] = [] + out[ky].append(tmp) + out[ky].append(val) + else: + out[ky] = val + out2 = {} + for ky in out: + if ky.startswith('GET'): + dat = out[ky] + d = out2[ky.lstrip('GET')] = dict() + for itm in dat.split(','): + k, val = itm.split('=') + try: + val = int(val) + except ValueError: + try: + val = float(val) + except ValueError: + pass + d[k] = val + else: + out2[ky] = out[ky] + return out2 + + def _init_burst_readers(self, ): + self._burst_readers = {} + for rdr_id, cfg in self._config.items(): + if rdr_id == 28: + self._burst_readers[rdr_id] = defs._calc_echo_struct( + cfg['_config'], cfg['n_cells']) + elif rdr_id == 23: + self._burst_readers[rdr_id] = defs._calc_bt_struct( + cfg['_config'], cfg['n_beams']) + else: + self._burst_readers[rdr_id] = defs._calc_burst_struct( + cfg['_config'], cfg['n_beams'], cfg['n_cells']) + + def init_data(self, ens_start, ens_stop): + outdat = {} + nens = int(ens_stop - ens_start) + n26 = ((self._index['ID'] == 26) & + (self._index['ens'] >= ens_start) & + (self._index['ens'] < ens_stop)).sum() + for ky in self._burst_readers: + if ky == 26: + n = n26 + ens = np.zeros(n, dtype='uint32') + else: + ens = np.arange(ens_start, + ens_stop).astype('uint32') + n = nens + outdat[ky] = self._burst_readers[ky].init_data(n) + outdat[ky]['ensemble'] = ens + outdat[ky]['units'] = self._burst_readers[ky].data_units() + return outdat + + def _read_hdr(self, do_cs=False): + res = defs.header.read2dict(self.f, cs=do_cs) + if res['sync'] != 165: + raise Exception("Out of sync!") + return res + + def _read_str(self, size): + string = self.f.read(size) + id = string[0] + string = string[1:-1] + return id, string + + def _read_burst(self, id, dat, c, echo=False): + rdr = self._burst_readers[id] + rdr.read_into(self.f, dat, c) + + def readfile(self, ens_start=0, ens_stop=None): + # If the lastblock is not whole, we don't read it. + # If it is, we do (don't subtract 1) + nens_total = len(self._ens_pos) - int(not self._lastblock_iswhole) + if ens_stop is None or ens_stop > nens_total: + ens_stop = nens_total + ens_start = int(ens_start) + ens_stop = int(ens_stop) + nens = ens_stop - ens_start + outdat = self.init_data(ens_start, ens_stop) + outdat['filehead_config'] = self.filehead_config + print('Reading file %s ...' % self.fname) + c = 0 + c26 = 0 + self.f.seek(self._ens_pos[ens_start], 0) + while True: + try: + hdr = self._read_hdr() + except IOError: + return outdat + id = hdr['id'] + if id in [21, 23, 24, 28]: # vel, bt, vel_b5, echo + self._read_burst(id, outdat[id], c) + elif id in [26]: # alt_raw (altimeter burst) + rdr = self._burst_readers[26] + if not hasattr(rdr, '_nsamp_index'): + first_pass = True + tmp_idx = rdr._nsamp_index = rdr._names.index('altraw_nsamp') # noqa + shift = rdr._nsamp_shift = calcsize( + defs._format(rdr._format[:tmp_idx], + rdr._N[:tmp_idx])) + else: + first_pass = False + tmp_idx = rdr._nsamp_index + shift = rdr._nsamp_shift + tmp_idx = tmp_idx + 2 # Don't add in-place + self.f.seek(shift, 1) + # Now read the num_samples + sz = unpack('= nens: + return outdat + + def _advance_ens_count(self, c, ens_start, nens_total): + """This method advances the counter when appropriate to do so. + """ + # It's unfortunate that all of this count checking is so + # complex, but this is the best I could come up with right + # now. + try: + # Checks to makes sure we're not already at the end of the + # self._ens_pos array + _posnow = self._ens_pos[c + ens_start + 1] + except IndexError: + # We are at the end of the array, set _posnow + # We use "+1" here because we want the >= in the while + # loop to fail for this case so that we go ahead and read + # the next ping without advancing the ens counter. + _posnow = self._eof + 1 + while (self.f.tell() >= _posnow): + c += 1 + if c + ens_start + 1 >= nens_total: + # Again check end of count list + break + try: + # Same check as above. + _posnow = self._ens_pos[c + ens_start + 1] + except IndexError: + _posnow = self._eof + 1 + return c + + def sci_data(self, dat): + for id in dat: + dnow = dat[id] + if id not in self._burst_readers: + continue + rdr = self._burst_readers[id] + rdr.sci_data(dnow) + if 'vel' in dnow and 'vel_scale' in dnow: + dnow['vel'] = (dnow['vel'] * + 10.0 ** dnow['vel_scale']).astype('float32') + + def __exit__(self, type, value, trace,): + self.f.close() + + def __enter__(self,): + return self + + +def _reorg(dat): + """This function grabs the data from the dictionary of data types + (organized by ID), and combines them into a single dictionary. + """ + outdat = {'data_vars': {}, 'coords': {}, 'attrs': {}, + 'units': {}, 'sys': {}, 'altraw': {}} + cfg = outdat['attrs'] + cfh = cfg['filehead_config'] = dat['filehead_config'] + cfg['inst_model'] = (cfh['ID'].split(',')[0][5:-1]) + cfg['inst_make'] = 'Nortek' + cfg['inst_type'] = 'ADCP' + cfg['rotate_vars'] = ['vel', ] + + for id, tag in [(21, ''), (23, '_bt'), (24, '_b5'), (26, '_ast'), (28, '_echo')]: + if id in [24, 26]: + collapse_exclude = [0] + else: + collapse_exclude = [] + if id not in dat: + continue + dnow = dat[id] + outdat['units'].update(dnow['units']) + cfg['burst_config' + tag] = lib._headconfig_int2dict( + lib._collapse(dnow['config'], exclude=collapse_exclude, + name='config')) + outdat['coords']['time' + tag] = lib._calc_time( + dnow['year'] + 1900, + dnow['month'], + dnow['day'], + dnow['hour'], + dnow['minute'], + dnow['second'], + dnow['usec100'].astype('uint32') * 100) + tmp = lib._beams_cy_int2dict( + lib._collapse(dnow['beam_config'], exclude=collapse_exclude, + name='beam_config'), 21) + cfg['n_cells' + tag] = tmp['n_cells'] + cfg['coord_sys_axes' + tag] = tmp['cy'] + cfg['n_beams' + tag] = tmp['n_beams'] + cfg['ambig_vel' + + tag] = lib._collapse(dnow['ambig_vel'], name='ambig_vel') + + for ky in ['SerialNum', 'cell_size', 'blank_dist', 'nominal_corr', + 'power_level_dB']: + cfg[ky + tag] = lib._collapse(dnow[ky], + exclude=collapse_exclude, + name=ky) + + for ky in ['c_sound', 'temp', 'pressure', 'heading', 'pitch', 'roll', + 'mag', 'accel', 'batt', 'temp_clock', 'error', + 'status', 'ensemble', + ]: + outdat['data_vars'][ky + tag] = dnow[ky] + if 'ensemble' in ky: + outdat['data_vars'][ky + tag] += 1 + outdat['units'][ky + tag] = '#' + + for ky in ['vel', 'amp', 'corr', 'prcnt_gd', 'echo', 'dist', + 'orientmat', 'angrt', 'quaternions', 'ast_pressure', + 'alt_dist', 'alt_quality', 'alt_status', + 'ast_dist', 'ast_quality', 'ast_offset_time', + 'altraw_nsamp', 'altraw_dsamp', 'altraw_samp', + 'status0', 'fom', 'temp_press', 'press_std', + 'pitch_std', 'roll_std', 'heading_std', 'xmit_energy', + ]: + if ky in dnow: + outdat['data_vars'][ky + tag] = dnow[ky] + + # Move 'altimeter raw' data to its own down-sampled structure + if 26 in dat: + ard = outdat['altraw'] + for ky in list(outdat['data_vars']): + if ky.endswith('_ast'): + grp = ky.split('.')[0] + if '.' in ky and grp not in ard: + ard[grp] = {} + ard[ky.rstrip('_ast')] = outdat['data_vars'].pop(ky) + + # Read altimeter status + alt_status = lib._alt_status2data(outdat['data_vars']['alt_status']) + for ky in alt_status: + outdat['attrs'][ky] = lib._collapse( + alt_status[ky].astype('uint8'), name=ky) + outdat['data_vars'].pop('alt_status') + + # 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')] + + # Read status data + status0_vars = [x for x in outdat['data_vars'] if 'status0' in x] + # Status data is the same across all tags, and there is always a 'status' and 'status0' + status0_key = status0_vars[0] + status0_data = lib._status02data(outdat['data_vars'][status0_key]) + status_key = status0_key.replace('0', '') + status_data = lib._status2data(outdat['data_vars'][status_key]) + + # Individual status codes + # Wake up state + wake = {0: 'bad power', 1: 'power on', 2: 'break', 3: 'clock'} + outdat['attrs']['wakeup_state'] = wake[lib._collapse( + status_data.pop('wakeup_state'), name=ky)] + + # Instrument direction + # 0: XUP, 1: XDOWN, 2: YUP, 3: YDOWN, 4: ZUP, 5: ZDOWN, + # 7: AHRS, handle as ZUP + nortek_orient = {0: 'horizontal', 1: 'horizontal', 2: 'horizontal', + 3: 'horizontal', 4: 'up', 5: 'down', 7: 'AHRS'} + outdat['attrs']['orientation'] = nortek_orient[lib._collapse( + status_data.pop('orient_up'), name='orientation')] + + # Orientation detection + orient_status = {0: 'fixed', 1: 'auto_UD', 3: 'AHRS-3D'} + outdat['attrs']['orient_status'] = orient_status[lib._collapse( + status_data.pop('auto_orientation'), name='orient_status')] + + # Status variables + for ky in ['low_volt_skip', 'active_config', 'telemetry_data', 'boost_running']: + outdat['data_vars'][ky] = status_data[ky].astype('uint8') + + # Processor idle state - need to save as 1/0 per netcdf attribute limitations + 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 + [outdat['data_vars'].pop(var) for var in status0_vars] + + # Set coordinate system + outdat['attrs']['coord_sys'] = {'XYZ': 'inst', + 'ENU': 'earth', + 'beam': 'beam'}[cfg['coord_sys_axes']] + + # Copy appropriate vars to rotate_vars + for ky in ['accel', 'angrt', 'mag']: + for dky in outdat['data_vars'].keys(): + if dky == ky or dky.startswith(ky + '_'): + outdat['attrs']['rotate_vars'].append(dky) + if 'vel_bt' in outdat['data_vars']: + outdat['attrs']['rotate_vars'].append('vel_bt') + + return outdat + + +def _reduce(data): + """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. + """ + dv = data['data_vars'] + dc = data['coords'] + da = data['attrs'] + + # Average these fields + for ky in ['c_sound', 'temp', 'pressure', + 'temp_press', 'temp_clock', 'batt']: + lib._reduce_by_average(dv, ky, ky + '_b5') + + # Angle-averaging is treated separately + for ky in ['heading', 'pitch', 'roll']: + lib._reduce_by_average_angle(dv, ky, ky + '_b5') + + dc['range'] = ((np.arange(dv['vel'].shape[1])+1) * + da['cell_size'] + + da['blank_dist']) + if 'vel_b5' in dv: + dc['range_b5'] = ((np.arange(dv['vel_b5'].shape[1])+1) * + da['cell_size_b5'] + + da['blank_dist_b5']) + if 'echo_echo' in dv: + dv['echo'] = dv.pop('echo_echo') + dc['range_echo'] = ((np.arange(dv['echo'].shape[0])+1) * + da['cell_size_echo'] + + da['blank_dist_echo']) + + if 'orientmat' in data['data_vars']: + da['has_imu'] = 1 # logical + # Signature AHRS rotation matrix returned in "inst->earth" + # Change to dolfyn's "earth->inst" + dv['orientmat'] = np.rollaxis(dv['orientmat'], 1) + else: + da['has_imu'] = 0 + + da['fs'] = da['filehead_config']['BURST'].pop('SR') + tmat = da['filehead_config'].pop('XFBURST') + tm = np.zeros((tmat['ROWS'], tmat['COLS']), dtype=np.float32) + for irow in range(tmat['ROWS']): + for icol in range(tmat['COLS']): + tm[irow, icol] = tmat['M' + str(irow + 1) + str(icol + 1)] + dv['beam2inst_orientmat'] = tm diff --git a/mhkit/dolfyn/io/nortek2_defs.py b/mhkit/dolfyn/io/nortek2_defs.py new file mode 100644 index 000000000..cdc2b53c9 --- /dev/null +++ b/mhkit/dolfyn/io/nortek2_defs.py @@ -0,0 +1,316 @@ +import numpy as np +from copy import copy +from struct import Struct +from . import nortek2_lib as lib + +dt32 = 'float32' +dt64 = 'float64' + +grav = 9.81 +# The starting value for the checksum: +cs0 = int('0xb58c', 0) + + +def _nans(*args, **kwargs): + out = np.empty(*args, **kwargs) + if out.dtype.kind == 'f': + out[:] = np.NaN + else: + out[:] = 0 + return out + + +def _format(form, N): + out = '' + for f, n in zip(form, N): + if n > 1: + out += '{}'.format(n) + out += f + return out + + +class _DataDef(): + def __init__(self, list_of_defs): + self._names = [] + self._format = [] + self._shape = [] + self._sci_func = [] + self._units = [] + self._N = [] + for itm in list_of_defs: + self._names.append(itm[0]) + self._format.append(itm[1]) + self._shape.append(itm[2]) + self._sci_func.append(itm[3]) + if len(itm) == 5: + self._units.append(itm[4]) + else: + self._units.append('') + if itm[2] == []: + self._N.append(1) + else: + self._N.append(int(np.prod(itm[2]))) + self._struct = Struct('<' + self.format) + self.nbyte = self._struct.size + self._cs_struct = Struct('<' + '{}H'.format(int(self.nbyte // 2))) + + def init_data(self, npings): + out = {} + for nm, fmt, shp in zip(self._names, self._format, self._shape): + # fmt[0] uses only the first format specifier + # (ie, skip '15x' in 'B15x') + out[nm] = _nans(shp + [npings], dtype=np.dtype(fmt[0])) + return out + + def read_into(self, fobj, data, ens, cs=None): + dat_tuple = self.read(fobj, cs=cs) + for nm, shp, d in zip(self._names, self._shape, dat_tuple): + try: + data[nm][..., ens] = d + except ValueError: + data[nm][..., ens] = np.asarray(d).reshape(shp) + + @property + def format(self, ): + return _format(self._format, self._N) + + def read(self, fobj, cs=None): + bytes = fobj.read(self.nbyte) + if len(bytes) != self.nbyte: + raise IOError("End of file.") + data = self._struct.unpack(bytes) + if cs is not None: + if cs is True: + # if cs is True, then it should be the last value that + # was read. + csval = data[-1] + off = cs0 - csval + elif isinstance(cs, int): + csval = cs + off = cs0 + cs_res = sum(self._cs_struct.unpack(bytes)) + off + if csval is not False and (cs_res % 65536) != csval: + raise Exception('Checksum failed!') + out = [] + c = 0 + for idx, n in enumerate(self._N): + if n == 1: + out.append(data[c]) + else: + out.append(data[c:(c + n)]) + c += n + return out + + def read2dict(self, fobj, cs=False): + return {self._names[idx]: dat + for idx, dat in enumerate(self.read(fobj, cs=cs))} + + def sci_data(self, data): + for ky, func in zip(self._names, + self._sci_func): + if func is None: + continue + data[ky] = func(data[ky]) + + def data_units(self): + units = {} + for ky, unit in zip(self._names, self._units): + units[ky] = unit + return units + + +class _LinFunc(): + """A simple linear offset and scaling object. + + Usage: + scale_func = _LinFunc(scale=3, offset=5) + + new_data = scale_func(old_data) + + This will do: + new_data = (old_data + 5) * 3 + """ + + def __init__(self, scale=1, offset=0, dtype=None): + self.scale = scale + self.offset = offset + self.dtype = dtype + + def __call__(self, array): + if self.scale != 1 or self.offset != 0: + array = (array + self.offset) * self.scale + if self.dtype is not None: + array = array.astype(self.dtype) + return array + + +header = _DataDef([ + ('sync', 'B', [], None), + ('hsz', 'B', [], None), + ('id', 'B', [], None), + ('fam', 'B', [], None), + ('sz', 'H', [], None), + ('cs', 'H', [], None), + ('hcs', 'H', [], None), +]) + +_burst_hdr = [ + ('ver', 'B', [], None), + ('DatOffset', 'B', [], None), + ('config', 'H', [], None), + ('SerialNum', 'I', [], None), + ('year', 'B', [], None), + ('month', 'B', [], None), + ('day', 'B', [], None), + ('hour', 'B', [], None), + ('minute', 'B', [], None), + ('second', 'B', [], None), + ('usec100', 'H', [], None), + ('c_sound', 'H', [], _LinFunc(0.1, dtype=dt32), 'm/s'), + ('temp', 'H', [], _LinFunc(0.01, dtype=dt32), 'deg C'), + ('pressure', 'I', [], _LinFunc(0.001, dtype=dt32), 'dbar'), + ('heading', 'H', [], _LinFunc(0.01, dtype=dt32), 'deg'), + ('pitch', 'h', [], _LinFunc(0.01, dtype=dt32), 'deg'), + ('roll', 'h', [], _LinFunc(0.01, dtype=dt32), 'deg'), + ('beam_config', 'H', [], None), + ('cell_size', 'H', [], _LinFunc(0.001), 'm'), + ('blank_dist', 'H', [], _LinFunc(0.01), 'm'), + ('nominal_corr', 'B', [], None, '%'), + ('temp_press', 'B', [], _LinFunc(0.2, -20, dtype=dt32), 'deg C'), + ('batt', 'H', [], _LinFunc(0.1, dtype=dt32), 'V'), + ('mag', 'h', [3], _LinFunc(0.1, dtype=dt32), 'uT'), + ('accel', 'h', [3], _LinFunc(1. / 16384 * grav, dtype=dt32), 'm/s^2'), + ('ambig_vel', 'h', [], _LinFunc(0.001, dtype=dt32), 'm/s'), + ('data_desc', 'H', [], None), + ('xmit_energy', 'H', [], None, 'dB'), + ('vel_scale', 'b', [], None), + ('power_level_dB', 'b', [], _LinFunc(dtype=dt32)), + ('temp_mag', 'h', [], None), # uncalibrated + ('temp_clock', 'h', [], _LinFunc(0.01, dtype=dt32), 'deg C'), + ('error', 'H', [], None), + ('status0', 'H', [], None), + ('status', 'I', [], None), + ('_ensemble', 'I', [], None), +] + +_bt_hdr = [ + ('ver', 'B', [], None), + ('DatOffset', 'B', [], None), + ('config', 'H', [], None), + ('SerialNum', 'I', [], None), + ('year', 'B', [], None), + ('month', 'B', [], None), + ('day', 'B', [], None), + ('hour', 'B', [], None), + ('minute', 'B', [], None), + ('second', 'B', [], None), + ('usec100', 'H', [], None), + ('c_sound', 'H', [], _LinFunc(0.1, dtype=dt32), 'm/s'), + ('temp', 'H', [], _LinFunc(0.01, dtype=dt32), 'deg C'), + ('pressure', 'I', [], _LinFunc(0.001, dtype=dt32), 'dbar'), + ('heading', 'H', [], _LinFunc(0.01, dtype=dt32), 'deg'), + ('pitch', 'h', [], _LinFunc(0.01, dtype=dt32), 'deg'), + ('roll', 'h', [], _LinFunc(0.01, dtype=dt32), 'deg'), + ('beam_config', 'H', [], None), + ('cell_size', 'H', [], _LinFunc(0.001), 'm'), + ('blank_dist', 'H', [], _LinFunc(0.01), 'm'), + ('nominal_corr', 'B', [], None, '%'), + ('unused', 'B', [], None), + ('batt', 'H', [], _LinFunc(0.1, dtype=dt32), 'V'), + ('mag', 'h', [3], None, 'gauss'), + ('accel', 'h', [3], _LinFunc(1. / 16384 * grav, dtype=dt32), 'm/s^2'), + ('ambig_vel', 'I', [], _LinFunc(0.001, dtype=dt32), 'm/s'), + ('data_desc', 'H', [], None), + ('xmit_energy', 'H', [], None, 'dB'), + ('vel_scale', 'b', [], None), + ('power_level_dB', 'b', [], _LinFunc(dtype=dt32)), + ('temp_mag', 'h', [], None), # uncalibrated + ('temp_clock', 'h', [], _LinFunc(0.01, dtype=dt32), 'deg C'), + ('error', 'I', [], None), + ('status', 'I', [], None, 'binary'), + ('_ensemble', 'I', [], None), +] + +_ahrs_def = [ + ('orientmat', 'f', [3, 3], None), + ('quaternions', 'f', [4], None), + ('angrt', 'f', [3], _LinFunc(np.pi / 180, dtype=dt32), 'rad/s'), +] + + +def _calc_bt_struct(config, nb): + flags = lib._headconfig_int2dict(config, mode='bt') + dd = copy(_bt_hdr) + if flags['vel']: + # units handled in Ad2cpReader.sci_data + dd.append(('vel', 'i', [nb], None, 'm/s')) + if flags['dist']: + dd.append(('dist', 'i', [nb], _LinFunc(0.001, dtype=dt32), 'm')) + if flags['fom']: + dd.append(('fom', 'H', [nb], None)) + if flags['ahrs']: + dd += _ahrs_def + return _DataDef(dd) + + +def _calc_echo_struct(config, nc): + flags = lib._headconfig_int2dict(config) + dd = copy(_burst_hdr) + dd[19] = ('blank_dist', 'H', [], _LinFunc(0.001)) # m + if any([flags[nm] for nm in ['vel', 'amp', 'corr', 'alt', 'ast', + 'alt_raw', 'p_gd', 'std']]): + raise Exception("Echosounder ping contains invalid data?") + if flags['echo']: + dd += [('echo', 'H', [nc], _LinFunc(0.01, dtype=dt32), 'dB')] + if flags['ahrs']: + dd += _ahrs_def + return _DataDef(dd) + + +def _calc_burst_struct(config, nb, nc): + flags = lib._headconfig_int2dict(config) + dd = copy(_burst_hdr) + if flags['echo']: + raise Exception("Echosounder data found in velocity ping?") + if flags['vel']: + dd.append(('vel', 'h', [nb, nc], None, 'm/s')) + if flags['amp']: + dd.append(('amp', 'B', [nb, nc], + _LinFunc(0.5, dtype=dt32), 'dB')) + if flags['corr']: + dd.append(('corr', 'B', [nb, nc], None, '%')) + if flags['alt']: + # There may be a problem here with reading 32bit floats if + # nb and nc are odd + dd += [('alt_dist', 'f', [], _LinFunc(dtype=dt32), 'm'), + ('alt_quality', 'H', [], _LinFunc(0.01, dtype=dt32), 'dB'), + ('alt_status', 'H', [], None)] + if flags['ast']: + dd += [ + ('ast_dist', 'f', [], _LinFunc(dtype=dt32), 'm'), + ('ast_quality', 'H', [], _LinFunc(0.01, dtype=dt32), 'dB'), + ('ast_offset_time', 'h', [], _LinFunc(0.0001, dtype=dt32), 's'), + ('ast_pressure', 'f', [], None, 'dbar'), + ('ast_spare', 'B7x', [], None), + ] + if flags['alt_raw']: + dd += [ + ('altraw_nsamp', 'I', [], None), + ('altraw_dsamp', 'H', [], _LinFunc(0.0001, dtype=dt32), 'm'), + ('altraw_samp', 'h', [], None), + ] + if flags['ahrs']: + dd += _ahrs_def + if flags['p_gd']: + dd += [('percent_good', 'B', [nc], None, '%')] + if flags['std']: + dd += [('pitch_std', 'h', [], + _LinFunc(0.01, dtype=dt32), 'deg'), + ('roll_std', 'h', [], + _LinFunc(0.01, dtype=dt32), 'deg'), + ('heading_std', 'h', [], + _LinFunc(0.01, dtype=dt32), 'deg'), + ('press_std', 'h', [], + _LinFunc(0.1, dtype=dt32), 'dbar'), + ('std_spare', 'H22x', [], None)] + return _DataDef(dd) diff --git a/mhkit/dolfyn/io/nortek2_lib.py b/mhkit/dolfyn/io/nortek2_lib.py new file mode 100644 index 000000000..de11d16bc --- /dev/null +++ b/mhkit/dolfyn/io/nortek2_lib.py @@ -0,0 +1,484 @@ +import struct +import os.path as path +import numpy as np +import warnings +from .. import time +from .base import _abspath + + +def _reduce_by_average(data, ky0, ky1): + # Average two arrays together, if they both exist. + if ky1 in data: + tmp = data.pop(ky1) + if ky0 in data: + data[ky0] += tmp + data[ky0] = data[ky0] / 2 + else: + data[ky0] = tmp + + +def _reduce_by_average_angle(data, ky0, ky1, degrees=True): + # Average two arrays of angles together, if they both exist. + if degrees: + rad_fact = np.pi / 180 + else: + rad_fact = 1 + if ky1 in data: + if ky0 in data: + data[ky0] = np.angle( + np.exp(1j * data.pop(ky0) * rad_fact) + + np.exp(1j * data.pop(ky1) * rad_fact)) / rad_fact + else: + data[ky0] = data.pop(ky1) + + +# This is the data-type of the index file. +# This must match what is written-out by the create_index function. +_index_version = 1 +_hdr = struct.Struct(' 60) + # This probably indicates a corrupted byte, so we just insert None. + dt[idx] = None + # None -> NaN in this step + return dt + + +def _create_index(infile, outfile, N_ens, debug): + print("Indexing {}...".format(infile), end='') + fin = open(_abspath(infile), 'rb') + fout = open(_abspath(outfile), 'wb') + fout.write(b'Index Ver:') + fout.write(struct.pack(' 0: + # Covers all id keys saved in "burst mode" + ens[idk] = last_ens[idk]+1 + + if last_ens[idk] > 0 and last_ens[idk] != ens[idk]: + N[idk] += 1 + + fout.write(struct.pack(' 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])) + 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 + + if np.any(np.diff(ens) > 1) and FLAG: + idx['ens'] = np.unwrap(hwe.astype(np.int64), period=period) - hwe[0] + + +def _boolarray_firstensemble_ping(index): + """Return a boolean of the index that indicates only the first ping in + each ensemble. + """ + dens = np.ones(index['ens'].shape, dtype='bool') + dens[1:] = np.diff(index['ens']) != 0 + return dens + + +def get_index(infile, reload=False, debug=False): + """This function reads ad2cp.index files + + Parameters + ---------- + infile: str + Path and filename of ad2cp datafile, not including ".index" + reload: bool + If true, ignore existing .index file and create a new one + debug: bool + If true, run code in debug mode + + Returns + ------- + out: tuple + Tuple containing info held within index file + + """ + index_file = infile + '.index' + if not path.isfile(index_file) or reload: + _create_index(infile, index_file, 2 ** 32, debug) + f = open(_abspath(index_file), 'rb') + file_head = f.read(12) + if file_head[:10] == b'Index Ver:': + index_ver = struct.unpack('> start) & mask + ot = self._get_out_type(mask) + if ot is not None: + out = out.astype(ot) + return out + + +def _getbit(val, n): + return bool((val >> n) & 1) + + +def _headconfig_int2dict(val, mode='burst'): + """Convert the burst Configuration bit-mask to a dict of bools. + + mode: {'burst', 'bt'} + For 'burst' configs, or 'bottom-track' configs. + """ + if mode == 'burst': + return dict( + press_valid=_getbit(val, 0), + temp_valid=_getbit(val, 1), + compass_valid=_getbit(val, 2), + tilt_valid=_getbit(val, 3), + # bit 4 is unused + vel=_getbit(val, 5), + amp=_getbit(val, 6), + corr=_getbit(val, 7), + alt=_getbit(val, 8), + alt_raw=_getbit(val, 9), + ast=_getbit(val, 10), + echo=_getbit(val, 11), + ahrs=_getbit(val, 12), + p_gd=_getbit(val, 13), + std=_getbit(val, 14), + # bit 15 is unused + ) + elif mode == 'bt': + return dict( + press_valid=_getbit(val, 0), + temp_valid=_getbit(val, 1), + compass_valid=_getbit(val, 2), + tilt_valid=_getbit(val, 3), + # bit 4 is unused + vel=_getbit(val, 5), + # bits 6-7 unused + dist=_getbit(val, 8), + fom=_getbit(val, 9), + ahrs=_getbit(val, 10), + # bits 10-15 unused + ) + + +def _status02data(val): + # This is detailed in the 6.1.2 of the Nortek Signature + # Integrators Guide (2017) + bi = _BitIndexer(val) + out = {} + if any(bi[15]): # 'status0_in_use' + out['proc_idle_less_3pct'] = bi[0] + out['proc_idle_less_6pct'] = bi[1] + out['proc_idle_less_12pct'] = bi[2] + + return out + + +def _status2data(val): + # This is detailed in the 6.1.2 of the Nortek Signature + # Integrators Guide (2017) + bi = _BitIndexer(val) + out = {} + out['wakeup_state'] = bi[28:32] + out['orient_up'] = bi[25:28] + out['auto_orientation'] = bi[22:25] + out['previous_wakeup_state'] = bi[18:22] + out['low_volt_skip'] = bi[17] + out['active_config'] = bi[16] + out['echo_index'] = bi[12:16] + out['telemetry_data'] = bi[11] + out['boost_running'] = bi[10] + out['echo_freq_bin'] = bi[5:10] + # 2,3,4 unused + out['bd_scaling'] = bi[1] # if True: cm scaling of blanking dist + # 0 unused + return out + + +def _alt_status2data(val): + # This is detailed in the 6.1.2 of the Nortek Signature + # Integrators Guide (2017) + bi = _BitIndexer(val) + out = {} + out['tilt_over_5deg'] = bi[0] + out['tilt_over_10deg'] = bi[1] + out['multibeam_alt'] = bi[2] + out['n_beams_alt'] = bi[3:7] + out['power_level_idx_alt'] = bi[7:10] + + return out + + +def _beams_cy_int2dict(val, id): + """Convert the beams/coordinate-system bytes to a dict of values. + """ + if id == 28: # 0x1C (echosounder) + return dict(n_cells=val) + + return dict( + n_cells=val & (2 ** 10 - 1), + cy=['ENU', 'XYZ', 'beam', None][val >> 10 & 3], + n_beams=val >> 12) + + +def _isuniform(vec, exclude=[]): + if len(exclude): + return len(set(np.unique(vec)) - set(exclude)) <= 1 + return np.all(vec == vec[0]) + + +def _collapse(vec, name=None, exclude=[]): + """Check that the input vector is uniform, then collapse it to a + single value, otherwise raise a warning. + """ + if _isuniform(vec): + return vec[0] + elif _isuniform(vec, exclude=exclude): + return list(set(np.unique(vec)) - set(exclude))[0] + else: + uniq, idx, counts = np.unique( + vec, return_index=True, return_counts=True) + + if all(e == counts[0] for e in counts): + val = max(vec) # pings saved out of order, but equal # of pings + else: + val = vec[idx[np.argmax(counts)]] + + if not set(uniq) == set([0, val]) and set(counts) == set([1, np.max(counts)]): + # warn when the 'wrong value' is not just a single zero. + warnings.warn("The variable {} is expected to be uniform, but it is not.\n" + "Values found: {} (counts: {}).\n" + "Using the most common value: {}".format( + name, list(uniq), list(counts), val)) + return val + + +def _calc_config(index): + """Calculate the configuration information (e.g., number of pings, + number of beams, struct types, etc.) from the index data. + + Returns + ======= + config : dict + A dict containing the key information for initializing arrays. + """ + ids = np.unique(index['ID']) + config = {} + for id in ids: + if id not in [21, 23, 24, 26, 28]: + continue + if id == 23: + type = 'bt' + else: + type = 'burst' + 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)) + # 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)) + config[id]['_config'] = _config[0] + config[id]['_beams_cy'] = _beams_cy[0] + config[id]['type'] = type + config[id].pop('cy', None) + return config diff --git a/mhkit/dolfyn/io/nortek_defs.py b/mhkit/dolfyn/io/nortek_defs.py new file mode 100644 index 000000000..5caeb1705 --- /dev/null +++ b/mhkit/dolfyn/io/nortek_defs.py @@ -0,0 +1,303 @@ +import numpy as np +nan = np.nan + + +class _VarAtts(): + """A data variable attributes class. + + Parameters + ---------- + dims : (list, optional) + The dimensions of the array other than the 'time' + dimension. By default the time dimension is appended to the + end. To specify a point to place it, place 'n' in that + location. + dtype : (type, optional) + The data type of the array to create (default: float32). + group : (string, optional) + The data group to which this variable should be a part + (default: 'main'). + view_type : (type, optional) + Specify a numpy view to cast the array into. + default_val : (numeric, optional) + The value to initialize with (default: use an empty array). + offset : (numeric, optional) + The offset, 'b', by which to adjust the data when converting to + scientific units. + factor : (numeric, optional) + The factor, 'm', by which to adjust the data when converting to + scientific units. + title_name : (string, optional) + The name of the variable. + units : (string, optional) + The units of this variable. + dim_names : (list, optional) + A list of names for each dimension of the array. + + """ + + def __init__(self, dims=[], dtype=None, group='main', + view_type=None, default_val=None, + offset=0, factor=1, + title_name=None, units=None, dim_names=None, + ): + self.dims = list(dims) + if dtype is None: + dtype = np.float32 + self.dtype = dtype + self.group = group + self.view_type = view_type + self.default_val = default_val + self.offset = offset + self.factor = factor + self.title_name = title_name + self.units = units + self.dim_names = dim_names + + def shape(self, **kwargs): + a = list(self.dims) + hit = False + for ky in kwargs: + if ky in self.dims: + hit = True + a[a.index(ky)] = kwargs[ky] + if hit: + return a + else: + return self.dims + [kwargs['n']] + + def _empty_array(self, **kwargs): + out = np.zeros(self.shape(**kwargs), dtype=self.dtype) + try: + out[:] = np.NaN + except: + pass + if self.view_type is not None: + out = out.view(self.view_type) + return out + + def sci_func(self, data): + """Scale the data to scientific units. + + Parameters + ---------- + data : :class:`` + The data to scale. + + Returns + ------- + retval : {None, data} + If this funciton modifies the data in place it returns None, + otherwise it returns the new data object. + """ + if self.offset != 0: + data += self.offset + if self.factor != 1: + data *= self.factor + return data + + +vec_data = { + 'AnaIn2LSB': _VarAtts(dims=[], + dtype=np.uint8, + group='sys', + units='', + ), + 'Count': _VarAtts(dims=[], + dtype=np.uint8, + group='sys', + units='', + ), + 'PressureMSB': _VarAtts(dims=[], + dtype=np.uint8, + group='data_vars', + units='dbar', + ), + 'AnaIn2MSB': _VarAtts(dims=[], + dtype=np.uint8, + group='sys', + units='', + ), + 'PressureLSW': _VarAtts(dims=[], + dtype=np.uint16, + group='data_vars', + units='dbar', + ), + 'AnaIn1': _VarAtts(dims=[], + dtype=np.uint16, + group='sys', + units='' + ), + 'vel': _VarAtts(dims=[3], + dtype=np.float32, + group='data_vars', + factor=0.001, + default_val=nan, + units='m/s', + ), + 'amp': _VarAtts(dims=[3], + dtype=np.uint8, + group='data_vars', + units='dB', + ), + 'corr': _VarAtts(dims=[3], + dtype=np.uint8, + group='data_vars', + units='%', + ), +} + +vec_sysdata = { + 'time': _VarAtts(dims=[], + dtype=np.float64, + group='coords', + default_val=nan, + units='', + ), + 'batt': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='V', + ), + 'c_sound': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='m/s', + ), + 'heading': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='deg', + ), + 'pitch': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='deg', + ), + 'roll': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='deg', + ), + 'temp': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.01, + units='deg C', + ), + 'error': _VarAtts(dims=[], + dtype=np.uint8, + group='data_vars', + default_val=nan, + units='', + ), + 'status': _VarAtts(dims=[], + dtype=np.uint8, + group='data_vars', + default_val=nan, + units='', + ), + 'AnaIn': _VarAtts(dims=[], + dtype=np.float32, + group='sys', + default_val=nan, + units='', + ), +} + +awac_profile = { + 'time': _VarAtts(dims=[], + dtype=np.float64, + group='coords', + units='', + ), + 'error': _VarAtts(dims=[], + dtype=np.uint16, + group='data_vars', + units='', + ), + 'AnaIn1': _VarAtts(dims=[], + dtype=np.float32, + group='sys', + default_val=nan, + units='n/a', + ), + 'batt': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='V', + ), + 'c_sound': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='m/s', + ), + 'heading': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='deg', + ), + 'pitch': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='deg', + ), + 'roll': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.1, + units='deg', + ), + 'pressure': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.001, + units='dbar', + ), + 'status': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + units='', + ), + 'temp': _VarAtts(dims=[], + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.01, + units='deg C', + ), + 'vel': _VarAtts(dims=[3, 'nbins', 'n'], # how to change this for different # of beams? + dtype=np.float32, + group='data_vars', + default_val=nan, + factor=0.001, + units='m/s', + ), + 'amp': _VarAtts(dims=[3, 'nbins', 'n'], + dtype=np.uint8, + group='data_vars', + units='counts', + ), +} diff --git a/mhkit/dolfyn/io/rdi.py b/mhkit/dolfyn/io/rdi.py new file mode 100644 index 000000000..9cafa06ac --- /dev/null +++ b/mhkit/dolfyn/io/rdi.py @@ -0,0 +1,945 @@ +import numpy as np +import xarray as xr +from .. import time as tmlib +import warnings +from os.path import getsize +from ._read_bin import bin_reader +from .base import _find_userdata, _create_dataset, _abspath +from ..rotate.rdi import _calc_beam_orientmat, _calc_orientmat +from ..rotate.base import _set_coords +from ..rotate.api import set_declination + + +def read_rdi(fname, userdata=None, nens=None, debug=0): + """Read a TRDI binary data file. + + Parameters + ---------- + filename : string + Filename of TRDI file to read. + userdata : True, False, or string of userdata.json filename (default ``True``) + Whether to read the '.userdata.json' file. + nens : None (default: read entire file), int, or 2-element tuple (start, stop) + Number of pings to read from the file + + Returns + ------- + ds : xarray.Dataset + An xarray dataset from the binary instrument data + + """ + # Reads into a dictionary of dictionaries using netcdf naming conventions + # Should be easier to debug + with _RdiReader(fname, debug_level=debug) as ldr: + dat = ldr.load_data(nens=nens) + + # Read in userdata + userdata = _find_userdata(fname, userdata) + for nm in userdata: + dat['attrs'][nm] = userdata[nm] + + if 'time_gps' in dat['coords']: + # GPS data not necessarily sampling at the same rate as ADCP DAQ. + dat = _remove_gps_duplicates(dat) + + # Create xarray dataset from upper level dictionary + ds = _create_dataset(dat) + ds = _set_coords(ds, ref_frame=ds.coord_sys) + + # Create orientation matrices + if 'beam2inst_orientmat' not in ds: + ds['beam2inst_orientmat'] = xr.DataArray(_calc_beam_orientmat( + ds.beam_angle, + ds.beam_pattern == 'convex'), + coords={'x': [1, 2, 3, 4], + 'x*': [1, 2, 3, 4]}, + dims=['x', 'x*']) + + if 'orientmat' not in ds: + ds['orientmat'] = xr.DataArray(_calc_orientmat(ds), + coords={'earth': ['E', 'N', 'U'], + 'inst': ['X', 'Y', 'Z'], + 'time': ds['time']}, + dims=['earth', 'inst', 'time']) + + # Check magnetic declination if provided via software and/or userdata + _set_rdi_declination(ds, fname, inplace=True) + + # VMDAS applies gps correction on velocity in .ENX files only + if fname.rsplit('.')[-1] == 'ENX': + ds.attrs['vel_gps_corrected'] = 1 + else: # (not ENR or ENS) or WinRiver files + ds.attrs['vel_gps_corrected'] = 0 + + # Convert time coords to dt64 + t_coords = [t for t in ds.coords if 'time' in t] + for ky in t_coords: + dt = tmlib.epoch2dt64(ds[ky]) + ds = ds.assign_coords({ky: dt}) + + # Convert time vars to dt64 + t_data = [t for t in ds.data_vars if 'time' in t] + for ky in t_data: + dt = tmlib.epoch2dt64(ds[ky]) + ds[ky].data = dt + + return ds + + +def _remove_gps_duplicates(dat): + """ + Removes duplicate and nan timestamp values in 'time_gps' coordinate, and + ads hardware (ADCP DAQ) timestamp corresponding to GPS acquisition + (in addition to the GPS unit's timestamp). + """ + + dat['data_vars']['hdwtime_gps'] = dat['coords']['time'] + dat['units']['hdwtime'] = 'seconds since 1970-01-01 00:00:00' + + # Remove duplicate timestamp values, if applicable + dat['coords']['time_gps'], idx = np.unique(dat['coords']['time_gps'], + return_index=True) + # Remove nan values, if applicable + nan = np.zeros(dat['coords']['time'].shape, dtype=bool) + if any(np.isnan(dat['coords']['time_gps'])): + nan = np.isnan(dat['coords']['time_gps']) + dat['coords']['time_gps'] = dat['coords']['time_gps'][~nan] + + for key in dat['data_vars']: + if 'gps' in key: + dat['data_vars'][key] = dat['data_vars'][key][idx] + if sum(nan) > 0: + dat['data_vars'][key] = dat['data_vars'][key][~nan] + + return dat + + +def _set_rdi_declination(dat, fname, inplace): + # If magnetic_var_deg is set, this means that the declination is already + # included in the heading and in the velocity data. + + declin = dat.attrs.pop('declination', None) # userdata declination + + if dat.attrs['magnetic_var_deg'] != 0: # from TRDI software if set + dat.attrs['declination'] = dat.attrs['magnetic_var_deg'] + dat.attrs['declination_in_orientmat'] = 1 # logical + + if dat.attrs['magnetic_var_deg'] != 0 and declin is not None: + warnings.warn( + "'magnetic_var_deg' is set to {:.2f} degrees in the binary " + "file '{}', AND 'declination' is set in the 'userdata.json' " + "file. DOLfYN WILL USE THE VALUE of {:.2f} degrees in " + "userdata.json. If you want to use the value in " + "'magnetic_var_deg', delete the value from userdata.json and " + "re-read the file." + .format(dat.attrs['magnetic_var_deg'], fname, declin)) + dat.attrs['declination'] = declin + + if declin is not None: + set_declination(dat, declin, inplace) + + +century = 2000 +data_defs = {'number': ([], 'data_vars', 'uint32', ''), + 'rtc': ([7], 'sys', 'uint16', ''), + 'builtin_test_fail': ([], 'data_vars', 'bool', ''), + 'c_sound': ([], 'data_vars', 'float32', 'm/s'), + 'depth': ([], 'data_vars', 'float32', 'm'), + 'pitch': ([], 'data_vars', 'float32', 'deg'), + 'roll': ([], 'data_vars', 'float32', 'deg'), + 'heading': ([], 'data_vars', 'float32', 'deg'), + 'temp': ([], 'data_vars', 'float32', 'C'), + 'salinity': ([], 'data_vars', 'float32', 'psu'), + 'min_preping_wait': ([], 'data_vars', 'float32', 's'), + 'heading_std': ([], 'data_vars', 'float32', 'deg'), + 'pitch_std': ([], 'data_vars', 'float32', 'deg'), + 'roll_std': ([], 'data_vars', 'float32', 'deg'), + 'adc': ([8], 'sys', 'uint8', ''), + 'error_status_wd': ([], 'attrs', 'float32', ''), + 'pressure': ([], 'data_vars', 'float32', 'dbar'), + 'pressure_std': ([], 'data_vars', 'float32', 'dbar'), + 'vel': (['nc', 4], 'data_vars', 'float32', 'm/s'), + 'amp': (['nc', 4], 'data_vars', 'uint8', 'counts'), + 'corr': (['nc', 4], 'data_vars', 'uint8', 'counts'), + 'prcnt_gd': (['nc', 4], 'data_vars', 'uint8', '%'), + 'status': (['nc', 4], 'data_vars', 'float32', ''), + 'dist_bt': ([4], 'data_vars', 'float32', 'm'), + 'vel_bt': ([4], 'data_vars', 'float32', 'm/s'), + 'corr_bt': ([4], 'data_vars', 'uint8', 'counts'), + 'amp_bt': ([4], 'data_vars', 'uint8', 'counts'), + 'prcnt_gd_bt': ([4], 'data_vars', 'uint8', '%'), + 'time': ([], 'coords', 'float64', ''), + 'etime_gps': ([], 'coords', 'float64', ''), + 'elatitude_gps': ([], 'data_vars', 'float64', 'deg'), + 'elongitude_gps': ([], 'data_vars', 'float64', 'deg'), + 'time_gps': ([], 'coords', 'float64', ''), + 'latitude_gps': ([], 'data_vars', 'float64', 'deg'), + 'longitude_gps': ([], 'data_vars', 'float64', 'deg'), + 'ntime': ([], 'coords', 'float64', ''), + 'flags': ([], 'data_vars', 'float32', ''), + } + + +def _get(dat, nm): + grp = data_defs[nm][1] + if grp is None: + return dat[nm] + else: + return dat[grp][nm] + + +def _in_group(dat, nm): + grp = data_defs[nm][1] + if grp is None: + return nm in dat + else: + return nm in dat[grp] + + +def _pop(dat, nm): + grp = data_defs[nm][1] + if grp is None: + dat.pop(nm) + else: + dat[grp].pop(nm) + + +def _setd(dat, nm, val): + grp = data_defs[nm][1] + if grp is None: + dat[nm] = val + else: + dat[grp][nm] = val + + +def _idata(dat, nm, sz): + group = data_defs[nm][1] + dtype = data_defs[nm][2] + units = data_defs[nm][3] + arr = np.empty(sz, dtype=dtype) + if dtype.startswith('float'): + arr[:] = np.NaN + dat[group][nm] = arr + dat['units'][nm] = units + return dat + + +def _get_size(name, n=None, ncell=0): + sz = list(data_defs[name][0]) # create a copy! + if 'nc' in sz: + sz.insert(sz.index('nc'), ncell) + sz.remove('nc') + if n is None: + return tuple(sz) + return tuple(sz + [n]) + + +class _variable_setlist(set): + def __iadd__(self, vals): + if vals[0] not in self: + self |= set(vals) + return self + + +class _ensemble(): + n_avg = 1 + k = -1 # This is the counter for filling the ensemble object + + def __getitem__(self, nm): + return getattr(self, nm) + + def __init__(self, navg, n_cells): + if navg is None or navg == 0: + navg = 1 + self.n_avg = navg + for nm in data_defs: + setattr(self, nm, + np.zeros(_get_size(nm, n=navg, ncell=n_cells), + dtype=data_defs[nm][2])) + + def clean_data(self,): + self['vel'][self['vel'] == -32.768] = np.NaN + + +class _RdiReader(): + _n_beams = 4 # Placeholder for 5-beam adcp, not currently used. + _pos = 0 + progress = 0 + _cfgnames = dict.fromkeys([4, 5], 'bb-adcp') + _cfgnames.update(dict.fromkeys([8, 9, 16], 'wh-adcp')) + _cfgnames.update(dict.fromkeys([14, 23], 'os-adcp')) + _cfac = 180 / 2 ** 31 + _source = 0 + _fixoffset = 0 + _nbyte = 0 + _winrivprob = False + _search_num = 30000 # Maximum distance? to search + _debug7f79 = None + extrabytes = 0 + + def __init__(self, fname, navg=1, debug_level=0): + self.fname = _abspath(fname) + print('\nReading file {} ...'.format(fname)) + self._debug_level = debug_level + self.cfg = {} + self.cfg['name'] = 'wh-adcp' + self.cfg['sourceprog'] = 'instrument' + self.cfg['prog_ver'] = 0 + self.hdr = {} + self.f = bin_reader(self.fname) + self.read_hdr() + self.read_cfg() + self.f.seek(self._pos, 0) + self.n_avg = navg + self.ensemble = _ensemble(self.n_avg, self.cfg['n_cells']) + self._filesize = getsize(self.fname) + self._npings = int(self._filesize / (self.hdr['nbyte'] + 2 + + self.extrabytes)) + self.vars_read = _variable_setlist(['time']) + + if self._debug_level > 0: + print(' %d pings estimated in this file' % self._npings) + + def read_hdr(self,): + fd = self.f + cfgid = list(fd.read_ui8(2)) + nread = 0 + if self._debug_level > 2: + print(self.f.pos) + print(' cfgid0: [{:x}, {:x}]'.format(*cfgid)) + while (cfgid[0] != 127 or cfgid[1] != 127) or not self.checkheader(): + nextbyte = fd.read_ui8(1) + pos = fd.tell() + nread += 1 + cfgid[1] = cfgid[0] + cfgid[0] = nextbyte + if not pos % 1000: + print(' Still looking for valid cfgid at file ' + 'position %d ...' % pos) + self._pos = self.f.tell() - 2 + if self._debug_level > 0: + print(fd.tell()) + self.read_hdrseg() + + def read_cfg(self,): + cfgid = self.f.read_ui16(1) + self.read_cfgseg() + + def init_data(self,): + outd = {'data_vars': {}, 'coords': {}, + 'attrs': {}, 'units': {}, 'sys': {}} + outd['attrs']['inst_make'] = 'TRDI' + outd['attrs']['inst_model'] = 'Workhorse' + outd['attrs']['inst_type'] = 'ADCP' + outd['attrs']['rotate_vars'] = ['vel', ] + # Currently RDI doesn't use IMUs + outd['attrs']['has_imu'] = 0 + for nm in data_defs: + outd = _idata(outd, nm, + sz=_get_size(nm, self._nens, self.cfg['n_cells'])) + self.outd = outd + + def mean(self, dat): + if self.n_avg == 1: + return dat[..., 0] + return np.nanmean(dat, axis=-1) + + def load_data(self, nens=None): + if nens is None: + self._nens = int(self._npings / self.n_avg) + self._ens_range = (0, self._nens) + elif (nens.__class__ is tuple or nens.__class__ is list) and \ + len(nens) == 2: + nens = list(nens) + if nens[1] == -1: + nens[1] = self._npings + self._nens = int((nens[1] - nens[0]) / self.n_avg) + self._ens_range = nens + self.f.seek((self.hdr['nbyte'] + 2 + self.extrabytes) * + self._ens_range[0], 1) + else: + self._nens = nens + self._ens_range = (0, nens) + if self._debug_level > 0: + print(' taking data from pings %d - %d' % tuple(self._ens_range)) + print(' %d ensembles will be produced.' % self._nens) + self.init_data() + dat = self.outd + dat['coords']['range'] = (self.cfg['bin1_dist_m'] + + np.arange(self.cfg['n_cells']) * + self.cfg['cell_size']) + for nm in self.cfg: + dat['attrs'][nm] = self.cfg[nm] + for iens in range(self._nens): + try: + self.read_buffer() + except: + self.remove_end(iens) + break + self.ensemble.clean_data() + # Fix the 'real-time-clock' century + clock = self.ensemble.rtc[:, :] + if clock[0, 0] < 100: + clock[0, :] += century + # Copy the ensemble to the dataset. + for nm in self.vars_read: + _get(dat, nm)[..., iens] = self.mean(self.ensemble[nm]) + try: + dats = tmlib.date2epoch( + tmlib.datetime(*clock[:6, 0], + microsecond=clock[6, 0] * 10000))[0] + except ValueError: + warnings.warn("Invalid time stamp in ping {}.".format( + int(self.ensemble.number[0]))) + dat['coords']['time'][iens] = np.NaN + else: + dat['coords']['time'][iens] = np.median(dats) + self.finalize() + if 'vel_bt' in dat['data_vars']: + dat['attrs']['rotate_vars'].append('vel_bt') + return dat + + def read_buffer(self,): + fd = self.f + self.ensemble.k = -1 # so that k+=1 gives 0 on the first loop. + self.print_progress() + hdr = self.hdr + while self.ensemble.k < self.ensemble.n_avg - 1: + self.search_buffer() + startpos = fd.tell() - 2 + self.read_hdrseg() + byte_offset = self._nbyte + 2 + for n in range(len(hdr['dat_offsets'])): + id = fd.read_ui16(1) + self._winrivprob = False + self.print_pos() + retval = self.read_dat(id) + if retval == 'FAIL': + break + byte_offset += self._nbyte + if n < (len(hdr['dat_offsets']) - 1): + oset = hdr['dat_offsets'][n + 1] - byte_offset + if oset != 0: + if self._debug_level > 0: + print(' %s: Adjust location by %d\n' % (id, oset)) + fd.seek(oset, 1) + byte_offset = hdr['dat_offsets'][n + 1] + else: + if hdr['nbyte'] - 2 != byte_offset: + if not self._winrivprob: + if self._debug_level > 0: + print(' {:d}: Adjust location by {:d}\n' + .format(id, hdr['nbyte'] - 2 - byte_offset)) + self.f.seek(hdr['nbyte'] - 2 - byte_offset, 1) + byte_offset = hdr['nbyte'] - 2 + readbytes = fd.tell() - startpos + offset = hdr['nbyte'] + 2 - byte_offset + self.check_offset(offset, readbytes) + self.print_pos(byte_offset=byte_offset) + + def search_buffer(self): + """ + Check to see if the next bytes indicate the beginning of a + data block. If not, search for the next data block, up to + _search_num times. + """ + id1 = list(self.f.read_ui8(2)) + search_cnt = 0 + fd = self.f + if self._debug_level > 3: + print(' -->In search_buffer...') + while (search_cnt < self._search_num and + ((id1[0] != 127 or id1[1] != 127) or + not self.checkheader())): + search_cnt += 1 + nextbyte = fd.read_ui8(1) + id1[1] = id1[0] + id1[0] = nextbyte + if search_cnt == self._search_num: + raise Exception( + 'Searched {} entries... Bad data encountered. -> {}' + .format(search_cnt, id1)) + elif search_cnt > 0: + if self._debug_level > 0: + print(' WARNING: Searched {} bytes to find next ' + 'valid ensemble start [{:x}, {:x}]'.format(search_cnt, + *id1)) + + def checkheader(self,): + if self._debug_level > 1: + print(" ###In checkheader.") + fd = self.f + valid = 0 + # print(self.f.pos) + numbytes = fd.read_i16(1) + if numbytes > 0: + fd.seek(numbytes - 2, 1) + cfgid = fd.read_ui8(2) + if len(cfgid) == 2: + fd.seek(-numbytes - 2, 1) + if cfgid[0] == 127 and cfgid[1] in [127, 121]: + if cfgid[1] == 121 and self._debug7f79 is None: + self._debug7f79 = True + valid = 1 + else: + fd.seek(-2, 1) + if self._debug_level > 1: + print(" ###Leaving checkheader.") + return valid + + def read_hdrseg(self,): + fd = self.f + hdr = self.hdr + hdr['nbyte'] = fd.read_i16(1) + if self._debug_level > 2: + print(fd.tell()) + fd.seek(1, 1) + ndat = fd.read_i8(1) + hdr['dat_offsets'] = fd.read_i16(ndat) + self._nbyte = 4 + ndat * 2 + + def print_progress(self,): + self.progress = self.f.tell() + if self._debug_level > 1: + print(' pos %0.0fmb/%0.0fmb\n' % + (self.f.tell() / 1048576., self._filesize / 1048576.)) + if (self.f.tell() - self.progress) < 1048576: + return + + def print_pos(self, byte_offset=-1): + """Print the position in the file, used for debugging. + """ + if self._debug_level > 3: + if hasattr(self, 'ensemble'): + k = self.ensemble.k + else: + k = 0 + print(' pos: %d, pos_: %d, nbyte: %d, k: %d, byte_offset: %d' % + (self.f.tell(), self._pos, self._nbyte, k, byte_offset)) + + def check_offset(self, offset, readbytes): + fd = self.f + if offset != 4 and self._fixoffset == 0: + if self._debug_level >= 1: + print('\n ********************************************\n') + if fd.tell() == self._filesize: + print(' EOF reached unexpectedly - discarding this last ensemble\n') + else: + print(" Adjust location by {:d} (readbytes={:d},hdr['nbyte']={:d}\n" + .format(offset, readbytes, self.hdr['nbyte'])) + print(""" + NOTE - If this appears at the beginning of the file, it may be + a dolfyn problem. Please report this message, with details here: + https://github.com/lkilcher/dolfyn/issues/8 + + - If this appears at the end of the file it means + The file is corrupted and only a partial record + has been read\n + """) + print('\n ********************************************\n') + self._fixoffset = offset - 4 + fd.seek(4 + self._fixoffset, 1) + + def read_dat(self, id): + function_map = {0: (self.read_fixed, []), # 0000 + 128: (self.read_var, []), # 0080 + 256: (self.read_vel, []), # 0100 + 512: (self.read_corr, []), # 0200 + 768: (self.read_amp, []), # 0300 + 1024: (self.read_prcnt_gd, []), # 0400 + 1280: (self.read_status, []), # 0500 + 1536: (self.read_bottom, []), # 0600 + 8192: (self.read_vmdas, []), # 2000 + 8226: (self.read_winriver2, []), # 2022 + 8448: (self.read_winriver, [38]), # 2100 + 8449: (self.read_winriver, [97]), # 2101 + 8450: (self.read_winriver, [45]), # 2102 + 8451: (self.read_winriver, [60]), # 2103 + 8452: (self.read_winriver, [38]), # 2104 + # Loading of these data is currently not implemented: + 1793: (self.skip_Ncol, [4]), # 0701 number of pings + 1794: (self.skip_Ncol, [4]), # 0702 sum of squared vel + 1795: (self.skip_Ncol, [4]), # 0703 sum of velocities + 2560: (self.skip_Ncol, []), # 0A00 Beam 5 velocity + # 0301 Beam 5 Number of good pings + 769: (self.skip_Ncol, []), + # 0302 Beam 5 Sum of squared velocities + 770: (self.skip_Ncol, []), + # 0303 Beam 5 Sum of velocities + 771: (self.skip_Ncol, []), + # 020C Ambient sound profile + 524: (self.skip_Nbyte, [4]), + 12288: (self.skip_Nbyte, [32]), + # 3000 Fixed attitude data format for OS-ADCPs + } + # Call the correct function: + if id in function_map: + if self._debug_level >= 2: + print(' Reading code {}...'.format(hex(id)), end='') + retval = function_map.get(id)[0](*function_map[id][1]) + if retval: + return retval + if self._debug_level >= 2: + print(' success!') + else: + self.read_nocode(id) + + def read_fixed(self,): + if hasattr(self, 'configsize'): + self.f.seek(self.configsize, 1) + self._nbyte = self.configsize + else: + self.read_cfgseg() + if self._debug_level >= 1: + print(self._pos) + self._nbyte += 2 + + def read_cfgseg(self,): + cfgstart = self.f.tell() + cfg = self.cfg + fd = self.f + tmp = fd.read_ui8(5) + prog_ver0 = tmp[0] + cfg['prog_ver'] = tmp[0] + tmp[1] / 100. + cfg['name'] = self._cfgnames.get(tmp[0], + 'unrecognized firmware version') + config = tmp[2:4] + cfg['beam_angle'] = [15, 20, 30][(config[1] & 3)] + #cfg['numbeams'] = [4, 5][int((config[1] & 16) == 16)] + cfg['freq'] = ([75, 150, 300, 600, 1200, 2400, 38][(config[0] & 7)]) + cfg['beam_pattern'] = (['concave', + 'convex'][int((config[0] & 8) == 8)]) + cfg['orientation'] = ['down', 'up'][int((config[0] & 128) == 128)] + #cfg['simflag'] = ['real', 'simulated'][tmp[4]] + fd.seek(1, 1) + cfg['n_beams'] = fd.read_ui8(1) + cfg['n_cells'] = fd.read_ui8(1) + cfg['pings_per_ensemble'] = fd.read_ui16(1) + cfg['cell_size'] = fd.read_ui16(1) * .01 + cfg['blank'] = fd.read_ui16(1) * .01 + cfg['prof_mode'] = fd.read_ui8(1) + cfg['corr_threshold'] = fd.read_ui8(1) + cfg['prof_codereps'] = fd.read_ui8(1) + cfg['min_pgood'] = fd.read_ui8(1) + cfg['evel_threshold'] = fd.read_ui16(1) + cfg['sec_between_ping_groups'] = ( + np.sum(np.array(fd.read_ui8(3)) * + np.array([60., 1., .01]))) + coord_sys = fd.read_ui8(1) + cfg['coord_sys'] = (['beam', 'inst', + 'ship', 'earth'][((coord_sys >> 3) & 3)]) + cfg['use_pitchroll'] = ['no', 'yes'][(coord_sys & 4) == 4] + cfg['use_3beam'] = ['no', 'yes'][(coord_sys & 2) == 2] + cfg['bin_mapping'] = ['no', 'yes'][(coord_sys & 1) == 1] + cfg['xducer_misalign_deg'] = fd.read_i16(1) * .01 + cfg['magnetic_var_deg'] = fd.read_i16(1) * .01 + cfg['sensors_src'] = np.binary_repr(fd.read_ui8(1), 8) + cfg['sensors_avail'] = np.binary_repr(fd.read_ui8(1), 8) + cfg['bin1_dist_m'] = fd.read_ui16(1) * .01 + cfg['xmit_pulse'] = fd.read_ui16(1) * .01 + cfg['water_ref_cells'] = list(fd.read_ui8(2)) # list for attrs + cfg['fls_target_threshold'] = fd.read_ui8(1) + fd.seek(1, 1) + cfg['xmit_lag_m'] = fd.read_ui16(1) * .01 + self._nbyte = 40 + self.configsize = self.f.tell() - cfgstart + + def read_var(self,): + """ Read variable leader """ + fd = self.f + self.ensemble.k += 1 + ens = self.ensemble + k = ens.k + self.vars_read += ['number', + 'rtc', + 'number', + 'builtin_test_fail', + 'c_sound', + 'depth', + 'heading', + 'pitch', + 'roll', + 'salinity', + 'temp', + 'min_preping_wait', + 'heading_std', + 'pitch_std', + 'roll_std', + 'adc'] + ens.number[k] = fd.read_ui16(1) + ens.rtc[:, k] = fd.read_ui8(7) + ens.number[k] += 65535 * fd.read_ui8(1) + ens.builtin_test_fail[k] = fd.read_ui16(1) + ens.c_sound[k] = fd.read_ui16(1) + ens.depth[k] = fd.read_ui16(1) * 0.1 + ens.heading[k] = fd.read_ui16(1) * 0.01 + ens.pitch[k] = fd.read_i16(1) * 0.01 + ens.roll[k] = fd.read_i16(1) * 0.01 + ens.salinity[k] = fd.read_i16(1) + ens.temp[k] = fd.read_i16(1) * 0.01 + ens.min_preping_wait[k] = (fd.read_ui8( + 3) * np.array([60, 1, .01])).sum() + ens.heading_std[k] = fd.read_ui8(1) + ens.pitch_std[k] = fd.read_ui8(1) * 0.1 + ens.roll_std[k] = fd.read_ui8(1) * 0.1 + ens.adc[:, k] = fd.read_i8(8) + self._nbyte = 2 + 40 + + def read_vel(self,): + ens = self.ensemble + self.vars_read += ['vel'] + k = ens.k + ens['vel'][:, :, k] = np.array( + self.f.read_i16(4 * self.cfg['n_cells']) + ).reshape((self.cfg['n_cells'], 4)) * .001 + self._nbyte = 2 + 4 * self.cfg['n_cells'] * 2 + + def read_corr(self,): + k = self.ensemble.k + self.vars_read += ['corr'] + self.ensemble.corr[:, :, k] = np.array( + self.f.read_ui8(4 * self.cfg['n_cells']) + ).reshape((self.cfg['n_cells'], 4)) + self._nbyte = 2 + 4 * self.cfg['n_cells'] + + def read_amp(self,): + k = self.ensemble.k + self.vars_read += ['amp'] + self.ensemble.amp[:, :, k] = np.array( + self.f.read_ui8(4 * self.cfg['n_cells']) + ).reshape((self.cfg['n_cells'], 4)) + self._nbyte = 2 + 4 * self.cfg['n_cells'] + + def read_prcnt_gd(self,): + self.vars_read += ['prcnt_gd'] + self.ensemble.prcnt_gd[:, :, self.ensemble.k] = np.array( + self.f.read_ui8(4 * self.cfg['n_cells']) + ).reshape((self.cfg['n_cells'], 4)) + self._nbyte = 2 + 4 * self.cfg['n_cells'] + + def read_status(self,): + self.vars_read += ['status'] + self.ensemble.status[:, :, self.ensemble.k] = np.array( + self.f.read_ui8(4 * self.cfg['n_cells']) + ).reshape((self.cfg['n_cells'], 4)) + self._nbyte = 2 + 4 * self.cfg['n_cells'] + + def read_bottom(self,): + self.vars_read += ['dist_bt', 'vel_bt', 'corr_bt', 'amp_bt', + 'prcnt_gd_bt'] + fd = self.f + ens = self.ensemble + k = ens.k + cfg = self.cfg + if self._source == 2: + self.vars_read += ['latitude_gps', 'longitude_gps'] + fd.seek(2, 1) + long1 = fd.read_ui16(1) + fd.seek(6, 1) + ens.latitude_gps[k] = fd.read_i32(1) * self._cfac + if ens.latitude_gps[k] == 0: + ens.latitude_gps[k] = np.NaN + else: + fd.seek(14, 1) + ens.dist_bt[:, k] = fd.read_ui16(4) * 0.01 + ens.vel_bt[:, k] = fd.read_i16(4) * 0.001 + ens.corr_bt[:, k] = fd.read_ui8(4) + ens.amp_bt[:, k] = fd.read_ui8(4) + ens.prcnt_gd_bt[:, k] = fd.read_ui8(4) + if self._source == 2: + fd.seek(2, 1) + ens.longitude_gps[k] = ( + long1 + 65536 * fd.read_ui16(1)) * self._cfac + if ens.longitude_gps[k] > 180: + ens.longitude_gps[k] = ens.longitude_gps[k] - 360 + if ens.longitude_gps[k] == 0: + ens.longitude_gps[k] = np.NaN + fd.seek(16, 1) + qual = fd.read_ui8(1) + if qual == 0: + print(' qual==%d,%f %f' % (qual, + ens.latitude_gps[k], + ens.longitude_gps[k])) + ens.latitude_gps[k] = np.NaN + ens.longitude_gps[k] = np.NaN + fd.seek(71 - 45 - 16 - 17, 1) + self._nbyte = 2 + 68 + else: + fd.seek(71 - 45, 1) + self._nbyte = 2 + 68 + if cfg['prog_ver'] >= 5.3: + fd.seek(78 - 71, 1) + ens.dist_bt[:, k] = ens.dist_bt[:, k] + fd.read_ui8(4) * 655.36 + self._nbyte += 11 + if cfg['name'] == 'wh-adcp': + if cfg['prog_ver'] >= 16.20: + fd.seek(4, 1) + self._nbyte += 4 + + def read_vmdas(self,): + """ Read something from VMDAS """ + fd = self.f + # The raw files produced by VMDAS contain a binary navigation data + # block. + self.cfg['sourceprog'] = 'VMDAS' + ens = self.ensemble + k = ens.k + if self._source != 1 and self._debug_level >= 1: + print(' \n***** Apparently a VMDAS file \n\n') + self._source = 1 + self.vars_read += ['time_gps', + 'latitude_gps', + 'longitude_gps', + 'etime_gps', + 'elatitude_gps', + 'elongitude_gps', + 'flags', + 'ntime', ] + utim = fd.read_ui8(4) + date = tmlib.datetime(utim[2] + utim[3] * 256, utim[1], utim[0]) + # This byte is in hundredths of seconds (10s of milliseconds): + time = tmlib.timedelta(milliseconds=(int(fd.read_ui32(1) / 10))) + fd.seek(4, 1) # "PC clock offset from UTC" - clock drift in ms? + ens.time_gps[k] = tmlib.date2epoch(date + time)[0] + ens.latitude_gps[k] = fd.read_i32(1) * self._cfac + ens.longitude_gps[k] = fd.read_i32(1) * self._cfac + ens.etime_gps[k] = tmlib.date2epoch(date + tmlib.timedelta( + milliseconds=int(fd.read_ui32(1) * 10)))[0] + ens.elatitude_gps[k] = fd.read_i32(1) * self._cfac + ens.elongitude_gps[k] = fd.read_i32(1) * self._cfac + fd.seek(12, 1) + ens.flags[k] = fd.read_ui16(1) + fd.seek(6, 1) + utim = fd.read_ui8(4) + date = tmlib.datetime(utim[0] + utim[1] * 256, utim[3], utim[2]) + ens.ntime[k] = tmlib.date2epoch(date + tmlib.timedelta( + milliseconds=int(fd.read_ui32(1) / 10)))[0] + fd.seek(16, 1) + self._nbyte = 2 + 76 + + def read_winriver2(self, ): + startpos = self.f.tell() + self._winrivprob = True + self.cfg['sourceprog'] = 'WINRIVER' + ens = self.ensemble + k = ens.k + if self._source != 3 and self._debug_level >= 1: + warnings.warn(' \n***** Apparently a WINRIVER2 file\n' + '***** WARNING: Raw NMEA data ' + 'handler not yet fully implemented\n\n') + self._source = 3 + spid = self.f.read_ui16(1) + if spid == 104: + sz = self.f.read_ui16(1) + dtime = self.f.read_f64(1) + start_string = self.f.reads(6) + _ = self.f.reads(1) + if start_string != '$GPGGA': + if self._debug_level > 1: + warnings.warn(f'Invalid GPGGA string found in ensemble {k},' + ' skipping...') + return 'FAIL' + gga_time = str(self.f.reads(9)) + time = tmlib.timedelta(hours=int(gga_time[0:2]), + minutes=int(gga_time[2:4]), + seconds=int(gga_time[4:6]), + milliseconds=int(gga_time[7:])*100) + clock = self.ensemble.rtc[:, :] + if clock[0, 0] < 100: + clock[0, :] += century + ens.time_gps[k] = tmlib.date2epoch(tmlib.datetime( + *clock[:3, 0]) + time)[0] + self.f.seek(1, 1) + ens.latitude_gps[k] = self.f.read_f64(1) + tcNS = self.f.reads(1) + if tcNS == 'S': + ens.latitude_gps[k] *= -1 + elif tcNS != 'N': + if self._debug_level > 1: + warnings.warn(f'Invalid GPGGA string found in ensemble {k},' + ' skipping...') + return 'FAIL' + ens.longitude_gps[k] = self.f.read_f64(1) + tcEW = self.f.reads(1) + if tcEW == 'W': + ens.longitude_gps[k] *= -1 + elif tcEW != 'E': + if self._debug_level > 1: + warnings.warn(f'Invalid GPGGA string found in ensemble {k},' + ' skipping...') + return 'FAIL' + ucqual, n_sat = self.f.read_ui8(2) + tmp = self.f.read_float(2) + ens.hdop, ens.altitude = tmp + if self.f.reads(1) != 'M': + if self._debug_level > 1: + warnings.warn(f'Invalid GPGGA string found in ensemble {k},' + ' skipping...') + return 'FAIL' + ggeoid_sep = self.f.read_float(1) + if self.f.reads(1) != 'M': + if self._debug_level > 1: + warnings.warn(f'Invalid GPGGA string found in ensemble {k},' + ' skipping...') + return 'FAIL' + gage = self.f.read_float(1) + gstation_id = self.f.read_ui16(1) + # 4 unknown bytes (2 reserved+2 checksum?) + # 78 bytes for GPGGA string (including \r\n) + # 2 reserved + 2 checksum + self.vars_read += ['longitude_gps', 'latitude_gps', 'time_gps'] + self._nbyte = self.f.tell() - startpos + 2 + if self._debug_level >= 5: + print('') + print(sz, ens.longitude_gps[k]) + + def read_winriver(self, nbt): + self._winrivprob = True + self.cfg['sourceprog'] = 'WINRIVER' + if self._source not in [2, 3]: + if self._debug_level >= 1: + warnings.warn('\n ***** Apparently a WINRIVER file - ' + 'Raw NMEA data handler not yet implemented\n\n') + self._source = 2 + startpos = self.f.tell() + sz = self.f.read_ui16(1) + tmp = self.f.reads(sz) + self._nbyte = self.f.tell() - startpos + 2 + + def skip_Ncol(self, n_skip=1): + self.f.seek(n_skip * self.cfg['n_cells'], 1) + self._nbyte = 2 + n_skip * self.cfg['n_cells'] + + def skip_Nbyte(self, n_skip): + self.f.seek(n_skip, 1) + self._nbyte = self._nbyte = 2 + n_skip + + def read_nocode(self, id): + print(' Unrecognized ID code: %0.4X\n' % id) + + def remove_end(self, iens): + dat = self.outd + print(' Encountered end of file. Cleaning up data.') + for nm in self.vars_read: + _setd(dat, nm, _get(dat, nm)[..., :iens]) + + def finalize(self, ): + """Remove the attributes from the data that were never loaded. + """ + dat = self.outd + for nm in set(data_defs.keys()) - self.vars_read: + _pop(dat, nm) + for nm in self.cfg: + dat['attrs'][nm] = self.cfg[nm] + dat['attrs']['fs'] = (dat['attrs']['sec_between_ping_groups'] * + dat['attrs']['pings_per_ensemble']) ** (-1) + for nm in data_defs: + shp = data_defs[nm][0] + if len(shp) and shp[0] == 'nc' and _in_group(dat, nm): + _setd(dat, nm, np.swapaxes(_get(dat, nm), 0, 1)) + + def __exit__(self, type, value, traceback): + self.f.close() + + def __enter__(self,): + return self diff --git a/mhkit/dolfyn/rotate/api.py b/mhkit/dolfyn/rotate/api.py new file mode 100644 index 000000000..419ca13d1 --- /dev/null +++ b/mhkit/dolfyn/rotate/api.py @@ -0,0 +1,318 @@ +from . import vector as r_vec +from . import awac as r_awac +from . import signature as r_sig +from . import rdi as r_rdi +from .base import _make_model +import numpy as np +import xarray as xr +import warnings + + +# The 'rotation chain' +rc = ['beam', 'inst', 'earth', 'principal'] + +rot_module_dict = { + # Nortek instruments + 'vector': r_vec, + 'awac': r_awac, + 'signature': r_sig, + 'ad2cp': r_sig, + + # TRDI instruments + 'rdi': r_rdi} + + +def rotate2(ds, out_frame='earth', inplace=True): + """Rotate a dataset to a new coordinate system. + + Parameters + ---------- + ds : xr.Dataset + The dolfyn dataset (ADV or ADCP) to rotate. + out_frame : string {'beam', 'inst', 'earth', 'principal'} + The coordinate system to rotate the data into. + inplace : bool (default: True) + When True ``ds`` is modified. When False a copy is returned. + + Returns + ------- + ds : xarray.Dataset or None + Returns a new rotated dataset **when ``inplace=False``**, otherwise + returns None. + + Notes + ----- + - This function rotates all variables in ``ds.attrs['rotate_vars']``. + + - In order to rotate to the 'principal' frame, a value should exist for + ``ds.attrs['principal_heading']``. The function + :func:`calc_principal_heading ` + is recommended for this purpose, e.g.: + + ds.attrs['principal_heading'] = dolfyn.calc_principal_heading(ds['vel'].mean(range)) + + where here we are using the depth-averaged velocity to calculate + the principal direction. + + """ + # Create and return deep copy if not writing "in place" + if not inplace: + ds = ds.copy(deep=True) + + csin = ds.coord_sys.lower() + if csin == 'ship': + csin = 'inst' + + # Returns True/False if head2inst_rotmat has been set/not-set. + r_vec._check_inst2head_rotmat(ds) + + if out_frame == 'principal' and csin != 'earth': + warnings.warn( + "You are attempting to rotate into the 'principal' " + "coordinate system, but the dataset is in the {} " + "coordinate system. Be sure that 'principal_heading' is " + "defined based on the earth coordinate system.".format(csin)) + + rmod = None + for ky in rot_module_dict: + if ky in _make_model(ds): + rmod = rot_module_dict[ky] + break + if rmod is None: + raise ValueError("Rotations are not defined for " + "instrument '{}'.".format(_make_model(ds))) + + # Get the 'indices' of the rotation chain + try: + iframe_in = rc.index(csin) + except ValueError: + raise Exception("The coordinate system of the input " + "dataset, '{}', is invalid." + .format(ds.coord_sys)) + try: + iframe_out = rc.index(out_frame.lower()) + except ValueError: + raise Exception("The specified output coordinate system " + "is invalid, please select one of: 'beam', 'inst', " + "'earth', 'principal'.") + + if iframe_out == iframe_in: + print("Data is already in the {} coordinate system".format(out_frame)) + + if iframe_out > iframe_in: + reverse = False + else: + reverse = True + + while ds.coord_sys.lower() != out_frame.lower(): + csin = ds.coord_sys + if csin == 'ship': + csin = 'inst' + inow = rc.index(csin) + if reverse: + func = getattr(rmod, '_' + rc[inow - 1] + '2' + rc[inow]) + else: + func = getattr(rmod, '_' + rc[inow] + '2' + rc[inow + 1]) + ds = func(ds, reverse=reverse) + + if not inplace: + return ds + + +def calc_principal_heading(vel, tidal_mode=True): + """Compute the principal angle of the horizontal velocity. + + Parameters + ---------- + vel : np.ndarray (2,...,Nt), or (3,...,Nt) + The 2D or 3D velocity array (3rd-dim is ignored in this calculation) + tidal_mode : bool (default: True) + + Returns + ------- + p_heading : float or ndarray + The principal heading in degrees clockwise from North. + + Notes + ----- + When tidal_mode=True, this tool calculates the heading that is + aligned with the bidirectional flow. It does so following these + steps: + 1. rotates vectors with negative velocity by 180 degrees + 2. then doubles those angles to make a complete circle again + 3. computes a mean direction from this, and halves that angle + (to undo the doubled-angles in step 2) + 4. The returned angle is forced to be between 0 and 180. So, you + may need to add 180 to this if you want your positive + direction to be in the western-half of the plane. + + Otherwise, this function simply computes the average direction + using a vector method. + + """ + dt = vel[0] + vel[1] * 1j + if tidal_mode: + # Flip all vectors that are below the x-axis + dt[dt.imag <= 0] *= -1 + # Now double the angle, so that angles near pi and 0 get averaged + # together correctly: + dt *= np.exp(1j * np.angle(dt)) + dt = np.ma.masked_invalid(dt) + # Divide the angle by 2 to remove the doubling done on the previous + # line. + pang = np.angle( + np.nanmean(dt, -1, dtype=np.complex128)) / 2 + else: + pang = np.angle(np.nanmean(dt, -1)) + + return np.round((90 - np.rad2deg(pang)), decimals=4) + + +def set_declination(ds, declin, inplace=True): + """Set the magnetic declination + + Parameters + ---------- + ds : xarray.Dataset or :class:`dolfyn.velocity.Velocity` + declination : float + The value of the magnetic declination in degrees (positive + values specify that Magnetic North is clockwise from True North) + + inplace : bool (default: True) + When True ``ds`` is modified. When False a copy is returned. + + Returns + ------- + ds : xarray.Dataset or None + Returns a new dataset with declination set **when + ``inplace=False``**, otherwise returns None. + + Notes + ----- + This function modifies the data object in the following ways: + + - If the dataset is in the *earth* reference frame at the time of + setting declination, it will be rotated into the "*True-East*, + *True-North*, Up" (hereafter, ETU) coordinate system + + - ``dat['orientmat']`` is modified to be an ETU to + instrument (XYZ) rotation matrix (rather than the magnetic-ENU to + XYZ rotation matrix). Therefore, all rotations to/from the 'earth' + frame will now be to/from this ETU coordinate system. + + - The value of the specified declination will be stored in + ``dat.attrs['declination']`` + + - ``dat['heading']`` is adjusted for declination + (i.e., it is relative to True North). + + - If ``dat.attrs['principal_heading']`` is set, it is + adjusted to account for the orientation of the new 'True' + earth coordinate system (i.e., calling set_declination on a + data object in the principal coordinate system, then calling + dat.rotate2('earth') will yield a data object in the new + 'True' earth coordinate system) + + """ + # Create and return deep copy if not writing "in place" + if not inplace: + ds = ds.copy(deep=True) + + if 'declination' in ds.attrs: + angle = declin - ds.attrs.pop('declination') + else: + angle = declin + cd = np.cos(-np.deg2rad(angle)) + sd = np.sin(-np.deg2rad(angle)) + + # The ordering is funny here because orientmat is the + # transpose of the inst->earth rotation matrix: + Rdec = np.array([[cd, -sd, 0], + [sd, cd, 0], + [0, 0, 1]]) + + if ds.coord_sys == 'earth': + rotate2earth = True + rotate2(ds, 'inst', inplace=True) + else: + rotate2earth = False + + ds['orientmat'].values = np.einsum('kj...,ij->ki...', + ds['orientmat'].values, + Rdec, ) + if 'heading' in ds: + ds['heading'] += angle + if rotate2earth: + rotate2(ds, 'earth', inplace=True) + if 'principal_heading' in ds.attrs: + ds.attrs['principal_heading'] += angle + + ds.attrs['declination'] = declin + ds.attrs['declination_in_orientmat'] = 1 # logical + + if not inplace: + return ds + + +def set_inst2head_rotmat(ds, rotmat, inplace=True): + """ + Set the instrument to head rotation matrix for the Nortek ADV if it + hasn't already been set through a '.userdata.json' file. + + Parameters + ---------- + ds : xarray.Dataset + The data set to assign inst2head_rotmat + rotmat : float + 3x3 rotation matrix + inplace : bool (default: True) + When True ``ds`` is modified. When False a copy is returned. + + Returns + ------- + ds : xarray.Dataset or None + Returns a new dataset with inst2head_rotmat set **when + ``inplace=False``**, otherwise returns None. + + Notes + ----- + If the data object is in earth or principal coords, it is first + rotated to 'inst' before assigning inst2head_rotmat, it is then + rotated back to the coordinate system in which it was input. This + way the inst2head_rotmat gets applied correctly (in inst + coordinate system). + + """ + # Create and return deep copy if not writing "in place" + if not inplace: + ds = ds.copy(deep=True) + + if not ds.inst_model.lower() == 'vector': + raise Exception("Setting 'inst2head_rotmat' is only supported " + "for Nortek Vector ADVs.") + if ds.get('inst2head_rotmat', None) is not None: + raise Exception( + "You are setting 'inst2head_rotmat' after it has already " + "been set. You can only set it once.") + + csin = ds.coord_sys + if csin not in ['inst', 'beam']: + rotate2(ds, 'inst', inplace=True) + + ds['inst2head_rotmat'] = xr.DataArray(np.array(rotmat), + dims=['x', 'x*'], + coords={'x': [1, 2, 3], + 'x*': [1, 2, 3]}) + + ds.attrs['inst2head_rotmat_was_set'] = 1 # logical + # Note that there is no validation that the user doesn't + # change `ds.attrs['inst2head_rotmat']` after calling this + # function. + + if not csin == 'beam': # csin not 'beam', then we're in inst + ds = r_vec._rotate_inst2head(ds) + if csin not in ['inst', 'beam']: + rotate2(ds, csin, inplace=True) + + if not inplace: + return ds diff --git a/mhkit/dolfyn/rotate/awac.py b/mhkit/dolfyn/rotate/awac.py new file mode 100644 index 000000000..2ae0469a8 --- /dev/null +++ b/mhkit/dolfyn/rotate/awac.py @@ -0,0 +1,2 @@ +from .vector import _inst2earth, _earth2principal +from .base import _beam2inst diff --git a/mhkit/dolfyn/rotate/base.py b/mhkit/dolfyn/rotate/base.py new file mode 100644 index 000000000..6550a9387 --- /dev/null +++ b/mhkit/dolfyn/rotate/base.py @@ -0,0 +1,296 @@ +import numpy as np +from numpy.linalg import det, inv +from scipy.spatial.transform import Rotation as R + + +def _make_model(ds): + """The make and model of the instrument that collected the data + in this data object. + """ + return '{} {}'.format(ds.attrs['inst_make'], + ds.attrs['inst_model']).lower() + + +def _check_rotmat_det(rotmat, thresh=1e-3): + """Check that the absolute error of the determinant is small. + + abs(det(rotmat) - 1) < thresh + + Returns a boolean array. + """ + if rotmat.ndim > 2: + rotmat = np.transpose(rotmat) + return np.abs(det(rotmat) - 1) < thresh + + +def _set_coords(ds, ref_frame, forced=False): + """ + Checks the current reference frame and adjusts xarray coords/dims + as necessary. + Makes sure assigned dataarray coordinates match what DOLfYN is reading in. + + """ + make = _make_model(ds) + + XYZ = ['X', 'Y', 'Z'] + ENU = ['E', 'N', 'U'] + beam = list(range(1, ds['vel'].shape[0]+1)) + principal = ['streamwise', 'x-stream', 'vert'] + + # check make/model + if 'rdi' in make: + inst = ['X', 'Y', 'Z', 'err'] + earth = ['E', 'N', 'U', 'err'] + princ = ['streamwise', 'x-stream', 'vert', 'err'] + + elif 'nortek' in make: + if 'signature' in make or 'ad2cp' in make: + inst = ['X', 'Y', 'Z1', 'Z2'] + earth = ['E', 'N', 'U1', 'U2'] + princ = ['streamwise', 'x-stream', 'vert1', 'vert2'] + + else: # AWAC or Vector + inst = XYZ + earth = ENU + princ = principal + + orient = {'beam': beam, 'inst': inst, 'ship': inst, 'earth': earth, + 'principal': princ} + orientIMU = {'beam': XYZ, 'inst': XYZ, 'ship': XYZ, 'earth': ENU, + 'principal': principal} + + if forced: + ref_frame += '-forced' + + # update 'orient' and 'orientIMU' dimensions + ds['dir'] = orient[ref_frame] + if hasattr(ds, 'dirIMU'): + ds['dirIMU'] = orientIMU[ref_frame] + ds['dir'].attrs['ref_frame'] = ref_frame + ds.attrs['coord_sys'] = ref_frame + + # These are essentially one extra line to scroll through + tag = ['', '_echo', '_bt'] + for tg in tag: + if hasattr(ds, 'coord_sys_axes'+tg): + ds.attrs.pop('coord_sys_axes'+tg) + + return ds + + +def _beam2inst(dat, reverse=False, force=False): + """Rotate velocities from beam to instrument coordinates. + + Parameters + ---------- + dat : xarray.Dataset + The ADCP dataset + reverse : bool (default: False) + If True, this function performs the inverse rotation (inst->beam). + force : bool (default: False), or list + When true do not check which coordinate system the data is in + prior to performing this rotation. When forced-rotations are + applied, the string '-forced!' is appended to the + dat.props['coord_sys'] string. If force is a list, it contains + a list of variables that should be rotated (rather than the + default values in adpo.props['rotate_vars']). + + """ + if not force: + if not reverse and dat.coord_sys.lower() != 'beam': + raise ValueError('The input must be in beam coordinates.') + if reverse and dat.coord_sys != 'inst': + raise ValueError('The input must be in inst coordinates.') + + rotmat = dat['beam2inst_orientmat'] + + if isinstance(force, (list, set, tuple)): + # You can force a distinct set of variables to be rotated by + # specifying it here. + rotate_vars = force + else: + rotate_vars = [ky for ky in dat.rotate_vars if ky.startswith('vel')] + + cs = 'inst' + if reverse: + # Can't use transpose because rotation is not between + # orthogonal coordinate systems + rotmat = inv(rotmat) + cs = 'beam' + for ky in rotate_vars: + dat[ky].values = np.einsum('ij,j...->i...', rotmat, dat[ky].values) + + if force: + dat = _set_coords(dat, cs, forced=True) + else: + dat = _set_coords(dat, cs) + + return dat + + +def euler2orient(heading, pitch, roll, units='degrees'): + """ + Calculate the orientation matrix from DOLfYN-defined euler angles. + + This function is not likely to be called during data processing since it requires + DOLfYN-defined euler angles. It is intended for testing DOLfYN. + + The matrices H, P, R are the transpose of the matrices for rotation about z, y, x + as shown here https://en.wikipedia.org/wiki/Rotation_matrix. The transpose is used + because in DOLfYN the orientation matrix is organized for + rotation from EARTH --> INST, while the wiki's matrices are organized for + rotation from INST --> EARTH. + + Parameters + ---------- + heading : |np.ndarray| (Nt) + The heading angle. + pitch : |np.ndarray| (Nt) + The pitch angle. + roll : |np.ndarray| (Nt) + The roll angle. + units : str {'degrees' (default), 'radians'} + + Returns + ======= + omat : |np.ndarray| (3x3xNt) + The orientation matrix of the data. The returned orientation + matrix obeys the following conventions: + + - a "ZYX" rotation order. That is, these variables are computed + assuming that rotation from the earth -> instrument frame happens + by rotating around the z-axis first (heading), then rotating + around the y-axis (pitch), then rotating around the x-axis (roll). + Note this requires matrix multiplication in the reverse order. + + - heading is defined as the direction the x-axis points, positive + clockwise from North (this is *opposite* the right-hand-rule + around the Z-axis), range 0-360 degrees. + + - pitch is positive when the x-axis pitches up (this is *opposite* the + right-hand-rule around the Y-axis) + + - roll is positive according to the right-hand-rule around the + instrument's x-axis + + """ + if units.lower() == 'degrees': + pitch = np.deg2rad(pitch) + roll = np.deg2rad(roll) + heading = np.deg2rad(heading) + elif units.lower() == 'radians': + pass + else: + raise Exception("Invalid units") + + # Converts the DOLfYN-defined heading to one that follows the right-hand-rule + # reports heading as rotation of the y-axis positive counterclockwise from North + heading = np.pi / 2 - heading + + # Converts the DOLfYN-defined pitch to one that follows the right-hand-rule. + pitch = -pitch + + ch = np.cos(heading) + sh = np.sin(heading) + cp = np.cos(pitch) + sp = np.sin(pitch) + cr = np.cos(roll) + sr = np.sin(roll) + zero = np.zeros_like(sr) + one = np.ones_like(sr) + + H = np.array( + [[ch, sh, zero], + [-sh, ch, zero], + [zero, zero, one], ]) + P = np.array( + [[cp, zero, -sp], + [zero, one, zero], + [sp, zero, cp], ]) + R = np.array( + [[one, zero, zero], + [zero, cr, sr], + [zero, -sr, cr], ]) + + return np.einsum('ij...,jk...,kl...->il...', R, P, H) + + +def orient2euler(omat): + """ + Calculate DOLfYN-defined euler angles from the orientation matrix. + + Parameters + ---------- + omat : |np.ndarray| + The orientation matrix + + Returns + ------- + heading : |np.ndarray| + The heading angle. Heading is defined as the direction the x-axis points, + positive clockwise from North (this is *opposite* the right-hand-rule + around the Z-axis), range 0-360 degrees. + pitch : np.ndarray + The pitch angle (degrees). Pitch is positive when the x-axis + pitches up (this is *opposite* the right-hand-rule around the Y-axis). + roll : np.ndarray + The roll angle (degrees). Roll is positive according to the + right-hand-rule around the instrument's x-axis. + + """ + + if isinstance(omat, np.ndarray) and \ + omat.shape[:2] == (3, 3): + pass + elif hasattr(omat, 'orientmat'): + omat = omat['orientmat'].values + + # Note: orientation matrix is earth->inst unless supplied by an external IMU + hh = np.rad2deg(np.arctan2(omat[0, 0], omat[0, 1])) + hh %= 360 + return ( + # heading + hh, + # pitch + np.rad2deg(np.arcsin(omat[0, 2])), + # roll + np.rad2deg(np.arctan2(omat[1, 2], omat[2, 2])), + ) + + +def quaternion2orient(quaternions): + """ + Calculate orientation from Nortek AHRS quaternions, where q = [W, X, Y, Z] + instead of the standard q = [X, Y, Z, W] = [q1, q2, q3, q4] + + Parameters + ---------- + quaternions : xarray.DataArray + Quaternion dataArray from the raw dataset + + Returns + ------- + orientmat : |np.ndarray| + The inst2earth rotation maxtrix as calculated from the quaternions + + See Also + -------- + scipy.spatial.transform.Rotation + + """ + omat = type(quaternions)(np.empty((3, 3, quaternions.time.size))) + omat = omat.rename({'dim_0': 'earth', 'dim_1': 'inst', 'dim_2': 'time'}) + + for i in range(quaternions.time.size): + r = R.from_quat([quaternions.isel(q=1, time=i), + quaternions.isel(q=2, time=i), + quaternions.isel(q=3, time=i), + quaternions.isel(q=0, time=i)]) + omat[..., i] = r.as_matrix() + + # quaternions in inst2earth reference frame, need to rotate to earth2inst + omat.values = np.rollaxis(omat.values, 1) + + xyz = ['X', 'Y', 'Z'] + enu = ['E', 'N', 'U'] + return omat.assign_coords({'inst': xyz, 'earth': enu, 'time': quaternions.time}) diff --git a/mhkit/dolfyn/rotate/rdi.py b/mhkit/dolfyn/rotate/rdi.py new file mode 100644 index 000000000..b9cc5befe --- /dev/null +++ b/mhkit/dolfyn/rotate/rdi.py @@ -0,0 +1,163 @@ +import numpy as np +from .vector import _earth2principal +from .base import _beam2inst, _set_coords + + +def _inst2earth(adcpo, reverse=False, + fixed_orientation=False, force=False): + """ + Rotate velocities from the instrument to earth coordinates. + + This function also rotates data from the 'ship' frame, into the + earth frame when it is in the ship frame (and + ``adcpo.use_pitchroll == 'yes'``). It does not support the + 'reverse' rotation back into the ship frame. + + Parameters + ---------- + adpo : The ADP object containing the data. + + reverse : bool (default: False) + If True, this function performs the inverse rotation + (earth->inst). + fixed_orientation : bool (default: False) + When true, take the average orientation and apply it over the + whole record. + force : bool (default: False) + When true do not check which coordinate system the data is in + prior to performing this rotation. + + Notes + ----- + The rotation matrix is taken from the Teledyne RDI ADCP Coordinate + Transformation manual January 2008 + + """ + csin = adcpo.coord_sys.lower() + cs_allowed = ['inst', 'ship'] + if reverse: + cs_allowed = ['earth'] + if not force and csin not in cs_allowed: + raise ValueError("Invalid rotation for data in {}-frame " + "coordinate system.".format(csin)) + + if 'orientmat' in adcpo: + rmat = adcpo['orientmat'].values + else: + rmat = _calc_orientmat(adcpo) + + # rollaxis gives transpose of orientation matrix. + # The 'rotation matrix' is the transpose of the 'orientation matrix' + # NOTE: the double 'rollaxis' within this function, and here, has + # minimal computational impact because np.rollaxis returns a + # view (not a new array) + rotmat = np.rollaxis(rmat, 1) + if reverse: + cs_new = 'inst' + sumstr = 'jik,j...k->i...k' + else: + cs_new = 'earth' + sumstr = 'ijk,j...k->i...k' + + # Only operate on the first 3-components, b/c the 4th is err_vel + for nm in adcpo.rotate_vars: + dat = adcpo[nm].values + dat[:3] = np.einsum(sumstr, rotmat, dat[:3]) + adcpo[nm].values = dat.copy() + + adcpo = _set_coords(adcpo, cs_new) + + return adcpo + + +def _calc_beam_orientmat(theta=20, convex=True, degrees=True): + """Calculate the rotation matrix from beam coordinates to + instrument head coordinates for an RDI ADCP. + + Parameters + ---------- + theta : is the angle of the heads (usually 20 or 30 degrees) + + convex : is a flag for convex or concave head configuration. + + degrees : is a flag which specifies whether theta is in degrees + or radians (default: degrees=True) + """ + if degrees: + theta = np.deg2rad(theta) + if convex == 0 or convex == -1: + c = -1 + else: + c = 1 + a = 1 / (2. * np.sin(theta)) + b = 1 / (4. * np.cos(theta)) + d = a / (2. ** 0.5) + return np.array([[c * a, -c * a, 0, 0], + [0, 0, -c * a, c * a], + [b, b, b, b], + [d, d, -d, -d]]) + + +def _calc_orientmat(adcpo): + """ + Calculate the orientation matrix using the raw + heading, pitch, roll values from the RDI binary file. + + Parameters + ---------- + adcpo : The ADP object containing the data. + + ## RDI-ADCP-MANUAL (Jan 08, section 5.6 page 18) + The internal tilt sensors do not measure exactly the same + pitch as a set of gimbals would (the roll is the same). Only in + the case of the internal pitch sensor being selected (EZxxx1xxx), + the measured pitch is modified using the following algorithm. + + P = arctan[tan(Tilt1)*cos(Tilt2)] (Equation 18) + + Where: Tilt1 is the measured pitch from the internal sensor, and + Tilt2 is the measured roll from the internal sensor The raw pitch + (Tilt 1) is recorded in the variable leader. P is set to 0 if the + "use tilt" bit of the EX command is not set.""" + + r = np.deg2rad(adcpo['roll'].values) + p = np.arctan(np.tan(np.deg2rad(adcpo['pitch'].values)) * np.cos(r)) + h = np.deg2rad(adcpo['heading'].values) + + if 'rdi' in adcpo.inst_make.lower(): + if adcpo.orientation == 'up': + """ + ## RDI-ADCP-MANUAL (Jan 08, section 5.6 page 18) + Since the roll describes the ship axes rather than the + instrument axes, in the case of upward-looking + orientation, 180 degrees must be added to the measured + roll before it is used to calculate M. This is equivalent + to negating the first and third columns of M. R is set + to 0 if the "use tilt" bit of the EX command is not set. + """ + r += np.pi + if (adcpo.coord_sys == 'ship' and adcpo.use_pitchroll == 'yes'): + r[:] = 0 + p[:] = 0 + + ch = np.cos(h) + sh = np.sin(h) + cr = np.cos(r) + sr = np.sin(r) + cp = np.cos(p) + sp = np.sin(p) + rotmat = np.empty((3, 3, len(r))) + rotmat[0, 0, :] = ch * cr + sh * sp * sr + rotmat[0, 1, :] = sh * cp + rotmat[0, 2, :] = ch * sr - sh * sp * cr + rotmat[1, 0, :] = -sh * cr + ch * sp * sr + rotmat[1, 1, :] = ch * cp + rotmat[1, 2, :] = -sh * sr - ch * sp * cr + rotmat[2, 0, :] = -cp * sr + rotmat[2, 1, :] = sp + rotmat[2, 2, :] = cp * cr + + # The 'orientation matrix' is the transpose of the 'rotation matrix'. + omat = np.rollaxis(rotmat, 1) + + return omat diff --git a/mhkit/dolfyn/rotate/signature.py b/mhkit/dolfyn/rotate/signature.py new file mode 100644 index 000000000..a084e79a7 --- /dev/null +++ b/mhkit/dolfyn/rotate/signature.py @@ -0,0 +1,137 @@ +from .vector import _earth2principal, _euler2orient as euler2orient +from .base import _beam2inst +from . import base as rotb +import numpy as np +import warnings +from numpy.linalg import inv + + +def _inst2earth(adcpo, reverse=False, rotate_vars=None, force=False): + """ + Rotate data in an ADCP object to the earth from the instrument + frame (or vice-versa). + + Parameters + ---------- + adcpo : The adv object containing the data. + + reverse : bool (default: False) + If True, this function performs the inverse rotation + (earth->inst). + + rotate_vars : iterable + The list of variables to rotate. By default this is taken from + adcpo.props['rotate_vars']. + + force : Do not check which frame the data is in prior to + performing this rotation. + + """ + + if reverse: + # The transpose of the rotation matrix gives the inverse + # rotation, so we simply reverse the order of the einsum: + sumstr = 'jik,j...k->i...k' + cs_now = 'earth' + cs_new = 'inst' + else: + sumstr = 'ijk,j...k->i...k' + cs_now = 'inst' + cs_new = 'earth' + + # if ADCP is upside down + if adcpo.orientation == 'down': + down = True + else: # orientation = 'up' or 'AHRS' + down = False + + if rotate_vars is None: + if 'rotate_vars' in adcpo.attrs: + rotate_vars = adcpo.rotate_vars + else: + rotate_vars = ['vel'] + + cs = adcpo.coord_sys.lower() + if not force: + if cs == cs_new: + print("Data is already in the '%s' coordinate system" % cs_new) + return + elif cs != cs_now: + raise ValueError( + "Data must be in the '%s' frame when using this function" % + cs_now) + + if 'orientmat' in adcpo: + rmat = adcpo['orientmat'].values + else: + rmat = euler2orient(adcpo['heading'].values, adcpo['pitch'].values, + adcpo['roll'].values) + + # Take the transpose of the orientation to get the inst->earth rotation + # matrix. + rmat = np.rollaxis(rmat, 1) + + _dcheck = rotb._check_rotmat_det(rmat) + if not _dcheck.all(): + warnings.warn("Invalid orientation matrix" + " (determinant != 1) at" + " indices: {}." + .format(np.nonzero(~_dcheck)[0]), + UserWarning) + + # The dictionary of rotation matrices for different sized arrays. + rmd = {3: rmat, } + + # The 4-row rotation matrix assume that rows 0,1 are u,v, + # and 2,3 are independent estimates of w. + tmp = rmd[4] = np.zeros((4, 4, rmat.shape[-1]), dtype=np.float64) + tmp[:3, :3] = rmat + # Copy row 2 to 3 + tmp[3, :2] = rmat[2, :2] + tmp[3, 3] = rmat[2, 2] + # Extend rows 0,1 + tmp[0, 2:] = rmat[0, 2] / 2 + tmp[1, 2:] = rmat[1, 2] / 2 + + if reverse: + # 3-element inverse handled by sumstr definition (transpose) + rmd[4] = np.moveaxis(inv(np.moveaxis(rmd[4], -1, 0)), 0, -1) + + for nm in rotate_vars: + dat = adcpo[nm].values + n = dat.shape[0] + # Nortek documents sign change for upside-down instruments + if down: + sign = np.array([1, -1, -1, -1], ndmin=dat.ndim).T + signIMU = np.array([1, -1, -1], ndmin=dat.ndim).T + if not reverse: + if n == 3: + dat = np.einsum(sumstr, rmd[3], signIMU*dat) + elif n == 4: + dat = np.einsum('ijk,j...k->i...k', rmd[4], sign*dat) + else: + raise Exception("The entry {} is not a vector, it cannot" + "be rotated.".format(nm)) + + elif reverse: + if n == 3: + dat = signIMU*np.einsum(sumstr, rmd[3], dat) + elif n == 4: + dat = sign*np.einsum('ijk,j...k->i...k', rmd[4], dat) + else: + raise Exception("The entry {} is not a vector, it cannot" + "be rotated.".format(nm)) + + else: # 'up' and AHRS + if n == 3: + dat = np.einsum(sumstr, rmd[3], dat) + elif n == 4: + dat = np.einsum('ijk,j...k->i...k', rmd[4], dat) + else: + raise Exception("The entry {} is not a vector, it cannot" + "be rotated.".format(nm)) + adcpo[nm].values = dat.copy() + + adcpo = rotb._set_coords(adcpo, cs_new) + + return adcpo diff --git a/mhkit/dolfyn/rotate/vector.py b/mhkit/dolfyn/rotate/vector.py new file mode 100644 index 000000000..a4eedb6d2 --- /dev/null +++ b/mhkit/dolfyn/rotate/vector.py @@ -0,0 +1,249 @@ +import numpy as np +import warnings +from . import base as rotb + + +def _beam2inst(dat, reverse=False, force=False): + # Order of rotations matters + # beam->head(ADV instrument head)->inst(ADV battery case|imu) + if reverse: + # First rotate velocities from ADV inst frame back to head frame + dat = _rotate_inst2head(dat, reverse=reverse) + # Now rotate from the head frame to the beam frame + dat = rotb._beam2inst(dat, reverse=reverse, force=force) + + # inst(ADV battery case|imu)->head(ADV instrument head)->beam + else: + # First rotate velocities from beam to ADV head frame + dat = rotb._beam2inst(dat, force=force) + # Then rotate from ADV head frame to ADV inst frame + dat = _rotate_inst2head(dat) + + # Set the docstring to match the default rotation func + _beam2inst.__doc_ = rotb._beam2inst.__doc__ + + return dat + + +def _inst2earth(advo, reverse=False, rotate_vars=None, force=False): + """ + Rotate data in an ADV object to the earth from the instrument + frame (or vice-versa). + + Parameters + ---------- + advo : The adv object containing the data. + + reverse : bool (default: False) + If True, this function performs the inverse rotation + (earth->inst). + + rotate_vars : iterable + The list of variables to rotate. By default this is taken from + advo.props['rotate_vars']. + + force : Do not check which frame the data is in prior to + performing this rotation. + + """ + if reverse: # earth->inst + # The transpose of the rotation matrix gives the inverse + # rotation, so we simply reverse the order of the einsum: + sumstr = 'jik,j...k->i...k' + cs_now = 'earth' + cs_new = 'inst' + else: # inst->earth + sumstr = 'ijk,j...k->i...k' + cs_now = 'inst' + cs_new = 'earth' + + if rotate_vars is None: + if 'rotate_vars' in advo.attrs: + rotate_vars = advo.rotate_vars + else: + rotate_vars = ['vel'] + + cs = advo.coord_sys.lower() + if not force: + if cs == cs_new: + print("Data is already in the '%s' coordinate system" % cs_new) + return + elif cs != cs_now: + raise ValueError( + "Data must be in the '%s' frame when using this function" % + cs_now) + + if hasattr(advo, 'orientmat'): + omat = advo['orientmat'].values + else: + if 'vector' in advo.inst_model.lower(): + orientation_down = advo['orientation_down'] + + omat = _calc_omat(advo['heading'].values, advo['pitch'].values, + advo['roll'].values, orientation_down) + + # Take the transpose of the orientation to get the inst->earth rotation + # matrix. + rmat = np.rollaxis(omat, 1) + + _dcheck = rotb._check_rotmat_det(rmat) + if not _dcheck.all(): + warnings.warn("Invalid orientation matrix (determinant != 1) at indices: {}." + .format(np.nonzero(~_dcheck)[0]), UserWarning) + + for nm in rotate_vars: + n = advo[nm].shape[0] + if n != 3: + raise Exception("The entry {} is not a vector, it cannot " + "be rotated.".format(nm)) + advo[nm].values = np.einsum(sumstr, rmat, advo[nm]) + + advo = rotb._set_coords(advo, cs_new) + + return advo + + +def _calc_omat(hh, pp, rr, orientation_down=None): + rr = rr.copy() + pp = pp.copy() + hh = hh.copy() + if np.isnan(rr[-1]) and np.isnan(pp[-1]) and np.isnan(hh[-1]): + # The end of the data may not have valid orientations + lastgd = np.nonzero(~np.isnan(rr + pp + hh))[0][-1] + rr[lastgd:] = rr[lastgd] + pp[lastgd:] = pp[lastgd] + hh[lastgd:] = hh[lastgd] + if orientation_down is not None: + # For Nortek Vector ADVs: 'down' configuration means the head was + # pointing 'up', where the 'up' orientation corresponds to the + # communication cable being up. Check the Nortek coordinate + # transform matlab script for more info. + rr[orientation_down.astype(bool)] += 180 + + return _euler2orient(hh, pp, rr) + + +def _rotate_inst2head(advo, reverse=False): + if not _check_inst2head_rotmat(advo): + # This object doesn't have a head2inst_rotmat, so we do nothing. + return advo + if reverse: # head->inst + advo['vel'].values = np.dot(advo['inst2head_rotmat'].T, advo['vel']) + else: # inst->head + advo['vel'].values = np.dot(advo['inst2head_rotmat'], advo['vel']) + + return advo + + +def _check_inst2head_rotmat(advo): + if advo.get('inst2head_rotmat', None) is None: + # This is the default value, and we do nothing. + return False + if not advo.inst2head_rotmat_was_set: + raise Exception("The inst2head rotation matrix exists in props, " + "but it was not set using `set_inst2head_rotmat.") + if not rotb._check_rotmat_det(advo.inst2head_rotmat.values): + raise ValueError("Invalid inst2head_rotmat (determinant != 1).") + return True + + +def _earth2principal(advo, reverse=False): + """ + Rotate data in an ADV dataset to/from principal axes. Principal + heading must be within the dataset. + + All data in the advo.attrs['rotate_vars'] list will be + rotated by the principal heading, and also if the data objet has an + orientation matrix (orientmat) it will be rotated so that it + represents the orientation of the ADV in the principal + (reverse:earth) frame. + + Parameters + ---------- + advo : The adv object containing the data. + reverse : bool (default: False) + If True, this function performs the inverse rotation + (principal->earth). + + """ + # This is in degrees CW from North + ang = np.deg2rad(90 - advo.principal_heading) + # convert this to radians CCW from east (which is expected by + # the rest of the function) + + if reverse: + cs_now = 'principal' + cs_new = 'earth' + else: + ang *= -1 + cs_now = 'earth' + cs_new = 'principal' + + cs = advo.coord_sys.lower() + if cs == cs_new: + print('Data is already in the %s coordinate system' % cs_new) + return + elif cs != cs_now: + raise ValueError( + 'Data must be in the {} frame ' + 'to use this function'.format(cs_now)) + + # Calculate the rotation matrix: + cp, sp = np.cos(ang), np.sin(ang) + rotmat = np.array([[cp, -sp, 0], + [sp, cp, 0], + [0, 0, 1]], dtype=np.float32) + + # Perform the rotation: + for nm in advo.rotate_vars: + dat = advo[nm].values + dat[:2] = np.einsum('ij,j...->i...', rotmat[:2, :2], dat[:2]) + advo[nm].values = dat.copy() + + # Finalize the output. + advo = rotb._set_coords(advo, cs_new) + + return advo + + +def _euler2orient(heading, pitch, roll, units='degrees'): + # For Nortek data only. + # The heading, pitch, roll used here are from the Nortek binary files. + + # Heading input is clockwise from North + # Returns a rotation matrix that rotates earth (ENU) -> inst. + # This is based on the Nortek `Transforms.m` file, available in + # the refs folder. + if units.lower() == 'degrees': + pitch = np.deg2rad(pitch) + roll = np.deg2rad(roll) + heading = np.deg2rad(heading) + + # The definition of heading below is consistent with the right-hand-rule; + # heading is the angle positive counterclockwise from North of the y-axis. + + # This also involved swapping the sign on sh in the def of omat + # below from the values provided in the Nortek Matlab script. + heading = (np.pi / 2 - heading) + + ch = np.cos(heading) + sh = np.sin(heading) + cp = np.cos(pitch) + sp = np.sin(pitch) + cr = np.cos(roll) + sr = np.sin(roll) + + # Note that I've transposed these values (from what is defined in + # Nortek matlab script), so that the omat is earth->inst + omat = np.empty((3, 3, len(sh)), dtype=np.float32) + omat[0, 0, :] = ch * cp + omat[1, 0, :] = -ch * sp * sr - sh * cr + omat[2, 0, :] = -ch * cr * sp + sh * sr + omat[0, 1, :] = sh * cp + omat[1, 1, :] = -sh * sp * sr + ch * cr + omat[2, 1, :] = -sh * cr * sp - ch * sr + omat[0, 2, :] = sp + omat[1, 2, :] = sr * cp + omat[2, 2, :] = cp * cr + + return omat diff --git a/mhkit/dolfyn/time.py b/mhkit/dolfyn/time.py new file mode 100644 index 000000000..c69bd78c1 --- /dev/null +++ b/mhkit/dolfyn/time.py @@ -0,0 +1,266 @@ +from datetime import datetime, timedelta, timezone +import numpy as np +from .tools.misc import fillgaps + + +def _fullyear(year): + if year > 100: + return year + year += 1900 + 100 * (year < 90) + return year + + +def epoch2dt64(ep_time): + """ + Convert from epoch time (seconds since 1/1/1970 00:00:00) to + numpy.datetime64 array + + Parameters + ---------- + ep_time : xarray.DataArray + Time coordinate data-array or single time element + + Returns + ------- + time : numpy.datetime64 + The converted datetime64 array + + """ + # assumes t0=1970-01-01 00:00:00 + out = np.array(ep_time.astype('int')).astype('datetime64[s]') + out = out + ((ep_time % 1) * 1e9).astype('timedelta64[ns]') + return out + + +def dt642epoch(dt64): + """ + Convert numpy.datetime64 array to epoch time + (seconds since 1/1/1970 00:00:00) + + Parameters + ---------- + dt64 : numpy.datetime64 + Single or array of datetime64 object(s) + + Returns + ------- + time : float + Epoch time (seconds since 1/1/1970 00:00:00) + + """ + return dt64.astype('datetime64[ns]').astype('float') / 1e9 + + +def date2dt64(dt): + """ + Convert numpy.datetime64 array to list of datetime objects + + Parameters + ---------- + time : datetime.datetime + The converted datetime object + + Returns + ------- + dt64 : numpy.datetime64 + Single or array of datetime64 object(s) + + """ + return np.array(dt).astype('datetime64[ns]') + + +def dt642date(dt64): + """ + Convert numpy.datetime64 array to list of datetime objects + + Parameters + ---------- + dt64 : numpy.datetime64 + Single or array of datetime64 object(s) + + Returns + ------- + time : datetime.datetime + The converted datetime object + + """ + return epoch2date(dt642epoch(dt64)) + + +def epoch2date(ep_time, offset_hr=0, to_str=False): + """ + Convert from epoch time (seconds since 1/1/1970 00:00:00) to a list + of datetime objects + + Parameters + ---------- + ep_time : xarray.DataArray + Time coordinate data-array or single time element + offset_hr : int + Number of hours to offset time by (e.g. UTC -7 hours = PDT) + to_str : logical + Converts datetime object to a readable string + + Returns + ------- + time : datetime.datetime + The converted datetime object or list(strings) + + Notes + ----- + The specific time instance is set during deployment, usually sync'd to the + deployment computer. The time seen by |dlfn| is in the timezone of the + deployment computer, which is unknown to |dlfn|. + + """ + try: + ep_time = ep_time.values + except AttributeError: + pass + + if isinstance(ep_time, (np.ndarray)) and ep_time.ndim == 0: + ep_time = [ep_time.item()] + elif not isinstance(ep_time, (np.ndarray, list)): + ep_time = [ep_time] + + ######### IMPORTANT ######### + # Note the use of `utcfromtimestamp` here, rather than `fromtimestamp` + # This is CRITICAL! See the difference between those functions here: + # https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp + # Long story short: `fromtimestamp` used system-specific timezone + # info to calculate the datetime object, but returns a + # timezone-agnostic object. + if offset_hr != 0: + delta = timedelta(hours=offset_hr) + time = [datetime.utcfromtimestamp(t) + delta for t in ep_time] + else: + time = [datetime.utcfromtimestamp(t) for t in ep_time] + + if to_str: + time = date2str(time) + + return time + + +def date2str(dt, format_str=None): + """ + Convert list of datetime objects to legible strings + + Parameters + ---------- + dt : datetime.datetime + Single or list of datetime object(s) + format_str : string + Timestamp string formatting, default: '%Y-%m-%d %H:%M:%S.%f'. + See datetime.strftime documentation for timestamp string formatting + + Returns + ------- + time : string + Converted timestamps + + """ + if format_str is None: + format_str = '%Y-%m-%d %H:%M:%S.%f' + + if not isinstance(dt, list): + dt = [dt] + + return [t.strftime(format_str) for t in dt] + + +def date2epoch(dt): + """ + Convert list of datetime objects to epoch time + + Parameters + ---------- + dt : datetime.datetime + Single or list of datetime object(s) + + Returns + ------- + time : float + Datetime converted to epoch time (seconds since 1/1/1970 00:00:00) + + """ + if not isinstance(dt, list): + dt = [dt] + + return [t.replace(tzinfo=timezone.utc).timestamp() for t in dt] + + +def date2matlab(dt): + """ + Convert list of datetime objects to MATLAB datenum + + Parameters + ---------- + dt : datetime.datetime + List of datetime objects + + Returns + ------- + time : float + List of timestamps in MATLAB datnum format + + """ + time = list() + for i in range(len(dt)): + mdn = dt[i] + timedelta(days=366) + frac_seconds = (dt[i]-datetime(dt[i].year, dt[i].month, + dt[i].day, 0, 0, 0)).seconds / (24*60*60) + frac_microseconds = dt[i].microsecond / (24*60*60*1000000) + time.append(mdn.toordinal() + frac_seconds + frac_microseconds) + + return time + + +def matlab2date(matlab_dn): + """ + Convert MATLAB datenum to list of datetime objects + + Parameters + ---------- + matlab_dn : float + List of timestamps in MATLAB datnum format + + Returns + ------- + dt : datetime.datetime + List of datetime objects + + """ + time = list() + for i in range(len(matlab_dn)): + day = datetime.fromordinal(int(matlab_dn[i])) + dayfrac = timedelta(days=matlab_dn[i] % 1) - timedelta(days=366) + time.append(day + dayfrac) + + # Datenum is precise down to 100 microseconds - add difference to round + us = int(round(time[i].microsecond/100, 0))*100 + time[i] = time[i].replace(microsecond=time[i].microsecond) + \ + timedelta(microseconds=us-time[i].microsecond) + + return time + + +def _fill_time_gaps(epoch, sample_rate_hz): + """Fill gaps (NaN values) in the timeseries by simple linear + interpolation. The ends are extrapolated by stepping + forward/backward by 1/sample_rate_hz. + """ + # epoch is seconds since 1970 + dt = 1. / sample_rate_hz + epoch = fillgaps(epoch) + if np.isnan(epoch[0]): + i0 = np.nonzero(~np.isnan(epoch))[0][0] + delta = np.arange(-i0, 0, 1) * dt + epoch[:i0] = epoch[i0] + delta + if np.isnan(epoch[-1]): + # Search backward through the array to get the 'negative index' + ie = -np.nonzero(~np.isnan(epoch[::-1]))[0][0] - 1 + delta = np.arange(1, -ie, 1) * dt + epoch[(ie + 1):] = epoch[ie] + delta + + return epoch diff --git a/mhkit/dolfyn/tools/misc.py b/mhkit/dolfyn/tools/misc.py new file mode 100644 index 000000000..48c53aef2 --- /dev/null +++ b/mhkit/dolfyn/tools/misc.py @@ -0,0 +1,350 @@ +import numpy as np +from scipy.signal import medfilt2d, convolve2d + + +def _nans(*args, **kwargs): + out = np.empty(*args, **kwargs) + if np.issubdtype(out.flatten()[0], np.integer): + out[:] = 0 + return out + else: + out[:] = np.nan + return out + + +def _nans_like(*args, **kwargs): + out = np.empty_like(*args, **kwargs) + out[:] = np.nan + return out + + +def _find(arr): + return np.nonzero(np.ravel(arr))[0] + + +def detrend(arr, axis=-1, in_place=False): + """Remove a linear trend from arr. + + Parameters + ---------- + arr : array_like + The array from which to remove a linear trend. + axis : int + The axis along which to operate. + + Notes + ----- + This method is copied from the matplotlib.mlab library, but + implements the covariance calcs explicitly for added speed. + + This works much faster than mpl.mlab.detrend for multi-dimensional + arrays, and is also faster than linalg.lstsq methods. + """ + arr = np.asarray(arr) + if not in_place: + arr = arr.copy() + sz = np.ones(arr.ndim, dtype=int) + sz[axis] = arr.shape[axis] + x = np.arange(sz[axis], dtype=np.float_).reshape(sz) + x -= np.nanmean(x, axis=axis, keepdims=True) + arr -= np.nanmean(arr, axis=axis, keepdims=True) + b = np.nanmean((x * arr), axis=axis, keepdims=True) / \ + np.nanmean((x ** 2), axis=axis, keepdims=True) + arr -= b * x + return arr + + +def group(bl, min_length=0): + """Find continuous segments in a boolean array. + + Parameters + ---------- + bl : |np.ndarray| (dtype='bool') + The input boolean array. + min_length : int (optional) + Specifies the minimum number of continuos points to consider a + `group` (i.e. that will be returned). + + Returns + ------- + out : np.ndarray(slices,) + a vector of slice objects, which indicate the continuous + sections where `bl` is True. + + Notes + ----- + This function has funny behavior for single points. It will + return the same two indices for the beginning and end. + + """ + if not any(bl): + return np.empty(0) + vl = np.diff(bl.astype('int')) + ups = np.nonzero(vl == 1)[0] + 1 + dns = np.nonzero(vl == -1)[0] + 1 + if bl[0]: + if len(ups) == 0: + ups = np.array([0]) + else: + ups = np.concatenate((np.arange([0]), [len(ups)])) + if bl[-1]: + if len(dns) == 0: + dns = np.array([len(bl)]) + else: + dns = np.concatenate((dns, [len(bl)])) + out = np.empty(len(dns), dtype='O') + idx = 0 + for u, d in zip(ups, dns): + if d - u < min_length: + continue + out[idx] = slice(u, d) + idx += 1 + return out[:idx] + + +def slice1d_along_axis(arr_shape, axis=0): + """ + Return an iterator object for looping over 1-D slices, along ``axis``, of + an array of shape arr_shape. + + Parameters + ---------- + arr_shape : tuple,list + Shape of the array over which the slices will be made. + axis : integer + Axis along which `arr` is sliced. + + Returns + ------- + Iterator : object + The iterator object returns slice objects which slices arrays of + shape arr_shape into 1-D arrays. + + Examples + -------- + >> out=np.empty(replace(arr.shape,0,1)) + >> for slc in slice1d_along_axis(arr.shape,axis=0): + >> out[slc]=my_1d_function(arr[slc]) + + """ + nd = len(arr_shape) + if axis < 0: + axis += nd + ind = [0] * (nd - 1) + i = np.zeros(nd, 'O') + indlist = list(range(nd)) + indlist.remove(axis) + i[axis] = slice(None) + itr_dims = np.asarray(arr_shape).take(indlist) + Ntot = np.product(itr_dims) + i.put(indlist, ind) + k = 0 + while k < Ntot: + # increment the index + n = -1 + while (ind[n] >= itr_dims[n]) and (n > (1 - nd)): + ind[n - 1] += 1 + ind[n] = 0 + n -= 1 + i.put(indlist, ind) + yield tuple(i) + ind[-1] += 1 + k += 1 + + +def fillgaps(a, maxgap=np.inf, dim=0, extrapFlg=False): + """Linearly fill NaN value in an array. + + Parameters + ---------- + a : |np.ndarray| + The array to be filled. + maxgap : |np.ndarray| (optional: inf) + The maximum gap to fill. + dim : int (optional: 0) + The dimension to operate along. + extrapFlg : bool (optional: False) + Whether to extrapolate if NaNs are found at the ends of the + array. + + See Also + -------- + mhkit.dolfyn.tools.misc.interpgaps : Linearly interpolates in time. + + Notes + ----- + This function interpolates assuming spacing/timestep between + successive points is constant. If the spacing is not constant, use + interpgaps. + + """ + + # If this is a multi-dimensional array, operate along axis dim. + if a.ndim > 1: + for inds in slice1d_along_axis(a.shape, dim): + fillgaps(a[inds], maxgap, 0, extrapFlg) + return + + a = np.asarray(a) + nd = a.ndim + if dim < 0: + dim += nd + if (dim >= nd): + raise ValueError("dim must be less than a.ndim; dim=%d, rank=%d." + % (dim, nd)) + ind = [0] * (nd - 1) + i = np.zeros(nd, 'O') + indlist = list(range(nd)) + indlist.remove(dim) + i[dim] = slice(None, None) + i.put(indlist, ind) + + gd = np.nonzero(~np.isnan(a))[0] + + # Here we extrapolate the ends, if necessary: + if extrapFlg and gd.__len__() > 0: + if gd[0] != 0 and gd[0] <= maxgap: + a[:gd[0]] = a[gd[0]] + if gd[-1] != a.__len__() and (a.__len__() - (gd[-1] + 1)) <= maxgap: + a[gd[-1]:] = a[gd[-1]] + + # Here is the main loop + if gd.__len__() > 1: + inds = np.nonzero((1 < np.diff(gd)) & (np.diff(gd) <= maxgap + 1))[0] + for i2 in range(0, inds.__len__()): + ii = list(range(gd[inds[i2]] + 1, gd[inds[i2] + 1])) + a[ii] = (np.diff(a[gd[[inds[i2], inds[i2] + 1]]]) * + (np.arange(0, ii.__len__()) + 1) / + (ii.__len__() + 1) + a[gd[inds[i2]]]).astype(a.dtype) + + return a + + +def interpgaps(a, t, maxgap=np.inf, dim=0, extrapFlg=False): + """ + Fill gaps (NaN values) in ``a`` by linear interpolation along + dimension ``dim`` with the point spacing specified in ``t``. + + Parameters + ---------- + a : |np.ndarray| + The array containing NaN values to be filled. + t : |np.ndarray| (len(t) == a.shape[dim]) + Independent variable of the points in ``a``, e.g. timestep + maxgap : |np.ndarray| (optional: inf) + The maximum gap to fill. + dim : int (optional: 0) + The dimension to operate along. + extrapFlg : bool (optional: False) + Whether to extrapolate if NaNs are found at the ends of the + array. + + See Also + -------- + mhkit.dolfyn.tools.misc.fillgaps : Linearly interpolates in array-index space. + + """ + + # If this is a multi-dimensional array, operate along dim dim. + if a.ndim > 1: + for inds in slice1d_along_axis(a.shape, dim): + interpgaps(a[inds], t, maxgap, 0, extrapFlg) + return + + gd = _find(~np.isnan(a)) + + # Here we extrapolate the ends, if necessary: + if extrapFlg and gd.__len__() > 0: + if gd[0] != 0 and gd[0] <= maxgap: + a[:gd[0]] = a[gd[0]] + if gd[-1] != a.__len__() and (a.__len__() - (gd[-1] + 1)) <= maxgap: + a[gd[-1]:] = a[gd[-1]] + + # Here is the main loop + if gd.__len__() > 1: + inds = _find((1 < np.diff(gd)) & + (np.diff(gd) <= maxgap + 1)) + for i2 in range(0, inds.__len__()): + ii = np.arange(gd[inds[i2]] + 1, gd[inds[i2] + 1]) + ti = (t[ii] - t[gd[inds[i2]]]) / np.diff(t[[gd[inds[i2]], + gd[inds[i2] + 1]]]) + a[ii] = (np.diff(a[gd[[inds[i2], inds[i2] + 1]]]) * ti + + a[gd[inds[i2]]]).astype(a.dtype) + + return a + + +def medfiltnan(a, kernel, thresh=0): + """ + Do a running median filter of the data. Regions where more than + ``thresh`` fraction of the points are NaN are set to NaN. + + Parameters + ---------- + a : |np.ndarray| + 2D array containing data to be filtered. + kernel_size : |np.ndarray| or list, optional + A scalar or a list of length 2, giving the size of the median + filter window in each dimension. Elements of kernel_size should + be odd. If kernel_size is a scalar, then this scalar is used as + the size in each dimension. + thresh : int + Maximum gap in *a* to filter over + + Returns + ------- + out : |np.ndarray| + 2D array of same size containing filtered data + + See Also + -------- + scipy.signal.medfilt2d + + """ + flag_1D = False + if a.ndim == 1: + a = a[None, :] + flag_1D = True + try: + len(kernel) + except: + kernel = [1, kernel] + out = medfilt2d(a, kernel) + if thresh > 0: + out[convolve2d(np.isnan(a), + np.ones(kernel) / np.prod(kernel), + 'same') > thresh] = np.NaN + if flag_1D: + return out[0] + return out + + +def convert_degrees(deg, tidal_mode=True): + """ + Converts between the 'cartesian angle' (counter-clockwise from East) and + the 'polar angle' in (degrees clockwise from North) + + Parameters + ---------- + deg: float or array-like + Number or array in 'degrees CCW from East' or 'degrees CW from North' + tidal_mode : bool + If true, range is set from 0 to +/-180 degrees. If false, range is 0 to + 360 degrees + + Returns + ------- + out : float or array-like + Input data transformed to 'degrees CW from North' or + 'degrees CCW from East', respectively (based on `deg`) + + Notes + ----- + The same algorithm is used to convert back and forth between 'CCW from E' + and 'CW from N' + + """ + out = -(deg - 90) % 360 + if tidal_mode: + out[out > 180] -= 360 + return out diff --git a/mhkit/tests/dolfyn/__init__.py b/mhkit/tests/dolfyn/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mhkit/tests/dolfyn/base.py b/mhkit/tests/dolfyn/base.py new file mode 100644 index 000000000..24d78dd29 --- /dev/null +++ b/mhkit/tests/dolfyn/base.py @@ -0,0 +1,62 @@ +import mhkit.dolfyn.io.api as io +from mhkit.dolfyn import time +from xarray.testing import assert_allclose as _assert_allclose +from os.path import abspath, dirname, join, normpath, relpath +import numpy as np + + +def rfnm(filename): + testdir = dirname(abspath(__file__)) + datadir = normpath(join(testdir, relpath( + '../../../examples/data/dolfyn/test_data/'))) + return datadir + '/' + filename + + +def exdt(filename): + testdir = dirname(abspath(__file__)) + exdir = normpath(join(testdir, relpath('../../../examples/data/dolfyn/'))) + return exdir + '/' + filename + + +def assert_allclose(dat0, dat1, *args, **kwargs): + # For problematic time check + names = [] + for v in dat0.variables: + if np.issubdtype(dat0[v].dtype, np.datetime64): + dat0[v] = time.dt642epoch(dat0[v]) + dat1[v] = time.dt642epoch(dat1[v]) + names.append(v) + # Check coords and data_vars + _assert_allclose(dat0, dat1, *args, **kwargs) + # Check attributes + for nm in dat0.attrs: + assert dat0.attrs[nm] == dat1.attrs[nm], "The " + \ + nm + " attribute does not match." + # If test debugging + for v in names: + dat0[v] = time.epoch2dt64(dat0[v]) + dat1[v] = time.epoch2dt64(dat1[v]) + + +def drop_config(dataset): + # Can't save configuration string in netcdf + for key in list(dataset.attrs.keys()): + if 'config' in key: + dataset.attrs.pop(key) + return dataset + + +def load_netcdf(name, *args, **kwargs): + return io.load(rfnm(name), *args, **kwargs) + + +def save_netcdf(data, name, *args, **kwargs): + io.save(data, rfnm(name), *args, **kwargs) + + +def load_matlab(name, *args, **kwargs): + return io.load_mat(rfnm(name), *args, **kwargs) + + +def save_matlab(data, name, *args, **kwargs): + io.save_mat(data, rfnm(name), *args, **kwargs) diff --git a/mhkit/tests/dolfyn/test_orient.py b/mhkit/tests/dolfyn/test_orient.py new file mode 100644 index 000000000..523a54e78 --- /dev/null +++ b/mhkit/tests/dolfyn/test_orient.py @@ -0,0 +1,108 @@ +import numpy as np +from numpy.testing import assert_allclose +from mhkit.dolfyn.rotate.base import euler2orient, orient2euler, quaternion2orient +from mhkit.dolfyn.rotate.api import set_declination +from .base import load_netcdf as load +import unittest + + +def check_hpr(h, p, r, omatin): + omat = euler2orient(h, p, r) + assert_allclose(omat, omatin, atol=1e-13, err_msg='Orientation matrix different than expected!\nExpected:\n{}\nGot:\n{}' + .format(np.array(omatin), omat)) + hpr = orient2euler(omat) + assert_allclose(hpr, [h, p, r], atol=1e-13, err_msg="Angles different than specified, orient2euler and euler2orient are " + "antisymmetric!\nExpected:\n{}\nGot:\n{}" + .format(hpr, np.array([h, p, r]), )) + + +class orient_testcase(unittest.TestCase): + def test_hpr_defs(self): + """ + These tests confirm that the euler2orient and orient2euler functions + are consistent, and that they follow the conventions defined in the + DOLfYN documentation (data-structure.html#heading-pitch-roll), namely: + + - a "ZYX" rotation order. That is, these variables are computed + assuming that rotation from the earth -> instrument frame happens + by rotating around the z-axis first (heading), then rotating + around the y-axis (pitch), then rotating around the x-axis (roll). + + - heading is defined as the direction the x-axis points, positive + clockwise from North (this is the opposite direction from the + right-hand-rule around the Z-axis) + + - pitch is positive when the x-axis pitches up (this is opposite the + right-hand-rule around the Y-axis) + + - roll is positive according to the right-hand-rule around the + instument's x-axis + + IF YOU MAKE CHANGES TO THESE CONVENTIONS, BE SURE TO UPDATE THE + DOCUMENTATION. + + """ + check_hpr(0, 0, 0, [[0, 1, 0], + [-1, 0, 0], + [0, 0, 1], ]) + + check_hpr(90, 0, 0, [[1, 0, 0], + [0, 1, 0], + [0, 0, 1], ]) + + check_hpr(90, 0, 90, [[1, 0, 0], + [0, 0, 1], + [0, -1, 0], ]) + + sq2 = 1. / np.sqrt(2) + check_hpr(45, 0, 0, [[sq2, sq2, 0], + [-sq2, sq2, 0], + [0, 0, 1], ]) + + check_hpr(0, 45, 0, [[0, sq2, sq2], + [-1, 0, 0], + [0, -sq2, sq2], ]) + + check_hpr(0, 0, 45, [[0, 1, 0], + [-sq2, 0, sq2], + [sq2, 0, sq2], ]) + + check_hpr(90, 45, 90, [[sq2, 0, sq2], + [-sq2, 0, sq2], + [0, -1, 0], ]) + + c30 = np.cos(np.deg2rad(30)) + s30 = np.sin(np.deg2rad(30)) + check_hpr(30, 0, 0, [[s30, c30, 0], + [-c30, s30, 0], + [0, 0, 1], ]) + + def test_pr_declination(self): + # Test to confirm that pitch and roll don't change when you set + # declination + declin = 15.37 + + dat = load('vector_data_imu01.nc') + h0, p0, r0 = orient2euler(dat['orientmat'].values) + + set_declination(dat, declin, inplace=True) + h1, p1, r1 = orient2euler(dat['orientmat'].values) + + assert_allclose(p0, p1, atol=1e-5, + err_msg="Pitch changes when setting declination") + assert_allclose(r0, r1, atol=1e-5, + err_msg="Roll changes when setting declination") + assert_allclose(h0 + declin, h1, atol=1e-5, err_msg="incorrect heading change when " + "setting declination") + + def test_q_hpr(self): + dat = load('Sig1000_IMU.nc') + + dcm = quaternion2orient(dat.quaternions) + + assert_allclose(dat.orientmat, dcm, atol=5e-4, + err_msg="Disagreement b/t quaternion-calc'd & HPR-calc'd orientmat") + + +if __name__ == '__main__': + unittest.main() diff --git a/mhkit/tests/dolfyn/test_read_adp.py b/mhkit/tests/dolfyn/test_read_adp.py new file mode 100644 index 000000000..d698727ec --- /dev/null +++ b/mhkit/tests/dolfyn/test_read_adp.py @@ -0,0 +1,155 @@ +import mhkit.dolfyn.io.nortek2 as sig +from mhkit.dolfyn.io.nortek2_lib import crop_ensembles +from mhkit.dolfyn.io.api import read_example as read +from .base import assert_allclose +from . import base as tb +import warnings +import unittest +import pytest +import os + +make_data = False +load = tb.load_netcdf +save = tb.save_netcdf + +dat_rdi = load('RDI_test01.nc') +dat_rdi_7f79 = load('RDI_7f79.nc') +dat_rdi_bt = load('RDI_withBT.nc') +dat_rdi_vm = load('vmdas01.nc') +dat_wr1 = load('winriver01.nc') +dat_wr2 = load('winriver02.nc') + +dat_awac = load('AWAC_test01.nc') +dat_awac_ud = load('AWAC_test01_ud.nc') +dat_hwac = load('H-AWAC_test01.nc') +dat_sig = load('BenchFile01.nc') +dat_sig_i = load('Sig1000_IMU.nc') +dat_sig_i_ud = load('Sig1000_IMU_ud.nc') +dat_sig_ieb = load('VelEchoBT01.nc') +dat_sig_ie = load('Sig500_Echo.nc') +dat_sig_tide = load('Sig1000_tidal.nc') +dat_sig_skip = load('Sig_SkippedPings01.nc') +dat_sig_badt = load('Sig1000_BadTime01.nc') +dat_sig5_leiw = load('Sig500_last_ensemble_is_whole.nc') + + +class io_adp_testcase(unittest.TestCase): + def test_io_rdi(self): + warnings.simplefilter('ignore', UserWarning) + nens = 100 + td_rdi = tb.drop_config(read('RDI_test01.000')) + td_7f79 = tb.drop_config(read('RDI_7f79.000')) + td_rdi_bt = tb.drop_config(read('RDI_withBT.000', nens=nens)) + td_vm = tb.drop_config(read('vmdas01.ENX', nens=nens)) + td_wr1 = tb.drop_config(read('winriver01.PD0')) + td_wr2 = tb.drop_config(read('winriver02.PD0')) + + if make_data: + save(td_rdi, 'RDI_test01.nc') + save(td_7f79, 'RDI_7f79.nc') + save(td_rdi_bt, 'RDI_withBT.nc') + save(td_vm, 'vmdas01.nc') + save(td_wr1, 'winriver01.nc') + save(td_wr2, 'winriver02.nc') + return + + assert_allclose(td_rdi, dat_rdi, atol=1e-6) + assert_allclose(td_7f79, dat_rdi_7f79, atol=1e-6) + assert_allclose(td_rdi_bt, dat_rdi_bt, atol=1e-6) + assert_allclose(td_vm, dat_rdi_vm, atol=1e-6) + assert_allclose(td_wr1, dat_wr1, atol=1e-6) + assert_allclose(td_wr2, dat_wr2, atol=1e-6) + + def test_io_nortek(self): + nens = 100 + with pytest.warns(UserWarning): + td_awac = tb.drop_config( + read('AWAC_test01.wpr', userdata=False, nens=[0, nens])) + td_awac_ud = tb.drop_config(read('AWAC_test01.wpr', nens=nens)) + td_hwac = tb.drop_config(read('H-AWAC_test01.wpr')) + + if make_data: + save(td_awac, 'AWAC_test01.nc') + save(td_awac_ud, 'AWAC_test01_ud.nc') + save(td_hwac, 'H-AWAC_test01.nc') + return + + assert_allclose(td_awac, dat_awac, atol=1e-6) + assert_allclose(td_awac_ud, dat_awac_ud, atol=1e-6) + assert_allclose(td_hwac, dat_hwac, atol=1e-6) + + def test_io_nortek2(self): + nens = 100 + td_sig = tb.drop_config(read('BenchFile01.ad2cp', nens=nens)) + td_sig_i = tb.drop_config(read('Sig1000_IMU.ad2cp', userdata=False, + nens=nens)) + td_sig_i_ud = tb.drop_config(read('Sig1000_IMU.ad2cp', nens=nens)) + td_sig_ieb = tb.drop_config(read('VelEchoBT01.ad2cp', nens=nens)) + td_sig_ie = tb.drop_config(read('Sig500_Echo.ad2cp', nens=nens)) + td_sig_tide = tb.drop_config(read('Sig1000_tidal.ad2cp', nens=nens)) + + with pytest.warns(UserWarning): + # This issues a warning... + td_sig_skip = tb.drop_config(read('Sig_SkippedPings01.ad2cp')) + + with pytest.warns(UserWarning): + td_sig_badt = tb.drop_config(sig.read_signature( + tb.rfnm('Sig1000_BadTime01.ad2cp'))) + + # Make sure we read all the way to the end of the file. + # This file ends exactly at the end of an ensemble. + td_sig5_leiw = tb.drop_config( + read('Sig500_last_ensemble_is_whole.ad2cp')) + + os.remove(tb.exdt('BenchFile01.ad2cp.index')) + os.remove(tb.exdt('Sig1000_IMU.ad2cp.index')) + os.remove(tb.exdt('VelEchoBT01.ad2cp.index')) + os.remove(tb.exdt('Sig500_Echo.ad2cp.index')) + os.remove(tb.exdt('Sig1000_tidal.ad2cp.index')) + os.remove(tb.exdt('Sig_SkippedPings01.ad2cp.index')) + os.remove(tb.exdt('Sig500_last_ensemble_is_whole.ad2cp.index')) + os.remove(tb.rfnm('Sig1000_BadTime01.ad2cp.index')) + + if make_data: + save(td_sig, 'BenchFile01.nc') + save(td_sig_i, 'Sig1000_IMU.nc') + save(td_sig_i_ud, 'Sig1000_IMU_ud.nc') + save(td_sig_ieb, 'VelEchoBT01.nc') + save(td_sig_ie, 'Sig500_Echo.nc') + save(td_sig_tide, 'Sig1000_tidal.nc') + save(td_sig_skip, 'Sig_SkippedPings01.nc') + save(td_sig_badt, 'Sig1000_BadTime01.nc') + save(td_sig5_leiw, 'Sig500_last_ensemble_is_whole.nc') + return + + assert_allclose(td_sig, dat_sig, atol=1e-6) + assert_allclose(td_sig_i, dat_sig_i, atol=1e-6) + assert_allclose(td_sig_i_ud, dat_sig_i_ud, atol=1e-6) + assert_allclose(td_sig_ieb, dat_sig_ieb, atol=1e-6) + assert_allclose(td_sig_ie, dat_sig_ie, atol=1e-6) + assert_allclose(td_sig_tide, dat_sig_tide, atol=1e-6) + assert_allclose(td_sig5_leiw, dat_sig5_leiw, atol=1e-6) + assert_allclose(td_sig_skip, dat_sig_skip, atol=1e-6) + assert_allclose(td_sig_badt, dat_sig_badt, atol=1e-6) + + def test_nortek2_crop(self): + # Test file cropping function + crop_ensembles(infile=tb.exdt('Sig500_Echo.ad2cp'), + outfile=tb.exdt('Sig500_Echo_crop.ad2cp'), + range=[50, 100]) + td_sig_ie_crop = tb.drop_config(read('Sig500_Echo_crop.ad2cp')) + + if make_data: + save(td_sig_ie_crop, 'Sig500_Echo_crop.nc') + return + + os.remove(tb.exdt('Sig500_Echo.ad2cp.index')) + os.remove(tb.exdt('Sig500_Echo_crop.ad2cp')) + os.remove(tb.exdt('Sig500_Echo_crop.ad2cp.index')) + + cd_sig_ie_crop = load('Sig500_Echo_crop.nc') + assert_allclose(td_sig_ie_crop, cd_sig_ie_crop, atol=1e-6) + + +if __name__ == '__main__': + unittest.main() diff --git a/mhkit/tests/dolfyn/test_read_adv.py b/mhkit/tests/dolfyn/test_read_adv.py new file mode 100644 index 000000000..c46a551ca --- /dev/null +++ b/mhkit/tests/dolfyn/test_read_adv.py @@ -0,0 +1,49 @@ +from mhkit.dolfyn.rotate.api import set_inst2head_rotmat +from mhkit.dolfyn.io.api import read_example as read +from . import base as tb +import numpy as np +import unittest + +make_data = False +load = tb.load_netcdf +save = tb.save_netcdf +assert_allclose = tb.assert_allclose + +dat = load('vector_data01') +dat_imu = load('vector_data_imu01') +dat_imu_json = load('vector_data_imu01-json') +dat_burst = load('burst_mode01') + + +class io_adv_testcase(unittest.TestCase): + def test_io_adv(self): + nens = 100 + td = tb.drop_config(read('vector_data01.VEC', nens=nens)) + tdm = tb.drop_config(read('vector_data_imu01.VEC', userdata=False, + nens=nens)) + tdb = tb.drop_config(read('burst_mode01.VEC', nens=nens)) + tdm2 = tb.drop_config(read('vector_data_imu01.VEC', + userdata=tb.exdt( + 'vector_data_imu01.userdata.json'), + nens=nens)) + + # These values are not correct for this data but I'm adding them for + # test purposes only. + set_inst2head_rotmat(tdm, np.eye(3), inplace=True) + tdm.attrs['inst2head_vec'] = [-1.0, 0.5, 0.2] + + if make_data: + save(td, 'vector_data01.nc') + save(tdm, 'vector_data_imu01.nc') + save(tdb, 'burst_mode01.nc') + save(tdm2, 'vector_data_imu01-json.nc') + return + + assert_allclose(td, dat, atol=1e-6) + assert_allclose(tdm, dat_imu, atol=1e-6) + assert_allclose(tdb, dat_burst, atol=1e-6) + assert_allclose(tdm2, dat_imu_json, atol=1e-6) + + +if __name__ == '__main__': + unittest.main() diff --git a/mhkit/tests/dolfyn/test_read_io.py b/mhkit/tests/dolfyn/test_read_io.py new file mode 100644 index 000000000..b7cd8ebcb --- /dev/null +++ b/mhkit/tests/dolfyn/test_read_io.py @@ -0,0 +1,124 @@ +import mhkit.dolfyn.io.rdi as wh +import mhkit.dolfyn.io.nortek as awac +import mhkit.dolfyn.io.nortek2 as sig +from mhkit.dolfyn.io.api import read_example as read +from .base import assert_allclose, save_netcdf, save_matlab, load_matlab, exdt, rfnm, drop_config +from . import test_read_adp as tp +from . import test_read_adv as tv +import contextlib +import unittest +import pytest +import os +import io +make_data = False + + +class io_testcase(unittest.TestCase): + def test_save(self): + ds = tv.dat.copy(deep=True) + save_netcdf(ds, 'test_save', compression=True) + save_matlab(ds, 'test_save') + + assert os.path.exists(rfnm('test_save.nc')) + assert os.path.exists(rfnm('test_save.mat')) + + def test_matlab_io(self): + nens = 100 + td_vec = drop_config(read('vector_data_imu01.VEC', nens=nens)) + td_rdi_bt = drop_config(read('RDI_withBT.000', nens=nens)) + + # This read should trigger a warning about the declination being + # defined in two places (in the binary .ENX files), and in the + # .userdata.json file. NOTE: DOLfYN defaults to using what is in + # the .userdata.json file. + with pytest.warns(UserWarning, match='magnetic_var_deg'): + td_vm = drop_config(read('vmdas01.ENX', nens=nens)) + + if make_data: + save_matlab(td_vec, 'dat_vec') + save_matlab(td_rdi_bt, 'dat_rdi_bt') + save_matlab(td_vm, 'dat_vm') + return + + mat_vec = load_matlab('dat_vec.mat') + mat_rdi_bt = load_matlab('dat_rdi_bt.mat') + mat_vm = load_matlab('dat_vm.mat') + + assert_allclose(td_vec, mat_vec, atol=1e-6) + assert_allclose(td_rdi_bt, mat_rdi_bt, atol=1e-6) + assert_allclose(td_vm, mat_vm, atol=1e-6) + + def test_debugging(self): + def debug_output(f, func, datafile, nens, *args, **kwargs): + with contextlib.redirect_stdout(f): + drop_config(func(exdt(datafile), nens=nens, *args, **kwargs)) + + def remove_local_path(stringIO): + string = stringIO.getvalue() + start = string.find("Indexing") + if start != -1: + start += 8 + end = stringIO.getvalue().find("...") + string = string[0:start] + string[end+3:] + + start = string.find("Reading file") + 12 + end = string.find(" ...") + return string[0:start] + string[end:] + + def save_txt(fname, string): + with open(rfnm(fname), 'w') as f: + f.write(string) + + def read_txt(fname): + with open(rfnm(fname), 'r') as f: + string = f.read() + return string + + nens = 100 + db_rdi = io.StringIO() + db_awac = io.StringIO() + db_vec = io.StringIO() + db_sig = io.StringIO() + + debug_output(db_rdi, wh.read_rdi, 'RDI_withBT.000', nens, debug=11) + debug_output(db_awac, awac.read_nortek, 'AWAC_test01.wpr', + nens, debug=True, do_checksum=True) + debug_output(db_vec, awac.read_nortek, 'vector_data_imu01.VEC', + nens, debug=True, do_checksum=True) + debug_output(db_sig, sig.read_signature, 'Sig500_Echo.ad2cp', + nens, rebuild_index=True, debug=True) + os.remove(exdt('Sig500_Echo.ad2cp.index')) + + str_rdi = remove_local_path(db_rdi) + str_awac = remove_local_path(db_awac) + str_vec = remove_local_path(db_vec) + str_sig = remove_local_path(db_sig) + + if make_data: + save_txt('rdi_debug_out.txt', str_rdi) + save_txt('awac_debug_out.txt', str_awac) + save_txt('vec_debug_out.txt', str_vec) + save_txt('sig_debug_out.txt', str_sig) + return + + test_rdi = read_txt('rdi_debug_out.txt') + test_awac = read_txt('awac_debug_out.txt') + test_vec = read_txt('vec_debug_out.txt') + test_sig = read_txt('sig_debug_out.txt') + + assert test_rdi == str_rdi + assert test_awac == str_awac + assert test_vec == str_vec + assert test_sig == str_sig + + def test_read_warnings(self): + with self.assertRaises(Exception): + wh.read_rdi(exdt('H-AWAC_test01.wpr')) + with self.assertRaises(Exception): + awac.read_nortek(exdt('BenchFile01.ad2cp')) + with self.assertRaises(Exception): + sig.read_signature(exdt('AWAC_test01.wpr')) + with self.assertRaises(IOError): + read(rfnm('AWAC_test01.nc')) + with self.assertRaises(Exception): + save_netcdf(tp.dat_rdi, 'test_save.fail') diff --git a/mhkit/tests/dolfyn/test_rotate_adp.py b/mhkit/tests/dolfyn/test_rotate_adp.py new file mode 100644 index 000000000..4ec21353d --- /dev/null +++ b/mhkit/tests/dolfyn/test_rotate_adp.py @@ -0,0 +1,176 @@ +from . import test_read_adp as tr +from .base import load_netcdf as load, save_netcdf as save, assert_allclose +from mhkit.dolfyn.rotate.api import rotate2, calc_principal_heading +import numpy as np +import numpy.testing as npt +import unittest +make_data = False + + +class rotate_adp_testcase(unittest.TestCase): + def test_rotate_beam2inst(self): + + td_rdi = rotate2(tr.dat_rdi, 'inst', inplace=False) + td_sig = rotate2(tr.dat_sig, 'inst', inplace=False) + td_sig_i = rotate2(tr.dat_sig_i, 'inst', inplace=False) + td_sig_ieb = rotate2(tr.dat_sig_ieb, 'inst', inplace=False) + + if make_data: + save(td_rdi, 'RDI_test01_rotate_beam2inst.nc') + save(td_sig, 'BenchFile01_rotate_beam2inst.nc') + save(td_sig_i, 'Sig1000_IMU_rotate_beam2inst.nc') + save(td_sig_ieb, 'VelEchoBT01_rotate_beam2inst.nc') + return + + cd_rdi = load('RDI_test01_rotate_beam2inst.nc') + cd_sig = load('BenchFile01_rotate_beam2inst.nc') + cd_sig_i = load('Sig1000_IMU_rotate_beam2inst.nc') + cd_sig_ieb = load('VelEchoBT01_rotate_beam2inst.nc') + + assert_allclose(td_rdi, cd_rdi, atol=1e-5) + assert_allclose(td_sig, cd_sig, atol=1e-5) + assert_allclose(td_sig_i, cd_sig_i, atol=1e-5) + assert_allclose(td_sig_ieb, cd_sig_ieb, atol=1e-5) + + def test_rotate_inst2beam(self): + + td = load('RDI_test01_rotate_beam2inst.nc') + rotate2(td, 'beam', inplace=True) + td_awac = load('AWAC_test01_earth2inst.nc') + rotate2(td_awac, 'beam', inplace=True) + td_sig = load('BenchFile01_rotate_beam2inst.nc') + rotate2(td_sig, 'beam', inplace=True) + td_sig_i = load('Sig1000_IMU_rotate_beam2inst.nc') + rotate2(td_sig_i, 'beam', inplace=True) + td_sig_ie = load('Sig500_Echo_earth2inst.nc') + rotate2(td_sig_ie, 'beam', inplace=True) + + if make_data: + save(td_awac, 'AWAC_test01_inst2beam.nc') + save(td_sig_ie, 'Sig500_Echo_inst2beam.nc') + return + + cd_td = tr.dat_rdi.copy(deep=True) + cd_awac = load('AWAC_test01_inst2beam.nc') + cd_sig = tr.dat_sig.copy(deep=True) + cd_sig_i = tr.dat_sig_i.copy(deep=True) + cd_sig_ie = load('Sig500_Echo_inst2beam.nc') + + # # The reverse RDI rotation doesn't work b/c of NaN's in one beam + # # that propagate to others, so we impose that here. + cd_td['vel'].values[:, np.isnan(cd_td['vel'].values).any(0)] = np.NaN + + assert_allclose(td, cd_td, atol=1e-5) + assert_allclose(td_awac, cd_awac, atol=1e-5) + assert_allclose(td_sig, cd_sig, atol=1e-5) + assert_allclose(td_sig_i, cd_sig_i, atol=1e-5) + assert_allclose(td_sig_ie, cd_sig_ie, atol=1e-5) + + def test_rotate_inst2earth(self): + # AWAC & Sig500 are loaded in earth + td_awac = tr.dat_awac.copy(deep=True) + rotate2(td_awac, 'inst', inplace=True) + td_sig_ie = tr.dat_sig_ie.copy(deep=True) + rotate2(td_sig_ie, 'inst', inplace=True) + td_sig_o = td_sig_ie.copy(deep=True) + + td = rotate2(tr.dat_rdi, 'earth', inplace=False) + tdwr2 = rotate2(tr.dat_wr2, 'earth', inplace=False) + td_sig = load('BenchFile01_rotate_beam2inst.nc') + rotate2(td_sig, 'earth', inplace=True) + td_sig_i = load('Sig1000_IMU_rotate_beam2inst.nc') + rotate2(td_sig_i, 'earth', inplace=True) + + if make_data: + save(td_awac, 'AWAC_test01_earth2inst.nc') + save(td, 'RDI_test01_rotate_inst2earth.nc') + save(tdwr2, 'winriver02_rotate_ship2earth.nc') + save(td_sig, 'BenchFile01_rotate_inst2earth.nc') + save(td_sig_i, 'Sig1000_IMU_rotate_inst2earth.nc') + save(td_sig_ie, 'Sig500_Echo_earth2inst.nc') + return + + td_awac = rotate2(load('AWAC_test01_earth2inst.nc'), + 'earth', inplace=False) + td_sig_ie = rotate2(load('Sig500_Echo_earth2inst.nc'), + 'earth', inplace=False) + td_sig_o = rotate2(td_sig_o.drop_vars( + 'orientmat'), 'earth', inplace=False) + + cd = load('RDI_test01_rotate_inst2earth.nc') + cdwr2 = load('winriver02_rotate_ship2earth.nc') + cd_sig = load('BenchFile01_rotate_inst2earth.nc') + cd_sig_i = load('Sig1000_IMU_rotate_inst2earth.nc') + + assert_allclose(td, cd, atol=1e-5) + assert_allclose(tdwr2, cdwr2, atol=1e-5) + assert_allclose(td_awac, tr.dat_awac, atol=1e-5) + assert_allclose(td_sig, cd_sig, atol=1e-5) + assert_allclose(td_sig_i, cd_sig_i, atol=1e-5) + assert_allclose(td_sig_ie, tr.dat_sig_ie, atol=1e-5) + npt.assert_allclose(td_sig_o.vel, tr.dat_sig_ie.vel, atol=1e-5) + + def test_rotate_earth2inst(self): + + td_rdi = load('RDI_test01_rotate_inst2earth.nc') + rotate2(td_rdi, 'inst', inplace=True) + tdwr2 = load('winriver02_rotate_ship2earth.nc') + rotate2(tdwr2, 'inst', inplace=True) + + td_awac = tr.dat_awac.copy(deep=True) + rotate2(td_awac, 'inst', inplace=True) # AWAC is in earth coords + td_sig = load('BenchFile01_rotate_inst2earth.nc') + rotate2(td_sig, 'inst', inplace=True) + td_sig_i = load('Sig1000_IMU_rotate_inst2earth.nc') + rotate2(td_sig_i, 'inst', inplace=True) + + cd_rdi = load('RDI_test01_rotate_beam2inst.nc') + cd_wr2 = tr.dat_wr2 + # ship and inst are considered equivalent in dolfy + cd_wr2.attrs['coord_sys'] = 'inst' + cd_awac = load('AWAC_test01_earth2inst.nc') + cd_sig = load('BenchFile01_rotate_beam2inst.nc') + cd_sig_i = load('Sig1000_IMU_rotate_beam2inst.nc') + + assert_allclose(td_rdi, cd_rdi, atol=1e-5) + assert_allclose(tdwr2, cd_wr2, atol=1e-5) + assert_allclose(td_awac, cd_awac, atol=1e-5) + assert_allclose(td_sig, cd_sig, atol=1e-5) + # known failure due to orientmat, see test_vs_nortek + #assert_allclose(td_sig_i, cd_sig_i, atol=1e-3) + npt.assert_allclose(td_sig_i.accel.values, + cd_sig_i.accel.values, atol=1e-3) + + def test_rotate_earth2principal(self): + + td_rdi = load('RDI_test01_rotate_inst2earth.nc') + td_sig = load('BenchFile01_rotate_inst2earth.nc') + td_awac = tr.dat_awac.copy(deep=True) + + td_rdi.attrs['principal_heading'] = calc_principal_heading( + td_rdi.vel.mean('range')) + td_sig.attrs['principal_heading'] = calc_principal_heading( + td_sig.vel.mean('range')) + td_awac.attrs['principal_heading'] = calc_principal_heading(td_awac.vel.mean('range'), + tidal_mode=False) + rotate2(td_rdi, 'principal', inplace=True) + rotate2(td_sig, 'principal', inplace=True) + rotate2(td_awac, 'principal', inplace=True) + + if make_data: + save(td_rdi, 'RDI_test01_rotate_earth2principal.nc') + save(td_sig, 'BenchFile01_rotate_earth2principal.nc') + save(td_awac, 'AWAC_test01_earth2principal.nc') + return + + cd_rdi = load('RDI_test01_rotate_earth2principal.nc') + cd_sig = load('BenchFile01_rotate_earth2principal.nc') + cd_awac = load('AWAC_test01_earth2principal.nc') + + assert_allclose(td_rdi, cd_rdi, atol=1e-5) + assert_allclose(td_awac, cd_awac, atol=1e-5) + assert_allclose(td_sig, cd_sig, atol=1e-5) + + +if __name__ == '__main__': + unittest.main() diff --git a/mhkit/tests/dolfyn/test_rotate_adv.py b/mhkit/tests/dolfyn/test_rotate_adv.py new file mode 100644 index 000000000..b0f681fd8 --- /dev/null +++ b/mhkit/tests/dolfyn/test_rotate_adv.py @@ -0,0 +1,176 @@ +from . import test_read_adv as tr +from .base import load_netcdf as load, save_netcdf as save, assert_allclose +from mhkit.dolfyn.rotate.api import rotate2, calc_principal_heading, \ + set_declination, set_inst2head_rotmat +from mhkit.dolfyn.rotate.base import euler2orient, orient2euler +import numpy as np +import numpy.testing as npt +import unittest +make_data = False + + +class rotate_adv_testcase(unittest.TestCase): + def test_heading(self): + td = tr.dat_imu.copy(deep=True) + + head, pitch, roll = orient2euler(td) + td['pitch'].values = pitch + td['roll'].values = roll + td['heading'].values = head + + if make_data: + save(td, 'vector_data_imu01_head_pitch_roll.nc') + return + cd = load('vector_data_imu01_head_pitch_roll.nc') + + assert_allclose(td, cd, atol=1e-6) + + def test_inst2head_rotmat(self): + # Validated test + td = tr.dat.copy(deep=True) + + # Swap x,y, reverse z + set_inst2head_rotmat(td, [[0, 1, 0], + [1, 0, 0], + [0, 0, -1]], inplace=True) + + # Coords don't get altered here + npt.assert_allclose(td.vel[0].values, tr.dat.vel[1].values, atol=1e-6) + npt.assert_allclose(td.vel[1].values, tr.dat.vel[0].values, atol=1e-6) + npt.assert_allclose(td.vel[2].values, -tr.dat.vel[2].values, atol=1e-6) + + # Validation for non-symmetric rotations + td = tr.dat.copy(deep=True) + R = euler2orient(20, 30, 60, units='degrees') # arbitrary angles + td = set_inst2head_rotmat(td, R, inplace=False) + vel1 = td.vel + # validate that a head->inst rotation occurs (transpose of inst2head_rotmat) + vel2 = np.dot(R, tr.dat.vel) + + npt.assert_allclose(vel1.values, vel2, atol=1e-6) + + def test_rotate_inst2earth(self): + td = tr.dat.copy(deep=True) + rotate2(td, 'earth', inplace=True) + tdm = tr.dat_imu.copy(deep=True) + rotate2(tdm, 'earth', inplace=True) + tdo = tr.dat.copy(deep=True) + omat = tdo['orientmat'] + tdo = rotate2(tdo.drop_vars('orientmat'), 'earth', inplace=False) + tdo['orientmat'] = omat + + if make_data: + save(td, 'vector_data01_rotate_inst2earth.nc') + save(tdm, 'vector_data_imu01_rotate_inst2earth.nc') + return + + cd = load('vector_data01_rotate_inst2earth.nc') + cdm = load('vector_data_imu01_rotate_inst2earth.nc') + + assert_allclose(td, cd, atol=1e-6) + assert_allclose(tdm, cdm, atol=1e-6) + assert_allclose(tdo, cd, atol=1e-6) + + def test_rotate_earth2inst(self): + td = load('vector_data01_rotate_inst2earth.nc') + rotate2(td, 'inst', inplace=True) + tdm = load('vector_data_imu01_rotate_inst2earth.nc') + rotate2(tdm, 'inst', inplace=True) + + cd = tr.dat.copy(deep=True) + cdm = tr.dat_imu.copy(deep=True) + # The heading/pitch/roll data gets modified during rotation, so it + # doesn't go back to what it was. + cdm = cdm.drop_vars(['heading', 'pitch', 'roll']) + tdm = tdm.drop_vars(['heading', 'pitch', 'roll']) + + assert_allclose(td, cd, atol=1e-6) + assert_allclose(tdm, cdm, atol=1e-6) + + def test_rotate_inst2beam(self): + td = tr.dat.copy(deep=True) + rotate2(td, 'beam', inplace=True) + tdm = tr.dat_imu.copy(deep=True) + rotate2(tdm, 'beam', inplace=True) + + if make_data: + save(td, 'vector_data01_rotate_inst2beam.nc') + save(tdm, 'vector_data_imu01_rotate_inst2beam.nc') + return + + cd = load('vector_data01_rotate_inst2beam.nc') + cdm = load('vector_data_imu01_rotate_inst2beam.nc') + + assert_allclose(td, cd, atol=1e-6) + assert_allclose(tdm, cdm, atol=1e-6) + + def test_rotate_beam2inst(self): + td = load('vector_data01_rotate_inst2beam.nc') + rotate2(td, 'inst', inplace=True) + tdm = load('vector_data_imu01_rotate_inst2beam.nc') + rotate2(tdm, 'inst', inplace=True) + + cd = tr.dat.copy(deep=True) + cdm = tr.dat_imu.copy(deep=True) + + assert_allclose(td, cd, atol=1e-6) + assert_allclose(tdm, cdm, atol=1e-6) + + def test_rotate_earth2principal(self): + td = load('vector_data01_rotate_inst2earth.nc') + td.attrs['principal_heading'] = calc_principal_heading(td['vel']) + rotate2(td, 'principal', inplace=True) + tdm = load('vector_data_imu01_rotate_inst2earth.nc') + tdm.attrs['principal_heading'] = calc_principal_heading(tdm['vel']) + rotate2(tdm, 'principal', inplace=True) + + if make_data: + save(td, 'vector_data01_rotate_earth2principal.nc') + save(tdm, 'vector_data_imu01_rotate_earth2principal.nc') + return + + cd = load('vector_data01_rotate_earth2principal.nc') + cdm = load('vector_data_imu01_rotate_earth2principal.nc') + + assert_allclose(td, cd, atol=1e-6) + assert_allclose(tdm, cdm, atol=1e-6) + + def test_rotate_earth2principal_set_declination(self): + declin = 3.875 + td = load('vector_data01_rotate_inst2earth.nc') + td0 = td.copy(deep=True) + + td.attrs['principal_heading'] = calc_principal_heading(td['vel']) + rotate2(td, 'principal', inplace=True) + set_declination(td, declin, inplace=True) + rotate2(td, 'earth', inplace=True) + + set_declination(td0, -1, inplace=True) + set_declination(td0, declin, inplace=True) + td0.attrs['principal_heading'] = calc_principal_heading(td0['vel']) + rotate2(td0, 'earth', inplace=True) + + assert_allclose(td0, td, atol=1e-6) + + def test_rotate_warnings(self): + warn1 = tr.dat.copy(deep=True) + warn2 = tr.dat.copy(deep=True) + warn2.attrs['coord_sys'] = 'flow' + warn3 = tr.dat.copy(deep=True) + warn3.attrs['inst_model'] = 'ADV' + warn4 = tr.dat.copy(deep=True) + warn4.attrs['inst_model'] = 'adv' + + with self.assertRaises(Exception): + rotate2(warn1, 'ship') + with self.assertRaises(Exception): + rotate2(warn2, 'earth') + with self.assertRaises(Exception): + set_inst2head_rotmat(warn3, np.eye(3)) + set_inst2head_rotmat(warn3, np.eye(3)) + with self.assertRaises(Exception): + set_inst2head_rotmat(warn4, np.eye(3)) + + +if __name__ == '__main__': + unittest.main() diff --git a/mhkit/tests/dolfyn/test_time.py b/mhkit/tests/dolfyn/test_time.py new file mode 100644 index 000000000..52ed8bf4e --- /dev/null +++ b/mhkit/tests/dolfyn/test_time.py @@ -0,0 +1,52 @@ +from . import test_read_adv as trv +from . import test_read_adp as trp +from numpy.testing import assert_equal, assert_allclose +import numpy as np +import mhkit.dolfyn.time as time +from datetime import datetime +import unittest + + +class warnings_testcase(unittest.TestCase): + def test_time_conversion(self): + td = trv.dat_imu.copy(deep=True) + dat_sig = trp.dat_sig_i.copy(deep=True) + + dt = time.dt642date(td.time) + dt1 = time.dt642date(td.time[0]) + dt_off = time.epoch2date(time.dt642epoch(td.time), offset_hr=-7) + t_str = time.epoch2date(time.dt642epoch(td.time), to_str=True) + + assert_equal(dt[0], datetime(2012, 6, 12, 12, 0, 2, 687282)) + assert_equal(dt1, [datetime(2012, 6, 12, 12, 0, 2, 687282)]) + assert_equal(dt_off[0], datetime(2012, 6, 12, 5, 0, 2, 687282)) + assert_equal(t_str[0], '2012-06-12 12:00:02.687282') + + # Validated based on data in ad2cp.index file + assert_equal(time.dt642date(dat_sig.time[0])[0], + datetime(2017, 7, 24, 17, 0, 0, 63499)) + # This should always be true + assert_equal(time.epoch2date([0])[0], datetime(1970, 1, 1, 0, 0)) + + def test_datetime(self): + td = trv.dat_imu.copy(deep=True) + + dt = time.dt642date(td.time) + epoch = np.array(time.date2epoch(dt)) + + assert_allclose(time.dt642epoch(td.time.values), epoch, atol=1e-7) + + def test_datenum(self): + td = trv.dat_imu.copy(deep=True) + + dt = time.dt642date(td.time) + dn = time.date2matlab(dt) + dt2 = time.matlab2date(dn) + epoch = np.array(time.date2epoch(dt2)) + + assert_allclose(time.dt642epoch(td.time.values), epoch, atol=1e-6) + assert_equal(dn[0], 735032.5000311028) + + +if __name__ == '__main__': + unittest.main() diff --git a/mhkit/tests/dolfyn/test_tools.py b/mhkit/tests/dolfyn/test_tools.py new file mode 100644 index 000000000..cc84ba514 --- /dev/null +++ b/mhkit/tests/dolfyn/test_tools.py @@ -0,0 +1,101 @@ +import mhkit.dolfyn.tools.misc as tools +from numpy.testing import assert_equal, assert_allclose +import unittest +import numpy as np + + +class tools_testcase(unittest.TestCase): + @classmethod + def setUpClass(self): + self.array = np.arange(10, dtype=float) + self.nan = np.zeros(3)*np.NaN + + @classmethod + def tearDownClass(self): + pass + + def test_detrend(self): + d = tools.detrend(self.array) + assert_allclose(d, np.zeros(10), atol=1e-10) + + def test_group(self): + array = np.concatenate((self.array, self.array)) + d = tools.group(array) + + out = np.array([slice(1, 20, None)], dtype=object) + assert_equal(d, out) + + def test_slice(self): + tensor = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], + [[10, 11, 12], [13, 14, 15], [16, 17, 18]], + [[19, 20, 21], [22, 23, 24], [25, 26, 27]]]) + out = np.zeros((3, 3, 3)) + slices = list() + for slc in tools.slice1d_along_axis((3, 3, 3), axis=-1): + slices.append(slc) + out[slc] = tensor[slc] + + slc_out = [(0, 0, slice(None, None, None)), + (0, 1, slice(None, None, None)), + (0, 2, slice(None, None, None)), + (1, 0, slice(None, None, None)), + (1, 1, slice(None, None, None)), + (1, 2, slice(None, None, None)), + (2, 0, slice(None, None, None)), + (2, 1, slice(None, None, None)), + (2, 2, slice(None, None, None))] + + assert_equal(slc_out, slices) + assert_allclose(tensor, out, atol=1e-10) + + def test_fillgaps(self): + arr = np.concatenate((self.array, self.nan, self.array)) + d1 = tools.fillgaps(arr.copy()) + d2 = tools.fillgaps(arr.copy(), maxgap=1) + + out1 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6.75, 4.5, 2.25, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + out2 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, np.nan, np.nan, np.nan, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + + assert_allclose(d1, out1, atol=1e-10) + assert_allclose(d2, out2, atol=1e-10) + + def test_interpgaps(self): + arr = np.concatenate((self.array, self.nan, self.array, self.nan)) + + t = np.arange(0, arr.shape[0], 0.1) + d1 = tools.interpgaps(arr.copy(), t, extrapFlg=True) + d2 = tools.interpgaps(arr.copy(), t, maxgap=1) + + out1 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6.75, 4.5, 2.25, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9]) + out2 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, np.nan, np.nan, np.nan, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, np.nan, np.nan, np.nan]) + + assert_allclose(d1, out1, atol=1e-10) + assert_allclose(d2, out2, atol=1e-10) + + def test_medfiltnan(self): + arr = np.concatenate((self.array, self.nan, self.array)) + a = np.concatenate((arr[None, :], arr[None, :]), axis=0) + + d = tools.medfiltnan(a, [1, 5], thresh=3) + + out = np.array([[0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 8, 9, np.nan, np.nan, np.nan, 2, 3, 4, 5, + 6, 7, 7, 7], + [0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 8, 9, np.nan, np.nan, np.nan, 2, 3, 4, 5, + 6, 7, 7, 7]]) + + assert_allclose(d, out, atol=1e-10) + + def test_deg_conv(self): + d = tools.convert_degrees(self.array) + + out = np.array([90., 89., 88., 87., 86., 85., 84., 83., 82., 81.]) + + assert_allclose(d, out, atol=1e-10) + + +if __name__ == '__main__': + unittest.main() diff --git a/mhkit/tests/dolfyn/test_vs_nortek.py b/mhkit/tests/dolfyn/test_vs_nortek.py new file mode 100644 index 000000000..241071c86 --- /dev/null +++ b/mhkit/tests/dolfyn/test_vs_nortek.py @@ -0,0 +1,126 @@ +from . import test_read_adp as tr +from . import base +from mhkit.dolfyn.rotate.api import rotate2 +from numpy.testing import assert_allclose +import numpy as np +import scipy.io as sio +import unittest + +""" +Testing against velocity and bottom-track velocity data in Nortek mat files +exported from SignatureDeployment. + +inst2earth rotation fails for AHRS-equipped istruments and I don't know why - +I believe it's due to an RC filter (or some such) on Nortek's side after they +load in the orientation matrix from the AHRS (Check out the difference +colorplots compared to non-AHRS instruments.) Using HPR- or quaterion-calc'd +orientation matrices doesn't close the gap. +""" + + +def load_nortek_matfile(filename): + data = sio.loadmat(filename, + struct_as_record=False, + squeeze_me=True) + d = data['Data'] + # print(d._fieldnames) + burst = 'Burst' + bt = 'BottomTrack' + + beam = ['_VelBeam1', '_VelBeam2', '_VelBeam3', '_VelBeam4'] + b5 = 'IBurst_VelBeam5' + inst = ['_VelX', '_VelY', '_VelZ1', '_VelZ2'] + earth = ['_VelEast', '_VelNorth', '_VelUp1', '_VelUp2'] + axis = {'beam': beam, 'inst': inst, 'earth': earth} + AHRS = 'Burst_AHRSRotationMatrix' # , 'IBurst_AHRSRotationMatrix'] + + vel = {'beam': {}, 'inst': {}, 'earth': {}} + for ky in vel.keys(): + for i in range(len(axis[ky])): + vel[ky][i] = np.transpose(getattr(d, burst+axis[ky][i])) + vel[ky] = np.stack((vel[ky][0], vel[ky][1], + vel[ky][2], vel[ky][3]), axis=0) + + if AHRS in d._fieldnames: + vel['omat'] = np.transpose(getattr(d, AHRS)) + + if b5 in d._fieldnames: + vel['b5'] = np.transpose(getattr(d, b5)) + #vel['omat5'] = getattr(d, AHRS[1]) + + if bt+beam[0] in d._fieldnames: + vel_bt = {'beam': {}, 'inst': {}, 'earth': {}} + for ky in vel_bt.keys(): + for i in range(len(axis[ky])): + vel_bt[ky][i] = np.transpose(getattr(d, bt+axis[ky][i])) + vel_bt[ky] = np.stack((vel_bt[ky][0], vel_bt[ky][1], + vel_bt[ky][2], vel_bt[ky][3]), axis=0) + + return vel, vel_bt + else: + return vel + + +def rotate(axis): + # BenchFile01.ad2cp + td_sig = rotate2(tr.dat_sig, axis, inplace=False) + # Sig1000_IMU.ad2cp no userdata + td_sig_i = rotate2(tr.dat_sig_i, axis, inplace=False) + # VelEchoBT01.ad2cp + td_sig_ieb = rotate2(tr.dat_sig_ieb, axis, + inplace=False) + # Sig500_Echo.ad2cp + td_sig_ie = rotate2(tr.dat_sig_ie, axis, + inplace=False) + + td_sig_vel = load_nortek_matfile(base.rfnm('BenchFile01.mat')) + td_sig_i_vel = load_nortek_matfile(base.rfnm('Sig1000_IMU.mat')) + td_sig_ieb_vel, vel_bt = load_nortek_matfile(base.rfnm('VelEchoBT01.mat')) + td_sig_ie_vel = load_nortek_matfile(base.rfnm('Sig500_Echo.mat')) + + nens = 100 + # ARHS inst2earth orientation matrix check + # Checks the 1,1 element because the nortek orientmat's shape is [9,:] as + # opposed to [3,3,:] + if axis == 'inst': + assert_allclose(td_sig_i.orientmat[0][0].values, + td_sig_i_vel['omat'][0, :nens], atol=1e-7) + assert_allclose(td_sig_ieb.orientmat[0][0].values, + td_sig_ieb_vel['omat'][0, :][..., :nens], atol=1e-7) + + # 4-beam velocity + assert_allclose(td_sig.vel.values, td_sig_vel[axis][..., :nens], atol=1e-5) + assert_allclose(td_sig_i.vel.values, + td_sig_i_vel[axis][..., :nens], atol=5e-3) + assert_allclose(td_sig_ieb.vel.values, + td_sig_ieb_vel[axis][..., :nens], atol=5e-3) + assert_allclose(td_sig_ie.vel.values, + td_sig_ie_vel[axis][..., :nens], atol=1e-5) + + # 5th-beam velocity + if axis == 'beam': + assert_allclose(td_sig_i.vel_b5.values, + td_sig_i_vel['b5'][..., :nens], atol=1e-5) + assert_allclose(td_sig_ieb.vel_b5.values, + td_sig_ieb_vel['b5'][..., :nens], atol=1e-5) + assert_allclose(td_sig_ie.vel_b5.values, + td_sig_ie_vel['b5'][..., :nens], atol=1e-5) + + # bottom-track + assert_allclose(td_sig_ieb.vel_bt.values, + vel_bt[axis][..., :nens], atol=5e-3) + + +class nortek_testcase(unittest.TestCase): + def test_rotate2_beam(self): + rotate('beam') + + def test_rotate2_inst(self): + rotate('inst') + + def test_rotate2_earth(self): + rotate('earth') + + +if __name__ == '__main__': + unittest.main() diff --git a/requirements.txt b/requirements.txt index 6d06ddbbe..619ed87f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ pandas>=1.0.0 -numpy<1.21.0 +numpy>=1.21.0 scipy matplotlib requests @@ -11,4 +11,5 @@ NREL-rex>=0.2.63 six>=1.13.0 netCDF4 xarray -statsmodels \ No newline at end of file +statsmodels +dolfyn>=1.0.0 diff --git a/setup.py b/setup.py index 79391a682..2352619f4 100644 --- a/setup.py +++ b/setup.py @@ -11,27 +11,28 @@ MAINTAINER_EMAIL = '' LICENSE = 'Revised BSD' URL = 'https://github.com/MHKiT-Software/mhkit-python' -CLASSIFIERS=['Development Status :: 3 - Alpha', - 'Programming Language :: Python :: 3', - 'Topic :: Scientific/Engineering', - 'Intended Audience :: Science/Research', - 'Operating System :: OS Independent', - ] -DEPENDENCIES = ['pandas', - 'numpy', +CLASSIFIERS = ['Development Status :: 3 - Alpha', + 'Programming Language :: Python :: 3', + 'Topic :: Scientific/Engineering', + 'Intended Audience :: Science/Research', + 'Operating System :: OS Independent', + ] +DEPENDENCIES = ['pandas', + 'numpy', 'scipy', - 'matplotlib', - 'requests', + 'matplotlib', + 'requests', 'pecos>=0.1.9', 'fatpack', 'lxml', 'scikit-learn', - 'NREL-rex>=0.2.63', + 'NREL-rex>=0.2.63', 'six>=1.13.0', - 'netCDF4', + 'netCDF4', 'xarray', - 'statsmodels', - 'pytz'] + 'statsmodels', + 'pytz', + 'dolfyn>=1.0.0'] # use README file as the long description file_dir = os.path.abspath(os.path.dirname(__file__)) @@ -47,7 +48,7 @@ VERSION = version_match.group(1) else: raise RuntimeError("Unable to find version string.") - + setup(name=DISTNAME, version=VERSION, packages=PACKAGES, @@ -63,4 +64,4 @@ install_requires=DEPENDENCIES, scripts=[], include_package_data=True - ) + )