{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "EPtqtPIUvdc2" }, "source": [ "# FMCW Radar 101 - Intro\n", "\n", "Goal:\n", "\n", "> Explicit in one single place code and maths needed to understand FMCW radar.\n", "\n", "Status:\n", "\n", "* this work book runs in any browser and correctly computes distance and speed for a single target.\n", "\n", "Howto:\n", "\n", "> you can start with the maths or with the code, you should finish the otherway round and check they both match.\n", "\n", "1. if needed check the introduction to Google Colab to run notebooks in your web browser [here](https://colab.research.google.com/)\n", "\n", "2. Navigate to the cell you want to execute and press execute\n", "3. make changes to chirp ramp-time, distances, speeds and observe the changes (keep in mind that all measures are related to the FFT bin sizes).\n", "\n", "Next:\n", "\n", "* click to access other workbooks:\n", " * [FMCW 102 - CFAR](https://colab.research.google.com/gist/matt-chv/33e98a23d4b9d90dd27c1bf7f0a54781/fmcw-radar-102-cfar.ipynb) : CFAR or how to detect objects of interest from range FFT.\n", " * [FMCW 103 - AoA](https://colab.research.google.com/gist/matt-chv/d81f7e2166009a623a36781a0773ae47/fmcw-radar-103-aoa.ipynb) : angle of arrival (CAPON vs Bartlett)\n", " * [FMCW 104 - increased resolution vs FFT bin](https://colab.research.google.com/gist/matt-chv/0b25dbc4673f2d7d63804cc6241643b9/fmcw-radar-104-1-fft-freq-estimation.ipynb)increase accuracy option compared to standard FFT.\n", " * Also available on github as gist for forking:\n", " * [fmcw 101](https://gist.github.com/matt-chv/bdd8b835c5cb7e739bb8b68d00257690)\n", " * [fmcw 102](https://gist.github.com/matt-chv/33e98a23d4b9d90dd27c1bf7f0a54781)\n", " * ...\n", "\n", "History:\n", "\n", "* 2022-Dec-28: Added 2D FFT - 3 targets - 2 targets same range, 2 same speed\n", "* 2022-Dec-23: Added 2D FFT example - 1 targets\n", "* 2022-Dec-15: Clean-up to have MRE for range and speed as standalone cells\n", "* 2022-Apr-28: creation\n", "\n", "Related ressources:\n", "\n", "* How to run a google Colab notebook (requires a Google account to login): [Colab intro](https://colab.research.google.com/)\n", "* OpenRadar notebook on range: [Range example](https://github.com/PreSenseRadar/OpenRadar/blob/master/Presense%20Applied%20Radar/basics/Range%20-%20COMPLETED.ipynb)\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "oV8JqYMFNYd-" }, "source": [ "## FMCW Maths 101" ] }, { "cell_type": "markdown", "metadata": { "id": "kxWYcEQHig7p" }, "source": [ "\n", "### linear chirps and IF frequencies\n", "\n", "Signal model\n", "According to (Barriok 1973; Stove 1992; Komarov and Smolskiy 2003; Winkler 2007) the\n", "transmitted signal of an FMCW radar system can be modeled as\n", "where\n", "\n", "$$ y_T (t) = A_T \\cdot cos\\left( 2 \\pi \\cdot ( f_{0min} \\cdot t +\\int_0^tf_T({\\tau}) d\\tau)\\right)$$\n", "\n", "Given for a linear chirp that\n", "$$ f_T(\\tau) = \\frac{B}{T} \\cdot \\tau = s \\cdot \\tau $$\n", "We derive the phase, given\n", "$$ \\int_0^t f_T({\\tau}) d\\tau = \\frac{s}{2} \\cdot \\tau^2 - K$$\n", "\n", "which can be written as:\n", "$$ y_T (t) = A_T \\cdot cos \\left( 2 \\pi \\cdot ( f_{0min} \\cdot t + \\frac{s}{2} \\cdot t^2 )+\\Phi_0 \\right) $$\n", "\n", "Where:\n", "* $f_{0min}$ is the start frequency at the begining of the raising frequency of the chirp.\n", "* s is the slope at which the frequency is ramped ( $S = \\frac{B}{T}$)\n", "* B is the total bandwdith of the chirp\n", "* T is the total time of the chirp\n", "\n", "Considering a reflected signal with a time delay $\\delta = 2 · \\frac{R0+ v\\cdot t}{c}$ and Doppler shift $f_D = −2 · \\frac{f_c \\cdot v}{c}$\n", "\n", "Where:\n", "\n", "* c is the speed of light\n", "* $f_D$ is the doppler shift\n", "* R0 is the nominal distance to the target\n", "* $\\Delta$ is the time of flight (to and from the target)\n", "* v is the velocity of the target\n", "\n", "The receive signal $y_R(t)$ can be written as :\n", "\n", "$$y_R(t) = A_R \\cdot cos ( 2\\pi \\cdot (f_{0min} + \\frac{s}{2} \\cdot (t-\\delta)) \\cdot (t-\\delta))$$\n", "\n", "\n", "$y_{IF}(t)$ is the IF signal (after mixer) which is obtained by multiplication in the time domain, and passed to a low-pass filter (LPF)\n", "\n", "This can be done easily when remembering the trigonometric relation:\n", "\n", "$$cos(\\alpha) \\cdot cos(\\beta) = \\frac{cos(\\alpha + \\beta) + cos(\\alpha - \\beta)}{2}$$\n", "\n", "$$y_{MIX}(t) = y_R(t) \\cdot y_T(t)$$\n", "\n", "Noticing that the element which sums the elements will be higher frequency and will be filtered by the LPF, it remains that:\n", "\n", "$$y_{IF}(t) = \\frac{A_t \\cdot A_r}{2} \\cdot cos(2 \\pi \\cdot [f_{0min} \\cdot \\delta + s \\cdot \\delta \\cdot t - \\frac{s}{2} \\cdot \\delta^2])$$\n", "\n", "Where:\n", "\n", "* $f_{0min}$ the starting frequency of the chirp\n", "* s is the slope of the chirp\n", "* $\\delta$ is the total time of flight between antennas and target\n", "* At, Ar: Amplitude of the RX and TX waves\n", "\n", "## phase information\n", "\n", "$$ y_{IF}(t) = \\frac{A_t \\cdot A_r}{2} \\cdot cos(2 \\pi \\cdot [f_{0min} \\cdot \\delta + s \\cdot \\delta \\cdot t - \\frac{s}{2} \\cdot \\delta^2])$$\n", "\n", "could also be written as\n", "\n", "$$ y_{IF}(t) = A_{IF} \\cdot e^{j \\cdot \\Phi(t)} $$\n", "\n", "with\n", "$$ \\Phi(t) = 2 \\cdot \\pi \\cdot [f_{0min} \\cdot \\delta + s \\cdot \\delta \\cdot t - \\frac{s}{2} \\cdot \\delta^2] $$\n", "\n", "let's say $$ \\Phi_0 = \\Phi(\\tau) $$ and\n", "$\\Delta t$ later\n", "$$ \\Phi_1 = \\Phi(\\tau+\\Delta t) $$\n", "\n", "$$ \\Delta \\Phi = \\Phi_1 - \\Phi_0$$\n", "$$ \\iff $$\n", "$$ \\Delta \\Phi = \\Phi_1 - \\Phi_0 = f_0⋅\\Delta t−K \\cdot \\Delta t−K\\tau \\cdot \\Delta t-\\frac{K}{2}\\Delta t^2 $$\n", "\n", "which can be simplified as\n", "$$ \\Delta \\Phi = 2 \\cdot \\pi \\cdot f \\cdot \\Delta t $$\n", "with $c= \\lambda \\cdot f$\n", "\n", "$$ \\Delta \\Phi = 4 \\cdot \\pi \\cdot \\frac{v \\cdot T_c}{\\lambda} $$\n", "\n", "More ressources:\n", "* Eq 8 [FMCW Radar system](https://uwaterloo.ca/centre-for-intelligent-antenna-and-radio-systems/sites/ca.centre-for-intelligent-antenna-and-radio-systems/files/uploads/files/fmcwradarsystem.pdf)\n", "* Eq 7, 10 [spyy005 on ti.com](https://www.ti.com/lit/wp/spyy005a/spyy005a.pdf)\n", "* Eq 1, 2, 3, 4 [Design of an FMCW radar baseband\n", "signal processing system for automotive\n", "application](https://link.springer.com/content/pdf/10.1186/s40064-015-1583-5.pdf)\n", "* Eq 2.7 in [Object Detection with\n", "Automotive Radar\n", "Sensors using CFARAlgorithms](https://www.jku.at/fileadmin/gruppen/183/Docs/Finished_Theses/Bachelor_Thesis_Katzlberger_final.pdf)" ] }, { "cell_type": "markdown", "metadata": { "id": "BbRdF5nfR_Kh" }, "source": [ "### Distance from IF FFT" ] }, { "cell_type": "markdown", "metadata": { "id": "6quZUR4fSCJx" }, "source": [ "### Speed from IF FFT" ] }, { "cell_type": "markdown", "metadata": { "id": "5o6JdjegAhC1" }, "source": [ "## Minimum Reproductible Examples\n", "\n", "Below is code generating IF for single target and using the IF to comupte distance and speed.\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "WiJNA2I2Amt_" }, "source": [ "## Distance" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "6NJQ_925A4DN", "outputId": "4378e4c9-d0ae-4d61-c746-6dff79f0fa30" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "target at:12 is computed to be at 12\n", "target at:21 is computed to be at 21\n", "target at:40 is computed to be at 40\n" ] } ], "source": [ "from numpy import abs ,angle, arange, arcsin, cos, linspace, pi, sqrt, tan\n", "from scipy.fft import fft\n", "\n", "def y_IF(f0_min, slope, T, antenna_tx, antenna_rx, target, v=3e8):\n", " \"\"\" This function implements the mathematical IF defined in latex as\n", " y_{IF} = cos(2 \\pi [f_0\\delta + s * \\delta * t - s/2* \\delta^2])\n", " into following python code\n", " y_IF = cos (2*pi*(f_0 * delta + slope * delta * T - slope/2 * delta**2))\n", " Parameters:\n", " -----------\n", " f0_min: float\n", " the frequency at the begining of the chirp\n", " slope: float\n", " the slope with which the chirp frequency inceases over time\n", " T: ndarray\n", " the 1D vector containing time values\n", " antenna_tx: tuple of floats\n", " x, y, z coordinates\n", " antenna_rx: tuple of floats\n", " x, y, z coordinates\n", " target: tuple of floats\n", " x, y, z coordinates\n", " v: float\n", " speed of light in considered medium\n", " Returns:\n", " --------\n", " YIF: ndarray\n", " vector containing the IF values\n", " \"\"\"\n", " tx_x, tx_y, tx_z = antenna_tx\n", " rx_x, rx_y, rx_z = antenna_rx\n", " t_x, t_y, t_z = target\n", " # distance tx antenna to target\n", " distance = sqrt((tx_x-t_x)**2 + (tx_y-t_y)**2 + (tx_z-t_z)**2)\n", " # distance target to rx antenna\n", " distance += sqrt((rx_x-t_x)**2 + (rx_y-t_y)**2 + (rx_z-t_z)**2)\n", " # usually delta_t = 2*d/c, but\n", " # distance is already 2*D (TX to target + distance target to RX)\n", " # so delta = distance/v\n", " delta = distance/v\n", " YIF = cos(2 *pi *(f0_min * delta + slope * delta * T - slope/2 * delta**2))\n", " return YIF\n", "\n", "f0_min = 60e9\n", "c = 3e8\n", "# lambda ~5mm at 60GHz\n", "lambda0_max = 3e8/f0_min\n", "n_rx = 2\n", "Distance = 10\n", "k = 10e12\n", "n_samples = 512\n", "f_if = 2*k*Distance/c\n", "fs = 50e6\n", "ts = 1/fs\n", "\n", "# antenna_tx = (-lambda0_max/2,0,0)\n", "antenna_tx = (0,0,0)\n", "antenna_rx = (0,0,0)\n", "T = arange(0, n_samples*ts+ts, ts)\n", "\n", "for d in [12, 21, 40]:\n", " target = (0, d, 0)\n", "\n", " # sanity check\n", " f_if = 2*k*d/c\n", " assert f_if < fs/2\n", "\n", " YIF = y_IF(f0_min, k, T, antenna_tx, antenna_rx, target)\n", " FT = fft(YIF)\n", " MAG = abs(FT)[0: n_samples//2]\n", " ANG = angle(FT)[0: n_samples//2]\n", "\n", " # now find the peak\n", " amplitude_peak = sorted(MAG, reverse = True)[0]\n", " i_peak = list(MAG).index(amplitude_peak)\n", " # max un-ambiguous speed is fs*c/2/k\n", " distances = linspace(0, fs*c/2/k, n_samples)\n", " d_calc = distances[i_peak]\n", " print(f\"target at:{d:.2g} is computed to be at {d_calc:.2g}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "WiTVHL7RAoBq" }, "source": [ "## Speed w/ phase (no FFT)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "yEyC0CprDZPd", "outputId": "9dcc61fc-1e74-478d-d76c-eee0a59a988d" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "if we computed speed with FFT:\n", "speed resolution: 2.1e+03\n", "v_max: 1e+03\n", "speed is:1e+02 is computed to be at 1.1e+02\n", "speed is:2e+02 is computed to be at 2.2e+02\n", "speed is:3e+02 is computed to be at 3.4e+02\n" ] } ], "source": [ "from numpy import abs ,angle, arange, arcsin, cos, pi, sqrt, tan\n", "from scipy.fft import fft\n", "\n", "def y_IF(f0_min, slope, T, antenna_tx, antenna_rx, target, v=3e8):\n", " \"\"\" This function implements the mathematical IF defined in latex as\n", " y_{IF} = cos(2 \\pi [f_0\\delta + s * \\delta * t - s/2* \\delta^2])\n", " into following python code\n", " y_IF = cos (2*pi*(f_0 * delta + slope * delta * T - slope/2 * delta**2))\n", " Parameters:\n", " -----------\n", " f0_min: float\n", " the frequency at the begining of the chirp\n", " slope: float\n", " the slope with which the chirp frequency inceases over time\n", " T: ndarray\n", " the 1D vector containing time values\n", " antenna_tx: tuple of floats\n", " x, y, z coordinates\n", " antenna_rx: tuple of floats\n", " x, y, z coordinates\n", " target: tuple of floats\n", " x, y, z coordinates\n", " v: float\n", " speed of light in considered medium\n", " Returns:\n", " --------\n", " YIF: ndarray\n", " vector containing the IF values\n", " \"\"\"\n", " tx_x, tx_y, tx_z = antenna_tx\n", " rx_x, rx_y, rx_z = antenna_rx\n", " t_x, t_y, t_z = target\n", " # distance tx antenna to target\n", " distance = sqrt((tx_x-t_x)**2 + (tx_y-t_y)**2 + (tx_z-t_z)**2)\n", " # distance target to rx antenna\n", " distance += sqrt((rx_x-t_x)**2 + (rx_y-t_y)**2 + (rx_z-t_z)**2)\n", " # usually delta_t = 2*d/c, but\n", " # distance is already 2*D (TX to target + distance target to RX)\n", " # so delta = distance/v\n", " delta = distance/v\n", " YIF = cos(2 *pi *(f0_min * delta + slope * delta * T - slope/2 * delta**2))\n", " return YIF\n", "\n", "f0_min = 60e9\n", "c = 3e8\n", "# lambda ~5mm at 60GHz\n", "lambda0_max = 3e8/f0_min\n", "n_rx = 2\n", "Distance = 10\n", "k = 10e12\n", "n_samples = 512\n", "f_if = 2*k*Distance/c\n", "fs = 50e6\n", "ts = 1/fs\n", "\n", "antenna_tx = (0,0,0)\n", "antenna_rx = (0,0,0)\n", "T = arange(0, n_samples*ts+ts, ts)\n", "t_chirp_to_chirp = 1.2e-6\n", "print(\"if we computed speed with FFT:\")\n", "print(f\"speed resolution: {lambda0_max/(2*t_chirp_to_chirp):.2g}\")\n", "print(f\"v_max: {lambda0_max/(4*t_chirp_to_chirp):.2g}\")\n", "\n", "for d in [10]:\n", " for v in [100, 200, 300]:\n", " target_t0 = (d, 0, 0)\n", " f_if = 2*k*d/c\n", " # sanity check\n", " assert f_if < 1/ts/2\n", "\n", " YIF0 = y_IF(f0_min, k, T, antenna_tx, antenna_rx, target_t0)\n", "\n", " target_t1 = (d+v*t_chirp_to_chirp, 0, 0)\n", " YIF1 = y_IF(f0_min, k, T, antenna_tx, antenna_rx, target_t1)\n", "\n", " # since we have a real FFt, only want to see the first peak\n", " FT0 = fft(YIF0[:n_samples//2])\n", " FT1 = fft(YIF1[:n_samples//2])\n", " MAG0 = abs(FT0)\n", " amplitude_peak0 = sorted(MAG0, reverse = True)[0]\n", " i_peak0 = list(MAG0).index(amplitude_peak0)\n", "\n", " MAG1 = abs(FT1)\n", " amplitude_peak1 = sorted(MAG1, reverse = True)[0]\n", " i_peak1 = list(MAG1).index(amplitude_peak1)\n", " assert i_peak0 == i_peak1\n", "\n", " ANG0 = angle(FT0)\n", " ANG1 = angle(FT1)\n", "\n", " ph0 = ANG0[i_peak0]\n", " ph1 = ANG1[i_peak1]\n", " # d, v, ph0 10 100.0 -1.5646604036459237 -1.2627114022166515\n", " v_est = lambda0_max*(ph1-ph0)/(4*pi*t_chirp_to_chirp)\n", " print(f\"speed is:{v:.2g} is computed to be at {v_est:.2g}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "C0xtKfC_FbkJ" }, "source": [ "## Range-Doppler FFT (2D FFT)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 261 }, "id": "Y8lC-Q4GFeQA", "outputId": "96a4f77e-02ba-4e10-e2ad-a04f3da3b60c" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "512\n" ] }, { "data": { "text/plain": [ "Text(0.5, 1.0, 'Velocity-Range 2D FFT')" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkYAAADRCAYAAAApIX2+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABPWElEQVR4nO3dd1hUR9sH4N/ZBZaOgCAiVTTYEBWEiF0I4GtEEqOGoNjbZzexYDQYjUF9E6MxxqivLRawxEJMkCACsWBFEMSCBSmCqCgISNud7w/kxOMCsnTIc1/XueLOmTP7DJDlYc6cGY4xxkAIIYQQQiBq6AAIIYQQQhoLSowIIYQQQl6jxIgQQggh5DVKjAghhBBCXqPEiBBCCCHkNUqMCCGEEEJeo8SIEEIIIeQ1SowIIYQQQl6jxIgQQggh5DVKjAhppjiOw/Lly+uk7aSkJHAch127dtVJ+4QQ0lAoMSKkgXl4eEBdXR0vX76ssI63tzdUVFTw7NmzeoxMMX/++WedJGLLly8Hx3H8oaysDAsLC8yePRsvXryo9fdrKEeOHMGoUaPQtm1bqKurw9raGp9//nm5fXzz66GkpAQ9PT3Y2dlhzpw5SEhIqPJ7WlhYCNp68ygoKAAA7Nq1q8I6ixcvxoABAyo8/+ZRV0k6IbVNqaEDIOTfztvbG7///juOHj0KHx8fufP5+fk4fvw43N3doa+v3wARyjM3N8erV6+grKzMl/3555/YtGlTnf0C3Lx5MzQ1NZGXl4ewsDBs3LgR0dHROHv2bJ28X32bMmUKjI2NMXr0aJiZmSEuLg4//fQT/vzzT0RHR0NNTU1Q/4MPPoCPjw8YY8jOzkZsbCx2796Nn3/+GWvWrMH8+fOr9L7dunXD559/LleuoqIieL1ixQpYWloKyrp06QJnZ2dMmjSJL7t8+TJ+/PFHLFmyBB07duTLu3btWqV4CGlwjBDSoPLz85mWlhZzc3Mr9/z+/fsZABYYGKhQuwCYn59fLURYNTNmzGB18ZHi5+fHALAnT54IykeNGsUAsIsXL9b6ezaE8PBwubLdu3czAGzbtm2CcgBsxowZcvWfPn3KevXqxQCwP/74453vaW5uzoYMGVJpnZ07dzIA7PLly+9sjzHGDh06xACU2x9CmgK6lUZIA1NTU8PHH3+MsLAwZGZmyp3fv38/tLS04OHhAQB48eIF5s6dC1NTU0gkErRr1w5r1qyBTCZ753tdu3YNgwcPhra2NjQ1NeHs7IwLFy7I1Xvx4gXmzZsHCwsLSCQSmJiYwMfHB0+fPgUgP8do3Lhx2LRpEwDhbR7GGCwsLDBs2DC59ygoKICOjg6mTp1a5a/Vm/r27QsAuHfvHl+WlZWFL774AjY2NtDU1IS2tjYGDx6M2NhYwbURERHgOA4HDx7EqlWrYGJiAlVVVTg7O+Pu3bty77Vp0ya0bdsWampqcHBwwJkzZzBgwAAMGDBAUK+wsBB+fn5o164dJBIJTE1NsXDhQhQWFr6zP2+3BQAfffQRAODmzZvvvB4A9PX1ERgYCCUlJaxatapK1xBChOhWGiGNgLe3N3bv3o2DBw9i5syZfHlWVhZCQkLg5eUFNTU15Ofno3///khLS8PUqVNhZmaG8+fPw9fXF+np6Vi/fn2F73Hjxg307dsX2traWLhwIZSVlbFlyxYMGDAAkZGRcHR0BADk5uaib9++uHnzJiZMmIAePXrg6dOnCAoKQmpqKlq2bCnX9tSpU/Ho0SOEhoZiz549fDnHcRg9ejTWrl2LrKws6Onp8ed+//135OTkYPTo0dX6miUlJQEAdHV1+bL79+/j2LFjGDFiBCwtLfH48WNs2bIF/fv3R0JCAoyNjQVtrF69GiKRCF988QWys7Oxdu1aeHt74+LFi3ydzZs3Y+bMmejbty/mzZuHpKQkeHp6QldXFyYmJnw9mUwGDw8PnD17FlOmTEHHjh0RFxeHH374AXfu3MGxY8cU7mNGRgYAlPs1r4iZmRn69++P8PBw5OTkQFtbu9L6xcXFfMJbRl1dHerq6oKy7OxsuXqKxEVIk9HQQ1aEEMZKSkpY69atWa9evQTlv/zyCwPAQkJCGGOMrVy5kmloaLA7d+4I6i1evJiJxWKWnJzMl+GtW2menp5MRUWF3bt3jy979OgR09LSYv369ePLvvrqKwaAHTlyRC5OmUzGGGPswYMHDADbuXMnf66iW2m3b99mANjmzZsF5R4eHszCwoJvsyJlt9Ju377Nnjx5wpKSktiOHTuYmpoaMzAwYHl5eXzdgoICJpVKBdc/ePCASSQStmLFCr4sPDycAWAdO3ZkhYWFfPmGDRsYABYXF8cYY6ywsJDp6+uznj17suLiYr7erl27GADWv39/vmzPnj1MJBKxM2fOCN6/7Ht47ty5SvtZnokTJzKxWCz3/UYFt9LKzJkzhwFgsbGxlbZvbm7OAMgdb/7clN1KK+8oD91KI00d3UojpBEQi8X49NNPERUVxY+EAKW30Vq1agVnZ2cAwKFDh9C3b1/o6uri6dOn/OHi4gKpVIq///673PalUin++usveHp6om3btnx569at8dlnn+Hs2bPIyckBAPz222+wtbXlb+O8ieM4hfv23nvvwdHREfv27ePLsrKyEBwcDG9v7yq3aW1tDQMDA1hYWGDChAlo164dgoODBSMbEokEIpGI7/OzZ8+gqakJa2trREdHy7U5fvx4wSTjsttz9+/fBwBcuXIFz549w+TJk6Gk9M8Au7e3t2CkCij93nTs2BEdOnQQfG8GDRoEAAgPD69SP8vs378f27dvx+eff4727dsrdK2mpiYAVPqkYxlHR0eEhoYKjvIeAti0aZNcPUKaI7qVRkgj4e3tjR9++AH79+/HkiVLkJqaijNnzmD27NkQi8UAgMTERFy/fh0GBgbltlHeHCUAePLkCfLz82FtbS13rmPHjpDJZEhJSUHnzp1x7949DB8+vPY6BsDHxwczZ87Ew4cPYW5ujkOHDqG4uBhjxowBABQVFSErK0twjYGBAd9voDRh09bWxpMnT/Djjz/iwYMHck9qyWQybNiwAT///DMePHgAqVTKnyvviT4zMzPB67Jk5/nz5wCAhw8fAgDatWsnqKekpAQLCwtBWWJiIm7evKnw96Y8Z86cwcSJE+Hm5latuUK5ubkAAC0trXfWbdmyJVxcXN5Zz8HBAfb29grHQkhTQ4kRIY2EnZ0dOnTogICAACxZsgQBAQFgjMHb25uvI5PJ8MEHH2DhwoXltvHee+/VV7gK+fTTTzFv3jzs27cPS5Yswd69e2Fvb88naufPn8fAgQMF1zx48ECQfPTr14+f0zJ06FDY2NjA29sbV69e5UeJvv32WyxbtgwTJkzAypUroaenB5FIhLlz55Y7Of3NxOtNjDGF+yiTyWBjY4N169aVe97U1LRK7cTGxsLDwwNdunTB4cOHBSNVVRUfHw+xWCz3eD0h5N0oMSKkEfH29sayZctw/fp17N+/H+3bt0fPnj3581ZWVsjNza3SX/hvMjAwgLq6Om7fvi137tatWxCJRPwvbisrK8THxysce2W3xPT09DBkyBDs27cP3t7eOHfunGCiuK2trdytGSMjowrb09TUhJ+fH8aPH4+DBw/i008/BQAcPnwYAwcOxPbt2wX1X7x4Ua2Jwubm5gCAu3fvChK3kpISJCUlCdbmsbKyQmxsLJydnat1yxEofcLO3d0dhoaG+PPPP/lbYopITk5GZGQkevXqVaURI0KIEM0xIqQRKRsd+uqrrxATEyMYLQKAkSNHIioqCiEhIXLXvnjxAiUlJeW2KxaL4erqiuPHjwvmMD1+/Bj79+9Hnz59+KeXhg8fjtjYWBw9elSuncpGUjQ0NPg4yjNmzBgkJCRgwYIF/JyqMrq6unBxcREcqqqqFb4XUPq1MjExwZo1awT9fDvGQ4cOIS0trdK2KmJvbw99fX1s27ZN8LXdt28ff7utzMiRI5GWloZt27bJtfPq1Svk5eVV+l4ZGRlwdXWFSCRCSEhIhbfkKpOVlQUvLy9IpVJ8+eWXCl9PCKERI0IaFUtLSzg5OeH48eMAIJcYLViwAEFBQfjwww8xbtw42NnZIS8vD3FxcTh8+DCSkpIqHBn55ptvEBoaij59+uD//u//oKSkhC1btqCwsBBr164VvMfhw4cxYsQITJgwAXZ2dsjKykJQUBB++eUX2Nraltu+nZ0dAGD27Nlwc3OTS36GDBkCfX19HDp0CIMHD4ahoWGNvlbKysqYM2cOFixYgJMnT8Ld3R0ffvghVqxYgfHjx8PJyQlxcXHYt2+fYMK5IlRUVLB8+XLMmjULgwYNwsiRI5GUlIRdu3bByspKMDI0ZswYHDx4ENOmTUN4eDh69+4NqVSKW7du4eDBgwgJCal0jo67uzvu37+PhQsX4uzZs4IVvVu1aoUPPvhAUP/OnTvYu3cvGGPIyclBbGwsDh06hNzcXKxbtw7u7u7V6jMh/3oN+kwcIUTOpk2bGADm4OBQ7vmXL18yX19f1q5dO6aiosJatmzJnJyc2HfffceKior4eihn5evo6Gjm5ubGNDU1mbq6Ohs4cCA7f/683Hs8e/aMzZw5k7Vp04apqKgwExMTNnbsWPb06VPGWPmP65eUlLBZs2YxAwMDxnFcuY9z/9///R8DwPbv31/lr0dFK18zxlh2djbT0dHhH5svKChgn3/+OWvdujVTU1NjvXv3ZlFRUax///6CR+vLHtc/dOiQoL3y+sUYYz/++CMzNzdnEomEOTg4sHPnzjE7Ozvm7u4uqFdUVMTWrFnDOnfuzCQSCdPV1WV2dnbs66+/ZtnZ2ZX2ExU8Eo+3lgV4u65IJGItWrRg3bt3Z3PmzGE3btyo/Av6Blr5mhB5HGPVmGVICCHVMG/ePGzfvh0ZGRlyCwg2JTKZDAYGBvj444/LvXVGCGm6aI4RIaReFBQUYO/evRg+fHiTSooKCgrk5i39+uuvyMrKKncbD0JI00ZzjAghdSozMxOnTp3C4cOH8ezZM8yZM6ehQ1LIhQsXMG/ePIwYMQL6+vqIjo7G9u3b0aVLF4wYMaKhwyOE1DJKjAghdSohIQHe3t4wNDTEjz/+iG7dujV0SAqxsLCAqakpfvzxR36/Nx8fH6xevVqwajYhpHlo1nOMNm3ahP/+97/IyMiAra0tNm7cCAcHh4YOixBCCCGNVLOdY3TgwAHMnz8ffn5+iI6Ohq2tLdzc3BRalp8QQggh/y7NdsTI0dERPXv2xE8//QSg9CkSU1NTzJo1C4sXL27g6AghhBDSGDXLOUZFRUW4evUqfH19+TKRSAQXFxdERUWVe01hYSEKCwv51zKZDFlZWdDX16/28v6EEEIIqV+MMbx8+RLGxsb8PoqKaJaJ0dOnTyGVStGqVStBeatWrXDr1q1yr/H398fXX39dH+ERQgghpI6lpKTAxMRE4euaZWJUHb6+vpg/fz7/Ojs7G2ZmZuiD/0AJyg0YGXknkRiQSRs6CkLIvwV95jRqJSjGWfxZ7U2Um2Vi1LJlS4jFYjx+/FhQ/vjx4wp37JZIJJBIJHLlSlCGEkeJUaPGiQGu2T5HQAhpbOgzp3F7PXO6utNgmuV3VkVFBXZ2dggLC+PLZDIZwsLC0KtXrwaMjBBCCCGNWbMcMQKA+fPnY+zYsbC3t4eDgwPWr1+PvLw8jB8/vqFDI4QQQkgj1WwTo1GjRuHJkyf46quvkJGRgW7duuHkyZNyE7JJ08eJODDGAc1z5QlCCCH1qNmuY1RTOTk50NHRwQAMozlGjRynpAQmlVJiRAipHzT5ulErYcWIwHFkZ2dDW1tb4eub5Rwj8i9Ca0wRQgipRZQYEUIIIYS8RokRIYQQQshrjSox8vf3R8+ePaGlpQVDQ0N4enri9u3bgjpTp06FlZUV1NTUYGBggGHDhsmtZj179mzY2dlBIpGgW7du9dgDQgghhDRljSoxioyMxIwZM3DhwgWEhoaiuLgYrq6uyMvL4+vY2dlh586duHnzJkJCQsAYg6urK6RS4US4CRMmYNSoUfXdBdIQaKE1Qkh9YrKGjoDUoUb9VNqTJ09gaGiIyMhI9OvXr9w6169fh62tLe7evQsrKyvBueXLl+PYsWOIiYlR+L3pqbQmguPAKSmDlRTTU2mEkPrB0fIgjVlNn0pr1OsYZWdnAwD09PTKPZ+Xl4edO3fC0tISpqamNXqvwsJCFBYW8q9zcnJq1B4hhBBCmp5Gew9CJpNh7ty56N27N7p06SI49/PPP0NTUxOampoIDg5GaGgoVFRUavR+/v7+0NHR4Y+aJlqkHonokX1CCCG1o9EmRjNmzEB8fDwCAwPlznl7e+PatWuIjIzEe++9h5EjR6KgoKBG7+fr64vs7Gz+SElJqVF7hBBCCGl6FL6VVlhYiIsXL+Lhw4fIz8+HgYEBunfvDktLy1oLaubMmThx4gT+/vtvmJiYyJ0vG9Vp37493n//fejq6uLo0aPw8vKq9ntKJBJIJJKahE0aCMdxYJwIYLQSLSGEkJqpcmJ07tw5bNiwAb///juKi4uho6MDNTU1ZGVlobCwEG3btsWUKVMwbdo0aGlpVSsYxhhmzZqFo0ePIiIiokrJFmMMjDHB/CDyL0JPpBFCCKlFVfqt4uHhgVGjRsHCwgJ//fUXXr58iWfPniE1NRX5+flITEzE0qVLERYWhvfeew+hoaHVCmbGjBnYu3cv9u/fDy0tLWRkZCAjIwOvXr0CANy/fx/+/v64evUqkpOTcf78eYwYMQJqamr4z3/+w7dz9+5dxMTE8NfGxMQgJiYGRUVF1YqLNHKi1z/GtD0IIYSQGqrS4/pbtmzBhAkToKz87sfWExISkJ6eDmdnZ8WDqeAX286dOzFu3Dg8evQIkyZNwtWrV/H8+XO0atUK/fr1w1dffQVra2u+/oABAxAZGSnXzoMHD2BhYVGlWOhx/SaA4wBOBJGaKmSvCkrXFqFHaAkhdY0e12/Uavq4fqNex6ghUWLUBHAcOLEYnJoa2KtXYFIpfVgRQuoeJUaNWk0TI4UnaKSkpCA1NZV/fenSJcydOxdbt25V+M0JqQ2c+I0fY7qdRgghpAYUTow+++wzhIeHAwAyMjLwwQcf4NKlS/jyyy+xYsWKWg+QkHKVJUCcCFBSKv0vTcQmhBBSQwr/JomPj4eDgwMA4ODBg+jSpQvOnz+Pffv2YdeuXbUa3OrVq8FxHObOnSsoj4qKwqBBg6ChoQFtbW3069ePn6CdlJSEiRMnwtLSEmpqarCysoKfnx9NvG5O3kyKRBw4ZWX5USMaOSKEEFINCq9jVFxczK/3c+rUKXh4eAAAOnTogPT09FoL7PLly9iyZQu6du0qKI+KioK7uzt8fX2xceNGKCkpITY2FqLXTybdunULMpkMW7ZsQbt27RAfH4/JkycjLy8P3333Xa3FRxrAm8kOJwInKp1jBDVVIEcMTlQCJhP9s8EjzQMghBCiIIUTo86dO+OXX37BkCFDEBoaipUrVwIAHj16BH19/VoJKjc3F97e3ti2bRu++eYbwbl58+Zh9uzZWLx4MV/25hNp7u7ucHd351+3bdsWt2/fxubNmykxakoqGvF5fbuME3GAWAxOTRXFRjpQynkJSKWAVAYwDkxWlhCVsws2JUuEEEIqoPCttDVr1mDLli0YMGAAvLy8YGtrCwAICgrib7HV1IwZMzBkyBC4uLgIyjMzM3Hx4kUYGhrCyckJrVq1Qv/+/XH27NlK28vOzq5wI9oyhYWFyMnJERzNXtktp7o+ROJKD05JSXgoq4BTUi49VFQgkkggUlODSF0dIg11iHW0IdLXg9jYCPkOVtBek4a8XlbgTI0h1teFSEsLYk0NiNRUS6+TSMCpvNGmsorce74rxnr7WhFCCGlQVR4xys/Ph7q6OgYMGICnT58iJycHurq6/PkpU6ZAXV29xgEFBgYiOjoaly9fljt3//59AMDy5cvx3XffoVu3bvj111/h7OyM+Ph4tG/fXu6au3fvYuPGje8cLfL398fXX39d4/iblNoYOanKL3NWzqiN4PTb+bm0dESo9J+l231IZaXziTgOTCYDJ5WCAVDNzEfMhfawepwLLjcfrKAArLgEkErBGAPKRo7eiOGf0aSqx1glNBJFCCFNXpUTo5YtW2LQoEHw8PDAsGHD0KpVK8H5qi6cWJmUlBTMmTMHoaGhUFVVlTsvk5X+8po6dSrGjx8PAOjevTvCwsKwY8cO+Pv7C+qnpaXB3d0dI0aMwOTJkyt9b19fX8yfP59/nZOTA1NT05p2qfmrjWTgzT3OXidawm3PXr/gRGBA6VwiTgSuqAgiGYPFHxKIHz6G7GVuaVLEZP8kP5UlPJTIEEIIeUuVb6XdunULbm5uOHjwIMzNzeHo6IhVq1YhLi6u1oK5evUqMjMz0aNHDygpKUFJSQmRkZH48ccfoaSkxCdjnTp1ElzXsWNHJCcnC8oePXqEgQMHwsnJqUprLEkkEmhrawsO0gAYK/8AXq9sLQOTSkuPkhKw/HyopD4Hy80DKyriz5XVfWebhBBCyBuqnBiZmZlh1qxZOHXqFB4/foy5c+ciLi4Offv2Rdu2bTF37lycPn0aUmn1dzh3dnZGXFwcv7dZTEwM7O3t4e3tjZiYGLRt2xbGxsa4ffu24Lo7d+7A3Nycf52WloYBAwbAzs4OO3fu5J9YI01YOUkSkzGwkhJwea9Kb53JmHCEiBIgQgghClL4qTQA0NHRgZeXF7y8vFBcXIzw8HD8/vvvGD9+PF6+fImNGzfC29tb4Xa1tLTQpUsXQZmGhgb09fX58gULFsDPzw+2trbo1q0bdu/ejVu3buHw4cMA/kmKzM3N8d133+HJkyd8W0ZGRtXpLmlsGHv9KL4MkDGw4mIw6VsJESGEEFIN1UqM3qSsrAxXV1e4urpi48aNuHbtGkpKSmojtnLNnTsXBQUFmDdvHrKysmBra4vQ0FBYWVkBAEJDQ3H37l3cvXsXJiYmgmtpW7hmiMmAkhLhrTNCCCGkmqq1iWxBQQGuX7+OzMxMfkI0AHAch6FDh9ZqgA2FNpFtAmgTWUJIQ6DFYxu1mm4iq/CI0cmTJ+Hj44OnT5/KneM4rkZzjAhRFJMxcDJZ+Y/gE0IIIQpSeFbyrFmzMGLECKSnp0MmkwkOSopIveInYlNSRAghpHYonBg9fvwY8+fPl1vHiJAGI3vjsXxCCCGkBhROjD755BNERETUQSil0tLSMHr0aOjr60NNTQ02Nja4cuUKf/7x48cYN24cjI2Noa6uDnd3dyQmJpbbFmMMgwcPBsdxOHbsWJ3FTBoQTbgmhBBSixSeY/TTTz9hxIgROHPmDGxsbKCsLJyYPHv27GoH8/z5c/Tu3RsDBw5EcHAwDAwMkJiYyG89whiDp6cnlJWVcfz4cWhra2PdunVwcXFBQkICNDQ0BO2tX78eHO0/1ewxxihBIoQQUisUTowCAgLw119/QVVVFREREYLEg+O4GiVGa9asgampKXbu3MmXWVpa8v9OTEzEhQsXEB8fj86dOwMANm/eDCMjIwQEBGDSpEl83ZiYGHz//fe4cuUKWrduXe2YCCGEEPLvofCttC+//BJff/01srOzkZSUhAcPHvBH2Sav1RUUFAR7e3uMGDEChoaG6N69O7Zt28afLywsBADBPmoikQgSiQRnz57ly/Lz8/HZZ59h06ZNVV7UsbCwEDk5OYKDNBH0RBohhJBaonBiVFRUhFGjRtXJNhv379/H5s2b0b59e4SEhGD69OmYPXs2du/eDQDo0KEDzMzM4Ovri+fPn6OoqAhr1qxBamoq0tPT+XbmzZsHJycnDBs2rMrv7e/vDx0dHf6gDWQJIYSQfx+Fs5uxY8fiwIEDdRELZDIZevTogW+//Rbdu3fHlClTMHnyZPzyyy8ASlfZPnLkCO7cuQM9PT2oq6sjPDwcgwcP5hO1oKAgnD59GuvXr1fovX19fZGdnc0fKSkptd09QgghhDRyCs8xkkqlWLt2LUJCQtC1a1e5ydfr1q2rdjCtW7dGp06dBGUdO3bEb7/9xr+2s7NDTEwMsrOzUVRUBAMDAzg6OsLe3h4AcPr0ady7dw8tWrQQtDN8+HD07du3wifqJBIJJBJJtWMnhBBCSNOncGIUFxeH7t27AwDi4+MF52r6BFjv3r1x+/ZtQdmdO3dgbm4uV1dHRwdA6YTsK1euYOXKlQCAxYsXCyZhA4CNjQ1++OGHZrNdCXkLPZFGCCGkliicGIWHh9dFHAD+mRv07bffYuTIkbh06RK2bt2KrVu38nUOHToEAwMDmJmZIS4uDnPmzIGnpydcXV0BAEZGRuVOuDYzMxM84UYIIYQQ8jaFE6O61LNnTxw9ehS+vr5YsWIFLC0tsX79enh7e/N10tPTMX/+fDx+/BitW7eGj48Pli1b1oBRE0IIIaS54Bh79z4K06ZNw9KlS2FiYvLOBg8cOICSkhJBMtMU5eTkQEdHBwMwDEqc8rsvIA2D48CJxWBSKW0JQgipHxxHnzeNWAkrRgSOIzs7G9ra2gpfX6URIwMDA3Tu3Bm9e/fG0KFDYW9vD2NjY6iqquL58+dISEjA2bNnERgYCGNjY8GtL0LqFH04EUIIqUVVGjECSvco+9///ofAwEAkJCQIzmlpacHFxQWTJk2Cu7t7nQRa32jEqOnglJRoxIgQUn9oxKhRq+mIUZUTozc9f/4cycnJePXqFVq2bAkrK6tmtycZJUZNiEgMyKQNHQUh5N+CEqNGraaJUbWWr9bV1YWtrS3ef/99tGvXrlaTopcvX2Lu3LkwNzeHmpoanJyccPnyZQBAcXExFi1aBBsbG2hoaMDY2Bg+Pj549OiRoA0LCwtwHCc4Vq9eXWsxEkIIIaR5alRPpQHApEmTEB8fjz179sDY2Bh79+6Fi4sLEhISoKmpiejoaCxbtgy2trZ4/vw55syZAw8PD1y5ckXQzooVKzB58mT+tZaWVn13hRBCCCFNTKNKjF69eoXffvsNx48fR79+/QAAy5cvx++//47Nmzfjm2++QWhoqOCan376CQ4ODkhOToaZmRlfrqWlVeUNZIHSTWTLNqkFQJvIEkIIIf9Ctb8TbA2UlJRAKpVCVVVVUK6mpoazZ8+We012djY4jpPbAmT16tXQ19dH9+7d8d///hclJSWVvjdtIksIIYQQhSZfM8aQkpICQ0NDueSltjg5OUFFRQX79+9Hq1atEBAQgLFjx6Jdu3Zy24UUFBSgd+/e6NChA/bt28eXr1u3Dj169ICenh7Onz8PX19fjB8/vtJ93MobMTI1NaXJ100BTb4mhNQnmnzdqNXrU2kymQyqqqq4ceMG2rdvr/CbVcW9e/cwYcIE/P333xCLxejRowfee+89XL16FTdv3uTrFRcXY/jw4UhNTUVERESlnd+xYwemTp2K3NzcKm8US0+lNSGUGBFC6hMlRo1avT6VJhKJ0L59ezx79kzhN6oqKysrREZGIjc3FykpKbh06RKKi4vRtm1bvk5xcTFGjhyJhw8fIjQ09J0dd3R0RElJCZKSkuosbkIIIYQ0fQrPMVq9ejUWLFiA+Pj4uoiHp6GhgdatW+P58+cICQnBsGHDAPyTFCUmJuLUqVPQ19d/Z1sxMTEQiUQwNDSs05gJIYT8C3CNanouqWUKP5Xm4+OD/Px82NraQkVFBWpqaoLzWVlZNQooJCQEjDFYW1vj7t27WLBgATp06IDx48ejuLgYn3zyCaKjo3HixAlIpVJkZGQAAPT09KCiooKoqChcvHgRAwcOhJaWFqKiojBv3jyMHj0aurq6NYqNEEIIIc2bwonR+vXr6yCMf2RnZ8PX1xepqanQ09PD8OHDsWrVKigrKyMpKQlBQUEAgG7dugmuCw8Px4ABAyCRSBAYGIjly5ejsLAQlpaWmDdvHubPn1+ncRNCCCGk6avWliD/BjT5ugmhydeEkPpEnzmNWoNsCXLv3j0sXboUXl5eyMzMBAAEBwfjxo0b1WmOEEIIIaRRUDgxioyMhI2NDS5evIgjR44gNzcXABAbGws/P79aD5AQQgghpL4onBgtXryY35pDRUWFLx80aBAuXLhQo2AeP36McePGwdjYGOrq6nB3d0diYmK5dRljGDx4MDiOw7Fjx/jy2NhYeHl5wdTUFGpqaujYsSM2bNhQo7hII8dkDR0BIYSQZkLhyddxcXHYv3+/XLmhoSGePn1a7UAYY/D09ISysjKOHz8ObW1trFu3jt9AVkNDQ1B//fr14DhOrp2rV6/C0NAQe/fuhampKc6fP48pU6ZALBZj5syZ1Y6PEEIIIc2fwolRixYtkJ6eDktLS0H5tWvX0KZNm2oHkpiYiAsXLiA+Ph6dO3cGAGzevBlGRkYICAjApEmT+LoxMTH4/vvvceXKFbRu3VrQzoQJEwSv27Zti6ioKBw5coQSI0IIIYRUSuFbaZ9++ikWLVqEjIwMcBwHmUyGc+fO4YsvvoCPj0+1Aynbp+zNPdhEIhEkEolgA9n8/Hx89tln2LRpE4yMjKrUdnZ2NvT09N75/jk5OYKDEEIIIf8uCidG3377LTp06ABTU1Pk5uaiU6dO6NevH5ycnLB06dJqB9KhQweYmZnB19cXz58/R1FREdasWYPU1FSkp6fz9ebNmwcnJyd+Jex3OX/+PA4cOIApU6ZUWs/f3x86Ojr8YWpqWu2+EEIIIaRpUjgxUlFRwbZt23Dv3j2cOHECe/fuxa1bt7Bnzx6IxeIqt7Nv3z5oamryx4ULF3DkyBHcuXMHenp6UFdXR3h4OAYPHgyRqDTMoKAgnD59usqLTMbHx2PYsGHw8/ODq6trpXV9fX2RnZ3NHykpKVXuCyGEEEKaB4XnGJUxMzODmZlZtd/Yw8MDjo6O/Os2bdpATU0NMTExyM7ORlFREQwMDODo6Ah7e3sAwOnTp3Hv3j20aNFC0Nbw4cPRt29fRERE8GUJCQlwdnbGlClTqjSSJZFIIJFIqt0fQgghhDR9VVr5WpHtNNatW1ejgN6UmJiIDh06IDg4GK6ursjIyJB78s3GxgYbNmzA0KFD+QnhN27cwKBBgzB27FisXbu2Wu9NK183IRwH0ALuhJD6QitfN2o1Xfm6SiNG165dq1Jj5T0+r4hDhw7BwMAAZmZmiIuLw5w5c+Dp6cnfBjMyMip3wrWZmRmfFMXHx2PQoEFwc3PD/Pnz+U1mxWIxDAwMahQfIYQQQpq3KiVG4eHhdR0HACA9PR3z58/H48eP0bp1a/j4+GDZsmUKtXH48GE8efIEe/fuxd69e/lyc3NzJCUl1XLEhBBCCGlOarSJbNkE5eb4BBfdSmtC6FYaIaS+cBw4sRispKShIyEVqPdNZEtKSrBs2TLo6OjAwsICFhYW0NHRwdKlS1FcXKxwAIQQQgghjYXCT6XNmjULR44cwdq1a9GrVy8AQFRUFJYvX45nz55h8+bNtR4kIYQQQkh9UDgx2r9/PwIDAzF48GC+rGvXrjA1NYWXlxclRoQQQghpshS+lSaRSGBhYSFXbmlpCRUVlRoFw3Fcucd///tfvs6qVavg5OQEdXV1ufWMyiQnJ2PIkCFQV1eHoaEhFixYgBK6H0wIIaS21PApbNJ4KZwYzZw5EytXruT3NgNK9xlbtWpVjTdpTU9PFxw7duwAx3EYPnw4X6eoqAgjRozA9OnTy21DKpViyJAhKCoqwvnz57F7927s2rULX331VY1iI4QQQkjzp/BTaR999BHCwsIgkUhga2sLAIiNjUVRURGcnZ0FdY8cOVKj4Dw9PfHy5UuEhYXJndu1axfmzp2LFy9eCMqDg4Px4Ycf4tGjR2jVqhUA4JdffsGiRYvw5MmTKo9q0VNpTQg9lUYIqS8cB05JGaykmD53Gql6WeDxTS1atBCM4AB187j+48eP8ccff2D37t0KXRcVFQUbGxs+KQIANzc3TJ8+HTdu3ED37t3Lva6wsFAwCpaTk1O9wAkhhBDSZCmcGO3cubMu4pCze/duaGlp4eOPP1bouoyMDEFSBIB/XbYKdnn8/f3x9ddfKx4oIYQQQpoNhecYlcnMzMSZM2dw5swZZGZmKnz9vn37oKmpyR9nzpwRnN+xYwe8vb2hqqpa3RAV4uvri+zsbP4oW7ySEEIIERDRxOvmTOERo5ycHMyYMQOBgYGQSks30ROLxRg1ahQ2bdoEHR2dKrXj4eEBR0dH/nWbNm34f585cwa3b9/GgQMHFA0PRkZGuHTpkqDs8ePH/LmKSCQSSCQShd+PEEIIIc2HwiNGkydPxsWLF3HixAm8ePECL168wIkTJ3DlyhVMnTq1yu1oaWmhXbt2/KGmpsaf2759O+zs7PjJ3Yro1asX4uLiBKNYoaGh0NbWRqdOnRRujxBCCCH/HgqPGJ04cQIhISHo06cPX+bm5oZt27bB3d29xgHl5OTg0KFD+P7778s9n5ycjKysLCQnJ0MqlSImJgYA0K5dO2hqasLV1RWdOnXCmDFjsHbtWmRkZGDp0qWYMWMGjQgRQgipGU4EjuNAz6M1XwonRvr6+uXeLtPR0YGurm6NAwoMDARjDF5eXuWe/+qrrwRPqpU9ZRYeHo4BAwZALBbjxIkTmD59Onr16gUNDQ2MHTsWK1asqHFshBBC/sXKFnUUVXt6LmkCFF7HaOvWrTh06BD27NnDz9nJyMjA2LFj8fHHHyt0O60xo3WMmhBax4gQUh84DuBEEKlKICsoBGTSho6IlKPe1zHavHkz7t69CzMzM5iZmQEovb0lkUjw5MkTbNmyha8bHR2tcECEEEJIo1Y2ckR/lDVLCidGnp6edRAGIYQQ0vhxIg6cWNzQYZA6pHBi5OfnVxdxyJk2bRq2bNmCH374AXPnzgUAREREYODAgeXWv3TpEnr27AkAOHjwIL799lvcuXMHBgYGmDlzJhYsWFAvcRNCCGmG+FEiESAWgxNxYFLQqFEzpHBiVObq1au4efMmAKBz584VbrVRHUePHsWFCxdgbGwsKHdyckJ6erqgbNmyZQgLC4O9vT2A0r3SvL29sXHjRri6uuLmzZuYPHky1NTUarzJLSGEkH+hN5MiABCLXv+b5hg1RwonRpmZmfj0008RERGBFi1aAABevHiBgQMHIjAwEAYGBjUKKC0tDbNmzUJISAiGDBkiOKeioiJYpLG4uBjHjx/HrFmzwL3+wd2zZw88PT0xbdo0AEDbtm3h6+uLNWvWYMaMGXw9Qggh5J3e+p3BiUXgJBJAlFuaHDEZjRo1Mwo/czhr1iy8fPkSN27cQFZWFrKyshAfH4+cnBzMnj27RsHIZDKMGTMGCxYsQOfOnd9ZPygoCM+ePcP48eP5ssLCQrltRNTU1JCamoqHDx9W2FZhYSFycnIEByGEkH8pjhMmRZyodG6RsjKYhhq417fT+FGksvr0x3eTp3BidPLkSfz888/o2LEjX9apUyds2rQJwcHBNQpmzZo1UFJSqnKCtX37dri5ucHExIQvc3Nzw5EjRxAWFgaZTIY7d+7wi0W+fRvuTf7+/tDR0eEPU1PTGvWFEEJIE/BmQiM4RPzBicWlh7ISRBrqKDRtAU5dDZySEn+Or19Zm6RJUDgxkslkUFaWX9dHWVkZMpmsyu28vYlsZGQkNmzYgF27dlXpdldqaipCQkIwceJEQfnkyZMxc+ZMfPjhh1BRUcH777+PTz/9FAAgqmRRLtpElhBCGrEKExgFD5FYeLyd/IjF4JSUS5MgFWWIVCXg1NQg0tSASLcFiq1a4/5wMZhJK3A62hBpqIGTSCBSKb2GU1IuPd5oj0+a5N67lvpEapXCCzwOGzYML168QEBAAD85Oi0tDd7e3tDV1cXRo0er1M7Lly/5zV0B4NChQ/jyyy8FyYtUKoVIJIKpqSmSkpIE169cuRIbN25EWlpauYmaVCpFRkYGDAwMEBYWhv/85z/IzMys8hwoWuCxCaH7+4Q0PXX9C52r/O9+TsS9XVD639flHMcBYjEgEoFTUgJUlAEtDeR1MIDG56ko+toIkgdPwPILgOIiMKkMkEoBmQz8r1XZ6/8y4aABk73j84pVfZChWpr552VNF3hUODFKSUmBh4cHbty4wd9uSklJQZcuXRAUFCS4raWIZ8+eyd3qcnNzw5gxYzB+/HhYW1vz5YwxWFlZ4eOPP8Z33333zrZ9fHxw9+5dnD9/vsrxUGLUhFBiRAipqYoSNU7EzyXixCJwmhooec8ESjeTIcvPB6TSfxKdihIa+nyqV/W+8rWpqSmio6Nx6tQp3Lp1CwDQsWNHuLi4KPzmb9LX14e+vr6gTFlZGUZGRoKkCABOnz6NBw8eYNKkSXLtPH36FIcPH8aAAQNQUFCAnTt34tChQ4iMjKxRfIQQQpqx8pIXjgOY7PV6Raw08XlVAKXMHLDCwn+SojcTIkqCmrxqrWPEcRw++OADfPDBB7UdT5Vs374dTk5O6NChQ7nnd+/ejS+++AKMMfTq1QsRERFwcHCo5ygJIYQ0aWVJTlmCJBOBk0rB5b2C7O2kiBKiZkOhW2kymQy7du3CkSNHkJSUBI7jYGlpiU8++QRjxoxpVmsE0a20JoRupRFC6hpXdjtNDJGOFmQvssGkrxd4pM+fRqWmt9Kq/FQaYwweHh6YNGkS0tLSYGNjg86dO+Phw4cYN24cPvroI4XfnBBCCGkymKz0KCl5o4ySouamyrfSdu3ahb///hthYWFy+5WdPn0anp6e+PXXX+Hj41PrQRJCCCENirF/JmjL2LufLCNNVpVHjAICArBkyZJyN3EdNGgQFi9ejH379tVqcIQQQkhjwmTsn1topFmqcmJ0/fp1uLu7V3h+8ODBiI2NrXYgxcXFWLRoEWxsbKChoQFjY2P4+Pjg0aNH5dYvLCxEt27dwHEcYmJiBOdCQkLw/vvvQ0tLCwYGBhg+fLjcOkiEEEJItZTdPqPbaM1SlROjrKwstGrVqsLzrVq1wvPnz6sdSH5+PqKjo7Fs2TJER0fjyJEjuH37Njw8PMqtv3DhQn6ByTc9ePAAw4YNw6BBgxATE4OQkBA8ffoUH3/8cbVjI4QQQvhESCar+0UYSYOp8hwjqVQKJaWKq4vFYpS8OSFNQTo6OggNDRWU/fTTT3BwcEBycjLMzMz48uDgYPz111/47bff5PZnu3r1KqRSKb755ht+Fe0vvvgCw4YNQ3FxcbmrZBNCCCGEAAokRowxjBs3DhKJpNzzhYWFtRZUmezsbHAchxYtWvBljx8/xuTJk3Hs2DGoq6vLXWNnZweRSISdO3di3LhxyM3NxZ49e+Di4lJpUlRYWCjoQ05OTq32hRBCSDPA3tjygzRLVb6VNnbsWBgaGgp2oH/zMDQ0rNUn0goKCrBo0SJ4eXnx6xCUJWfTpk2Dvb19uddZWlrir7/+wpIlSyCRSNCiRQukpqbi4MGDlb6fv7+/oD9l250QQggh5N9D4b3Sasu+ffswdepU/nVwcDD69u0LoHQi9vDhw5GamoqIiAg+Mfrxxx9x8OBBREZGQiwWIykpCZaWlrh27Rq6desGAMjIyEC/fv3g6ekJLy8vvHz5El999RWUlJQQGhpa4SKU5Y0YmZqa0gKPTQEt8EgIqS8cB05JGaykmD53Gql63yuttnh4eMDR0ZF/3aZNGwClSdHIkSPx8OFDnD59WtCp06dPIyoqSu52nr29Pby9vbF7925s2rQJOjo6WLt2LX9+7969MDU1xcWLF/H++++XG49EIqnwNiEhhBBC/h0aLDHS0tKClpaWoKwsKUpMTER4eLjcprI//vgjvvnmG/71o0eP4ObmhgMHDvBJVn5+Pj/puoxYLAZQuqUJIYQQQkhFGiwxeltxcTE++eQTREdH48SJE5BKpcjIyAAA6OnpQUVFRfBkGgBoamoCAKysrGBiYgIAGDJkCH744QesWLGCv5W2ZMkSmJubo3v37vXbKUIIIYQ0KVWefF3X0tLSEBQUhNTUVHTr1g2tW7fmj/Pnz1e5nUGDBmH//v04duwYunfvDnd3d0gkEpw8eRJqamp12ANCCCH/CrSGUbPWYJOvG7ucnBzo6OjQ5OumQCQGZLREPyGkHnAcOLEYrAbr9pG6VdPJ141mxIgQQgghpKFRYkQIIYQQ8lqTS4yOHDkCV1dX6Ovrl7uBbJmoqCgMGjQIGhoa0NbWRr9+/fDq1av6DZYQQgghTUqTS4zy8vLQp08frFmzpsI6UVFRcHd3h6urKy5duoTLly9j5syZco/xE0IIIYS8qdE8rl9VY8aMAQAkJSVVWGfevHmYPXs2Fi9ezJdZW1vXdWiEEEKaO8bAZPTMUnPW7IZQMjMzcfHiRRgaGsLJyQmtWrVC//79cfbs2UqvKywsRE5OjuAghBBCyL9Ls0uM7t+/DwBYvnw5Jk+ejJMnT6JHjx5wdnZGYmJihdfRJrKEEEIIadS30irbaLYiZdt+TJ06FePHjwcAdO/eHWFhYdixYwf8/f3Lvc7X1xfz58/nX2dnZ8PMzAwlKAZo1LRxYzKA0TpGhJB6Qp85jVoJigEA1V2msVEnRhVtNFuZ1q1bAwA6deokKO/YsSOSk5MrvO7tTWSfPn0KADiLPxWKmTQAWoSWEFKf6DOnSXj58iV0dHQUvq5RJ0blbTT7LhYWFjA2Nsbt27cF5Xfu3MHgwYOr3I6enh4AIDk5uVpf2MYqJycHpqamSElJqdaKoI1Zc+0b9atpaa79Appv36hfTcu7+sUYw8uXL2FsbFyt9ht1YlSerKwsJCcn49GjRwDAJ0BGRkYwMjICx3FYsGAB/Pz8YGtri27dumH37t24desWDh8+XOX3KXu0X0dHp1n9QJXR1tZulv0Cmm/fqF9NS3PtF9B8+0b9aloq61dNBjSaXGIUFBTEzx0CgE8//RQA4Ofnh+XLlwMA5s6di4KCAsybNw9ZWVmwtbVFaGgorKysGiJkQgghhDQRTS4xGjduHMaNG/fOeosXLxasY0QIIYQQ8i7N7nH92iKRSODn5yeYkN0cNNd+Ac23b9SvpqW59gtovn2jfjUtdd0vjlX3eTZCCCGEkGaGRowIIYQQQl6jxIgQQggh5DVKjAghhBBCXqPEiBBCCCHkNUqMKrBp0yZYWFhAVVUVjo6OuHTpUkOHVGVSqRTLli2DpaUl1NTUYGVlhZUrV/L7xhQXF2PRokWwsbGBhoYGjI2N4ePjwy+a2Zj8/fffGDp0KIyNjcFxHI4dOyZX5+bNm/Dw8ICOjg40NDTQs2fPcrd/YYxh8ODBFbZTn/z9/dGzZ09oaWnB0NAQnp6ecqu1l3lX3Lt27ULXrl2hqqoKQ0NDzJgxo46jr9jmzZvRtWtXfuG1Xr16ITg4GEDp4qyzZs2CtbU11NTUYGZmhtmzZyM7O1vQxuXLl+Hs7IwWLVpAV1cXbm5uiI2NbYjuVGj16tXgOA5z587ly7Zu3YoBAwZAW1sbHMfhxYsXctdlZWXB29sb2traaNGiBSZOnIjc3Nz6C/wdyuvXvXv38NFHH8HAwADa2toYOXIkHj9+LLjOw8MDZmZmUFVVRevWrTFmzJgG/zxZvnw5OI4THB06dAAAJCUlyZ0rOw4dOsS3Ud75wMDAhuoSLy0tDaNHj4a+vj7U1NRgY2ODK1eu8OfHjRsnF7e7uzt/PiIiosL+X758uSG6BAsLi3LjKfs8mzp1KqysrKCmpgYDAwMMGzYMt27dErQRFhYGJycnaGlpwcjICIsWLUJJSYnCsVBiVI4DBw5g/vz58PPzQ3R0NGxtbeHm5obMzMyGDq1K1qxZg82bN+Onn37CzZs3sWbNGqxduxYbN24EAOTn5yM6OhrLli1DdHQ0jhw5gtu3b8PDw6OBI5eXl5cHW1tbbNq0qdzz9+7dQ58+fdChQwdERETg+vXrWLZsGVRVVeXqrl+/HhzH1XXIVRIZGYkZM2bgwoULCA0NRXFxMVxdXZGXlydXt7K4161bhy+//BKLFy/GjRs3cOrUKbi5udV1+BUyMTHB6tWrcfXqVVy5cgWDBg3CsGHDcOPGDTx69AiPHj3Cd999h/j4eOzatQsnT57ExIkT+etzc3Ph7u4OMzMzXLx4EWfPnoWWlhbc3NxQXFzcYP160+XLl7FlyxZ07dpVUJ6fnw93d3csWbKkwmu9vb1x48YNhIaG4sSJE/j7778xZcqUug65SsrrV15eHlxdXcFxHE6fPo1z586hqKgIQ4cO5TfsBoCBAwfi4MGDuH37Nn777Tfcu3cPn3zySUN0Q6Bz585IT0/nj7NnzwIATE1NBeXp6en4+uuvoampKbd11M6dOwX1PD09G6An/3j+/Dl69+4NZWVlBAcHIyEhAd9//z10dXUF9dzd3QVxBwQE8OecnJzk+j9p0iRYWlrC3t6+vrsEoPTn7814QkNDAQAjRowAANjZ2WHnzp24efMmQkJCwBiDq6srpNLSzXxjY2Pxn//8B+7u7rh27RoOHDiAoKCg6q1nyIgcBwcHNmPGDP61VCplxsbGzN/fvwGjqrohQ4awCRMmCMo+/vhj5u3tXeE1ly5dYgDYw4cP6zq8agPAjh49KigbNWoUGz169DuvvXbtGmvTpg1LT08vt52GlpmZyQCwyMhIQXllcWdlZTE1NTV26tSpeo5WMbq6uux///tfuecOHjzIVFRUWHFxMWOMscuXLzMALDk5ma9z/fp1BoAlJibWS7yVefnyJWvfvj0LDQ1l/fv3Z3PmzJGrEx4ezgCw58+fC8oTEhIYAHb58mW+LDg4mHEcx9LS0uo48spV1K+QkBAmEolYdnY2X/fFixeM4zgWGhpaYXvHjx9nHMexoqKiug69Qn5+fszW1rbK9bt16yb3udkYPysWLVrE+vTpU2mdsWPHsmHDhlW5zaKiImZgYMBWrFhRw+hqz5w5c5iVlRWTyWTlno+NjWUA2N27dxljjPn6+jJ7e3tBnaCgIKaqqspycnIUem8aMXpLUVERrl69ChcXF75MJBLBxcUFUVFRDRhZ1Tk5OSEsLAx37twBUJpJnz17ttJNdLOzs8FxHFq0aFFPUdacTCbDH3/8gffeew9ubm4wNDSEo6Oj3O2m/Px8fPbZZ9i0aROMjIwaJth3KLudVLZ5MfDuuENDQyGTyZCWloaOHTvCxMQEI0eOREpKSr3FXRmpVIrAwEDk5eWhV69e5dbJzs6GtrY2lJRKF+G3traGvr4+tm/fjqKiIrx69Qrbt29Hx44dYWFhUY/Rl2/GjBkYMmSI4POhqqKiotCiRQvBX+QuLi4QiUS4ePFibYapsIr6VVhYCI7jBAvpqaqqQiQS8aMvb8vKysK+ffvg5OQEZWXlOo37XRITE2FsbIy2bdvC29u73FvsAHD16lXExMQIRi/LzJgxAy1btoSDgwN27NjBT0loKEFBQbC3t8eIESNgaGiI7t27Y9u2bXL1IiIiYGhoCGtra0yfPh3Pnj2rtM1nz54JtttqSEVFRdi7dy8mTJhQ7mh5Xl4edu7cCUtLS5iamgIo/Vl9+06BmpoaCgoKcPXqVcUCqFYq14ylpaUxAOz8+fOC8gULFjAHB4cGikoxUqmULVq0iHEcx5SUlBjHcezbb7+tsP6rV69Yjx492GeffVaPUSoOb/31VjaKoq6uztatW8euXbvG/P39GcdxLCIigq83ZcoUNnHixArbaWhSqZQNGTKE9e7dW1D+rrj9/f2ZsrIys7a2ZidPnmRRUVHM2dmZWVtbs8LCwvoKX87169eZhoYGE4vFTEdHh/3xxx/l1nvy5AkzMzNjS5YsEZTHxcUxKysrJhKJmEgkYtbW1iwpKak+Qq9UQEAA69KlC3v16hVjjCk8YrRq1Sr23nvvydU3MDBgP//8c12EXCWV9SszM5Npa2uzOXPmsLy8PJabm8tmzpzJALApU6YI2lm4cCFTV1dnANj777/Pnj59Wt9dEfjzzz/ZwYMHWWxsLDt58iTr1asXMzMzK3f0YPr06axjx45y5StWrGBnz55l0dHRbPXq1UwikbANGzbUR/gVkkgkTCKRMF9fXxYdHc22bNnCVFVV2a5du/g6AQEB7Pjx4+z69evs6NGjrGPHjqxnz56spKSk3DYHDx7MBg8eXF9deKcDBw4wsVgsN5K6adMmpqGhwQAwa2trfrSIsX9GN/fv389KSkpYamoq69u3LwPA9u/fr9D7U2L0luaQGAUEBDATExMWEBDArl+/zn799Vemp6cn+B+nTFFRERs6dCjr3r27YLi8MXo7MSj7Xnl5eQnqDR06lH366aeMsdIh/Xbt2rGXL19W2E5DmzZtGjM3N2cpKSl8WVXiXrVqFQPAQkJC+LLMzEwmEonYyZMn6yX28hQWFrLExER25coVtnjxYtayZUt248YNQZ3s7Gzm4ODA3N3dBbdb8vPzmYODA/Px8WGXLl1iUVFRbPjw4axz584sPz+/vrvCS05OZoaGhiw2NpYvaw6JUVX6FRISwtq2bcs4jmNisZiNHj2a9ejRg02bNk3Q1pMnT9jt27fZX3/9xXr37s3+85//VHgbpCE8f/6caWtry93Wzc/PZzo6Ouy77757ZxvLli1jJiYmdRVilSgrK7NevXoJymbNmsXef//9Cq+5d+8eA1DubfeUlBQmEonY4cOHaz3W6nJ1dWUffvihXPmLFy/YnTt3WGRkJBs6dCjr0aMHn9Azxtj333/PtLW1mVgsZurq6szf358BYIGBgQq9PyVGbyksLGRisVjuF6ePjw/z8PBomKAUZGJiwn766SdB2cqVK5m1tbWgrKioiHl6erKuXbs2+F93VfF2YlBYWMiUlJTYypUrBfUWLlzInJycGGOl96nLPtDLDgBMJBKx/v3712P05ZsxYwYzMTFh9+/fF5RXJe4dO3YwAIKEijHGDA0N2datW+urC+/k7OwsGF3IyclhvXr1Ys7OzoIPNcYY+9///scMDQ2ZVCrlywoLC5m6ujoLCAiot5jfdvToUQZA7vtR9j168y/xihKj7du3sxYtWgjKiouLmVgsZkeOHKmPbshRpF9Pnjzh+9SqVSu2du3aCttNSUkp9w/MhmZvb88WL14sKPv111+ZsrIyy8zMfOf1J06cYABYQUFBXYX4TmZmZoKRZMYY+/nnn5mxsXGl17Vs2ZL98ssvcuUrVqxgBgYGDTof7E1JSUlMJBKxY8eOVVqv7HPh7dEgmUzG0tLSWH5+Pj+v79KlSwrFoKTYjbfmT0VFBXZ2dggLC+OfPpDJZAgLC8PMmTMbNrgqys/Ph0gknD4mFosFT5EUFxdj5MiRSExMRHh4OPT19es7zBpTUVFBz5495R5zv3PnDszNzQEAixcvxqRJkwTnbWxs8MMPP2Do0KH1FuvbGGOYNWsWjh49ioiICFhaWgrOVyXu3r17AwBu374NExMTAKXzO54+fcr3vzGQyWQoLCwEAOTk5MDNzQ0SiQRBQUFycwLKfnbfnFdQ9vrNn9/65uzsjLi4OEHZ+PHj0aFDByxatAhisfidbfTq1QsvXrzA1atXYWdnBwA4ffo0ZDIZHB0d6yTud1GkXy1btgRQGnNmZmalT7GWfa/Kvu+NQW5uLu7du4cxY8YIyrdv3w4PDw8YGBi8s42YmBjo6uo26KasvXv3rvQzrzypqal49uwZWrduLShnjGHnzp3w8fFp8PlgZXbu3AlDQ0MMGTKk0nqsdGBH7meM4zgYGxsDAAICAmBqaooePXooFoRCadS/RGBgIJNIJGzXrl0sISGBTZkyhbVo0YJlZGQ0dGhVMnbsWNamTRt24sQJ9uDBA3bkyBHWsmVLtnDhQsZY6UiRh4cHMzExYTExMSw9PZ0/GnJuSnlevnzJrl27xq5du8YA8HOJyp6eO3LkCFNWVmZbt25liYmJbOPGjUwsFrMzZ85U2CYawa206dOnMx0dHRYRESH4+ld2u6i8uIcNG8Y6d+7Mzp07x+Li4tiHH37IOnXq1GB//S1evJhFRkayBw8esOvXr7PFixczjuPYX3/9xbKzs5mjoyOzsbFhd+/eFfS7bGTi5s2bTCKRsOnTp7OEhAQWHx/PRo8ezXR0dNijR48apE8VefuWU3p6Ort27Rrbtm0bA8D+/vtvdu3aNfbs2TO+jru7O+vevTu7ePEiO3v2LGvfvr3creCG9na/duzYwaKiotjdu3fZnj17mJ6eHps/fz5//sKFC2zjxo3s2rVrLCkpiYWFhTEnJydmZWXVoCMrn3/+OYuIiGAPHjxg586dYy4uLqxly5aCkaHExETGcRwLDg6Wuz4oKIht27aNxcXFscTERPbzzz8zdXV19tVXX9VnN+RcunSJKSkpsVWrVrHExES2b98+pq6uzvbu3csYK/3M/OKLL1hUVBR78OABO3XqFOvRowdr37693Pfj1KlTDAC7efNmQ3RFjlQqZWZmZmzRokWC8nv37rFvv/2WXblyhT18+JCdO3eODR06lOnp6bHHjx/z9dauXcuuX7/O4uPj2YoVK5iysnK1PuspMarAxo0bmZmZGVNRUWEODg7swoULDR1SleXk5LA5c+YwMzMzpqqqytq2bcu+/PJLPul58OABA1DuER4e3rDBv6XstsTbx9ixY/k627dvZ+3atWOqqqrM1tb2nUOwjSExqujrv3PnzkqveTvu7OxsNmHCBNaiRQump6fHPvroI8Gj7vVtwoQJzNzcnKmoqDADAwPm7OzM/vrrL8ZYxd9LAOzBgwd8G2VzVHR0dJiuri4bNGgQi4qKaqAeVeztBMLPz++d39Nnz54xLy8vpqmpybS1tdn48eMF88gag7f7tWjRItaqVSumrKzM2rdvz77//nvB3KHr16+zgQMHMj09PSaRSJiFhQWbNm0aS01NbYDo/zFq1CjWunVrpqKiwtq0acNGjRolmKzLWOkj3qampoJbt2WCg4NZt27dmKamJtPQ0GC2trbsl19+Kbduffv9999Zly5dmEQiYR06dBDcOs/Pz2eurq7MwMCAKSsrM3NzczZ58uRy/7D38vLipx00BiEhIQwAu337tqA8LS2NDR48mBkaGjJlZWVmYmLCPvvsM3br1i1BvYEDBzIdHR2mqqrKHB0d2Z9//lmtODjGGvjZQ0IIIYSQRoLWMSKEEEIIeY0SI0IIIYSQ1ygxIoQQQgh5jRIjQgghhJDXKDEihBBCCHmNEiNCCCGEkNcoMSKEEEIIeY0SI0IIIYSQ1ygxIoQQBSxbtgxTpkypURsJCQkwMTFBXl5eLUVFCKktlBgRQurFuHHjwHEcOI6DsrIyLC0tsXDhQhQUFDR0aFWWkZGBDRs24Msvv6xRO506dcL777+PdevW1VJkhJDaQokRIaTeuLu7Iz09Hffv38cPP/yALVu2wM/Pr6HDqrL//e9/cHJyqnQn86oaP348Nm/ejJKSklqIjBBSWygxIoTUG4lEAiMjI5iamsLT0xMuLi4IDQ3lzz979gxeXl5o06YN1NXVYWNjg4CAAEEbAwYMwOzZs7Fw4ULo6enByMgIy5cvF9S5desW+vTpA1VVVXTq1AmnTp0Cx3E4duwYXyclJQUjR45EixYtoKenh2HDhiEpKanS+AMDAzF06FC5eGbNmoW5c+dCV1cXrVq1wrZt25CXl4fx48dDS0sL7dq1Q3BwsOC6Dz74AFlZWYiMjKz6F5AQUucoMSKENIj4+HicP38eKioqfFlBQQHs7Ozwxx9/ID4+HlOmTMGYMWNw6dIlwbW7d++GhoYGLl68iLVr12LFihV8giWVSuHp6Ql1dXVcvHgRW7dulbv1VVxcDDc3N2hpaeHMmTM4d+4cNDU14e7ujqKionLjzcrKQkJCAuzt7eXO7d69Gy1btsSlS5cwa9YsTJ8+HSNGjICTkxOio6Ph6uqKMWPGID8/n79GRUUF3bp1w5kzZ6r9NSSE1AFGCCH1YOzYsUwsFjMNDQ0mkUgYACYSidjhw4crvW7IkCHs888/51/379+f9enTR1CnZ8+ebNGiRYwxxoKDg5mSkhJLT0/nz4eGhjIA7OjRo4wxxvbs2cOsra2ZTCbj6xQWFjI1NTUWEhJSbhzXrl1jAFhycrKg/O14SkpKmIaGBhszZgxflp6ezgCwqKgowbUfffQRGzduXKX9J4TUL6WGTcsIIf8mAwcOxObNm5GXl4cffvgBSkpKGD58OH9eKpXi22+/xcGDB5GWloaioiIUFhZCXV1d0E7Xrl0Fr1u3bo3MzEwAwO3bt2FqagojIyP+vIODg6B+bGws7t69Cy0tLUF5QUEB7t27V27sr169AgCoqqrKnXszHrFYDH19fdjY2PBlrVq1AgA+xjJqamqCUSRCSMOjxIgQUm80NDTQrl07AMCOHTtga2uL7du3Y+LEiQCA//73v9iwYQPWr18PGxsbaGhoYO7cuXK3t5SVlQWvOY6DTCarchy5ubmws7PDvn375M4ZGBiUe03Lli0BAM+fP5erU148b5ZxHAcAcjFmZWXBysqqynETQuoezTEihDQIkUiEJUuWYOnSpfxozLlz5zBs2DCMHj0atra2aNu2Le7cuaNQu9bW1khJScHjx4/5ssuXLwvq9OjRA4mJiTA0NES7du0Eh46OTrntWllZQVtbGwkJCQr2tGLx8fHo3r17rbVHCKk5SowIIQ1mxIgREIvF2LRpEwCgffv2CA0Nxfnz53Hz5k1MnTpVkOBUxQcffAArKyuMHTsW169fx7lz57B06VIA/4zceHt7o2XLlhg2bBjOnDmDBw8eICIiArNnz0Zqamq57YpEIri4uODs2bM16PE/kpKSkJaWBhcXl1ppjxBSOygxIoQ0GCUlJcycORNr165FXl4eli5dih49esDNzQ0DBgyAkZERPD09FWpTLBbj2LFjyM3NRc+ePTFp0iT+qbSy+UHq6ur4+++/YWZmho8//hgdO3bExIkTUVBQAG1t7QrbnjRpEgIDAxW6bVeRgIAAuLq61sqaSISQ2sMxxlhDB0EIIXXp3Llz6NOnD+7evVujOT2MMTg6OmLevHnw8vKqdjtFRUVo37499u/fj969e1e7HUJI7aPJ14SQZufo0aPQ1NRE+/btcffuXcyZMwe9e/eu8URnjuOwdetWxMXF1aid5ORkLFmyhJIiQhohGjEihDQ7v/76K7755hskJyejZcuWcHFxwffffw99ff2GDo0Q0shRYkQIIYQQ8hpNviaEEEIIeY0SI0IIIYSQ1ygxIoQQQgh5jRIjQgghhJDXKDEihBBCCHmNEiNCCCGEkNcoMSKEEEIIeY0SI0IIIYSQ1/4fbdrRQbftkL4AAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from numpy import abs ,angle, arange, arcsin, cos, concatenate, pi, sqrt, tan, zeros, linspace\n", "from scipy.fft import fft, fft2\n", "import matplotlib.pyplot as plt\n", "\n", "\n", "def y_IF(f0_min, slope, T, antenna_tx, antenna_rx, target, v=3e8):\n", " \"\"\" This function implements the mathematical IF defined in latex as\n", " y_{IF} = cos(2 \\pi [f_0\\delta + s * \\delta * t - s/2* \\delta^2])\n", " into following python code\n", " y_IF = cos (2*pi*(f_0 * delta + slope * delta * T - slope/2 * delta**2))\n", " Parameters:\n", " -----------\n", " f0_min: float\n", " the frequency at the begining of the chirp\n", " slope: float\n", " the slope with which the chirp frequency inceases over time\n", " T: ndarray\n", " the 1D vector containing time values\n", " antenna_tx: tuple of floats\n", " x, y, z coordinates\n", " antenna_rx: tuple of floats\n", " x, y, z coordinates\n", " target: tuple of floats\n", " x, y, z coordinates\n", " v: float\n", " speed of light in considered medium\n", " Returns:\n", " --------\n", " YIF: ndarray\n", " vector containing the IF values\n", " \"\"\"\n", " tx_x, tx_y, tx_z = antenna_tx\n", " rx_x, rx_y, rx_z = antenna_rx\n", " t_x, t_y, t_z = target\n", " # distance tx antenna to target\n", " distance = sqrt((tx_x-t_x)**2 + (tx_y-t_y)**2 + (tx_z-t_z)**2)\n", " # distance target to rx antenna\n", " distance += sqrt((rx_x-t_x)**2 + (rx_y-t_y)**2 + (rx_z-t_z)**2)\n", " # usually delta_t = 2*d/c, but\n", " # distance is already 2*D (TX to target + distance target to RX)\n", " # so delta = distance/v\n", " delta = distance/v\n", " YIF = cos(2 *pi *(f0_min * delta + slope * delta * T - slope/2 * delta**2))\n", " return YIF\n", "\n", "f0_min = 60e9\n", "c = 3e8\n", "# lambda ~5mm at 60GHz\n", "lambda0_max = 3e8/f0_min\n", "n_rx = 2\n", "Distance = 10\n", "k = 10e12\n", "n_samples = 512\n", "f_if = 2*k*Distance/c\n", "fs = 50e6\n", "ts = 1/fs\n", "\n", "antenna_tx = (-lambda0_max/2,0,0)\n", "antenna_rx = (0,0,0)\n", "T = arange(0, n_samples*ts, ts)\n", "t_chirp_to_chirp = 1.2e-6\n", "n_chirps = 128\n", "print(len(T))\n", "\n", "cube2D = zeros((n_chirps, n_samples))\n", "for d in [160]:\n", " for v in [460]:\n", " for chirp_i in range(n_chirps):\n", " d_i = d + v*t_chirp_to_chirp*chirp_i\n", " f_if = 2*k*d_i/c\n", " target_t_i = (0, d_i, 0)\n", "\n", " # sanity check\n", " assert f_if < 1/ts/2\n", " assert v < lambda0_max/4/t_chirp_to_chirp\n", "\n", " YIFi = y_IF(f0_min, k, T, antenna_tx, antenna_rx, target_t_i)\n", " cube2D[chirp_i, :] = YIFi\n", "\n", "Z_fft2 = abs(fft2(cube2D))\n", "\n", "Data_fft2 = Z_fft2 # [0:n_chirps//2,0:n_samples//2]\n", "\n", "# change scale for 2D plot\n", "# range formula\n", "# ranges from 0 to max distance with n_samples values\n", "# max un-ambigous range is fs*c/2/k\n", "ranges = linspace(0, fs*c/2/k, n_samples)\n", "no_labels = 10 # how many labels to see on axis x\n", "step_x = int(n_samples / (no_labels - 1)) # step between consecutive labels\n", "x_positions = arange(0, n_samples, step_x) # pixel count at label position\n", "x_labels = ranges[::step_x] # labels you want to see\n", "# rounding up the values for easier display\n", "x_labels = [int(d) for d in x_labels]\n", "# allocate labels to ticks\n", "plt.xticks(x_positions, x_labels)\n", "\n", "# Max un-ambigous speed is lambda0_max/4/t_chirp_to_chirp\n", "# if we `assume` no negative speed then\n", "# Max non-un-ambiguous is 2x lambda0_max/4/t_chirp_to_chirp\n", "# i.e. from 0 to 2x lambda0_max/4/t_chirp_to_chirp\n", "# instead of [- lambda0_max/4/t_chirp_to_chirp ; lambda0_max/4/t_chirp_to_chirp]\n", "no_labels_y = 10 # how many labels to see on axis x\n", "step_y = int(n_chirps / (no_labels_y - 1)) # step between consecutive labels\n", "no_negative_speeds = False\n", "if no_negative_speeds:\n", " speeds = linspace(0, lambda0_max/4/t_chirp_to_chirp*2, n_chirps)\n", "else:\n", " # labels as we haven't done freqshift\n", " speeds_pos = linspace(0, lambda0_max/4/t_chirp_to_chirp, n_chirps//2)\n", " speeds_neg = linspace(-lambda0_max/4/t_chirp_to_chirp, 0, n_chirps//2)\n", " speeds = concatenate((speeds_pos, speeds_neg))\n", "y_positions = arange(0, n_chirps, step_y) # pixel count at label position\n", "y_labels = speeds[::step_y] # labels you want to see\n", "y_labels = [int(dop) for dop in y_labels]\n", "plt.yticks(y_positions, y_labels)\n", "\n", "# change scale for 2D plot\n", "\n", "plt.imshow(Data_fft2)\n", "plt.xlabel(\"Range (m)\")\n", "plt.ylabel(\"Doppler (m/s)\")\n", "plt.title('Velocity-Range 2D FFT')" ] }, { "cell_type": "markdown", "metadata": { "id": "S66vx0NVPbsz" }, "source": [ "## 2D FFT - 3 targets\n", "\n", "> 3 targets 2 same range, 2 same speed" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 367 }, "id": "FHn7s0BiPglc", "outputId": "214e40f1-259f-4982-dfa8-37bc0b2ed1b2" }, "outputs": [ { "data": { "text/plain": [ "Text(0.5, 1.0, 'Velocity-Range 2D FFT')" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAFNCAYAAADhB3APAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABD+UlEQVR4nO3deVxU9f4/8NcMy7CPyE6g4JJbpoiIuKQpCmYuydX04pKaWrlrmXrdrpmmfcvCS5q7NyUTS7Pcc1/IXXNfUUgFFwQE2efz+8Of5zqBxswZmMHzej4e83jo53yW95kZDm8+53POUQkhBIiIiIgUTG3uAIiIiIjMjQkRERERKR4TIiIiIlI8JkRERESkeEyIiIiISPGYEBEREZHiMSEiIiIixWNCRERERIrHhIiIiIgUjwkRkUKoVCpMmzatTPq+fv06VCoVli9fXib9ExGVNSZERBamc+fOcHBwwMOHD59ZJzo6Gra2trh//345RmaYTZs2lUkCNm3aNKhUKullY2ODgIAAjBgxAunp6SYfz1x++uknvP3226hWrRocHBxQq1YtjB07tsR9fPr9sLa2RuXKlREcHIyRI0fi3LlzpR4zICBAr6+nX7m5uQCA5cuXP7PO+PHj0bp162duf/pVVsk5kbGszR0AEemLjo7GL7/8gnXr1qFv377Ftj969Ag///wzIiMj4ebmZoYIi6tatSpycnJgY2MjlW3atAmxsbFl9otv/vz5cHJyQnZ2Nnbs2IF58+bh+PHj2L9/f5mMV94GDx4MX19f9O7dG1WqVMHp06fxn//8B5s2bcLx48dhb2+vV79du3bo27cvhBDIyMjAqVOnsGLFCnzzzTeYPXs2xowZU6pxGzZsiLFjxxYrt7W11fv/9OnTERgYqFf2yiuvoG3btnj33XelsiNHjiAmJgYTJ05EnTp1pPJXX321VPEQlRtBRBbl0aNHwtnZWURERJS4PS4uTgAQq1evNqhfAGLq1KkmiLB0hg4dKsriEDN16lQBQNy9e1ev/O233xYAxKFDh0w+pjns2rWrWNmKFSsEALFo0SK9cgBi6NChxerfu3dPhIWFCQBi48aNfztm1apVRceOHZ9bZ9myZQKAOHLkyN/2J4QQ8fHxAkCJ+0NkSXjKjMjC2Nvbo1u3btixYwfu3LlTbHtcXBycnZ3RuXNnAEB6ejpGjRoFf39/aDQa1KhRA7Nnz4ZOp/vbsU6cOIEOHTrAxcUFTk5OaNu2LX7//fdi9dLT0zF69GgEBARAo9HAz88Pffv2xb179wAUX0P0zjvvIDY2FoD+6RwhBAICAtClS5diY+Tm5kKr1WLIkCGlfq+e1rJlSwDA1atXpbK0tDR8+OGHqF+/PpycnODi4oIOHTrg1KlTem13794NlUqFNWvW4NNPP4Wfnx/s7OzQtm1bXLlypdhYsbGxqFatGuzt7dGkSRPs27cPrVu3RuvWrfXq5eXlYerUqahRowY0Gg38/f0xbtw45OXl/e3+/LUvAHjrrbcAAOfPn//b9gDg5uaG1atXw9raGp9++mmp2hApFU+ZEVmg6OhorFixAmvWrMGwYcOk8rS0NGzduhW9evWCvb09Hj16hFatWuHmzZsYMmQIqlSpgoMHD2LChAm4ffs2vvrqq2eOcfbsWbRs2RIuLi4YN24cbGxs8O2336J169bYs2cPQkNDAQBZWVlo2bIlzp8/jwEDBqBRo0a4d+8eNmzYgD///BPu7u7F+h4yZAhu3bqF7du347vvvpPKVSoVevfujTlz5iAtLQ2VK1eWtv3yyy/IzMxE7969jXrPrl+/DgBwdXWVyq5du4b169eje/fuCAwMRGpqKr799lu0atUK586dg6+vr14fn332GdRqNT788ENkZGRgzpw5iI6OxqFDh6Q68+fPx7Bhw9CyZUuMHj0a169fR9euXeHq6go/Pz+pnk6nQ+fOnbF//34MHjwYderUwenTpzF37lxcunQJ69evN3gfU1JSAKDE9/xZqlSpglatWmHXrl3IzMyEi4vLc+sXFBRIie4TDg4OcHBw0CvLyMgoVs+QuIgsjrmnqIiouMLCQuHj4yPCwsL0yhcsWCAAiK1btwohhPjkk0+Eo6OjuHTpkl698ePHCysrK5GUlCSV4S+nzLp27SpsbW3F1atXpbJbt24JZ2dn8dprr0llU6ZMEQDETz/9VCxOnU4nhBAiMTFRABDLli2Ttj3rlNnFixcFADF//ny98s6dO4uAgACpz2d5csrs4sWL4u7du+L69eti6dKlwt7eXnh4eIjs7Gypbm5urigqKtJrn5iYKDQajZg+fbpUtmvXLgFA1KlTR+Tl5UnlX3/9tQAgTp8+LYQQIi8vT7i5uYmQkBBRUFAg1Vu+fLkAIFq1aiWVfffdd0KtVot9+/bpjf/kMzxw4MBz97MkAwcOFFZWVsU+bzzjlNkTI0eOFADEqVOnntt/1apVBYBir6e/N09OmZX0KglPmVFFwVNmRBbIysoKPXv2REJCgjTzATw+Xebl5YW2bdsCAOLj49GyZUu4urri3r170is8PBxFRUXYu3dvif0XFRVh27Zt6Nq1K6pVqyaV+/j44J///Cf279+PzMxMAMCPP/6IBg0aSKdrnqZSqQzet5dffhmhoaFYtWqVVJaWlobNmzcjOjq61H3WqlULHh4eCAgIwIABA1CjRg1s3rxZbyZDo9FArVZL+3z//n04OTmhVq1aOH78eLE++/fvr7d4+MlpuGvXrgEAjh49ivv372PQoEGwtv7fBHt0dLTezBTw+LOpU6cOateurffZtGnTBgCwa9euUu3nE3FxcViyZAnGjh2LmjVrGtTWyckJAJ575eIToaGh2L59u96rpMX9sbGxxeoRVWQ8ZUZkoaKjozF37lzExcVh4sSJ+PPPP7Fv3z6MGDECVlZWAIDLly/jjz/+gIeHR4l9lLQGCQDu3r2LR48eoVatWsW21alTBzqdDsnJyahXrx6uXr2KqKgo0+0YgL59+2LYsGG4ceMGqlativj4eBQUFKBPnz4AgPz8fKSlpem18fDwkPYbeJyoubi44O7du4iJiUFiYmKxK690Oh2+/vprfPPNN0hMTERRUZG0raQr9KpUqaL3/ydJzoMHDwAAN27cAADUqFFDr561tTUCAgL0yi5fvozz588b/NmUZN++fRg4cCAiIiKMWguUlZUFAHB2dv7buu7u7ggPD//bek2aNEHjxo0NjoXIUjEhIrJQwcHBqF27Nr7//ntMnDgR33//PYQQiI6OlurodDq0a9cO48aNK7GPl19+ubzCNUjPnj0xevRorFq1ChMnTsTKlSvRuHFjKUE7ePAgXn/9db02iYmJeknHa6+9Jq1Z6dSpE+rXr4/o6GgcO3ZMmhWaOXMmJk+ejAEDBuCTTz5B5cqVoVarMWrUqBIXnT+dcD1NCGHwPup0OtSvXx9ffvllidv9/f1L1c+pU6fQuXNnvPLKK1i7dq3ezFRpnTlzBlZWVsUukyei/2FCRGTBoqOjMXnyZPzxxx+Ii4tDzZo1ERISIm2vXr06srKySvUX/dM8PDzg4OCAixcvFtt24cIFqNVq6Rd29erVcebMGYNjf96pr8qVK6Njx45YtWoVoqOjceDAAb0F4A0aNCh2Csbb2/uZ/Tk5OWHq1Kno378/1qxZg549ewIA1q5di9dffx1LlizRq5+enm7UAuCqVasCAK5cuaKXsBUWFuL69et699apXr06Tp06hbZt2xp1ahF4fMVcZGQkPD09sWnTJunUlyGSkpKwZ88ehIWFlWqGiEipuIaIyII9mQ2aMmUKTp48qTc7BAA9evRAQkICtm7dWqxteno6CgsLS+zXysoK7du3x88//6y3Rik1NRVxcXFo0aKFdDVSVFQUTp06hXXr1hXr53kzJ46OjlIcJenTpw/OnTuHjz76SFoz9YSrqyvCw8P1XnZ2ds8cC3j8Xvn5+WH27Nl6+/nXGOPj43Hz5s3n9vUsjRs3hpubGxYtWqT33q5atUo6rfZEjx49cPPmTSxatKhYPzk5OcjOzn7uWCkpKWjfvj3UajW2bt36zFNvz5OWloZevXqhqKgI//rXvwxuT6QknCEismCBgYFo1qwZfv75ZwAolhB99NFH2LBhA95880288847CA4ORnZ2Nk6fPo21a9fi+vXrz5wJmTFjBrZv344WLVrggw8+gLW1Nb799lvk5eVhzpw5emOsXbsW3bt3x4ABAxAcHIy0tDRs2LABCxYsQIMGDUrsPzg4GAAwYsQIREREFEt6OnbsCDc3N8THx6NDhw7w9PSU9V7Z2Nhg5MiR+Oijj7BlyxZERkbizTffxPTp09G/f380a9YMp0+fxqpVq/QWkhvC1tYW06ZNw/Dhw9GmTRv06NED169fx/Lly1G9enW9maA+ffpgzZo1eO+997Br1y40b94cRUVFuHDhAtasWYOtW7c+dw1OZGQkrl27hnHjxmH//v16d+D28vJCu3bt9OpfunQJK1euhBACmZmZOHXqFOLj45GVlYUvv/wSkZGRRu0zkWKY9Ro3IvpbsbGxAoBo0qRJidsfPnwoJkyYIGrUqCFsbW2Fu7u7aNasmfi///s/kZ+fL9VDCXeqPn78uIiIiBBOTk7CwcFBvP766+LgwYPFxrh//74YNmyYeOmll4Stra3w8/MT/fr1E/fu3RNClHzZfWFhoRg+fLjw8PAQKpWqxMuyP/jgAwFAxMXFlfr9eNadqoUQIiMjQ2i1Wuny99zcXDF27Fjh4+Mj7O3tRfPmzUVCQoJo1aqV3iXyTy67j4+P1+uvpP0SQoiYmBhRtWpVodFoRJMmTcSBAwdEcHCwiIyM1KuXn58vZs+eLerVqyc0Go1wdXUVwcHB4t///rfIyMh47n7iGZe24y+X9/+1rlqtFpUqVRJBQUFi5MiR4uzZs89/Q5/CO1WTkqmEMGK1IBGRCYwePRpLlixBSkpKsRv/VSQ6nQ4eHh7o1q1biafIiMjycQ0REZlFbm4uVq5ciaioqAqVDOXm5hZbl/Tf//4XaWlpJT5ug4gqBq4hIqJydefOHfz2229Yu3Yt7t+/j5EjR5o7JIP8/vvvGD16NLp37w43NzccP34cS5YswSuvvILu3bubOzwiMhITIiIqV+fOnUN0dDQ8PT0RExODhg0bmjskgwQEBMDf3x8xMTHS89j69u2Lzz77TO8u10RUsXANERERESke1xARERGR4jEhIiIiIsXjGqJS0ul0uHXrFpydnY2+DT8RERGVLyEEHj58CF9fX+k5hyVhQlRKt27dKvXDGImIiMiyJCcnw8/P75nbmRCV0pOHIrbAG7CGjZmjoReeOWchVfLOpKttjT+sqOw0ssbWZT0yuq0oKpI1tlkp9doYM/6cqKys5HUgo73Iz5c3tsK+L4UowH5s+tuHGzMhKqUnp8msYQNrFRMiKmMVOSGS8fOhUsm7bF2nKjC6rZC53+alrF9wEnMmRCqZCZGM9kIl9/NW2Pfl/+/u3y13qchHACIiIiKTUFRCFBsbi4CAANjZ2SE0NBSHDx82d0hERERkARSTEP3www8YM2YMpk6diuPHj6NBgwaIiIjAnTt3zB0aERERmZliEqIvv/wSgwYNQv/+/VG3bl0sWLAADg4OWLp0aYn18/LykJmZqfciIiKiF5MiEqL8/HwcO3YM4eHhUplarUZ4eDgSEhJKbDNr1ixotVrpxUvuiYiIXlyKSIju3buHoqIieHl56ZV7eXkhJSWlxDYTJkxARkaG9EpOTi6PUImIiMgMeNn9M2g0Gmg08u6JQkRERBWDImaI3N3dYWVlhdTUVL3y1NRUeHt7mykqIiIishSKSIhsbW0RHByMHTt2SGU6nQ47duxAWFiYGSMjIiIiS6CYU2ZjxoxBv3790LhxYzRp0gRfffUVsrOz0b9/f3OHRkRERGammITo7bffxt27dzFlyhSkpKSgYcOG2LJlS7GF1kRERKQ8KiEU9pQ3I2VmZkKr1aI1uvBZZlT2KvKzzGxlPMtM9sNds41uy4e7VkB8uKuRHSjr+1IoCrAbPyMjIwMuLi7PrKeYGSIiKidqGQmVjbyHuwLGJ0RyE0EInbz2cpgzga7I5HzmMhMiOQmVstKZ8qOIRdVEREREz8OEiIiIiBSPCREREREpHhMiIiIiUjwmRERERKR4TIiIiIhI8ZgQERERkeIxISIiIiLFY0JEREREiseEiIiIiBSPCREREREpHhMiIiIiUjwmRERERKR4TIiIiIhI8azNHQCRxVKpzB2B8VTG/62jsrKSN7StjfFtHe3ljf3wofGNCwtljS10/PuyopHzXVdrNPIGl/Nzkp8va2hRVCSrvbzBhfnG/hv8CSYiIiLFY0JEREREiseEiIiIiBSPCREREREpHhMiIiIiUjwmRERERKR4TIiIiIhI8ZgQERERkeIxISIiIiLFY0JEREREiseEiIiIiBSPCREREREpHhMiIiIiUjwmRERERKR4TIiIiIhI8azNHUBFo7K2hkplhrdNZcbcVa2S1VylktFeLXO/ZYytsrKSN7aN8d8Tla2trKGFo73RbQu9tLLGvvKWnfFt/7lA1tjNxrxndFun5FxZY1tl5clqryooMr5xkU7W2LLIPD4IG+N/zoocNbLGzvEyvn1qL3nfl3GvbjO67XdjOska2+FGhtFtVQ8fyRpb5Mr4OSnIN25MkQ+k/309zhARERGR4jEhIiIiIsVjQkRERESKZ9EJ0axZsxASEgJnZ2d4enqia9euuHjxol6d3NxcDB06FG5ubnByckJUVBRSU1P16owYMQLBwcHQaDRo2LBhOe4BERERVQQWnRDt2bMHQ4cOxe+//47t27ejoKAA7du3R3Z2tlRn9OjR+OWXXxAfH489e/bg1q1b6NatW7G+BgwYgLfffrs8wyciIqIKwqKvMtuyZYve/5cvXw5PT08cO3YMr732GjIyMrBkyRLExcWhTZs2AIBly5ahTp06+P3339G0aVMAQExMDADg7t27+OOPP8p3J4iIiMjiWfQM0V9lZDy+VLBy5coAgGPHjqGgoADh4eFSndq1a6NKlSpISEiQNVZeXh4yMzP1XkRERPRiqjAJkU6nw6hRo9C8eXO88sorAICUlBTY2tqiUqVKenW9vLyQkpIia7xZs2ZBq9VKL39/f1n9ERERkeWqMAnR0KFDcebMGaxevbpcxpswYQIyMjKkV3JycrmMS0REROXPotcQPTFs2DD8+uuv2Lt3L/z8/KRyb29v5OfnIz09XW+WKDU1Fd7e3rLG1Gg00Gjk3QWViIiIKgaLniESQmDYsGFYt24ddu7cicDAQL3twcHBsLGxwY4dO6SyixcvIikpCWFhYeUdLhEREVVQFj1DNHToUMTFxeHnn3+Gs7OztC5Iq9XC3t4eWq0WAwcOxJgxY1C5cmW4uLhg+PDhCAsLk64wA4ArV64gKysLKSkpyMnJwcmTJwEAdevWha3MZ0YRERFRxWfRCdH8+fMBAK1bt9YrX7ZsGd555x0AwNy5c6FWqxEVFYW8vDxERETgm2++0av/7rvvYs+ePdL/g4KCAACJiYkICAgos/iJiIioYrDohEgI8bd17OzsEBsbi9jY2GfW2b17twmjIiIioheNSpQm6yBkZmZCq9WiNbrAWmVj7nCIyo5KJa+5tfE/Hyo7eRcyiJwc49vqZB4KhU5ee6VSGb+UVaWW912VM7ba0V7e2DIu2tGlpcsaWhQVyWhsxu+5kelKoSjAbvyMjIwMuLi4PLOeRS+qJiIiIioPTIiIiIhI8ZgQERERkeIxISIiIiLFY0JEREREiseEiIiIiBSPCREREREpHhMiIiIiUjwmRERERKR4TIiIiIhI8ZgQERERkeIxISIiIiLFY0JEREREiseEiIiIiBSPCREREREpnrW5AyAiCyOEvOaFBca3zS6SNTZ0MttT+RPGf2ZCqOSNrTL+u67LyZU3dEGh0W1FkczvudDJaCvv+GDJOENEREREimfQDFF6ejrWrVuHffv24caNG3j06BE8PDwQFBSEiIgINGvWrKziJCIiIiozpZohunXrFt599134+PhgxowZyMnJQcOGDdG2bVv4+flh165daNeuHerWrYsffvihrGMmIiIiMqlSzRAFBQWhX79+OHbsGOrWrVtinZycHKxfvx5fffUVkpOT8eGHH5o0UCIiIqKyohLi71dI3b9/H25ubqXu1ND6FUFmZia0Wi1aowusVTbmDofIcqlkLHRVyVzWyEXVyiLnuwbI+r6pbORdk6SyNr693AXdSltUXSgKsBs/IyMjAy4uLs+sV6pvg6HJzYuWDBEREdGLzeD0eMWKFdi4caP0/3HjxqFSpUpo1qwZbty4YdLgiIiIiMqDwQnRzJkzYW9vDwBISEhAbGws5syZA3d3d4wePdrkARIRERGVNYNPYiYnJ6NGjRoAgPXr1yMqKgqDBw9G8+bN0bp1a1PHR0RERFTmDJ4hcnJywv379wEA27ZtQ7t27QAAdnZ2yMnJMW10REREROXA4Bmidu3a4d1330VQUBAuXbqEN954AwBw9uxZBAQEmDo+IiIiojJn8AxRbGwswsLCcPfuXfz444/SFWXHjh1Dr169TB4gERERUVkr1X2IAGDp0qXo3Lkz3N3dyzomi8T7EBGVEu9DROWF9yEyDu9DVKJSfxtWrlwJPz8/NGvWDLNnz8aFCxdMEigRERGRuZU6Rd25cycePHiAjRs3YsOGDfj000/h5eWFzp07o0uXLmjRogXUapl/3RHRY3L/8pU1tryfYzl/OatsbWWNrct+ZHxjOX81AxXyL+cKT/Z7LuMzL5I3Gykrcn5Xy4RBRz5XV1f07t0ba9aswb179zBv3jzk5OQgOjoanp6e6Nu3L9auXYvs7OyyipeIiIjI5Iz+U9DW1haRkZH45ptvkJycjC1btiAgIACffPIJvvzyS1PGSERERFSmSr2o2hAFBQWwsXmxFh5zUTWVK54yMwpPmZFBZPycqays5I0to73Iz5c3tsK+q6VdVG3wkUsIgbVr12LXrl24c+cOdLr/HURUKhV+/PHHFy4ZIiIiohebwX8Kjho1Cn369EFiYiKcnJyg1Wql1/MyL1P47LPPoFKpMGrUKKksNzcXQ4cOhZubG5ycnBAVFYXU1FRp+/379xEZGQlfX19oNBr4+/tj2LBhyMzMLNNYiYiIqOIweIbou+++w08//STdobq8HDlyBN9++y1effVVvfLRo0dj48aNiI+Ph1arxbBhw9CtWzccOHAAAKBWq9GlSxfMmDEDHh4euHLlCoYOHYq0tDTExcWV6z4QERGRZTI4IdJqtahWrVpZxPJMWVlZiI6OxqJFizBjxgypPCMjA0uWLEFcXBzatGkDAFi2bBnq1KmD33//HU2bNoWrqyvef/99qU3VqlXxwQcf4PPPP3/umHl5ecjLy5P+zxklIiKiF5fBp8ymTZuGf//73+X6INehQ4eiY8eOCA8P1ys/duwYCgoK9Mpr166NKlWqICEhocS+bt26hZ9++gmtWrV67pizZs3SOx3o7+8vf0eIiIjIIhmcEPXo0QMPHjyAp6cn6tevj0aNGum9TG316tU4fvw4Zs2aVWxbSkoKbG1tUalSJb1yLy8vpKSk6JX16tULDg4OeOmll+Di4oLFixc/d9wJEyYgIyNDeiUnJ8veFyIiIrJMBp8y69evH44dO4bevXvDy8sLqjK8PDg5ORkjR47E9u3bYWdnJ6uvuXPnYurUqbh06RImTJiAMWPG4JtvvnlmfY1GA41GI2tMIiIiqhgMTog2btyIrVu3okWLFmURj55jx47hzp07ejNPRUVF2Lt3L/7zn/9g69atyM/PR3p6ut4sUWpqKry9vfX68vb2hre3N2rXro3KlSujZcuWmDx5Mnx8fMp8P4iIiMiyGZwQ+fv7l/nl9U+0bdsWp0+f1ivr378/ateujY8//hj+/v6wsbHBjh07EBUVBQC4ePEikpKSEBYW9sx+n9w76elF00RERKRcBidEX3zxBcaNG4cFCxYgICCgDEL6H2dnZ7zyyit6ZY6OjnBzc5PKBw4ciDFjxqBy5cpwcXHB8OHDERYWhqZNmwIANm3ahNTUVISEhMDJyQlnz57FRx99hObNm5d5/ERERFQxGJwQ9e7dG48ePUL16tXh4OBQ7K7UaWlpJguuNObOnQu1Wo2oqCjk5eUhIiJCb22Qvb09Fi1ahNGjRyMvLw/+/v7o1q0bxo8fX65xEhERkeUy+FlmK1aseO72fv36yQrIUvFZZlSu+Cwzo/BZZmQQPstMEcrsWWYvasJDLyhzJhbmJCOpUanlvWdykhq1o4OssUWu8esCRZGsoQHITKjkUNgvOJOR8b4Jnbz3XKXiZ2ZpSnXUzM7ONqhTQ+sTERERmVOpEqIaNWrgs88+w+3bt59ZRwiB7du3o0OHDoiJiTFZgERERERlrVSnzHbv3o2JEydi2rRpaNCgARo3bgxfX1/Y2dnhwYMHOHfuHBISEmBtbY0JEyZgyJAhZR03ERERkcmUKiGqVasWfvzxRyQlJSE+Ph779u3DwYMHkZOTA3d3dwQFBWHRokXo0KEDrOQuNCMiIiIqZwZfZaZUvMqsguKiaiOaylxUbW9vdFu5i6qL7j8wuq0okrmqWu5VarLG5mG83Knl/fEv5yo1UVgga2ylfV9Ke5WZvOtriYiIiF4ATIiIiIhI8ZgQERERkeIxISIiIiLFY0JEREREimdwQhQQEIDp06cjKSmpLOIhIiIiKncGJ0SjRo3CTz/9hGrVqqFdu3ZYvXo18vKMf34QERERkbkZlRCdPHkShw8fRp06dTB8+HD4+Phg2LBhOH78eFnESERERFSmjF5D1KhRI8TExODWrVuYOnUqFi9ejJCQEDRs2BBLly4F7/dIREREFUWpHt1RkoKCAqxbtw7Lli3D9u3b0bRpUwwcOBB//vknJk6ciN9++w1xcXGmjNUyqFTKvftxRSTjjs3mJPdu0bLuVG1j9GEBAKB2cTa6rc6jkqyxVdmPjG+cny9rbMi807XQyfgjUu4hyZx32ZbDnH94y37PzPiYK3P+DrPgyRKDj3zHjx/HsmXL8P3330OtVqNv376YO3cuateuLdV56623EBISYtJAiYiIiMqKwQlRSEgI2rVrh/nz56Nr166wsSn+XK/AwED07NnTJAESERERlTWDE6Jr166hatWqz63j6OiIZcuWGR0UERERUXkyeKHB66+/jvv37xcrT09PR7Vq1UwSFBEREVF5Mjghun79OopKWDyYl5eHmzdvmiQoIiIiovJU6lNmGzZskP69detWaLVa6f9FRUXYsWMHAgICTBocERERUXkodULUtWtXAIBKpUK/fv30ttnY2CAgIABffPGFSYMjIiIiKg+lToh0usf3XAgMDMSRI0fg7u5eZkERERERlSeDrzJLTEwsiziIiIiIzKZUCVFMTAwGDx4MOzs7xMTEPLfuiBEjTBIYERERUXlRiVI8dCwwMBBHjx6Fm5sbAgMDn92ZSoVr166ZNEBLkZmZCa1Wi9aqrrBWFb8ZJVkoPrrD8KZyH93hWsnotnIf3YGryUY3FRX50R1y8dEdhpP5+AuVtfG/R0RhgayxzcoMn1mhKMBu/IyMjAy4uLg8s16pjnxPnybjKTMiIiJ60VTMP5+JiIiITMjghCgqKgqzZ88uVj5nzhx0797dJEERERERlSeDE6K9e/fijTfeKFbeoUMH7N271yRBEREREZUng1dPZmVlwdbWtli5jY0NMjMzTRKUJVNrbKFWFd//sh/YjGc3ZS4elBO7SvbYMtpbWckb29r4xcmqEn7GDCEc7IxuW+DhJGvspA72Rrf99z9Wyxr725H/MLqtfbK845cq65Gs9sgzflG3KCyUN7ac9jIXg5fiup5nk7mQXQ4hc2yVnOODlfl+H8j6vMxEJdRA3t/XM/hdrV+/Pn744Ydi5atXr0bdunUN7Y6IiIjI7AxOUSdPnoxu3brh6tWraNOmDQBgx44d+P777xEfH2/yAImIiIjKmsEJUadOnbB+/XrMnDkTa9euhb29PV599VX89ttvaNWqVVnESERERFSmjDoR2bFjRxw4cADZ2dm4d+8edu7cWWbJ0M2bN9G7d2+4ubnB3t4e9evXx9GjR6XtQghMmTIFPj4+sLe3R3h4OC5fvlxiX3l5eWjYsCFUKhVOnjxZJvESERFRxWP0yqxjx45h5cqVWLlyJU6cOGHKmCQPHjxA8+bNYWNjg82bN+PcuXP44osv4OrqKtWZM2cOYmJisGDBAhw6dAiOjo6IiIhAbm5usf7GjRsHX1/fMomViIiIKi6DT5nduXMHPXv2xO7du1GpUiUAQHp6Ol5//XWsXr0aHh4eJgtu9uzZ8Pf3x7Jly6Sypx8dIoTAV199hUmTJqFLly4AgP/+97/w8vLC+vXr0bNnT6nu5s2bsW3bNvz444/YvHmzyWIkIiKiis/gGaLhw4fj4cOHOHv2LNLS0pCWloYzZ84gMzPT5A923bBhAxo3bozu3bvD09MTQUFBWLRokbQ9MTERKSkpCA8Pl8q0Wi1CQ0ORkJAglaWmpmLQoEH47rvv4ODgUKqx8/LykJmZqfciIiKiF5PBCdGWLVvwzTffoE6dOlJZ3bp1ERsba/KZl2vXrmH+/PmoWbMmtm7divfffx8jRozAihUrAAApKSkAAC8vL712Xl5e0jYhBN555x289957aNy4canHnjVrFrRarfTy9/c30V4RERGRpTE4IdLpdLCxKf6UXhsbG+h0pn1isk6nQ6NGjTBz5kwEBQVh8ODBGDRoEBYsWFDqPubNm4eHDx9iwoQJBo09YcIEZGRkSK/kZOOfok1ERESWzeCEqE2bNhg5ciRu3bolld28eROjR49G27ZtTRqcj49PsZs91qlTB0lJSQAAb29vAI9PiT0tNTVV2rZz504kJCRAo9HA2toaNWrUAAA0btwY/fr1e+bYGo0GLi4uei8iIiJ6MRmcEP3nP/9BZmYmAgICUL16dVSvXh2BgYHIzMzEvHnzTBpc8+bNcfHiRb2yS5cuoWrVqgAeL7D29vbGjh07pO2ZmZk4dOgQwsLCAAAxMTE4deoUTp48iZMnT2LTpk0AgB9++AGffvqpSeMlIiKiisngq8z8/f1x/Phx/Pbbb7hw4QKAx7M2Ty9sNpXRo0ejWbNmmDlzJnr06IHDhw9j4cKFWLhwIYDHz7kaNWoUZsyYgZo1ayIwMBCTJ0+Gr68vunbtCgCoUqWKXp9OTo+f01S9enX4+fmZPGYiIiKqeIx6upxKpUK7du3Qrl07U8ejJyQkBOvWrcOECRMwffp0BAYG4quvvkJ0dLRUZ9y4ccjOzsbgwYORnp6OFi1aYMuWLbCzM/7hlkRERKQspUqIYmJiSt2hqS+9f/PNN/Hmm28+c7tKpcL06dMxffr0UvUXEBBQIZ/WS0RERGWnVAnR3LlzS9WZSqUyeUJEREREVNZKlRAlJiaWdRwVhi4vHzqVwmaYVEY/4cXsVGqVOQc3vq3MuFXWRp0NBwDY3NPIGrtauqfRbWOP9pA1tvMJ449V4lGOrLFFQYG89nJmrnUyj0nCtLdMMWzoink8lXts0eXnmygS+jtClO5n0+gjdn5+Pi5evIjCwkJjuyAiIiKyCAYnRI8ePcLAgQPh4OCAevXqSfcEGj58OD777DOTB0hERERU1gxOiCZMmIBTp05h9+7deldyhYeH44cffjBpcERERETlweCFBuvXr8cPP/yApk2bQqX63znUevXq4erVqyYNjoiIiKg8GDxDdPfuXXh6Fl80mZ2drZcgEREREVUUBidEjRs3xsaNG6X/P0mCFi9eLD0ug4iIiKgiMfiU2cyZM9GhQwecO3cOhYWF+Prrr3Hu3DkcPHgQe/bsKYsYiYiIiMpUqWeIzpw5AwBo0aIFTp48icLCQtSvXx/btm2Dp6cnEhISEBwcXGaBEhEREZWVUs8QvfrqqwgJCcG7776Lnj17YtGiRWUZFxEREVG5KfUM0Z49e1CvXj2MHTsWPj4+eOedd7Bv376yjI2IiIioXJQ6IWrZsiWWLl2K27dvY968eUhMTESrVq3w8ssvY/bs2UhJSSnLOImIiIjKjMFXmTk6OqJ///7Ys2cPLl26hO7duyM2NhZVqlRB586dyyJGIiIiojIl66mdNWrUwMSJEzFp0iQ4OzvrXY5PREREVFEY/UjsvXv3YunSpfjxxx+hVqvRo0cPDBw40JSxEREREZULgxKiW7duYfny5Vi+fDmuXLmCZs2aISYmBj169ICjo2NZxWhZhAAgzB1F+RJF5htb5t3PzRk6VDK+J3LjLjK+A1FYKGtodep9o9s6F8gbWzzMMr6tzLEhdDKbm/G4IjN2JRI6WSdYZA6usN9BcpXy/Sp1QtShQwf89ttvcHd3R9++fTFgwADUqlXL6PiIiIiILEWpEyIbGxusXbsWb775JqysrMoyJiIiIqJyVeqEaMOGDWUZBxEREZHZmPEkKBEREZFlYEJEREREiseEiIiIiBSPCREREREpHhMiIiIiUjwmRERERKR4TIiIiIhI8ZgQERERkeIxISIiIiLFY0JEREREiseEiIiIiBSPCREREREpXqkf7kpkFkKYb2yVSl57oTNNHEYNbfzfOioUyRs8L8/4sbMeyRpaVyQjdpmfl9DJ/K6a8ftCRuDn9cLhDBEREREpHhMiIiIiUjwmRERERKR4Fp0QFRUVYfLkyQgMDIS9vT2qV6+OTz75BOKpdSVCCEyZMgU+Pj6wt7dHeHg4Ll++XKyvjRs3IjQ0FPb29nB1dUXXrl3LcU+IiIjIkln0ourZs2dj/vz5WLFiBerVq4ejR4+if//+0Gq1GDFiBABgzpw5iImJwYoVKxAYGIjJkycjIiIC586dg52dHQDgxx9/xKBBgzBz5ky0adMGhYWFOHPmjDl3jYiIiCyISghzXsbzfG+++Sa8vLywZMkSqSwqKgr29vZYuXIlhBDw9fXF2LFj8eGHHwIAMjIy4OXlheXLl6Nnz54oLCxEQEAA/v3vf2PgwIGlHjsvLw95T10tk5mZCX9/f7RGF1irbEy3k2S55F5lZk4qGVeZqeXtt9rBwfixnZ1kjV10776MxvKuruNVZlRuLPfXtkUqFAXYjZ+RkZEBFxeXZ9az6FNmzZo1w44dO3Dp0iUAwKlTp7B//3506NABAJCYmIiUlBSEh4dLbbRaLUJDQ5GQkAAAOH78OG7evAm1Wo2goCD4+PigQ4cOfztDNGvWLGi1Wunl7+9fRntJRERE5mbRCdH48ePRs2dP1K5dGzY2NggKCsKoUaMQHR0NAEhJSQEAeHl56bXz8vKStl27dg0AMG3aNEyaNAm//vorXF1d0bp1a6SlpT1z7AkTJiAjI0N6JScnl8UuEhERkQWw6DVEa9aswapVqxAXF4d69erh5MmTGDVqFHx9fdGvX79S9aHTPZ6G/te//oWoqCgAwLJly+Dn54f4+HgMGTKkxHYajQYajcY0O0JEREQWzaIToo8++kiaJQKA+vXr48aNG5g1axb69esHb29vAEBqaip8fHykdqmpqWjYsCEASOV169aVtms0GlSrVg1JSUnltCdERERkySz6lNmjR4+gVuuHaGVlJc36BAYGwtvbGzt27JC2Z2Zm4tChQwgLCwMABAcHQ6PR4OLFi1KdgoICXL9+HVWrVi2HvSAiIiJLZ9EzRJ06dcKnn36KKlWqoF69ejhx4gS+/PJLDBgwAACgUqkwatQozJgxAzVr1pQuu/f19ZXuM+Ti4oL33nsPU6dOhb+/P6pWrYrPP/8cANC9e3dz7RoRERFZEItOiObNm4fJkyfjgw8+wJ07d+Dr64shQ4ZgypQpUp1x48YhOzsbgwcPRnp6Olq0aIEtW7ZI9yACgM8//xzW1tbo06cPcnJyEBoaip07d8LV1dUcu0VEREQWxqLvQ2RJMjMzodVqeR8iJeF9iIzC+xAZ3YG89qQc/LVtkNLeh8iiZ4iIzMqcBx25yZiMX65CJ29poSgsNL5xfoGsseUkNYpOaPgL1nAV+Q8mKpFFL6omIiIiKg9MiIiIiEjxmBARERGR4jEhIiIiIsVjQkRERESKx4SIiIiIFI8JERERESkeEyIiIiJSPCZEREREpHhMiIiIiEjxmBARERGR4jEhIiIiIsVjQkRERESKx4SIiIiIFI8JERERESmetbkDIKISCCGvvUplmjiModMZ37aw0HRxGErIiNvc5H5fiIgzRERERERMiIiIiEjxmBARERGR4jEhIiIiIsVjQkRERESKx4SIiIiIFI8JERERESkeEyIiIiJSPCZEREREpHhMiIiIiEjxmBARERGR4jEhIiIiIsVjQkRERESKx4SIiIiIFM/a3AEQkYUROnnNhTC6raqoSN7YOuPHNjsZ7xsRyccZIiIiIlI8JkRERESkeEyIiIiISPHMmhDt3bsXnTp1gq+vL1QqFdavX6+3XQiBKVOmwMfHB/b29ggPD8fly5f16qSlpSE6OhouLi6oVKkSBg4ciKysLL06W7duRdOmTeHs7AwPDw9ERUXh+vXrZbx3REREVFGYNSHKzs5GgwYNEBsbW+L2OXPmICYmBgsWLMChQ4fg6OiIiIgI5ObmSnWio6Nx9uxZbN++Hb/++iv27t2LwYMHS9sTExPRpUsXtGnTBidPnsTWrVtx7949dOvWrcz3j4iIiCoGlZBzSYgJqVQqrFu3Dl27dgXweHbI19cXY8eOxYcffggAyMjIgJeXF5YvX46ePXvi/PnzqFu3Lo4cOYLGjRsDALZs2YI33ngDf/75J3x9fbF27Vr06tULeXl5UKsf53+//PILunTpgry8PNjY2JQqvszMTGi1WrRGF1irSteGyGxUKvMNbWtrdFu1RiNr7KKsbOMby7y6TjbLOBRTaZnxZ4zfFcMUigLsxs/IyMiAi4vLM+tZ7BqixMREpKSkIDw8XCrTarUIDQ1FQkICACAhIQGVKlWSkiEACA8Ph1qtxqFDhwAAwcHBUKvVWLZsGYqKipCRkYHvvvsO4eHhz02G8vLykJmZqfciIiKiF5PFJkQpKSkAAC8vL71yLy8vaVtKSgo8PT31tltbW6Ny5cpSncDAQGzbtg0TJ06ERqNBpUqV8Oeff2LNmjXPHX/WrFnQarXSy9/f31S7RkRERBbGYhMiU0lJScGgQYPQr18/HDlyBHv27IGtrS3+8Y9/PPcGchMmTEBGRob0Sk5OLseoiYiIqDxZ7J2qvb29AQCpqanw8fGRylNTU9GwYUOpzp07d/TaFRYWIi0tTWofGxsLrVaLOXPmSHVWrlwJf39/HDp0CE2bNi1xfI1GA43M9QxERERUMVjsDFFgYCC8vb2xY8cOqSwzMxOHDh1CWFgYACAsLAzp6ek4duyYVGfnzp3Q6XQIDQ0FADx69EhaTP2ElZUVAECnM/MiSiIiIrIIZk2IsrKycPLkSZw8eRLA44XUJ0+eRFJSElQqFUaNGoUZM2Zgw4YNOH36NPr27QtfX1/pSrQ6deogMjISgwYNwuHDh3HgwAEMGzYMPXv2hK+vLwCgY8eOOHLkCKZPn47Lly/j+PHj6N+/P6pWrYqgoCAz7TkRERFZErMmREePHkVQUJCUmIwZMwZBQUGYMmUKAGDcuHEYPnw4Bg8ejJCQEGRlZWHLli2ws7OT+li1ahVq166Ntm3b4o033kCLFi2wcOFCaXubNm0QFxeH9evXIygoCJGRkdBoNNiyZQvs7e3Ld4eJiIjIIlnMfYgsHe9DRBUK70NkON6HiAzB+xBVGKW9D5HFLqq2NE/yxkIUAPwuksUzY0IkjB9bLaMtABSJAuMbMyEigzAhqigK8fi48HfzP0yISunhw4cAgP3YZOZIiErBnMfLPDO1JSpPzEkqnIcPH0Kr1T5zO0+ZlZJOp8OtW7fg7OwM1V+mSjMzM+Hv74/k5OTnTseR5eBnVrHw86p4+JlVLC/y5yWEwMOHD+Hr61vsqvOncYaolNRqNfz8/J5bx8XF5YX7Ir3o+JlVLPy8Kh5+ZhXLi/p5PW9m6AmLvQ8RERERUXlhQkRERESKx4TIBDQaDaZOncpHfVQg/MwqFn5eFQ8/s4qFnxcXVRMRERFxhoiIiIiICREREREpHhMiIiIiUjwmRERERKR4TIhMIDY2FgEBAbCzs0NoaCgOHz5s7pAUae/evejUqRN8fX2hUqmwfv36YnXOnz+Pzp07Q6vVwtHRESEhIUhKStKrk5CQgDZt2sDR0REuLi547bXXkJOTU057oRzz58/Hq6++Kt0ILiwsDJs3b5a2DxkyBNWrV4e9vT08PDzQpUsXXLhwQdp+//59REZGwtfXFxqNBv7+/hg2bBgyMzPNsTuKMG3aNKhUKr1X7dq1pe25ubkYOnQo3Nzc4OTkhKioKKSmpur1kZSUhI4dO8LBwQGenp746KOPUFhYWN67ohg3b95E79694ebmBnt7e9SvXx9Hjx7Vq8Pj4mNMiGT64YcfMGbMGEydOhXHjx9HgwYNEBERgTt37pg7NMXJzs5GgwYNEBsbW+L2q1evokWLFqhduzZ2796NP/74A5MnT4adnZ1UJyEhAZGRkWjfvj0OHz6MI0eOYNiwYc+93TsZx8/PD5999hmOHTuGo0ePok2bNujSpQvOnj0LAAgODsayZctw/vx5bN26FUIItG/fHkVFRQAe3z2+S5cu2LBhAy5duoTly5fjt99+w3vvvWfO3Xrh1atXD7dv35Ze+/fvl7aNHj0av/zyC+Lj47Fnzx7cunUL3bp1k7YXFRWhY8eOyM/Px8GDB7FixQosX74cU6ZMMceuvPAePHiA5s2bw8bGBps3b8a5c+fwxRdfwNXVVarD4+JTBMnSpEkTMXToUOn/RUVFwtfXV8yaNcuMUREAsW7dOr2yt99+W/Tu3fu57UJDQ8WkSZPKMDJ6HldXV7F48eISt506dUoAEFeuXHlm+6+//lr4+fmVVXiKN3XqVNGgQYMSt6WnpwsbGxsRHx8vlZ0/f14AEAkJCUIIITZt2iTUarVISUmR6syfP1+4uLiIvLy8Mo1diT7++GPRokWL59bhcfF/XrD0rnzl5+fj2LFjCA8Pl8rUajXCw8ORkJBgxsjor3Q6HTZu3IiXX34ZERER8PT0RGhoqN5ptTt37uDQoUPw9PREs2bN4OXlhVatWun9BUxlo6ioCKtXr0Z2djbCwsKKbc/OzsayZcsQGBgIf3//Evu4desWfvrpJ7Rq1aqsw1W0y5cvw9fXF9WqVUN0dLR0auXYsWMoKCjQOx7Wrl0bVapUkY6HCQkJqF+/Pry8vKQ6ERERyMzMlGYGyXQ2bNiAxo0bo3v37vD09ERQUBAWLVokbedxUR8TIhnu3buHoqIivR9uAPDy8kJKSoqZoqKS3LlzB1lZWfjss88QGRmJbdu24a233kK3bt2wZ88eAMC1a9cAPF4nMWjQIGzZsgWNGjVC27ZtcfnyZXOG/8I6ffo0nJycoNFo8N5772HdunWoW7eutP2bb76Bk5MTnJycsHnzZmzfvh22trZ6ffTq1QsODg546aWX4OLigsWLF5f3bihGaGgoli9fji1btmD+/PlITExEy5Yt8fDhQ6SkpMDW1haVKlXSa/P08TAlJaXE4+WTbWRa165dw/z581GzZk1s3boV77//PkaMGIEVK1YA4HGxGHNPUVVkN2/eFADEwYMH9co/+ugj0aRJEzNFRUIUP2X25LPq1auXXr1OnTqJnj17CiGEOHDggAAgJkyYoFenfv36Yvz48WUesxLl5eWJy5cvi6NHj4rx48cLd3d3cfbsWWl7enq6uHTpktizZ4/o1KmTaNSokcjJydHr4/bt2+L8+fPi559/FnXr1hXvv/9+ee+GYj148EC4uLiIxYsXi1WrVglbW9tidUJCQsS4ceOEEEIMGjRItG/fXm97dna2ACA2bdpULjEriY2NjQgLC9MrGz58uGjatKkQgsfFv+IMkQzu7u6wsrIqdhVFamoqvL29zRQVlcTd3R3W1tZ6sw8AUKdOHWnK38fHBwCeW4dMy9bWFjVq1EBwcDBmzZqFBg0a4Ouvv5a2a7Va1KxZE6+99hrWrl2LCxcuYN26dXp9eHt7o3bt2ujcuTO+/fZbzJ8/H7dv3y7vXVGkSpUq4eWXX8aVK1fg7e2N/Px8pKen69V5+njo7e1d4vHyyTYyLR8fn+cez3hc1MeESAZbW1sEBwdjx44dUplOp8OOHTtKXAdB5mNra4uQkBBcvHhRr/zSpUuoWrUqACAgIAC+vr7PrUNlS6fTIS8vr8RtQggIIZ65/Ul7AM+tQ6aTlZWFq1evwsfHB8HBwbCxsdE7Hl68eBFJSUnS8TAsLAynT5/Wuwp3+/btcHFxKfYLl+Rr3rz5c49nPC7+hbmnqCq61atXC41GI5YvXy7OnTsnBg8eLCpVqqR3FQWVj4cPH4oTJ06IEydOCADiyy+/FCdOnBA3btwQQgjx008/CRsbG7Fw4UJx+fJlMW/ePGFlZSX27dsn9TF37lzh4uIi4uPjxeXLl8WkSZOEnZ3dc69sIuOMHz9e7NmzRyQmJoo//vhDjB8/XqhUKrFt2zZx9epVMXPmTHH06FFx48YNceDAAdGpUydRuXJlkZqaKoQQYuPGjWLp0qXi9OnTIjExUfz666+iTp06onnz5mbesxfX2LFjxe7du0ViYqI4cOCACA8PF+7u7uLOnTtCCCHee+89UaVKFbFz505x9OhRERYWpnfKprCwULzyyiuiffv24uTJk2LLli3Cw8Oj2OkYMo3Dhw8La2tr8emnn4rLly+LVatWCQcHB7Fy5UqpDo+L/8OEyATmzZsnqlSpImxtbUWTJk3E77//bu6QFGnXrl0CQLFXv379pDpLliwRNWrUEHZ2dqJBgwZi/fr1xfqZNWuW8PPzEw4ODiIsLEzvwECmM2DAAFG1alVha2srPDw8RNu2bcW2bduEEI/XNnTo0EF4enoKGxsb4efnJ/75z3+KCxcuSO137twpwsLChFarFXZ2dqJmzZri448/Fg8ePDDTHr343n77beHj4yNsbW3FSy+9JN5++229X4o5OTnigw8+EK6ursLBwUG89dZb4vbt23p9XL9+XXTo0EHY29sLd3d3MXbsWFFQUFDeu6IYv/zyi3jllVeERqMRtWvXFgsXLixWh8fFx1RCCGHOGSoiIiIic+MaIiIiIlI8JkRERESkeEyIiIiISPGYEBEREZHiMSEiIiIixWNCRERERIrHhIiIiIgUjwkRERERKR4TIiIiGSZPnozBgwfL6uPcuXPw8/NDdna2iaIiIkMxISIis3jnnXegUqmgUqlgY2ODwMBAjBs3Drm5ueYOrdRSUlLw9ddf41//+pesfurWrYumTZviyy+/NFFkRGQoJkREZDaRkZG4ffs2rl27hrlz5+Lbb7/F1KlTzR1WqS1evBjNmjUzyVO/+/fvj/nz56OwsNAEkRGRoZgQEZHZaDQaeHt7w9/fH127dkV4eDi2b98ubb9//z569eqFl156CQ4ODqhfvz6+//57vT5at26NESNGYNy4cahcuTK8vb0xbdo0vToXLlxAixYtYGdnh7p16+K3336DSqXC+vXrpTrJycno0aMHKlWqhMqVK6NLly64fv36c+NfvXo1OnXqVCye4cOHY9SoUXB1dYWXlxcWLVqE7Oxs9O/fH87OzqhRowY2b96s165du3ZIS0vDnj17Sv8GEpHJMCEiIotw5swZHDx4ELa2tlJZbm4ugoODsXHjRpw5cwaDBw9Gnz59cPjwYb22K1asgKOjIw4dOoQ5c+Zg+vTpUmJVVFSErl27wsHBAYcOHcLChQuLneIqKChAREQEnJ2dsW/fPhw4cABOTk6IjIxEfn5+ifGmpaXh3LlzaNy4cbFtK1asgLu7Ow4fPozhw4fj/fffR/fu3dGsWTMcP34c7du3R58+ffDo0SOpja2tLRo2bIh9+/YZ/R4SkQyCiMgM+vXrJ6ysrISjo6PQaDQCgFCr1WLt2rXPbdexY0cxduxY6f+tWrUSLVq00KsTEhIiPv74YyGEEJs3bxbW1tbi9u3b0vbt27cLAGLdunVCCCG+++47UatWLaHT6aQ6eXl5wt7eXmzdurXEOE6cOCEAiKSkJL3yv8ZTWFgoHB0dRZ8+faSy27dvCwAiISFBr+1bb70l3nnnnefuPxGVDWvzpmNEpGSvv/465s+fj+zsbMydOxfW1taIioqSthcVFWHmzJlYs2YNbt68ifz8fOTl5cHBwUGvn1dffVXv/z4+Prhz5w4A4OLFi/D394e3t7e0vUmTJnr1T506hStXrsDZ2VmvPDc3F1evXi0x9pycHACAnZ1dsW1Px2NlZQU3NzfUr19fKvPy8gIAKcYn7O3t9WaNiKj8MCEiIrNxdHREjRo1AABLly5FgwYNsGTJEgwcOBAA8Pnnn+Prr7/GV199hfr168PR0RGjRo0qdhrLxsZG7/8qlQo6na7UcWRlZSE4OBirVq0qts3Dw6PENu7u7gCABw8eFKtTUjxPl6lUKgAoFmNaWhqqV69e6riJyHS4hoiILIJarcbEiRMxadIkafblwIED6NKlC3r37o0GDRqgWrVquHTpkkH91qpVC8nJyUhNTZXKjhw5olenUaNGuHz5Mjw9PVGjRg29l1arLbHf6tWrw8XFBefOnTNwT5/tzJkzCAoKMll/RFR6TIiIyGJ0794dVlZWiI2NBQDUrFkT27dvx8GDB3H+/HkMGTJEL7EpjXbt2qF69ero168f/vjjDxw4cACTJk0C8L+ZmujoaLi7u6NLly7Yt28fEhMTsXv3bowYMQJ//vlnif2q1WqEh4dj//79Mvb4f65fv46bN28iPDzcJP0RkWGYEBGRxbC2tsawYcMwZ84cZGdnY9KkSWjUqBEiIiLQunVreHt7o2vXrgb1aWVlhfXr1yMrKwshISF49913pavMnqz/cXBwwN69e1GlShV069YNderUwcCBA5GbmwsXF5dn9v3uu+9i9erVBp2ee5bvv/8e7du3N8k9jYjIcCohhDB3EERE5enAgQNo0aIFrly5ImvNjhACoaGhGD16NHr16mV0P/n5+ahZsybi4uLQvHlzo/shIuNxUTURvfDWrVsHJycn1KxZE1euXMHIkSPRvHlz2QuYVSoVFi5ciNOnT8vqJykpCRMnTmQyRGRGnCEiohfef//7X8yYMQNJSUlwd3dHeHg4vvjiC7i5uZk7NCKyEEyIiIiISPG4qJqIiIgUjwkRERERKR4TIiIiIlI8JkRERESkeEyIiIiISPGYEBEREZHiMSEiIiIixWNCRERERIr3/wANK+1HMJtYSQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from numpy import abs ,angle, arange, arcsin, cos, pi, sqrt, tan, zeros\n", "from scipy.fft import fft, fft2\n", "import matplotlib.pyplot as plt\n", "\n", "\n", "def y_IF(f0_min, slope, T, antenna_tx, antenna_rx, target, v=3e8):\n", " \"\"\" This function implements the mathematical IF defined in latex as\n", " y_{IF} = cos(2 \\pi [f_0\\delta + s * \\delta * t - s/2* \\delta^2])\n", " into following python code\n", " y_IF = cos (2*pi*(f_0 * delta + slope * delta * T - slope/2 * delta**2))\n", " Parameters:\n", " -----------\n", " f0_min: float\n", " the frequency at the begining of the chirp\n", " slope: float\n", " the slope with which the chirp frequency inceases over time\n", " T: ndarray\n", " the 1D vector containing time values\n", " antenna_tx: tuple of floats\n", " x, y, z coordinates\n", " antenna_rx: tuple of floats\n", " x, y, z coordinates\n", " target: tuple of floats\n", " x, y, z coordinates\n", " v: float\n", " speed of light in considered medium\n", " Returns:\n", " --------\n", " YIF: ndarray\n", " vector containing the IF values\n", " \"\"\"\n", " tx_x, tx_y, tx_z = antenna_tx\n", " rx_x, rx_y, rx_z = antenna_rx\n", " t_x, t_y, t_z = target\n", " # distance tx antenna to target\n", " distance = sqrt((tx_x-t_x)**2 + (tx_y-t_y)**2 + (tx_z-t_z)**2)\n", " # distance target to rx antenna\n", " distance += sqrt((rx_x-t_x)**2 + (rx_y-t_y)**2 + (rx_z-t_z)**2)\n", " # usually delta_t = 2*d/c, but\n", " # distance is already 2*D (TX to target + distance target to RX)\n", " # so delta = distance/v\n", " delta = distance/v\n", " YIF = cos(2 *pi *(f0_min * delta + slope * delta * T - slope/2 * delta**2))\n", " return YIF\n", "\n", "f0_min = 60e9\n", "c = 3e8\n", "# lambda ~5mm at 60GHz\n", "lambda0_max = 3e8/f0_min\n", "n_rx = 2\n", "Distance = 10\n", "k = 10e12\n", "n_samples = 64\n", "f_if = 2*k*Distance/c\n", "fs = 50e6\n", "ts = 1/fs\n", "\n", "antenna_tx = (-lambda0_max/2,0,0)\n", "antenna_rx = (0,0,0)\n", "T = arange(0, n_samples*ts, ts)\n", "t_chirp_to_chirp = 1.2e-6\n", "n_chirps = 32\n", "\n", "# define empty 2D array to be filled by 2D FFT\n", "cube2D = zeros((n_chirps, n_samples))\n", "\n", "# define 3 targets\n", "# one target is a 2-uple (distance, speed)\n", "targets = [(160, 600), (160, 200), (300, 200)]\n", "\n", "for chirp_i in range(n_chirps):\n", " d0, v0 = targets[0]\n", " d0_t = d0 + v0*t_chirp_to_chirp*chirp_i\n", "\n", " d1, v1 = targets[1]\n", " d1_t = d1 + v1*t_chirp_to_chirp*chirp_i\n", "\n", " d2, v2 = targets[2]\n", " d2_t = d2 + v2*t_chirp_to_chirp*chirp_i\n", "\n", " f_if = 2*k*d0_t/c\n", "\n", " target_0_t = (0, d0_t, 0)\n", " target_1_t = (0, d1_t, 0)\n", " target_2_t = (0, d2_t, 0)\n", "\n", " # sanity check\n", " assert f_if < 1/ts/2\n", " assert v0 < lambda0_max/4/t_chirp_to_chirp\n", "\n", " YIFi0 = y_IF(f0_min, k, T, antenna_tx, antenna_rx, target_0_t)\n", " YIFi1 = y_IF(f0_min, k, T, antenna_tx, antenna_rx, target_1_t)\n", " YIFi2 = y_IF(f0_min, k, T, antenna_tx, antenna_rx, target_2_t)\n", "\n", " YIFi = YIFi0 + YIFi1 + YIFi2\n", "\n", " cube2D[chirp_i, :] = YIFi\n", "\n", "Z_fft2 = abs(fft2(cube2D))\n", "\n", "Data_fft2 = Z_fft2[0:n_chirps//2,0:n_samples//2]\n", "\n", "# change scale for 2D plot to display range and velocity values\n", "# https://stackoverflow.com/a/53746824\n", "# range formula\n", "ranges = linspace(0, fs*c/2/k *2, n_samples)\n", "no_labels = 10 # how many labels to see on axis x\n", "step_x = int(n_samples / (no_labels - 1)) # step between consecutive labels\n", "x_positions = arange(0, n_samples, step_x) # pixel count at label position\n", "x_labels = ranges[::step_x] # labels you want to see\n", "x_labels = [int(x) for x in x_labels]\n", "plt.xticks(x_positions, x_labels)\n", "\n", "# we plot up to 2x max un-ambigous speed as we have no negative speeds\n", "# to display so the positive speeds wrap-up and are displayed above\n", "# the max un-ambigous speed\n", "# hence speeds from 0 to max-speed *2\n", "speeds = linspace(0, lambda0_max/4/t_chirp_to_chirp *2,\n", " n_chirps)\n", "no_labels_y = 10 # how many labels to see on axis x\n", "step_y = int(n_chirps / (no_labels_y - 1)) # step between consecutive labels\n", "y_positions = arange(0, n_chirps, step_y) # pixel count at label position\n", "y_labels = speeds[::step_y] # labels you want to see\n", "y_labels = [int(y) for y in y_labels] # remove decimlas for cleaner plotting\n", "plt.yticks(y_positions, y_labels)\n", "\n", "# change scale for 2D plot\n", "\n", "plt.imshow(Data_fft2)\n", "plt.xlabel(\"Range (m)\")\n", "plt.ylabel(\"Velocity (m/s)\")\n", "plt.title('Velocity-Range 2D FFT')" ] } ], "metadata": { "colab": { "name": "FMCW-Radar-101_Intro.ipynb", "provenance": [], "toc_visible": true }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.9" } }, "nbformat": 4, "nbformat_minor": 4 }