├── .gitignore ├── 1_1_Introduction_to_Labs.ipynb ├── 1_2_Introduction_to_Radar.ipynb ├── 2_1_Radar_Range_Equation.ipynb ├── 2_2_Basic_Radar_Design.ipynb ├── 3_1_Radar_Transmissions_and_Receptions.ipynb ├── 3_2_Detection.ipynb ├── 4_1_Target_Parameter_Estimation.ipynb ├── 4_2_Target_Tracking.ipynb ├── 5_1_Radar_Design_Revisited.ipynb ├── LICENSE ├── README.md ├── Reference.ipynb ├── img ├── 1_1_sine.png ├── 2_1_dets_noise.fig ├── 2_1_dets_noise.png ├── active.ai ├── active.png ├── add_cell.gif ├── add_cell.png ├── angle_cut.png ├── angle_cut.pptx ├── angle_obs.png ├── anttarget2.png ├── aperture.ai ├── aperture.png ├── array_steer.ai ├── array_steer.png ├── assoc_like.ai ├── assoc_like.png ├── azimuth.ai ├── azimuth.png ├── bands.png ├── bkwd.png ├── circ_pol.gif ├── conda_prompt.png ├── cross_range.ai ├── cross_range.png ├── cw.ai ├── cw.png ├── dark_mode.png ├── data_assoc.ai ├── data_assoc.png ├── data_assoc_prob.ai ├── data_assoc_prob.png ├── db.ai ├── db.png ├── edit_cell.gif ├── eval_no.png ├── ext_button.png ├── focus_steer.ai ├── focus_steer.png ├── friis.ai ├── friis.png ├── friis_0.png ├── friis_1.png ├── friis_2.png ├── fwd.png ├── haystack.jpg ├── hide_code_icon.png ├── impulse.ai ├── impulse.png ├── incr_font.png ├── integ.ai ├── integ.png ├── mtt.ai ├── mtt.png ├── obs.ai ├── obs.png ├── pan.png ├── pause.png ├── play.png ├── power.ai ├── power.png ├── power_seg.png ├── predict.ai ├── predict.png ├── prop_delay.ai ├── prop_delay.png ├── pulse_dopp.ai ├── pulse_dopp.png ├── radar_range.ai ├── radar_range.png ├── radar_sys.ai ├── radar_sys1.png ├── radar_sys2.png ├── radar_sys3.png ├── radar_sys4.png ├── range_obs.png ├── range_rate_obs.png ├── rcs_model.ai ├── rcs_model.png ├── reset_view.png ├── run_all_icon.png ├── run_cell_icon.png ├── scan.png ├── sinc.ai ├── sinc.eps ├── sinc.fig ├── sine.ai ├── sine.eps ├── sine.fig ├── sine2.ai ├── sine2.eps ├── sine_ex.png ├── sine_ex.pptx ├── sine_fig.ai ├── sine_fig.png ├── sine_pulse.ai ├── sine_pulse.png ├── sine_pulse_multi.ai ├── sine_pulse_multi.png ├── snr_design.png ├── state_est.ai ├── state_est.png ├── state_est_flow.ai ├── state_est_flow.png ├── state_est_prob.ai ├── state_est_prob.png ├── stop.png ├── toc.png ├── track.ai ├── track.png ├── track_init.ai ├── track_init.png ├── tx_dof.ai ├── tx_dof.png ├── vert.gif ├── wave.png ├── wave_seg.png ├── waveform.png ├── wavelength.ai └── zoom_box.png ├── logo.png ├── rad ├── __init__.py ├── air.py ├── const.py ├── css.py ├── example.py ├── plot.py ├── quiz.py ├── radar.py ├── robby.py └── toys.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #Ignore thumbnails created by Windows 3 | Thumbs.db 4 | #Ignore files built by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | .vs/ 31 | #Nuget packages folder 32 | packages/ 33 | rad/__pycache__/ 34 | .ipynb_checkpoints/ 35 | env/ 36 | .venv/ 37 | -------------------------------------------------------------------------------- /2_2_Basic_Radar_Design.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "36bb8f35-f615-4e26-b387-7bad63e371b5", 6 | "metadata": {}, 7 | "source": [ 8 | "# Lab 2.2: Basic Radar Design" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "4acc6bb8-1bb5-46a2-9d4e-18f2b9583cb8", 15 | "metadata": { 16 | "jupyter": { 17 | "source_hidden": true 18 | }, 19 | "tags": [] 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "%matplotlib widget\n", 24 | "import rad.css as css\n", 25 | "import rad.example as ex\n", 26 | "import rad.quiz as qz\n", 27 | "from rad.const import c, k\n", 28 | "from rad.radar import to_db, from_db, deg2rad, rad2deg\n", 29 | "from math import sqrt, sin, asin, cos, acos, tan, atan2, pi, log, log10\n", 30 | "css.add_custom_css()" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "id": "dea5fb7e-b2c1-4a42-91f0-e7361d8ccca0", 36 | "metadata": {}, 37 | "source": [ 38 | "**Reminders**: \n", 39 | "\n", 40 | "- Hit the *Run All* button button above before continuing\n", 41 | "- Useful formulae and definitions are available in [Reference](Reference.ipynb)" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "3f53e4f3-c68d-4196-b644-a232064196b0", 47 | "metadata": {}, 48 | "source": [ 49 | "From [Lab 2.1: Radar Range Equation](2_1_Range_Range_Equation.ipynb), we saw that if we know certain aspects of the radar system (e.g., transmit energy, transmit gain, etc.), it is possible to derive the received energy from a target echo. If we also know the characteristics of the noise in the radar system, we can also calculate the signal-to-noise ratio (SNR). The SNR is a key quantity that allows us to deduce whether we should be able to detect a target echo in a given situation. If the SNR is high ($\\gg 0~\\mathrm{dB}$), the echo should be confidently detected; if the SNR is low ($\\leq 0~\\mathrm{dB}$), it will be difficult to discern the echo from the noise. (We will discuss this in more detail in [Lab 3.2: Detection](3_2_Detection.ipynb).)" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "id": "53bbfb8d-6c9a-4863-8de8-c367abef5f42", 55 | "metadata": {}, 56 | "source": [ 57 | "As a quick recap, we can see in the interactive example below how the received energy of the echo (in **black**) compares to the energy of the noise (in **red**) for a dish radar with adjustable properties:" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "id": "4b668d14-9bd4-4a7c-a238-202af56fc5ee", 64 | "metadata": { 65 | "jupyter": { 66 | "source_hidden": true 67 | }, 68 | "tags": [] 69 | }, 70 | "outputs": [], 71 | "source": [ 72 | "ex.ex_2_2_1()" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "id": "e2c79cbd-5a57-4a52-b00c-8f62a2a8a92d", 78 | "metadata": {}, 79 | "source": [ 80 | "## Sensitivity Design" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "id": "617ab4d9-4f9b-4143-8607-970ec7359786", 86 | "metadata": {}, 87 | "source": [ 88 | "Designing a radar to obtain a desired SNR is often called controlling its **sensitivity**; this is one of the primary factors used when crafting a radar system. A sensitivity objective will often come in the form like the following: \"$15~\\mathrm{dB}$ SNR on a $-5~\\mathrm{dBsm}$ target at a range of $100~\\mathrm{km}$\". To see how to satisfy this requirement and understand what design decisions are important, let us look back at the formula for SNR; specifically, the SNR from a target echo was shown to be:\n", 89 | "\n", 90 | "$$\n", 91 | "\\mathrm{SNR} = \\frac{\\mathcal{E}_t G_t(\\theta) G_r(\\theta) \\lambda^2 \\sigma}{(4\\pi)^3 r^4 k T_s}\n", 92 | "$$\n", 93 | "\n", 94 | "where\n", 95 | "\n", 96 | "- $\\mathcal{E}_t$ is the transmitted energy $(\\mathrm{J})$\n", 97 | "- $G_t(\\theta)$ is the transmit gain in the transmit direction $\\theta$\n", 98 | "- $G_r(\\theta)$ is the receive gain in the receive direction $\\theta$\n", 99 | "- $\\lambda$ is the transmission wavelength ($\\mathrm{m}$)\n", 100 | "- $\\sigma$ is the radar cross section of the target ($\\mathrm{m^2}$)\n", 101 | "- $r$ is the range to the target ($\\mathrm{m}$)" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "id": "bbee0a61-94c4-4fd8-b452-ff761d64247d", 107 | "metadata": {}, 108 | "source": [ 109 | "It is helpful at this point to identify which terms in the SNR equation are controlled by the radar designer. Here is the SNR from a target echo split into terms regarding the radar and the target:" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "id": "3206f86d-b82c-47fe-9132-fb32e7da5a41", 115 | "metadata": {}, 116 | "source": [ 117 | "
" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "id": "c46611d0-5b60-4c14-867a-a561bcff6c2d", 123 | "metadata": {}, 124 | "source": [ 125 | "More specifically, the factors we can control as a radar designer are:\n", 126 | "- $\\mathcal{E}_t$ is controlled by transmit power and transmit duration\n", 127 | "- $G_t(\\theta)$ is controlled by transmit aperture shape and transmit frequency\n", 128 | "- $G_r(\\theta)$ is controlled by receive aperture shape and transmit frequency\n", 129 | "- $\\lambda$ is controlled by transmit frequency\n", 130 | "- $T_s$ is the system noise temperature" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "id": "393c731a-7087-497d-8290-5f37da046e7f", 136 | "metadata": {}, 137 | "source": [ 138 | "The factors dictated by the scenarios of interest:\n", 139 | "- $\\sigma$: Target shape, material, orientation\n", 140 | "- $r$: Radar-target geometry" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "id": "6f4cd4c3-5af3-4235-bba0-9104f7ea4e16", 146 | "metadata": {}, 147 | "source": [ 148 | "Thus, the application dictates what values of RCS and range are relevant, and then it is our job to design a radar that can achieve a desired SNR by choosing: transmit energy, transmit/receive aperture, and transmit frequency. In general, cost *increases* as:\n", 149 | "\n", 150 | "- Transmit energy, $\\mathcal{E}_t$ , increases\n", 151 | "- Aperture size increases\n", 152 | "- Transmit frequency, $f$ , increases\n", 153 | "- System noise temperature, $T_s$ , decreases\n", 154 | "\n", 155 | "To impart intuition, let us work through an example. We are building a dish radar system to achieve the given sensitivity objective from above: \"$15~\\mathrm{dB}$ SNR on a $-5~\\mathrm{dBsm}$ target at a range of $100~\\mathrm{km}$\". We have a budget of $\\$100\\mathrm{k}$ and the radar manufacturer gives us the following price sheet:" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "id": "c94ff606-4fde-4e34-8035-d0fca75c9cb4", 161 | "metadata": {}, 162 | "source": [ 163 | "\n", 164 | "\n", 165 | "| Hardware | Price |\n", 166 | "|--------------------------|------------------------------------------|\n", 167 | "| Dish Radius | $\\$10000/\\mathrm{m}$ |\n", 168 | "| Transmit Energy | $\\$20/\\mathrm{mJ}$ |\n", 169 | "| Transmit Frequency | $\\$10/\\mathrm{MHz}$ |\n", 170 | "| Noise Temperature | $\\$50/^\\circ K$ below $1500 ^\\circ K$ |" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "id": "e0c0070c-e45c-4afd-b858-9369f23cc1f4", 176 | "metadata": {}, 177 | "source": [ 178 | "In the interactive example below, try changing the values of the radar design parameters to achieve the sensitivity requirement while staying under budget. In short, try to simultaneously satisfy:\n", 179 | "\n", 180 | "* Price $\\leq \\$100000$\n", 181 | "* SNR $\\geq 15~\\mathrm{dB}$\n", 182 | "* RCS $\\leq -5~\\mathrm{dBsm}$\n", 183 | "* Range $\\geq 100~\\mathrm{km}$" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "id": "d573ac73-550a-48a2-964d-0c040ddc1225", 190 | "metadata": { 191 | "jupyter": { 192 | "source_hidden": true 193 | }, 194 | "tags": [] 195 | }, 196 | "outputs": [], 197 | "source": [ 198 | "ex.ex_2_2_2()" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "id": "54d1e659-0f52-4214-893e-8ad4232c1abe", 204 | "metadata": {}, 205 | "source": [ 206 | "## Beamwidth Design" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "id": "6b1c267f-c7a2-4478-9a64-4ec9561eee99", 212 | "metadata": {}, 213 | "source": [ 214 | "Another key factor in designing a radar is a desired beamwidth; this will decide how accurately we can measure the angle of targets. (We will discuss range measurement accuracy in [Lab 3.1: Radar Transmissions and Receptions](3_1_Radar_Transmissions_and_Receptions.ipynb) and angle accuracy will be revisited in detail in [Lab 4.1: Target Parameter Estimation](4_1_Target_Parameter_Estimation.ipynb).)" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "id": "ecf82541-c0db-4bc2-83cb-d23dfdd4474a", 220 | "metadata": {}, 221 | "source": [ 222 | "We saw in [Lab 1.2: Introduction to Radar](1_2_Introduction_to_Radar.ipynb) that the beamwidth of circular apertures (very common in radar designs) is:\n", 223 | "\n", 224 | "$$\n", 225 | "\\Delta\\theta = 70^\\circ\\frac{\\lambda}{D}\n", 226 | "$$\n", 227 | "\n", 228 | "where $D$ is the diameter of the aperture and $\\lambda$ is the transmit wavelength. The beamwidth and gain for a circular aperture can be studied in the interactive example below." 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "id": "2718581d-f48e-4d52-bb69-79ba5f8fd554", 235 | "metadata": { 236 | "jupyter": { 237 | "source_hidden": true 238 | }, 239 | "tags": [] 240 | }, 241 | "outputs": [], 242 | "source": [ 243 | "ex.ex_2_2_3()" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "id": "b7615f76-598e-4647-a93f-e68a9f50149a", 249 | "metadata": {}, 250 | "source": [ 251 | "Another common radar aperture shape is a rectangle. Because the shape is not necessarily symmetrical around its center (like a circular aperture), it has two different beamwidths: one in the horizontal dimension and one in the vertical dimension. The vertical beamwidth, $\\Delta \\theta_v$ , and horizontal beamwidth, $\\Delta \\theta_h$ , of a rectangular aperture (in degrees) of height $a$ (in $\\mathrm{m}$) and width $b$ (in $\\mathrm{m}$) are:\n", 252 | "\n", 253 | "$$\n", 254 | "\\Delta\\theta_v = 57.3^\\circ\\frac{\\lambda}{a}\n", 255 | "$$\n", 256 | "\n", 257 | "$$\n", 258 | "\\Delta\\theta_h = 57.3^\\circ\\frac{\\lambda}{b}\n", 259 | "$$\n", 260 | "\n", 261 | "where $\\lambda$ is the transmit wavelength (in $\\mathrm{m}$). Like the circular aperture, you can investigate how the beamwidth and gain for a rectangular aperture change with shape and frequency in the interactive example below." 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": null, 267 | "id": "5c3beafd-31f3-4f78-953f-aaa781ce2753", 268 | "metadata": { 269 | "jupyter": { 270 | "source_hidden": true 271 | }, 272 | "tags": [] 273 | }, 274 | "outputs": [], 275 | "source": [ 276 | "ex.ex_2_2_4()" 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "id": "bd5b752a-3b80-473b-b202-0178a936b2e8", 282 | "metadata": {}, 283 | "source": [ 284 | "Now, we can revisit building a radar with a given budget, but now we will have both a sensitivity objective and a desired beamwidth. Try staying under budget in the interactive example below while meeting the following objectives:\n", 285 | "\n", 286 | "* Price $\\leq \\$110000$\n", 287 | "* SNR $\\geq 14~\\mathrm{dB}$\n", 288 | "* Beamwidth $\\leq 2~\\mathrm{deg}$\n", 289 | "* RCS $\\leq -10~\\mathrm{dBsm}$\n", 290 | "* Range $\\geq 120~\\mathrm{km}$" 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": null, 296 | "id": "201c001e-90f2-487f-a7ec-ba40281be58e", 297 | "metadata": { 298 | "jupyter": { 299 | "source_hidden": true 300 | }, 301 | "tags": [] 302 | }, 303 | "outputs": [], 304 | "source": [ 305 | "ex.ex_2_2_5()" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "id": "99c0b523-a9c1-441f-b082-b7cae6dda9ba", 311 | "metadata": {}, 312 | "source": [ 313 | "Note that there are many more potential design objectives for a radar, e.g., target parameter estimation, tracking, discrimination. We will discuss how radar parameters affect the performance of these objectives in following labs and revisit radar design in the last lab, [Lab 5.1: Radar Design Revisited](5_1_Radar_Design_Revisited.ipynb)" 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "id": "1216908d-8e53-4ed5-8dde-204f3269cae5", 319 | "metadata": {}, 320 | "source": [ 321 | "## Meet Robby" 322 | ] 323 | }, 324 | { 325 | "cell_type": "markdown", 326 | "id": "6bcee96e-341a-472b-b93f-55a9ab92366d", 327 | "metadata": {}, 328 | "source": [ 329 | "As a final exercise, we will meet Robby the radar, which we will use numerous times throughout the remainder of the course. Robby is a test radar in a simulated environment that allows you to change radar parameters on the fly and see how things evolve. More specifically, Robby is a rotating dish radar whose display shows the received signals for every angle on a single screen; a pulse is recorded for every $0.5^\\circ$ in azimuth angle. (We will dive into the processing that is happening behind-the-scenes in [Lab 3.1: Radar Transmissions and Receptions](3_1_Radar_Transmissions_and_Receptions.ipynb).) \n", 330 | "\n", 331 | "As the labs progress, more adjustments can be made to Robby; however, the initial variables are *Noise Temperature*, *Dish Radius*, and *Transmit Frequency*. To use the radar, click on the *Scan* button; this will make one full rotation and plot all received pulses. To see an individual pulse, we can use the *Azimuth* slider in the **Pulse Display** section to display the pulse for a desired azimuth angle. Note that the data are normalized by the average noise energy, so noise samples will be distributed around $0~\\mathrm{dB}$ and echoes will have values equal to their signal-to-noise ratio; this is why the scale is marked as SNR.\n", 332 | "\n", 333 | "In the example below, there is a single target with RCS of $5~\\mathrm{dBsm}$ located at a range of $6~\\mathrm{km}$ and azimuth of $45^\\circ$. Try changing radar parameters to see how the response varies." 334 | ] 335 | }, 336 | { 337 | "cell_type": "code", 338 | "execution_count": null, 339 | "id": "1d2d5d07-8566-4466-b3ee-cfc8404fa257", 340 | "metadata": { 341 | "jupyter": { 342 | "source_hidden": true 343 | }, 344 | "tags": [] 345 | }, 346 | "outputs": [], 347 | "source": [ 348 | "ex.ex_2_2_6()" 349 | ] 350 | }, 351 | { 352 | "cell_type": "markdown", 353 | "id": "38ec872c-301e-43d3-a751-8d8e37e7891f", 354 | "metadata": {}, 355 | "source": [ 356 | "## Summary" 357 | ] 358 | }, 359 | { 360 | "cell_type": "markdown", 361 | "id": "8c231761-8900-4c4f-a59a-ec1e9aa1f41d", 362 | "metadata": {}, 363 | "source": [ 364 | "In this lab, we discussed high-level radar design for two common criteria: *sensitivity* and *beamwidth*. We studied through examples how we can tradeoff different radar design choices, e.g., transmit frequency, aperture size, to satisfy these requirements. Additionally, we were introduced to Robby the radar, a test radar that can be altered on the fly to observe changes in radar observations." 365 | ] 366 | }, 367 | { 368 | "cell_type": "markdown", 369 | "id": "6ceaaa2e-a58f-4d40-8e24-9ae619123db6", 370 | "metadata": {}, 371 | "source": [ 372 | "## Footnotes" 373 | ] 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "id": "3b2511ba-1b82-4a60-bd39-68acc8cd4fc0", 378 | "metadata": {}, 379 | "source": [ 380 | "n/a" 381 | ] 382 | }, 383 | { 384 | "cell_type": "markdown", 385 | "id": "e4dbdae8-acc2-41b9-ab29-e9e0568c70c0", 386 | "metadata": {}, 387 | "source": [ 388 | "## References" 389 | ] 390 | }, 391 | { 392 | "cell_type": "markdown", 393 | "id": "4ed3b80a-481f-449a-ae1b-749651462336", 394 | "metadata": {}, 395 | "source": [ 396 | "n/a" 397 | ] 398 | } 399 | ], 400 | "metadata": { 401 | "kernelspec": { 402 | "display_name": "Python 3 (ipykernel)", 403 | "language": "python", 404 | "name": "python3" 405 | }, 406 | "language_info": { 407 | "codemirror_mode": { 408 | "name": "ipython", 409 | "version": 3 410 | }, 411 | "file_extension": ".py", 412 | "mimetype": "text/x-python", 413 | "name": "python", 414 | "nbconvert_exporter": "python", 415 | "pygments_lexer": "ipython3", 416 | "version": "3.10.10" 417 | } 418 | }, 419 | "nbformat": 4, 420 | "nbformat_minor": 5 421 | } 422 | -------------------------------------------------------------------------------- /3_2_Detection.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6f8f88a4-85bb-4cd8-ba16-4961f4aa331b", 6 | "metadata": {}, 7 | "source": [ 8 | "# Lab 3.2: Detection" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "f91f3d77-6845-4852-b8e3-ba50e9633ad6", 15 | "metadata": { 16 | "jupyter": { 17 | "source_hidden": true 18 | }, 19 | "tags": [] 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "%matplotlib widget\n", 24 | "import rad.example as ex\n", 25 | "import rad.quiz as qz\n", 26 | "from rad.const import c, k\n", 27 | "from rad.radar import to_db, from_db, deg2rad, rad2deg\n", 28 | "from math import sqrt, sin, asin, cos, acos, tan, atan2, pi, log, log10" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "id": "bbf527a4-8312-4477-ab22-e8aa8bd7a53d", 34 | "metadata": {}, 35 | "source": [ 36 | "**Reminders**: \n", 37 | "\n", 38 | "- Hit the *Run All* button button above before continuing\n", 39 | "- Useful formulae and definitions are available in [Reference](Reference.ipynb)" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "id": "f9f9a299", 45 | "metadata": {}, 46 | "source": [ 47 | "From [Lab 1.2: Introduction to Radar](1_2_Introduction_to_Radar.ipynb), we know that a *detection* is the acknowledgment that an echo of the transmitted wave has been received. When the noise energy of the radar system is comparable to the received energy from the echoes, making the right call is not trivial and requires the radar designer to guard against **false alarms**, i.e., incorrect declaration of the reception of an echo.\n", 48 | "\n", 49 | "For our discussion, we will focus only on system noise as the source of false alarms; however, false alarms can arise from unwanted energy being received via other means:\n", 50 | "\n", 51 | "- **Clutter**: echoes of background or uninteresting objects, e.g., buildings, sea surface\n", 52 | "- **Interference**: signals generated by other sources, e.g., cell phone towers, other radar systems\n", 53 | "\n", 54 | "Before getting to detection methods, let us first review signal-to-noise ratio." 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "id": "d45690b6-f799-4e5d-a216-417ad48efd53", 60 | "metadata": {}, 61 | "source": [ 62 | "## Review: Signal-to-Noise Ratio (SNR)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "66e5126b-9c65-4185-a63b-c07250a62729", 68 | "metadata": {}, 69 | "source": [ 70 | "In [Lab 1.2: Introduction to Radar](1_2_Introduction_to_Radar.ipynb), we learned that the relative strength of an echo versus the system noise is quantified using a *signal-to-noise ratio* (SNR). Large values of SNR ($\\gg 0~\\mathrm{dB}$) should facilitate confident formation of a detection whereas low values of SNR ($\\leq 0~\\mathrm{dB}$) will make correct declaration of a detection difficult. Recalling a previous interactive example, we can see how radar and target parameters affect the SNR and how that intuitively affects the acknowledgment of an echo (plotted in **black**) in the presence of noise (plotted in **red**):" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "id": "eda1a409-b7ca-4949-9f0c-95547fe3e32e", 77 | "metadata": { 78 | "jupyter": { 79 | "source_hidden": true 80 | }, 81 | "tags": [] 82 | }, 83 | "outputs": [], 84 | "source": [ 85 | "ex.ex_3_2_1()" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "d22b2c69-8072-4ae0-bc8a-ce4b92fe1b43", 91 | "metadata": {}, 92 | "source": [ 93 | "In reality, we will not have the benefit of having the true echo labeled with a different color, and it is our objective to discern which parts of the signal are detections; the practical problem looks more like the following example:" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "id": "15ce584d-eb56-4a11-8d1d-701b1a45a7ba", 100 | "metadata": { 101 | "jupyter": { 102 | "source_hidden": true 103 | }, 104 | "tags": [] 105 | }, 106 | "outputs": [], 107 | "source": [ 108 | "ex.ex_3_2_2()" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "id": "25bdde18-9690-411d-bc35-7bbca2ddd308", 114 | "metadata": {}, 115 | "source": [ 116 | "We can note that at SNR values near or below zero, the target echo is indistinguishable from the noise." 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "id": "864ace08-3b76-4d13-8820-9650a30ff672", 122 | "metadata": {}, 123 | "source": [ 124 | "## Detection Theory" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "id": "b83c7b50-6ced-488c-934a-f2f336429584", 130 | "metadata": {}, 131 | "source": [ 132 | "The academic area concerned with finding the presence of a signal within noise is **detection theory**. In this field, we try to develop detection rules that will successfully designate data points as being a detection or not. Before we can create a \"good\" detection rule (called a **detector**), we have to define some ways to quantify success." 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "id": "e12c3254", 138 | "metadata": {}, 139 | "source": [ 140 | "When labeling a data point as a detection or not, some of the possible outcomes have been given special names, as listed below:\n", 141 | "\n", 142 | "#### Possible Outcomes\n", 143 | "\n", 144 | "| Category | Status | Description |\n", 145 | "|--------------------------|----------|------------------------------------------------------------------------|\n", 146 | "| Detection | ✔ | **Correctly** declaring a true echo as an echo |\n", 147 | "| Miss | ❌ | **Erroneously** remain silent though a true echo is actually present |\n", 148 | "| False Alarm | ❌ | **Erroneously** declaring noise as an echo |\n", 149 | "| Correct Rejection | ✔ | **Correctly** staying silent when only noise is present |\n", 150 | "\n", 151 | "If we take each of these cases and divide them by the total number of correct opportunities, the resulting rates define our accuracy or lack thereof. For instance, if we had data points that contained 100 true echoes and declared detections on only 50, we would have a **probability of detection** of $P_D = 50/100 = 0.5$. Likewise, if we have 100 data points that contain only noise and we incorrectly declare 25 of them to have echoes, we have a **probability of false alarm** of $P_{FA} = 25/100 = 0.25$. To summarize these measures, we have the following table:\n", 152 | "\n", 153 | "#### Accuracy Measures\n", 154 | "| Category | Symbol | Formula |\n", 155 | "|----------------------------|--------------------|------------------------------------------------------------------|\n", 156 | "| Probability of Detection | $P_D$ | $\\frac{\\sum{\\mathrm{Detections}}}{\\sum{\\mathrm{True~Echoes}}}$ |\n", 157 | "| Probability of False Alarm | $P_{FA}$ | $\\frac{\\sum{\\mathrm{False~Alarms}}}{\\sum{\\mathrm{Noise~Only}}}$ |\n", 158 | "\n", 159 | "$P_D$ and $P_{FA}$ fully describe the performance of a detector[1](#foot_pdpfa) and their simultaneous representation will prove so definitive that a special form of graph of the two used to illustrate system performance. However, before we examine that graph, let us further study how $P_D$ and $P_{FA}$ are calculated by looking at interactive example.\n", 160 | "\n", 161 | "We are now going to look at a sequence of data samples (shown now as bars) and decide which are echoes and which are just noise. Choose the expected *Target SNR* to use for the game, then hit *Start* (once the game is started, we can no longer change the *Target SNR*). For the current data point (thick **black** line), select *Detection* or *No Detection*; note that the target echoes will show up near *Target SNR* and noise will show up near $0~\\mathrm{dB}$. For each answer, the result will be color coded as **green** for a correct detection, **blue** for a correct rejection, **red** for a false alarm, and **magenta** for a missed detection. The running values for $P_D$ and $P_{FA}$ will be displayed to the right of the plot." 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "id": "5d413125", 168 | "metadata": { 169 | "jupyter": { 170 | "source_hidden": true 171 | }, 172 | "tags": [] 173 | }, 174 | "outputs": [], 175 | "source": [ 176 | "ex.ex_3_2_3()" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "id": "833effdd", 182 | "metadata": {}, 183 | "source": [ 184 | "Note that perfect detector would have a probability of detection of $P_D = 1$ and a probability of false alarm of $P_{FA} = 0$.\n", 185 | "\n", 186 | "What determined which data points were called detections? A reasonable strategy was to define some threshold energy then declare everything above that as a detection and everything below that as noise. This is not a bad strategy, but if we were to play the game that way under different noise levels, the threshold would need to be adjusted accordingly. You can see that in the next exercise. Moreover, we will notice how you need to choose which of the error types—false alarms or misses—are most important to you." 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "id": "0866be5a-8dd9-4d2a-ae79-b2b2d0ad9626", 192 | "metadata": {}, 193 | "source": [ 194 | "In the following interactive example, we will now set a constant threshold where: (i) data values above are declared detections, (ii) data values below are labelled as no detection. Try moving the *Threshold* around to see how close you can get to a probability of detection of $P_D = 1$ and a probability of false alarm of $P_{FA} = 0$. Additionally, we can change the *SNR* and then create a new set of data using the *New* button. Results are colored the same way as the last interactive example." 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "id": "1e0c86fd", 201 | "metadata": { 202 | "jupyter": { 203 | "source_hidden": true 204 | }, 205 | "tags": [] 206 | }, 207 | "outputs": [], 208 | "source": [ 209 | "ex.ex_3_2_4()" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "id": "a8963bce-648e-40c3-a756-76aa2008aea8", 215 | "metadata": {}, 216 | "source": [ 217 | "We can notice that at high SNR values ($20-30~\\mathrm{dB}$), finding a good threshold value is easy as there is a large separation between the echo energies and the noise energies. As the SNR decreases, however, there is usually no way to achieve a perfect probability of detection and probability of false alarm; in fact, we have to design the threshold to tradeoff a number of false alarms for a number of missed detections. This is the typical trade encountered when designing detectors, and it summarized using a **receiver operating characteristic** (ROC) curve.\n", 218 | "\n", 219 | "A ROC curve shows how $P_D$ and $P_{FA}$ evolve as the threshold is changed. In the interactive example below, we can see a ROC curve[2](#foot_roc) with a variable SNR." 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "id": "98cfc901", 226 | "metadata": { 227 | "jupyter": { 228 | "source_hidden": true 229 | }, 230 | "tags": [] 231 | }, 232 | "outputs": [], 233 | "source": [ 234 | "ex.ex_3_2_5()" 235 | ] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "id": "1b8714ae-ee90-4d11-96db-e5c639a9f1da", 240 | "metadata": {}, 241 | "source": [ 242 | "Each point on the ROC curve corresponds to a unique threshold value; thus, this curve tells us what probability of detection, $P_{D}$ , can be obtained at a given probability of false alarm, $P_{FA}$. As the SNR increases, the $P_D$ rises more sharply with increasing $P_{FA}$ meaning that we can get better $P_D$ for the same amount of false alarms with a lower SNR. A perfect detector will $P_D = 1$ for all values of $P_{FA}$ , and a random detector (worst case; uses no information from the data) follows the **black** dashed line." 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "id": "89a987ee-2976-4df6-8aee-15bb7df7aceb", 248 | "metadata": {}, 249 | "source": [ 250 | "***" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "id": "44f758a0-7277-4f88-8efc-a478ed035f32", 256 | "metadata": {}, 257 | "source": [ 258 | "### Question 1" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "id": "58c65cc2-8fd0-40ae-ab16-eda1110a12ab", 264 | "metadata": {}, 265 | "source": [ 266 | "**(a)** What $P_D$ is obtained when $P_{FA} = 0.1$ and the SNR is $\\mathrm{SNR} = 5~\\mathrm{dB}$?" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": null, 272 | "id": "3723620f-7028-465b-8282-e19fa6ed528f", 273 | "metadata": { 274 | "jupyter": { 275 | "source_hidden": true 276 | }, 277 | "tags": [] 278 | }, 279 | "outputs": [], 280 | "source": [ 281 | "qz.quiz_3_2_1a()" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": null, 287 | "id": "ce528615-a54c-4b30-9c23-c47e68a040f4", 288 | "metadata": {}, 289 | "outputs": [], 290 | "source": [ 291 | "# Scratch space" 292 | ] 293 | }, 294 | { 295 | "cell_type": "markdown", 296 | "id": "b64c403f-d392-4c92-b593-7e4cd1ba6cc8", 297 | "metadata": {}, 298 | "source": [ 299 | "**(b)** What $P_{FA}$ is required to achieve a $P_{D} = 0.9$ and the SNR is $\\mathrm{SNR} = 7~\\mathrm{dB}$?" 300 | ] 301 | }, 302 | { 303 | "cell_type": "code", 304 | "execution_count": null, 305 | "id": "a8f9ac3f-8b03-4dcf-8197-f2899e97d851", 306 | "metadata": { 307 | "jupyter": { 308 | "source_hidden": true 309 | }, 310 | "tags": [] 311 | }, 312 | "outputs": [], 313 | "source": [ 314 | "qz.quiz_3_2_1b()" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": null, 320 | "id": "b8a00f69-362e-4983-9d71-0c932a3b7ff2", 321 | "metadata": {}, 322 | "outputs": [], 323 | "source": [ 324 | "# Scratch space" 325 | ] 326 | }, 327 | { 328 | "cell_type": "markdown", 329 | "id": "0c70d5a8-529c-4ddb-b2bd-41948912bd8f", 330 | "metadata": {}, 331 | "source": [ 332 | "***" 333 | ] 334 | }, 335 | { 336 | "cell_type": "markdown", 337 | "id": "ee9eb949-f4eb-465f-95c8-dfcf9a8fdc1c", 338 | "metadata": {}, 339 | "source": [ 340 | "Note that the detection analysis we have done so far assumes that we know something about the average noise energy, $\\mathcal{E}_n$ , which affects the SNR. In real systems, this value may vary depend due to many environmental variables, e.g., outside temperature. There is a commonly-used detector class called **constant false-alarm rate** (CFAR) detectors that continuously estimates average noise energy to ensure that the threshold is being adapted to the current noise characteristics." 341 | ] 342 | }, 343 | { 344 | "cell_type": "markdown", 345 | "id": "47cc9a7d-cbc7-4f8d-af27-50c13ab34524", 346 | "metadata": {}, 347 | "source": [ 348 | "## Robby Revisited" 349 | ] 350 | }, 351 | { 352 | "cell_type": "markdown", 353 | "id": "02500274-c1e8-43ee-a96e-5c0429052f4b", 354 | "metadata": {}, 355 | "source": [ 356 | "To finish this lab, we will revisit Robby the radar system, except now we can create detections by selecting a constant threshold. In the interactive example below, detections are shown as white dots overlayed on top of the radar display. Try changing the *Threshold* to see how the number of detections and false alarms evolves." 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": null, 362 | "id": "4dd013e3-c893-454b-9853-d892f261321c", 363 | "metadata": { 364 | "jupyter": { 365 | "source_hidden": true 366 | }, 367 | "tags": [] 368 | }, 369 | "outputs": [], 370 | "source": [ 371 | "ex.ex_3_2_6()" 372 | ] 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "id": "6e9a0057-1797-4264-a5b9-5cb3ca6518a5", 377 | "metadata": {}, 378 | "source": [ 379 | "In [Lab 4.2: Target Tracking](4_2_Target_Tracking.ipynb), we will see the importance of properly balancing detections and false alarms." 380 | ] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "id": "0432fce1-63f1-4538-bd02-254d857a1052", 385 | "metadata": {}, 386 | "source": [ 387 | "## Summary" 388 | ] 389 | }, 390 | { 391 | "cell_type": "markdown", 392 | "id": "de489a6e-e94a-4f80-b424-86d9525a41ee", 393 | "metadata": {}, 394 | "source": [ 395 | "In this lab, we discussed how SNR relates to the relative detectability of a target echo. Further, the field of creating detection rules, or detectors, is known as detection theory. In this, we make formal definitions of success and failure, e.g., detection, false alarm, miss, and use these to design a constant threshold detector. For most applications, a tradeoff is encountered between detections and false alarms; this tradeoff is best illustrated using a receiver operating characteristic (ROC) curve. Finally, we looked the impact of a constant threshold detector on the output of Robby." 396 | ] 397 | }, 398 | { 399 | "cell_type": "markdown", 400 | "id": "6e5ec71c-5ed3-4a68-9b04-0a9d280e0421", 401 | "metadata": {}, 402 | "source": [ 403 | "## Footnotes" 404 | ] 405 | }, 406 | { 407 | "cell_type": "markdown", 408 | "id": "9b8ed30d-cfc8-4cac-973f-0c33861cd4ea", 409 | "metadata": {}, 410 | "source": [ 411 | "1Probability of miss is simply $1 - P_D$ and probability of correct rejection is $1 - P_{FA}$.\n", 412 | "\n", 413 | "2This ROC curve assumes that the system noise follows a Gaussian distribution. See Section 3.4 of [[1]](#ref_key)." 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "id": "ae818476-4b33-4439-9f39-b37385cef92b", 419 | "metadata": {}, 420 | "source": [ 421 | "## References" 422 | ] 423 | }, 424 | { 425 | "cell_type": "markdown", 426 | "id": "b771ba72-0c9f-43f4-a81a-9b1eef36894e", 427 | "metadata": {}, 428 | "source": [ 429 | "[1] S. M. Kay, *Fundamentals of Statistical Signal Processing: Detection Theory*. Prentice Hall,1993" 430 | ] 431 | } 432 | ], 433 | "metadata": { 434 | "kernelspec": { 435 | "display_name": "Python 3 (ipykernel)", 436 | "language": "python", 437 | "name": "python3" 438 | }, 439 | "language_info": { 440 | "codemirror_mode": { 441 | "name": "ipython", 442 | "version": 3 443 | }, 444 | "file_extension": ".py", 445 | "mimetype": "text/x-python", 446 | "name": "python", 447 | "nbconvert_exporter": "python", 448 | "pygments_lexer": "ipython3", 449 | "version": "3.10.2" 450 | } 451 | }, 452 | "nbformat": 4, 453 | "nbformat_minor": 5 454 | } 455 | -------------------------------------------------------------------------------- /4_2_Target_Tracking.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6f991635-92a3-4b97-81e8-14548f344d33", 6 | "metadata": {}, 7 | "source": [ 8 | "# Lab 4.2: Target Tracking" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "c48d54ac-c642-48f1-9f94-677eb425fe69", 15 | "metadata": { 16 | "jupyter": { 17 | "source_hidden": true 18 | }, 19 | "tags": [] 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "%matplotlib widget\n", 24 | "import rad.css as css\n", 25 | "import rad.example as ex\n", 26 | "import rad.quiz as qz\n", 27 | "from rad.const import c, k\n", 28 | "from rad.radar import to_db, from_db, deg2rad, rad2deg\n", 29 | "from math import sqrt, sin, asin, cos, acos, tan, atan2, pi, log, log10\n", 30 | "css.add_custom_css()" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "id": "caae829c-c1c0-44a5-91ed-5caeabb3a169", 36 | "metadata": {}, 37 | "source": [ 38 | "**Reminders**: \n", 39 | "\n", 40 | "- Hit the *Run All* button button above before continuing\n", 41 | "- Useful formulae and definitions are available in [Reference](Reference.ipynb)" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "28d31b20-dd2d-43e5-b664-f1bc6865db04", 47 | "metadata": {}, 48 | "source": [ 49 | "In the previous labs, we have looked at how radar systems generate and transmit pulses, receive incoming signals, detect target echoes, and extract target attribute information from a detection. In this lab, we will begin looking at what to do now that we are getting a steady stream of detections from the sensor; more specifically, we will discuss the process subsequent to detection: *tracking*." 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "id": "43c8b1e5-a26c-4263-86dd-70fbc07f5f2d", 55 | "metadata": {}, 56 | "source": [ 57 | "
" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "id": "7053ebd4-f6ec-402a-8bac-2c8d3b6d5cfc", 63 | "metadata": { 64 | "tags": [] 65 | }, 66 | "source": [ 67 | "Every time a radar transmits and receives (which can be up to thousands of times per second), it will generate detections. These detections will ideally be created from target echoes, but some portion will be false alarms. In the interactive plot below, we can see the raw detections created by a rotating dish radar system (similar to air traffic control radars). In the scene, there is one moving target. As you proceed through the scans, you can watch the target move across the radar display." 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "id": "a3c5c60f-0da8-40a2-aa5c-0d056ee9c165", 74 | "metadata": { 75 | "jupyter": { 76 | "source_hidden": true 77 | }, 78 | "tags": [] 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "ex.ex_4_2_1()" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "id": "2d1ccacb-5340-4f7a-b79c-7940bc423818", 88 | "metadata": {}, 89 | "source": [ 90 | "An important observation from the example above is that detections from the target follow a continuous path across the radar field of view, whereas false alarms are randomly distributed throughout. This is one of the key ideas that is used to build a **multitarget tracker**, which collects sets of detections over time that are designated to be true targets—these are called *tracks*. Let us now look more formally at a track and how one is formed." 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "id": "e27ae769-8069-445b-8851-4d65d20bea79", 96 | "metadata": {}, 97 | "source": [ 98 | "## Tracks" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "id": "1a31523e-b82b-437f-9fb9-b32125566a4a", 104 | "metadata": {}, 105 | "source": [ 106 | "As mentioned above, a **track** is a history of detections thought to belong to a true target. For each new set of received detections, the radar has to decide how to assign the new detections with existing tracks; this process is known as **data association**." 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "id": "f0a074d7-d1e7-41e4-ba3c-2e143a8352c8", 112 | "metadata": {}, 113 | "source": [ 114 | "
" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "id": "131d264f-263a-417b-93f4-cce378ff3054", 120 | "metadata": {}, 121 | "source": [ 122 | "A tracker will not only string together detections, it will also estimate the motion of the target (e.g., position, velocity); this is known as **state estimation**. A target **state** describes its motion at a specific point in time: it usually consists of at least position and velocity but may also include other motion variables like acceleration. An estimate of the target state with an accompanying quantification of uncertainty is known as a **state estimate**. " 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "id": "7c09b632-a2fc-4438-8e62-540e0c88c71e", 128 | "metadata": {}, 129 | "source": [ 130 | "
" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "id": "1c2cbf23-ff52-4265-be7f-178b4103ab2c", 136 | "metadata": {}, 137 | "source": [ 138 | "Knowledge of a target's motion is critical for the radar to be able to decide what regions of space to interrogate next. Typically, it would like to get another observation of the target, and it needs to know where it will be next." 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "id": "01dd7086-d8b5-4aa9-bbff-80e76074d26a", 144 | "metadata": {}, 145 | "source": [ 146 | "Thus, the three main objectives of a multitarget tracker are:\n", 147 | "\n", 148 | "1. Designate sets of detections as targets while rejecting false alarms\n", 149 | "2. Estimate target motion\n", 150 | "3. Inform radar control for further observations\n", 151 | "\n", 152 | "In this lab, we will take a closer look at the first two: data association and state estimation." 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "id": "8029afc5-74fa-42fa-862e-262f4503e574", 158 | "metadata": { 159 | "tags": [] 160 | }, 161 | "source": [ 162 | "## Multitarget Tracker Structure" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "b9b20c14-8afa-4b35-86f5-59c7d84d495b", 168 | "metadata": {}, 169 | "source": [ 170 | "Multitarget trackers (MTT) tend to run cyclically, updating the current tracks every time new detections are gathered. The main loop for a MTT can be seen in the following figure:" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "id": "c32c6b08-3eab-4ff6-809d-1e35c9df1613", 176 | "metadata": {}, 177 | "source": [ 178 | "
" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "id": "ce2c7516-3c3f-4815-84d3-4c4ad5e3d9cd", 184 | "metadata": {}, 185 | "source": [ 186 | "Let us assume that the last tracker cycle took place at time $t_0$, and we are now getting a new set of detections at time $t$. As the new detections arrive, the first step taken is called **track prediction** (also called *propagation*), which predicts where the tracked targets would be at time $t$. This is done by passing the previous state estimates (last updated at $t_0$) through a model of target motion (often called a *dynamic model*)." 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "id": "e60e38d1-62fc-445c-8e6a-14da132f9b0c", 192 | "metadata": {}, 193 | "source": [ 194 | "
" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "id": "9bacb63f-a88d-44a4-a479-b9669b65f8f7", 200 | "metadata": {}, 201 | "source": [ 202 | "In the following interactive example, we look at track prediction using different dynamic models. In the example, we can select the *Initial Range* and *Initial Altitude* of a target (shown as a **red** dot), along with their corresponding rates. Further, we can choose a dynamic model to use for prediction: *Gravity* or *No Gravity*. Try changing initial states and using different dynamic models (to change dynamic model, you will need to reset the animation using the *Stop* button)." 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "id": "d9d02b69-1ded-4efa-aee0-88ef32644d86", 209 | "metadata": { 210 | "jupyter": { 211 | "source_hidden": true 212 | }, 213 | "tags": [] 214 | }, 215 | "outputs": [], 216 | "source": [ 217 | "ex.ex_4_2_2()" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "id": "71089980-bd86-49bc-af86-c5db448d4b56", 223 | "metadata": { 224 | "tags": [] 225 | }, 226 | "source": [ 227 | "The next step is to match the new detections with the current set of tracks, so the tracks can be updated. As mentioned above, this step (i.e., mapping detections to tracks) is called **data association**. For scenes with lots of targets and/or false alarms, this step can be difficult. More specifically, it can lead to situations like the figure below, which shows the confusion incurred by densely packed detections near the path of a track." 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "id": "57f88fa9-f87b-4195-b5b9-1b520381920e", 233 | "metadata": {}, 234 | "source": [ 235 | "
" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "id": "0e19dc6a-5849-4571-a03b-630230df9b69", 241 | "metadata": {}, 242 | "source": [ 243 | "Now that we know which detection goes with which track, we update the state estimates for each track using a **state estimation** algorithm. In short, this fuses the detection information (with its uncertainty information) with the current track state estimates." 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "id": "0ec3fa09-682d-4d75-a297-7b19983ef9dc", 249 | "metadata": {}, 250 | "source": [ 251 | "
" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "id": "81c6a075-ac1f-4ad0-99af-676443a633e6", 257 | "metadata": {}, 258 | "source": [ 259 | "Finally, we go through **track initiation and maintenance**, which decides when to start new tracks and when to end stale tracks. This part of the multitarget tracker loop is very expert-driven, and its logic will vary depending on application." 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "id": "b017cd39-c6b7-490c-ad8d-a41b53860c4b", 265 | "metadata": {}, 266 | "source": [ 267 | "
" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "id": "e9f989d9-8d93-4b2e-a7ea-01f08b1106df", 273 | "metadata": {}, 274 | "source": [ 275 | "In the following, we will study data association and state estimation in more detail." 276 | ] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "id": "0c149712-ce2f-4942-ace2-5a5fb12b2bff", 281 | "metadata": {}, 282 | "source": [ 283 | "## Data Association" 284 | ] 285 | }, 286 | { 287 | "cell_type": "markdown", 288 | "id": "0e37263d-4d16-4be7-a748-7b0e94bd5bf2", 289 | "metadata": {}, 290 | "source": [ 291 | "The main objective of data association is to match new detections with existing tracks; the underlying principle is *the closer a detection is to a predicted target state, the more likely it came from that target*. In the figure below, we can see that there are two new detections along with the predicted track state. The detection close to the predicted position is *highly likely to have originated from the target*; the other detection has a low likelihood of being a target detection." 292 | ] 293 | }, 294 | { 295 | "cell_type": "markdown", 296 | "id": "553fe141-8cae-4aa7-95c5-f0181874d2fc", 297 | "metadata": {}, 298 | "source": [ 299 | "
" 300 | ] 301 | }, 302 | { 303 | "cell_type": "markdown", 304 | "id": "42ec8687-5802-4317-b454-4248e005707f", 305 | "metadata": {}, 306 | "source": [ 307 | "Things are often not as simple as the figure above: commonly there will be multiple likely detections near a predicted track state or there could be *no* nearby detections. To help solve these issues, there are many data association algorithms developed in the literature. Here are a few:\n", 308 | "\n", 309 | "- **Nearest neighbor**: Greedily associate the nearest detection to each track\n", 310 | " - *Pro*: Extremely simple and fast\n", 311 | " - *Con*: Poor performance in moderately complex scenes\n", 312 | "- **Global nearest neighbor**: Find the best way to associate all new detections to tracks to minimize a total distance metric\n", 313 | " - *Pro*: Simple, fast\n", 314 | " - *Con*: Performance degradation in highly complex scenes\n", 315 | "- **Multihypothesis tracking**: Perform data association over short history of detections, finding sets of detections to associate to a track\n", 316 | " - *Pro*: Can be resilient in highly complex scenes\n", 317 | " - *Con*: Complex, requires appreciable tuning\n", 318 | "\n", 319 | "In the interactive example below, you can see how nearest neighbor and global nearest neighbor associations compare for different scenes. We can vary the number of *Tracks* and *Detections* to be used for consideration. Additionally, we can change the *Track Accuracy* and *Detection Accuracy*; these are illustrated using uncertainty ellipses around the tracks and detections. An uncertainty ellipse shows the area in which the target is confidently known to exist. Finally, we can see how the tracks and detections are associated using the *Nearest Neighbor* and *Global Nearest Neighbor* algorithms. Associations are shown as **black** lines between associated tracks and detections. To change parameters and obtain a new scene, click the *New* button." 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": null, 325 | "id": "2e80cc21-49c2-4ddc-8dc5-398247aa84a8", 326 | "metadata": { 327 | "jupyter": { 328 | "source_hidden": true 329 | }, 330 | "tags": [] 331 | }, 332 | "outputs": [], 333 | "source": [ 334 | "ex.ex_4_2_3()" 335 | ] 336 | }, 337 | { 338 | "cell_type": "markdown", 339 | "id": "eb85eee2-e085-4b73-8595-664c625616cd", 340 | "metadata": {}, 341 | "source": [ 342 | "For an equal number of tracks and detections with good accuracies, the problem can usually be solved quickly by eye; we can notice, however, that as the number of detection versus tracks grow and/or the accuracies degrade, the association problem is not trivial. This is another reason why initial suppression of false alarms is very helpful for a multitarget tracker." 343 | ] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "id": "7b4ab014-7951-4578-8411-c643dd2f4586", 348 | "metadata": {}, 349 | "source": [ 350 | "Now, let us assume that we have associated new detections with our current tracks. Next, we should update our state estimates." 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "id": "35781989-15fd-488c-804e-e829fcd174c3", 356 | "metadata": {}, 357 | "source": [ 358 | "## State Estimation" 359 | ] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "id": "1069f4bb-669f-4eaf-8c85-a5ebc6b91bdb", 364 | "metadata": {}, 365 | "source": [ 366 | "The goal of state estimation is to create an estimate of a target state using a set of detections, where each detection is seen as a measurement of target position (i.e., range, angle) with known accuracies (discussed in [Lab 4.1: Target Parameter Estimation](4_1_Target_Parameter_Estimation.ipynb)). The general flow of a state estimator is given in the following figure:" 367 | ] 368 | }, 369 | { 370 | "cell_type": "markdown", 371 | "id": "60cd8815-de13-4cd5-9baf-eea07024c737", 372 | "metadata": {}, 373 | "source": [ 374 | "
" 375 | ] 376 | }, 377 | { 378 | "cell_type": "markdown", 379 | "id": "757be6fe-c1f7-4740-9e3e-cae42d03f0ec", 380 | "metadata": {}, 381 | "source": [ 382 | "The state estimation process iteratively updates using a weighted sum of its current estimate with the incoming detection. The weights are chosen based on the type of expected target motion and the specific state estimation algorithm being used. The following are some common state estimation algorithms:\n", 383 | "\n", 384 | "- **Alpha-beta filters**: Weights are chosen once and fixed for all operation\n", 385 | " - *Pro*: Extremely simple and fast\n", 386 | " - *Con*: Poor performance except for very simple target motion\n", 387 | "- **Kalman and extended Kalman filters**: Weights are chosen at each update based on relative uncertainty of current estimate versus detection\n", 388 | " - *Pro*: Simple and fast, works well with many target motions\n", 389 | " - *Con*: Exhibits difficulty with highly complex target motion" 390 | ] 391 | }, 392 | { 393 | "cell_type": "markdown", 394 | "id": "ad08e5cd-d956-4a1a-b8a0-0dc0da2819f9", 395 | "metadata": {}, 396 | "source": [ 397 | "The main workhorse for many state estimation processes is the ubiquitous Kalman filter[[1]](#ref_barshalom) due to its ease of implementation and flexibility to adapt to many different problems. The extended Kalman filter[[1]](#ref_barshalom) (EKF) is a variant of the Kalman filter that allows for target motion models and measurement models that cannot be described as linear functions. We will not delve into the details of state estimation in this course, but it suffices to say that an EKF is generally required for most radar tracking algorithms." 398 | ] 399 | }, 400 | { 401 | "cell_type": "markdown", 402 | "id": "c3f2bb7f-43a8-465a-ac54-f988bde730a1", 403 | "metadata": {}, 404 | "source": [ 405 | "In the interactive example below, we can see the output of an EKF over time as it is being fed new detections. More specifically, we are tracking a low-flying target starting due East (estimates will be in the East and North dimensions) and taking radar measurements of the target once every second. The plotted output shows the error of the state estimate as a function of time (in solid **red**) and the uncertainty bounds on the estimate (in dashed **red**). Try changing the radar parameters to see how the detection accuracies change and, subsequently, the state estimates." 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": null, 411 | "id": "de187518-fd83-4683-a940-e5035177fd95", 412 | "metadata": { 413 | "jupyter": { 414 | "source_hidden": true 415 | }, 416 | "tags": [] 417 | }, 418 | "outputs": [], 419 | "source": [ 420 | "ex.ex_4_2_4()" 421 | ] 422 | }, 423 | { 424 | "cell_type": "markdown", 425 | "id": "df531b0d-5d2e-489e-ad29-f90dc020a1ac", 426 | "metadata": {}, 427 | "source": [ 428 | "We can see a few interesting trends:\n", 429 | "\n", 430 | "- The state estimate accuracy improves over time; this is called the **convergence** of a state estimator. This makes intuitive sense as we are getting more information as we observe the target for more time.\n", 431 | "\n", 432 | "- There are many different knobs that affect track accuracy, e.g., dish radius affects: (i) beamwidth, which affects angle measurement accuracy, and (ii) SNR, which affects all parameter estimate accuracies. The more accurate we can make the estimates of range, angle, range rate, etc., the tighter the uncertainty bounds around a track.\n", 433 | "\n", 434 | "- The error in the East dimension is smaller than the error in the North dimension. This is because the target starts due East and, thus, East aligns with our range dimension. Since North is perpendicular to East, it align with our cross-range dimension. Most radar systems (this example included) have much greater accuracy in the range dimension than in the cross-range dimension." 435 | ] 436 | }, 437 | { 438 | "cell_type": "markdown", 439 | "id": "349d948a-01e2-44d3-8c0d-8aab57605521", 440 | "metadata": {}, 441 | "source": [ 442 | "## Tracking" 443 | ] 444 | }, 445 | { 446 | "cell_type": "markdown", 447 | "id": "4d63052d-19de-4952-bbc8-7634e969b529", 448 | "metadata": {}, 449 | "source": [ 450 | "To finish this lab, we will return to Robby to get a feel for the amount of work a multitarget tracker saves a radar operator. In the following interactive example, we will see the output of Robby as multiple targets enter and leave. Press the *Scan* button periodically and imagine trying to track the targets by hand; keep in mind what radar parameters would work well. In [Lab 5.1: Radar Design Revisited](5_1_Radar_Design_Revisited.ipynb), we will do hand tracking with a personally designed radar. " 451 | ] 452 | }, 453 | { 454 | "cell_type": "code", 455 | "execution_count": null, 456 | "id": "4fba561f-eff8-4f8b-810a-266ccdfb2d78", 457 | "metadata": { 458 | "jupyter": { 459 | "source_hidden": true 460 | }, 461 | "tags": [] 462 | }, 463 | "outputs": [], 464 | "source": [ 465 | "ex.ex_4_2_5()" 466 | ] 467 | }, 468 | { 469 | "cell_type": "markdown", 470 | "id": "45ad4f78-1afd-4863-885d-994014bfbc28", 471 | "metadata": {}, 472 | "source": [ 473 | " ## Summary" 474 | ] 475 | }, 476 | { 477 | "cell_type": "markdown", 478 | "id": "e780dbdd-cbc8-4e6f-a6f4-2134eee08fc1", 479 | "metadata": {}, 480 | "source": [ 481 | "In this lab, we studied the subsequent process to detection: multitarget tracking. In this step of the radar processing chain, detections are strung together into object tracks and their states (i.e., summaries of their motion) are estimated. The multitarget tracker is an important part of the radar system as it eliminates false alarms and gives important target position information to the radar control system to allow for further observations of a target. A multitarget tracker typically relies on algorithms for data association (i.e., mapping of new detections to existing tracks) and state estimation (i.e., calculation of estimate of target state from collection of detections). " 482 | ] 483 | }, 484 | { 485 | "cell_type": "markdown", 486 | "id": "b3824ac6-2d07-4ba3-ae50-fad72b9d2284", 487 | "metadata": {}, 488 | "source": [ 489 | "## Footnotes" 490 | ] 491 | }, 492 | { 493 | "cell_type": "markdown", 494 | "id": "de9ae407-9cc2-4b0b-afc2-8fee6d034132", 495 | "metadata": {}, 496 | "source": [ 497 | "n/a" 498 | ] 499 | }, 500 | { 501 | "cell_type": "markdown", 502 | "id": "2a8e2e51-f024-4943-9417-0d5662397a79", 503 | "metadata": {}, 504 | "source": [ 505 | "## References" 506 | ] 507 | }, 508 | { 509 | "cell_type": "markdown", 510 | "id": "6f373949-9b9d-49cf-b50c-3f08a96c5762", 511 | "metadata": {}, 512 | "source": [ 513 | "[1] Y. Bar-Shalom, X. Li, and T. Kirubarajan, *Estimation with Applications to Tracking and Navigation*.\n", 514 | "John Wiley & Sons, Inc., 2001." 515 | ] 516 | } 517 | ], 518 | "metadata": { 519 | "kernelspec": { 520 | "display_name": "Python 3 (ipykernel)", 521 | "language": "python", 522 | "name": "python3" 523 | }, 524 | "language_info": { 525 | "codemirror_mode": { 526 | "name": "ipython", 527 | "version": 3 528 | }, 529 | "file_extension": ".py", 530 | "mimetype": "text/x-python", 531 | "name": "python", 532 | "nbconvert_exporter": "python", 533 | "pygments_lexer": "ipython3", 534 | "version": "3.10.2" 535 | } 536 | }, 537 | "nbformat": 4, 538 | "nbformat_minor": 5 539 | } 540 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LL Introduction to Radar Course 2 | Copyright (C) 2021 Zachary Chance 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Welcome to the course material for the MIT Lincoln Laboratory *Introduction to Radar* class. The main lectures come in the form of Jupyter notebooks with interactive elements. The easiest way to try out the course is to use the notebooks hosted by [Binder](https://mybinder.org): simply click the launch button below. 4 | 5 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/mit-ll/radar-intro/HEAD) 6 | 7 | # Local Installation 8 | 9 | If you would like to keep a working copy of all notebooks and experience the smoothest animations, a local installation is probably the best choice. This can be done quickly by installing Python, creating a virtual environment, and installing the necessary dependencies. Below you will find step-by-step directions for different operating systems. 10 | 11 | ## Windows 12 | 13 | ### Download Course 14 | 15 | You can download the course as a ZIP file [here](https://github.com/mit-ll/radar-intro/archive/refs/heads/master.zip). After downloading the ZIP archive, extract it to a desired path (referred to later as ``). 16 | 17 | ### JupyterLab Installation 18 | 19 | If you have not, first install Python on your system; you can download the Python installer from [here](https://www.python.org/downloads/). 20 | 21 | Next, we will have to make a virtual environment to run JupyterLab and the radar course. A *virtual environment* is a minimal, isolated copy of Python that will keep the lab software from interfering with your other Python projects; you can read more about them [here](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment). 22 | 23 | To create our virtual environment, first create a folder of your choice, which will now be referred to as ``. After creating the directory, open a command prompt and run: 24 | 25 | cd 26 | py -m venv . 27 | 28 | This will construct the Python virtual environment in this directory. Next, we will activate the virtual environment. Staying in the `` directory, run: 29 | 30 | .\Scripts\activate 31 | 32 | The command prompt should now show that you are in your virtual environment, i.e., if the virtual environment directory was `c:\test`, the command prompt should now look like 33 | 34 | (test) c:\test> 35 | 36 | Next, we will install the necessary prerequisite packages. To do this, we move to radar course directory and use the Python installation routine `pip` by typing: 37 | 38 | cd 39 | pip install -r requirements.txt 40 | 41 | **Note**: If you are behind a proxy server at ``, you will have to run `pip` using: 42 | 43 | pip install --proxy -r requirements.txt 44 | 45 | ### Radar Course 46 | 47 | To start the labs, we first start with a new terminal/command prompt and activating the virtual environment by typing: 48 | 49 | cd 50 | .\Scripts\activate 51 | 52 | Navigate to the path of the radar course material: 53 | 54 | cd 55 | 56 | Lastly, we can start JupyterLab in the current location: 57 | 58 | jupyter lab 59 | 60 | 61 | ## macOS 62 | 63 | ### Download Course 64 | 65 | You can download the course as a ZIP file [here](https://github.com/mit-ll/radar-intro/archive/refs/heads/master.zip). After downloading the ZIP archive, extract it to a desired path (referred to later as ``). 66 | 67 | ### JupyterLab Installation 68 | 69 | If you have not, first install Python on your system; you can download the Python installer from [here](https://www.python.org/downloads/). 70 | 71 | Next, we will have to make a virtual environment to run JupyterLab and the radar course. A *virtual environment* is a minimal, isolated copy of Python that will keep the lab software from interfering with your other Python projects; you can read more about them [here](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment). 72 | 73 | To create our virtual environment, first create a folder of your choice, which will now be referred to as ``. After creating the directory, open a command prompt and run: 74 | 75 | cd 76 | python3 -m venv . 77 | 78 | This will construct the Python virtual environment in this directory. Next, we will activate the virtual environment. Staying in the `` directory, run: 79 | 80 | source ./bin/activate 81 | 82 | The terminal should now show that you are in your virtual environment, i.e., if the virtual environment directory was `~/test`, the command prompt should now look like 83 | 84 | (test) user@machine:~/test$ 85 | 86 | Next, we will install the necessary prerequisite packages. To do this, we move to radar course directory and use the Python installation routine `pip` by typing: 87 | 88 | cd 89 | pip install -r requirements.txt 90 | 91 | **Note**: If you are behind a proxy server at ``, you will have to run `pip` using: 92 | 93 | pip install --proxy -r requirements.txt 94 | 95 | ### Radar Course 96 | 97 | To start the labs, we first start with a new terminal/command prompt and type: 98 | 99 | cd 100 | source ./bin/activate 101 | 102 | Navigate to the path of the radar course material: 103 | 104 | cd 105 | 106 | Lastly, we can start JupyterLab in the current location: 107 | 108 | jupyter lab 109 | 110 | ## Ubuntu 111 | 112 | ### Download Course 113 | 114 | You can download the course as a ZIP file [here](https://github.com/mit-ll/radar-intro/archive/refs/heads/master.zip). After downloading the ZIP archive, extract it to a desired path (referred to later as ``). 115 | 116 | ### JupyterLab Installation 117 | 118 | If you have not, first install Python on your system. This can be done using `apt` by: 119 | 120 | sudo apt update 121 | sudo apt install python3 python3-venv 122 | 123 | Next, we will have to make a virtual environment to run JupyterLab and the radar course. A *virtual environment* is a minimal, isolated copy of Python that will keep the lab software from interfering with your other Python projects; you can read more about them [here](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment). 124 | 125 | To create our virtual environment, first create a folder of your choice, which will now be referred to as ``. After creating the directory, open a command prompt and run: 126 | 127 | cd 128 | python3 -m venv . 129 | 130 | This will construct the Python virtual environment in this directory. Next, we will activate the virtual environment. Staying in the `` directory, run: 131 | 132 | source ./bin/activate 133 | 134 | The terminal should now show that you are in your virtual environment, i.e., if the virtual environment directory was `~/test`, the command prompt should now look like 135 | 136 | (test) user@machine:~/test$ 137 | 138 | Next, we will install the necessary prerequisite packages. To do this, we move to radar course directory and use the Python installation routine `pip` by typing: 139 | 140 | cd 141 | pip install -r requirements.txt 142 | 143 | **Note**: If you are behind a proxy server at ``, you will have to run `pip` using: 144 | 145 | pip install --proxy -r requirements.txt 146 | 147 | ### Radar Course 148 | 149 | To start the labs, we first start with a new terminal/command prompt and type: 150 | 151 | cd 152 | source ./bin/activate 153 | 154 | Navigate to the path of the radar course material: 155 | 156 | cd 157 | 158 | Lastly, we can start JupyterLab in the current location: 159 | 160 | jupyter lab 161 | 162 | # Authors 163 | 164 | Zachary Chance, Robert Freking, Victoria Helus 165 | 166 | MIT Lincoln Laboratory 167 | Lexington, MA 02421 168 | 169 | # Distribution Statement 170 | 171 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 172 | 173 | This material is based upon work supported by the United States Air Force under Air Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the United States Air Force. 174 | 175 | © 2022 Massachusetts Institute of Technology. 176 | 177 | The software/firmware is provided to you on an As-Is basis 178 | 179 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. Government rights in this work are defined by DFARS 252.227-7013 or DFARS 252.227-7014 as detailed above. Use of this work other than as specifically authorized by the U.S. Government may violate any copyrights that exist in this work. 180 | 181 | RAMS ID: 1016938 182 | -------------------------------------------------------------------------------- /img/1_1_sine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/1_1_sine.png -------------------------------------------------------------------------------- /img/2_1_dets_noise.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/2_1_dets_noise.fig -------------------------------------------------------------------------------- /img/2_1_dets_noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/2_1_dets_noise.png -------------------------------------------------------------------------------- /img/active.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/active.ai -------------------------------------------------------------------------------- /img/active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/active.png -------------------------------------------------------------------------------- /img/add_cell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/add_cell.gif -------------------------------------------------------------------------------- /img/add_cell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/add_cell.png -------------------------------------------------------------------------------- /img/angle_cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/angle_cut.png -------------------------------------------------------------------------------- /img/angle_cut.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/angle_cut.pptx -------------------------------------------------------------------------------- /img/angle_obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/angle_obs.png -------------------------------------------------------------------------------- /img/anttarget2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/anttarget2.png -------------------------------------------------------------------------------- /img/aperture.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/aperture.ai -------------------------------------------------------------------------------- /img/aperture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/aperture.png -------------------------------------------------------------------------------- /img/array_steer.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/array_steer.ai -------------------------------------------------------------------------------- /img/array_steer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/array_steer.png -------------------------------------------------------------------------------- /img/assoc_like.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/assoc_like.ai -------------------------------------------------------------------------------- /img/assoc_like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/assoc_like.png -------------------------------------------------------------------------------- /img/azimuth.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/azimuth.ai -------------------------------------------------------------------------------- /img/azimuth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/azimuth.png -------------------------------------------------------------------------------- /img/bands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/bands.png -------------------------------------------------------------------------------- /img/bkwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/bkwd.png -------------------------------------------------------------------------------- /img/circ_pol.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/circ_pol.gif -------------------------------------------------------------------------------- /img/conda_prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/conda_prompt.png -------------------------------------------------------------------------------- /img/cross_range.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/cross_range.ai -------------------------------------------------------------------------------- /img/cross_range.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/cross_range.png -------------------------------------------------------------------------------- /img/cw.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/cw.ai -------------------------------------------------------------------------------- /img/cw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/cw.png -------------------------------------------------------------------------------- /img/dark_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/dark_mode.png -------------------------------------------------------------------------------- /img/data_assoc.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/data_assoc.ai -------------------------------------------------------------------------------- /img/data_assoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/data_assoc.png -------------------------------------------------------------------------------- /img/data_assoc_prob.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/data_assoc_prob.ai -------------------------------------------------------------------------------- /img/data_assoc_prob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/data_assoc_prob.png -------------------------------------------------------------------------------- /img/db.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/db.ai -------------------------------------------------------------------------------- /img/db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/db.png -------------------------------------------------------------------------------- /img/edit_cell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/edit_cell.gif -------------------------------------------------------------------------------- /img/eval_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/eval_no.png -------------------------------------------------------------------------------- /img/ext_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/ext_button.png -------------------------------------------------------------------------------- /img/focus_steer.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/focus_steer.ai -------------------------------------------------------------------------------- /img/focus_steer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/focus_steer.png -------------------------------------------------------------------------------- /img/friis.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/friis.ai -------------------------------------------------------------------------------- /img/friis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/friis.png -------------------------------------------------------------------------------- /img/friis_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/friis_0.png -------------------------------------------------------------------------------- /img/friis_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/friis_1.png -------------------------------------------------------------------------------- /img/friis_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/friis_2.png -------------------------------------------------------------------------------- /img/fwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/fwd.png -------------------------------------------------------------------------------- /img/haystack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/haystack.jpg -------------------------------------------------------------------------------- /img/hide_code_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/hide_code_icon.png -------------------------------------------------------------------------------- /img/impulse.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/impulse.ai -------------------------------------------------------------------------------- /img/impulse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/impulse.png -------------------------------------------------------------------------------- /img/incr_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/incr_font.png -------------------------------------------------------------------------------- /img/integ.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/integ.ai -------------------------------------------------------------------------------- /img/integ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/integ.png -------------------------------------------------------------------------------- /img/mtt.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/mtt.ai -------------------------------------------------------------------------------- /img/mtt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/mtt.png -------------------------------------------------------------------------------- /img/obs.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/obs.ai -------------------------------------------------------------------------------- /img/obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/obs.png -------------------------------------------------------------------------------- /img/pan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/pan.png -------------------------------------------------------------------------------- /img/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/pause.png -------------------------------------------------------------------------------- /img/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/play.png -------------------------------------------------------------------------------- /img/power.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/power.ai -------------------------------------------------------------------------------- /img/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/power.png -------------------------------------------------------------------------------- /img/power_seg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/power_seg.png -------------------------------------------------------------------------------- /img/predict.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/predict.ai -------------------------------------------------------------------------------- /img/predict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/predict.png -------------------------------------------------------------------------------- /img/prop_delay.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/prop_delay.ai -------------------------------------------------------------------------------- /img/prop_delay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/prop_delay.png -------------------------------------------------------------------------------- /img/pulse_dopp.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/pulse_dopp.ai -------------------------------------------------------------------------------- /img/pulse_dopp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/pulse_dopp.png -------------------------------------------------------------------------------- /img/radar_range.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/radar_range.ai -------------------------------------------------------------------------------- /img/radar_range.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/radar_range.png -------------------------------------------------------------------------------- /img/radar_sys.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/radar_sys.ai -------------------------------------------------------------------------------- /img/radar_sys1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/radar_sys1.png -------------------------------------------------------------------------------- /img/radar_sys2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/radar_sys2.png -------------------------------------------------------------------------------- /img/radar_sys3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/radar_sys3.png -------------------------------------------------------------------------------- /img/radar_sys4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/radar_sys4.png -------------------------------------------------------------------------------- /img/range_obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/range_obs.png -------------------------------------------------------------------------------- /img/range_rate_obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/range_rate_obs.png -------------------------------------------------------------------------------- /img/rcs_model.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/rcs_model.ai -------------------------------------------------------------------------------- /img/rcs_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/rcs_model.png -------------------------------------------------------------------------------- /img/reset_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/reset_view.png -------------------------------------------------------------------------------- /img/run_all_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/run_all_icon.png -------------------------------------------------------------------------------- /img/run_cell_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/run_cell_icon.png -------------------------------------------------------------------------------- /img/scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/scan.png -------------------------------------------------------------------------------- /img/sinc.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sinc.ai -------------------------------------------------------------------------------- /img/sinc.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sinc.fig -------------------------------------------------------------------------------- /img/sine.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine.ai -------------------------------------------------------------------------------- /img/sine.eps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF-3.0 2 | %%Creator: (MATLAB, The Mathworks, Inc. Version 9.10.0.1649659 \(R2021a\) Update 1. Operating System: Windows 10) 3 | %%Title: C:/code/ircll/lab/img/sine.eps 4 | %%CreationDate: 2021-05-11T13:17:12 5 | %%Pages: (atend) 6 | %%BoundingBox: 0 0 675 525 7 | %%LanguageLevel: 3 8 | %%EndComments 9 | %%BeginProlog 10 | %%BeginResource: procset (Apache XML Graphics Std ProcSet) 1.2 0 11 | %%Version: 1.2 0 12 | %%Copyright: (Copyright 2001-2003,2010 The Apache Software Foundation. License terms: http://www.apache.org/licenses/LICENSE-2.0) 13 | /bd{bind def}bind def 14 | /ld{load def}bd 15 | /GR/grestore ld 16 | /GS/gsave ld 17 | /RM/rmoveto ld 18 | /C/curveto ld 19 | /t/show ld 20 | /L/lineto ld 21 | /ML/setmiterlimit ld 22 | /CT/concat ld 23 | /f/fill ld 24 | /N/newpath ld 25 | /S/stroke ld 26 | /CC/setcmykcolor ld 27 | /A/ashow ld 28 | /cp/closepath ld 29 | /RC/setrgbcolor ld 30 | /LJ/setlinejoin ld 31 | /GC/setgray ld 32 | /LW/setlinewidth ld 33 | /M/moveto ld 34 | /re {4 2 roll M 35 | 1 index 0 rlineto 36 | 0 exch rlineto 37 | neg 0 rlineto 38 | cp } bd 39 | /_ctm matrix def 40 | /_tm matrix def 41 | /BT { _ctm currentmatrix pop matrix _tm copy pop 0 0 moveto } bd 42 | /ET { _ctm setmatrix } bd 43 | /iTm { _ctm setmatrix _tm concat } bd 44 | /Tm { _tm astore pop iTm 0 0 moveto } bd 45 | /ux 0.0 def 46 | /uy 0.0 def 47 | /F { 48 | /Tp exch def 49 | /Tf exch def 50 | Tf findfont Tp scalefont setfont 51 | /cf Tf def /cs Tp def 52 | } bd 53 | /ULS {currentpoint /uy exch def /ux exch def} bd 54 | /ULE { 55 | /Tcx currentpoint pop def 56 | gsave 57 | newpath 58 | cf findfont cs scalefont dup 59 | /FontMatrix get 0 get /Ts exch def /FontInfo get dup 60 | /UnderlinePosition get Ts mul /To exch def 61 | /UnderlineThickness get Ts mul /Tt exch def 62 | ux uy To add moveto Tcx uy To add lineto 63 | Tt setlinewidth stroke 64 | grestore 65 | } bd 66 | /OLE { 67 | /Tcx currentpoint pop def 68 | gsave 69 | newpath 70 | cf findfont cs scalefont dup 71 | /FontMatrix get 0 get /Ts exch def /FontInfo get dup 72 | /UnderlinePosition get Ts mul /To exch def 73 | /UnderlineThickness get Ts mul /Tt exch def 74 | ux uy To add cs add moveto Tcx uy To add cs add lineto 75 | Tt setlinewidth stroke 76 | grestore 77 | } bd 78 | /SOE { 79 | /Tcx currentpoint pop def 80 | gsave 81 | newpath 82 | cf findfont cs scalefont dup 83 | /FontMatrix get 0 get /Ts exch def /FontInfo get dup 84 | /UnderlinePosition get Ts mul /To exch def 85 | /UnderlineThickness get Ts mul /Tt exch def 86 | ux uy To add cs 10 mul 26 idiv add moveto Tcx uy To add cs 10 mul 26 idiv add lineto 87 | Tt setlinewidth stroke 88 | grestore 89 | } bd 90 | /QT { 91 | /Y22 exch store 92 | /X22 exch store 93 | /Y21 exch store 94 | /X21 exch store 95 | currentpoint 96 | /Y21 load 2 mul add 3 div exch 97 | /X21 load 2 mul add 3 div exch 98 | /X21 load 2 mul /X22 load add 3 div 99 | /Y21 load 2 mul /Y22 load add 3 div 100 | /X22 load /Y22 load curveto 101 | } bd 102 | /SSPD { 103 | dup length /d exch dict def 104 | { 105 | /v exch def 106 | /k exch def 107 | currentpagedevice k known { 108 | /cpdv currentpagedevice k get def 109 | v cpdv ne { 110 | /upd false def 111 | /nullv v type /nulltype eq def 112 | /nullcpdv cpdv type /nulltype eq def 113 | nullv nullcpdv or 114 | { 115 | /upd true def 116 | } { 117 | /sametype v type cpdv type eq def 118 | sametype { 119 | v type /arraytype eq { 120 | /vlen v length def 121 | /cpdvlen cpdv length def 122 | vlen cpdvlen eq { 123 | 0 1 vlen 1 sub { 124 | /i exch def 125 | /obj v i get def 126 | /cpdobj cpdv i get def 127 | obj cpdobj ne { 128 | /upd true def 129 | exit 130 | } if 131 | } for 132 | } { 133 | /upd true def 134 | } ifelse 135 | } { 136 | v type /dicttype eq { 137 | v { 138 | /dv exch def 139 | /dk exch def 140 | /cpddv cpdv dk get def 141 | dv cpddv ne { 142 | /upd true def 143 | exit 144 | } if 145 | } forall 146 | } { 147 | /upd true def 148 | } ifelse 149 | } ifelse 150 | } if 151 | } ifelse 152 | upd true eq { 153 | d k v put 154 | } if 155 | } if 156 | } if 157 | } forall 158 | d length 0 gt { 159 | d setpagedevice 160 | } if 161 | } bd 162 | /RE { % /NewFontName [NewEncodingArray] /FontName RE - 163 | findfont dup length dict begin 164 | { 165 | 1 index /FID ne 166 | {def} {pop pop} ifelse 167 | } forall 168 | /Encoding exch def 169 | /FontName 1 index def 170 | currentdict definefont pop 171 | end 172 | } bind def 173 | %%EndResource 174 | %%BeginResource: procset (Apache XML Graphics EPS ProcSet) 1.0 0 175 | %%Version: 1.0 0 176 | %%Copyright: (Copyright 2002-2003 The Apache Software Foundation. License terms: http://www.apache.org/licenses/LICENSE-2.0) 177 | /BeginEPSF { %def 178 | /b4_Inc_state save def % Save state for cleanup 179 | /dict_count countdictstack def % Count objects on dict stack 180 | /op_count count 1 sub def % Count objects on operand stack 181 | userdict begin % Push userdict on dict stack 182 | /showpage { } def % Redefine showpage, { } = null proc 183 | 0 setgray 0 setlinecap % Prepare graphics state 184 | 1 setlinewidth 0 setlinejoin 185 | 10 setmiterlimit [ ] 0 setdash newpath 186 | /languagelevel where % If level not equal to 1 then 187 | {pop languagelevel % set strokeadjust and 188 | 1 ne % overprint to their defaults. 189 | {false setstrokeadjust false setoverprint 190 | } if 191 | } if 192 | } bd 193 | /EndEPSF { %def 194 | count op_count sub {pop} repeat % Clean up stacks 195 | countdictstack dict_count sub {end} repeat 196 | b4_Inc_state restore 197 | } bd 198 | %%EndResource 199 | %FOPBeginFontDict 200 | %%IncludeResource: font Courier-Oblique 201 | %%IncludeResource: font Courier-BoldOblique 202 | %%IncludeResource: font Courier-Bold 203 | %%IncludeResource: font ZapfDingbats 204 | %%IncludeResource: font Symbol 205 | %%IncludeResource: font Helvetica 206 | %%IncludeResource: font Helvetica-Oblique 207 | %%IncludeResource: font Helvetica-Bold 208 | %%IncludeResource: font Helvetica-BoldOblique 209 | %%IncludeResource: font Times-Roman 210 | %%IncludeResource: font Times-Italic 211 | %%IncludeResource: font Times-Bold 212 | %%IncludeResource: font Times-BoldItalic 213 | %%IncludeResource: font Courier 214 | %FOPEndFontDict 215 | %%BeginResource: encoding WinAnsiEncoding 216 | /WinAnsiEncoding [ 217 | /.notdef /.notdef /.notdef /.notdef /.notdef 218 | /.notdef /.notdef /.notdef /.notdef /.notdef 219 | /.notdef /.notdef /.notdef /.notdef /.notdef 220 | /.notdef /.notdef /.notdef /.notdef /.notdef 221 | /.notdef /.notdef /.notdef /.notdef /.notdef 222 | /.notdef /.notdef /.notdef /.notdef /.notdef 223 | /.notdef /.notdef /space /exclam /quotedbl 224 | /numbersign /dollar /percent /ampersand /quotesingle 225 | /parenleft /parenright /asterisk /plus /comma 226 | /hyphen /period /slash /zero /one 227 | /two /three /four /five /six 228 | /seven /eight /nine /colon /semicolon 229 | /less /equal /greater /question /at 230 | /A /B /C /D /E 231 | /F /G /H /I /J 232 | /K /L /M /N /O 233 | /P /Q /R /S /T 234 | /U /V /W /X /Y 235 | /Z /bracketleft /backslash /bracketright /asciicircum 236 | /underscore /quoteleft /a /b /c 237 | /d /e /f /g /h 238 | /i /j /k /l /m 239 | /n /o /p /q /r 240 | /s /t /u /v /w 241 | /x /y /z /braceleft /bar 242 | /braceright /asciitilde /bullet /Euro /bullet 243 | /quotesinglbase /florin /quotedblbase /ellipsis /dagger 244 | /daggerdbl /circumflex /perthousand /Scaron /guilsinglleft 245 | /OE /bullet /Zcaron /bullet /bullet 246 | /quoteleft /quoteright /quotedblleft /quotedblright /bullet 247 | /endash /emdash /asciitilde /trademark /scaron 248 | /guilsinglright /oe /bullet /zcaron /Ydieresis 249 | /space /exclamdown /cent /sterling /currency 250 | /yen /brokenbar /section /dieresis /copyright 251 | /ordfeminine /guillemotleft /logicalnot /sfthyphen /registered 252 | /macron /degree /plusminus /twosuperior /threesuperior 253 | /acute /mu /paragraph /middot /cedilla 254 | /onesuperior /ordmasculine /guillemotright /onequarter /onehalf 255 | /threequarters /questiondown /Agrave /Aacute /Acircumflex 256 | /Atilde /Adieresis /Aring /AE /Ccedilla 257 | /Egrave /Eacute /Ecircumflex /Edieresis /Igrave 258 | /Iacute /Icircumflex /Idieresis /Eth /Ntilde 259 | /Ograve /Oacute /Ocircumflex /Otilde /Odieresis 260 | /multiply /Oslash /Ugrave /Uacute /Ucircumflex 261 | /Udieresis /Yacute /Thorn /germandbls /agrave 262 | /aacute /acircumflex /atilde /adieresis /aring 263 | /ae /ccedilla /egrave /eacute /ecircumflex 264 | /edieresis /igrave /iacute /icircumflex /idieresis 265 | /eth /ntilde /ograve /oacute /ocircumflex 266 | /otilde /odieresis /divide /oslash /ugrave 267 | /uacute /ucircumflex /udieresis /yacute /thorn 268 | /ydieresis 269 | ] def 270 | %%EndResource 271 | %FOPBeginFontReencode 272 | /Courier-Oblique findfont 273 | dup length dict begin 274 | {1 index /FID ne {def} {pop pop} ifelse} forall 275 | /Encoding WinAnsiEncoding def 276 | currentdict 277 | end 278 | /Courier-Oblique exch definefont pop 279 | /Courier-BoldOblique findfont 280 | dup length dict begin 281 | {1 index /FID ne {def} {pop pop} ifelse} forall 282 | /Encoding WinAnsiEncoding def 283 | currentdict 284 | end 285 | /Courier-BoldOblique exch definefont pop 286 | /Courier-Bold findfont 287 | dup length dict begin 288 | {1 index /FID ne {def} {pop pop} ifelse} forall 289 | /Encoding WinAnsiEncoding def 290 | currentdict 291 | end 292 | /Courier-Bold exch definefont pop 293 | /Helvetica findfont 294 | dup length dict begin 295 | {1 index /FID ne {def} {pop pop} ifelse} forall 296 | /Encoding WinAnsiEncoding def 297 | currentdict 298 | end 299 | /Helvetica exch definefont pop 300 | /Helvetica-Oblique findfont 301 | dup length dict begin 302 | {1 index /FID ne {def} {pop pop} ifelse} forall 303 | /Encoding WinAnsiEncoding def 304 | currentdict 305 | end 306 | /Helvetica-Oblique exch definefont pop 307 | /Helvetica-Bold findfont 308 | dup length dict begin 309 | {1 index /FID ne {def} {pop pop} ifelse} forall 310 | /Encoding WinAnsiEncoding def 311 | currentdict 312 | end 313 | /Helvetica-Bold exch definefont pop 314 | /Helvetica-BoldOblique findfont 315 | dup length dict begin 316 | {1 index /FID ne {def} {pop pop} ifelse} forall 317 | /Encoding WinAnsiEncoding def 318 | currentdict 319 | end 320 | /Helvetica-BoldOblique exch definefont pop 321 | /Times-Roman findfont 322 | dup length dict begin 323 | {1 index /FID ne {def} {pop pop} ifelse} forall 324 | /Encoding WinAnsiEncoding def 325 | currentdict 326 | end 327 | /Times-Roman exch definefont pop 328 | /Times-Italic findfont 329 | dup length dict begin 330 | {1 index /FID ne {def} {pop pop} ifelse} forall 331 | /Encoding WinAnsiEncoding def 332 | currentdict 333 | end 334 | /Times-Italic exch definefont pop 335 | /Times-Bold findfont 336 | dup length dict begin 337 | {1 index /FID ne {def} {pop pop} ifelse} forall 338 | /Encoding WinAnsiEncoding def 339 | currentdict 340 | end 341 | /Times-Bold exch definefont pop 342 | /Times-BoldItalic findfont 343 | dup length dict begin 344 | {1 index /FID ne {def} {pop pop} ifelse} forall 345 | /Encoding WinAnsiEncoding def 346 | currentdict 347 | end 348 | /Times-BoldItalic exch definefont pop 349 | /Courier findfont 350 | dup length dict begin 351 | {1 index /FID ne {def} {pop pop} ifelse} forall 352 | /Encoding WinAnsiEncoding def 353 | currentdict 354 | end 355 | /Courier exch definefont pop 356 | %FOPEndFontReencode 357 | %%EndProlog 358 | %%Page: 1 1 359 | %%PageBoundingBox: 0 0 675 525 360 | %%BeginPageSetup 361 | [1 0 0 -1 0 525] CT 362 | %%EndPageSetup 363 | GS 364 | [0.75 0 0 0.75 0 0] CT 365 | 1 0 0 RC 366 | 1 LJ 367 | 4 LW 368 | N 369 | 150 327.5 M 370 | 159.677 299.297 L 371 | 169.355 271.376 L 372 | 179.032 244.016 L 373 | 188.71 217.489 L 374 | 198.387 192.062 L 375 | 208.065 167.988 L 376 | 217.742 145.509 L 377 | 227.419 124.847 L 378 | 237.097 106.21 L 379 | 246.774 89.784 L 380 | 256.452 75.734 L 381 | 266.129 64.199 L 382 | 275.806 55.295 L 383 | 285.484 49.11 L 384 | 295.161 45.708 L 385 | 304.839 45.12 L 386 | 314.516 47.355 L 387 | 324.194 52.388 L 388 | 333.871 60.17 L 389 | 343.548 70.623 L 390 | 353.226 83.643 L 391 | 362.903 99.1 L 392 | 372.581 116.838 L 393 | 382.258 136.682 L 394 | 391.935 158.432 L 395 | 401.613 181.871 L 396 | 411.29 206.765 L 397 | 420.968 232.866 L 398 | 430.645 259.912 L 399 | 440.323 287.634 L 400 | 450 315.753 L 401 | 459.677 343.991 L 402 | 469.355 372.063 L 403 | 479.032 399.69 L 404 | 488.71 426.596 L 405 | 498.387 452.512 L 406 | 508.065 477.179 L 407 | 517.742 500.35 L 408 | 527.419 521.794 L 409 | 537.097 541.297 L 410 | 546.774 558.663 L 411 | 556.452 573.72 L 412 | 566.129 586.317 L 413 | 575.806 596.328 L 414 | 585.484 603.652 L 415 | 595.161 608.218 L 416 | 604.839 609.978 L 417 | 614.516 608.917 L 418 | 624.194 605.043 L 419 | 633.871 598.396 L 420 | 643.548 589.043 L 421 | 653.226 577.076 L 422 | 662.903 562.616 L 423 | 672.581 545.806 L 424 | 682.258 526.815 L 425 | 691.935 505.833 L 426 | 701.613 483.069 L 427 | 711.29 458.75 L 428 | 720.968 433.12 L 429 | 730.645 406.435 L 430 | 740.323 378.961 L 431 | 750 350.973 L 432 | S 433 | GR 434 | GS 435 | [0.75 0 0 0.75 0 0] CT 436 | 1 GC 437 | 2 setlinecap 438 | 1 LJ 439 | 2.667 LW 440 | N 441 | 150 610 M 442 | 750 610 L 443 | S 444 | GR 445 | GS 446 | [0.75 0 0 0.75 0 0] CT 447 | 1 GC 448 | 2 setlinecap 449 | 1 LJ 450 | 2.667 LW 451 | N 452 | 150 45 M 453 | 750 45 L 454 | S 455 | GR 456 | GS 457 | [0.75 0 0 0.75 0 0] CT 458 | 1 GC 459 | 2 setlinecap 460 | 1 LJ 461 | 2.667 LW 462 | N 463 | 150 610 M 464 | 150 604 L 465 | S 466 | GR 467 | GS 468 | [0.75 0 0 0.75 0 0] CT 469 | 1 GC 470 | 2 setlinecap 471 | 1 LJ 472 | 2.667 LW 473 | N 474 | 246.774 610 M 475 | 246.774 604 L 476 | S 477 | GR 478 | GS 479 | [0.75 0 0 0.75 0 0] CT 480 | 1 GC 481 | 2 setlinecap 482 | 1 LJ 483 | 2.667 LW 484 | N 485 | 343.548 610 M 486 | 343.548 604 L 487 | S 488 | GR 489 | GS 490 | [0.75 0 0 0.75 0 0] CT 491 | 1 GC 492 | 2 setlinecap 493 | 1 LJ 494 | 2.667 LW 495 | N 496 | 440.323 610 M 497 | 440.323 604 L 498 | S 499 | GR 500 | GS 501 | [0.75 0 0 0.75 0 0] CT 502 | 1 GC 503 | 2 setlinecap 504 | 1 LJ 505 | 2.667 LW 506 | N 507 | 537.097 610 M 508 | 537.097 604 L 509 | S 510 | GR 511 | GS 512 | [0.75 0 0 0.75 0 0] CT 513 | 1 GC 514 | 2 setlinecap 515 | 1 LJ 516 | 2.667 LW 517 | N 518 | 633.871 610 M 519 | 633.871 604 L 520 | S 521 | GR 522 | GS 523 | [0.75 0 0 0.75 0 0] CT 524 | 1 GC 525 | 2 setlinecap 526 | 1 LJ 527 | 2.667 LW 528 | N 529 | 730.645 610 M 530 | 730.645 604 L 531 | S 532 | GR 533 | GS 534 | [0.75 0 0 0.75 0 0] CT 535 | 1 GC 536 | 2 setlinecap 537 | 1 LJ 538 | 2.667 LW 539 | N 540 | 150 45 M 541 | 150 51 L 542 | S 543 | GR 544 | GS 545 | [0.75 0 0 0.75 0 0] CT 546 | 1 GC 547 | 2 setlinecap 548 | 1 LJ 549 | 2.667 LW 550 | N 551 | 246.774 45 M 552 | 246.774 51 L 553 | S 554 | GR 555 | GS 556 | [0.75 0 0 0.75 0 0] CT 557 | 1 GC 558 | 2 setlinecap 559 | 1 LJ 560 | 2.667 LW 561 | N 562 | 343.548 45 M 563 | 343.548 51 L 564 | S 565 | GR 566 | GS 567 | [0.75 0 0 0.75 0 0] CT 568 | 1 GC 569 | 2 setlinecap 570 | 1 LJ 571 | 2.667 LW 572 | N 573 | 440.323 45 M 574 | 440.323 51 L 575 | S 576 | GR 577 | GS 578 | [0.75 0 0 0.75 0 0] CT 579 | 1 GC 580 | 2 setlinecap 581 | 1 LJ 582 | 2.667 LW 583 | N 584 | 537.097 45 M 585 | 537.097 51 L 586 | S 587 | GR 588 | GS 589 | [0.75 0 0 0.75 0 0] CT 590 | 1 GC 591 | 2 setlinecap 592 | 1 LJ 593 | 2.667 LW 594 | N 595 | 633.871 45 M 596 | 633.871 51 L 597 | S 598 | GR 599 | GS 600 | [0.75 0 0 0.75 0 0] CT 601 | 1 GC 602 | 2 setlinecap 603 | 1 LJ 604 | 2.667 LW 605 | N 606 | 730.645 45 M 607 | 730.645 51 L 608 | S 609 | GR 610 | GS 611 | [0.75 0 0 0.75 112.5 463.89999] CT 612 | 1 GC 613 | /Helvetica-Bold 29.333 F 614 | GS 615 | [1 0 0 1 0 0] CT 616 | -8.5 28 moveto 617 | 1 -1 scale 618 | (0) t 619 | GR 620 | GR 621 | GS 622 | [0.75 0 0 0.75 185.08065 463.89999] CT 623 | 1 GC 624 | /Helvetica-Bold 29.333 F 625 | GS 626 | [1 0 0 1 0 0] CT 627 | -8.5 28 moveto 628 | 1 -1 scale 629 | (1) t 630 | GR 631 | GR 632 | GS 633 | [0.75 0 0 0.75 257.6613 463.89999] CT 634 | 1 GC 635 | /Helvetica-Bold 29.333 F 636 | GS 637 | [1 0 0 1 0 0] CT 638 | -8.5 28 moveto 639 | 1 -1 scale 640 | (2) t 641 | GR 642 | GR 643 | GS 644 | [0.75 0 0 0.75 330.24193 463.89999] CT 645 | 1 GC 646 | /Helvetica-Bold 29.333 F 647 | GS 648 | [1 0 0 1 0 0] CT 649 | -8.5 28 moveto 650 | 1 -1 scale 651 | (3) t 652 | GR 653 | GR 654 | GS 655 | [0.75 0 0 0.75 402.8226 463.89999] CT 656 | 1 GC 657 | /Helvetica-Bold 29.333 F 658 | GS 659 | [1 0 0 1 0 0] CT 660 | -8.5 28 moveto 661 | 1 -1 scale 662 | (4) t 663 | GR 664 | GR 665 | GS 666 | [0.75 0 0 0.75 475.40323 463.89999] CT 667 | 1 GC 668 | /Helvetica-Bold 29.333 F 669 | GS 670 | [1 0 0 1 0 0] CT 671 | -8.5 28 moveto 672 | 1 -1 scale 673 | (5) t 674 | GR 675 | GR 676 | GS 677 | [0.75 0 0 0.75 547.98386 463.89999] CT 678 | 1 GC 679 | /Helvetica-Bold 29.333 F 680 | GS 681 | [1 0 0 1 0 0] CT 682 | -8.5 28 moveto 683 | 1 -1 scale 684 | (6) t 685 | GR 686 | GR 687 | GS 688 | [0.75 0 0 0.75 0 0] CT 689 | 1 GC 690 | 2 setlinecap 691 | 1 LJ 692 | 2.667 LW 693 | N 694 | 150 610 M 695 | 150 45 L 696 | S 697 | GR 698 | GS 699 | [0.75 0 0 0.75 0 0] CT 700 | 1 GC 701 | 2 setlinecap 702 | 1 LJ 703 | 2.667 LW 704 | N 705 | 750 610 M 706 | 750 45 L 707 | S 708 | GR 709 | GS 710 | [0.75 0 0 0.75 0 0] CT 711 | 1 GC 712 | 2 setlinecap 713 | 1 LJ 714 | 2.667 LW 715 | N 716 | 150 610 M 717 | 156 610 L 718 | S 719 | GR 720 | GS 721 | [0.75 0 0 0.75 0 0] CT 722 | 1 GC 723 | 2 setlinecap 724 | 1 LJ 725 | 2.667 LW 726 | N 727 | 150 468.75 M 728 | 156 468.75 L 729 | S 730 | GR 731 | GS 732 | [0.75 0 0 0.75 0 0] CT 733 | 1 GC 734 | 2 setlinecap 735 | 1 LJ 736 | 2.667 LW 737 | N 738 | 150 327.5 M 739 | 156 327.5 L 740 | S 741 | GR 742 | GS 743 | [0.75 0 0 0.75 0 0] CT 744 | 1 GC 745 | 2 setlinecap 746 | 1 LJ 747 | 2.667 LW 748 | N 749 | 150 186.25 M 750 | 156 186.25 L 751 | S 752 | GR 753 | GS 754 | [0.75 0 0 0.75 0 0] CT 755 | 1 GC 756 | 2 setlinecap 757 | 1 LJ 758 | 2.667 LW 759 | N 760 | 150 45 M 761 | 156 45 L 762 | S 763 | GR 764 | GS 765 | [0.75 0 0 0.75 0 0] CT 766 | 1 GC 767 | 2 setlinecap 768 | 1 LJ 769 | 2.667 LW 770 | N 771 | 750 610 M 772 | 744 610 L 773 | S 774 | GR 775 | GS 776 | [0.75 0 0 0.75 0 0] CT 777 | 1 GC 778 | 2 setlinecap 779 | 1 LJ 780 | 2.667 LW 781 | N 782 | 750 468.75 M 783 | 744 468.75 L 784 | S 785 | GR 786 | GS 787 | [0.75 0 0 0.75 0 0] CT 788 | 1 GC 789 | 2 setlinecap 790 | 1 LJ 791 | 2.667 LW 792 | N 793 | 750 327.5 M 794 | 744 327.5 L 795 | S 796 | GR 797 | GS 798 | [0.75 0 0 0.75 0 0] CT 799 | 1 GC 800 | 2 setlinecap 801 | 1 LJ 802 | 2.667 LW 803 | N 804 | 750 186.25 M 805 | 744 186.25 L 806 | S 807 | GR 808 | GS 809 | [0.75 0 0 0.75 0 0] CT 810 | 1 GC 811 | 2 setlinecap 812 | 1 LJ 813 | 2.667 LW 814 | N 815 | 750 45 M 816 | 744 45 L 817 | S 818 | GR 819 | GS 820 | [0.75 0 0 0.75 106.09999 457.5] CT 821 | 1 GC 822 | /Helvetica-Bold 29.333 F 823 | GS 824 | [1 0 0 1 0 0] CT 825 | -27 11 moveto 826 | 1 -1 scale 827 | (-1) t 828 | GR 829 | GR 830 | GS 831 | [0.75 0 0 0.75 106.09999 351.5625] CT 832 | 1 GC 833 | /Helvetica-Bold 29.333 F 834 | GS 835 | [1 0 0 1 0 0] CT 836 | -51 11 moveto 837 | 1 -1 scale 838 | (-0.5) t 839 | GR 840 | GR 841 | GS 842 | [0.75 0 0 0.75 106.09999 245.625] CT 843 | 1 GC 844 | /Helvetica-Bold 29.333 F 845 | GS 846 | [1 0 0 1 0 0] CT 847 | -17 11 moveto 848 | 1 -1 scale 849 | (0) t 850 | GR 851 | GR 852 | GS 853 | [0.75 0 0 0.75 106.09999 139.6875] CT 854 | 1 GC 855 | /Helvetica-Bold 29.333 F 856 | GS 857 | [1 0 0 1 0 0] CT 858 | -41 11 moveto 859 | 1 -1 scale 860 | (0.5) t 861 | GR 862 | GR 863 | GS 864 | [0.75 0 0 0.75 106.09999 33.75] CT 865 | 1 GC 866 | /Helvetica-Bold 29.333 F 867 | GS 868 | [1 0 0 1 0 0] CT 869 | -17 11 moveto 870 | 1 -1 scale 871 | (1) t 872 | GR 873 | GR 874 | %%Trailer 875 | %%Pages: 1 876 | %%EOF 877 | -------------------------------------------------------------------------------- /img/sine.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine.fig -------------------------------------------------------------------------------- /img/sine2.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine2.ai -------------------------------------------------------------------------------- /img/sine_ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_ex.png -------------------------------------------------------------------------------- /img/sine_ex.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_ex.pptx -------------------------------------------------------------------------------- /img/sine_fig.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_fig.ai -------------------------------------------------------------------------------- /img/sine_fig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_fig.png -------------------------------------------------------------------------------- /img/sine_pulse.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_pulse.ai -------------------------------------------------------------------------------- /img/sine_pulse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_pulse.png -------------------------------------------------------------------------------- /img/sine_pulse_multi.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_pulse_multi.ai -------------------------------------------------------------------------------- /img/sine_pulse_multi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/sine_pulse_multi.png -------------------------------------------------------------------------------- /img/snr_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/snr_design.png -------------------------------------------------------------------------------- /img/state_est.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/state_est.ai -------------------------------------------------------------------------------- /img/state_est.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/state_est.png -------------------------------------------------------------------------------- /img/state_est_flow.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/state_est_flow.ai -------------------------------------------------------------------------------- /img/state_est_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/state_est_flow.png -------------------------------------------------------------------------------- /img/state_est_prob.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/state_est_prob.ai -------------------------------------------------------------------------------- /img/state_est_prob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/state_est_prob.png -------------------------------------------------------------------------------- /img/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/stop.png -------------------------------------------------------------------------------- /img/toc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/toc.png -------------------------------------------------------------------------------- /img/track.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/track.ai -------------------------------------------------------------------------------- /img/track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/track.png -------------------------------------------------------------------------------- /img/track_init.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/track_init.ai -------------------------------------------------------------------------------- /img/track_init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/track_init.png -------------------------------------------------------------------------------- /img/tx_dof.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/tx_dof.ai -------------------------------------------------------------------------------- /img/tx_dof.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/tx_dof.png -------------------------------------------------------------------------------- /img/vert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/vert.gif -------------------------------------------------------------------------------- /img/wave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/wave.png -------------------------------------------------------------------------------- /img/wave_seg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/wave_seg.png -------------------------------------------------------------------------------- /img/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/waveform.png -------------------------------------------------------------------------------- /img/wavelength.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/wavelength.ai -------------------------------------------------------------------------------- /img/zoom_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/img/zoom_box.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/logo.png -------------------------------------------------------------------------------- /rad/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mit-ll/radar-intro/fe3b7931ca1102465afdfa09f1669e144b754cf3/rad/__init__.py -------------------------------------------------------------------------------- /rad/air.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | from math import atan2 35 | from matplotlib.patches import Circle 36 | import numpy as np 37 | from numpy.random import permutation, rand, randint 38 | import rad.plot as plt 39 | import rad.radar as rd 40 | from typing import List 41 | 42 | N_COMP = 4 43 | N_SIDE = 4 44 | 45 | class Route(): 46 | """ 47 | Container class for air traffic route. 48 | """ 49 | def __init__(self): 50 | self.start = None 51 | self.end = None 52 | self.vel = None 53 | self.speed = None 54 | self.lifetime = None 55 | self.max_range = None 56 | 57 | def plot_routes( 58 | routes: List[Route] 59 | ): 60 | """ 61 | Plot air traffic routes. 62 | 63 | Inputs: 64 | - routes [List[Route]]: List of Routes 65 | 66 | Outputs: 67 | (none) 68 | """ 69 | 70 | # Maximum range 71 | max_range = routes[0].max_range 72 | 73 | fig, ax = plt.new_plot() 74 | ax.set_xlabel('East (m)') 75 | ax.set_ylabel('North (m)') 76 | ax.set_xlim([-max_range, max_range]) 77 | ax.set_ylim([-max_range, max_range]) 78 | 79 | radar_view = Circle((0, 0), max_range, color='black', linestyle='dashed', fill=False) 80 | ax.add_patch(radar_view) 81 | 82 | index = 0 83 | for rt in routes: 84 | line = ax.plot([rt.start[0], rt.end[0]], [rt.start[1], rt.end[1]])[0] 85 | rt_angle = np.arctan2(rt.vel[1], rt.vel[0]) 86 | dv = rt_angle + np.pi/2 87 | dx = 5*(max_range/100)*np.cos(dv) 88 | dy = 5*(max_range/100)*np.sin(dv) 89 | alpha = np.random.uniform(low=0.4, high=0.6) 90 | ax.text(rt.start[0] + rt.vel[0]*rt.lifetime*alpha + dx, rt.start[1] + rt.vel[1]*rt.lifetime*alpha + dy, str(index), color=line.get_color()) 91 | index += 1 92 | 93 | def routes(n, max_range, avg_speed): 94 | """ 95 | Generate random air traffic routes 96 | 97 | Inputs: 98 | - n [int]: Number of routes 99 | - max_range [float]: Maximum range of route (m) 100 | - avg_speed [float]: Average air speed (m/s) 101 | 102 | Outputs: 103 | - routes [List[Route]]: Output Routes 104 | """ 105 | 106 | # Initailize output 107 | out = [] 108 | 109 | # Draw routes 110 | for ii in range(n): 111 | 112 | # Draw starting side 113 | start_side = randint(low=0, high=N_SIDE) 114 | 115 | # Draw offset for end side 116 | offset = randint(low=1, high=N_SIDE) 117 | 118 | # Ending side 119 | end_side = np.mod(start_side + offset, N_SIDE) 120 | 121 | # Draw start/end point 122 | start_alpha = 0.3 + 0.4*rand() 123 | end_alpha = 0.3 + 0.4*rand() 124 | 125 | # Build start point 126 | if (start_side == 0): 127 | start_x = start_alpha*2*max_range - max_range 128 | start_y = max_range 129 | elif (start_side == 1): 130 | start_x = max_range 131 | start_y = start_alpha*2*max_range - max_range 132 | elif (start_side == 2): 133 | start_x = start_alpha*2*max_range - max_range 134 | start_y = -max_range 135 | elif (start_side == 3): 136 | start_x = -max_range 137 | start_y = start_alpha*2*max_range - max_range 138 | 139 | # Build end point 140 | if (end_side == 0): 141 | end_x = end_alpha*2*max_range - max_range 142 | end_y = max_range 143 | elif (end_side == 1): 144 | end_x = max_range 145 | end_y = end_alpha*2*max_range - max_range 146 | elif (end_side == 2): 147 | end_x = end_alpha*2*max_range - max_range 148 | end_y = -max_range 149 | elif (end_side == 3): 150 | end_x = -max_range 151 | end_y = end_alpha*2*max_range - max_range 152 | 153 | # Velocity 154 | vel_x = end_x - start_x 155 | vel_y = end_y - start_y 156 | vel_norm = np.sqrt(vel_x**2 + vel_y**2) 157 | vel_x = avg_speed*vel_x/vel_norm 158 | vel_y = avg_speed*vel_y/vel_norm 159 | 160 | # Make route 161 | rt = Route() 162 | rt.start = np.array([start_x, start_y]) 163 | rt.end = np.array([end_x, end_y]) 164 | rt.vel = np.array([vel_x, vel_y]) 165 | rt.speed = avg_speed 166 | rt.lifetime = np.sqrt((start_x - end_x)**2 + (start_y - end_y)**2)/avg_speed 167 | rt.max_range = max_range 168 | 169 | # Add to output 170 | out.append(rt) 171 | 172 | # Output 173 | return out 174 | 175 | def to_target(routes, min_rcs=-5, max_rcs=5): 176 | """ 177 | Convert Routes to Targets. 178 | 179 | Inputs: 180 | - routes [List[Route]]: Input Routes 181 | - min_rcs [float]: Minimum radar cross section (dBsm) 182 | - max_rcs [float]: Maximum radar cross section (dBsm) 183 | 184 | 185 | Outputs: 186 | - targets [List[Target]]: Output Targets 187 | """ 188 | 189 | # Shuffled RCS values 190 | rcs = permutation(np.linspace(min_rcs, max_rcs, len(routes))) 191 | 192 | # Initialize output 193 | targets = [] 194 | 195 | # Build targets 196 | index = 0 197 | for rt in routes: 198 | 199 | tgt = rd.Target() 200 | tgt.pos = np.array([rt.start[0], rt.start[1], rt.vel[0], rt.vel[1]]) 201 | tgt.rcs = rcs[index] 202 | tgt.route = index 203 | index += 1 204 | 205 | targets.append(tgt) 206 | 207 | targets.sort(key=lambda x: x.rcs) 208 | 209 | # Output 210 | return targets -------------------------------------------------------------------------------- /rad/const.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | # Constants 35 | c = 3E8 # Speed of light (m/s) 36 | eta = 376.730313461 # Natural impedance (ohms) 37 | k = 1.38064852E-23 # Boltzmann constant -------------------------------------------------------------------------------- /rad/css.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | from IPython.display import display, HTML 35 | 36 | # Custom CSS 37 | def add_custom_css(): 38 | display(HTML("")) 39 | display(HTML("")) -------------------------------------------------------------------------------- /rad/example.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | from IPython.display import display, HTML 35 | import ipywidgets as wdg 36 | import matplotlib.patches as ptch 37 | import matplotlib.pyplot as pyp 38 | import rad.air as air 39 | import rad.plot as plt 40 | import rad.const as cnst 41 | import rad.radar as rd 42 | import rad.robby as rby 43 | import rad.toys as ts 44 | import math 45 | import numpy as np 46 | 47 | #-------Lab 1.1: Introduction to Labs------- 48 | 49 | # Example 1.1.1 50 | def ex_1_1_1(): 51 | ts.sine( 52 | amp = 1, 53 | freq = 1, 54 | phase = 0 55 | ) 56 | 57 | # Example 1.1.2 58 | def ex_1_1_2(): 59 | ts.sine( 60 | amp=3, 61 | freq=2, 62 | phase=0, 63 | widgets=[] 64 | ) 65 | 66 | #-------Lab 1.2: Introduction to Radar------- 67 | 68 | # Example 1.2.1 69 | def ex_1_2_1(): 70 | ts.wave() 71 | 72 | # Example 1.2.2 73 | def ex_1_2_2(): 74 | ts.prop_loss() 75 | 76 | # Example 1.2.3 77 | def ex_1_2_3(): 78 | ts.sine_prop_generic() 79 | 80 | # Example 1.2.4 81 | def ex_1_2_4(): 82 | ts.ranging( 83 | rx_omni=True, 84 | tx_omni=True, 85 | tgt_hide=False, 86 | tgt_x = 50, 87 | tgt_y = 50, 88 | widgets=['run', 'x', 'y'] 89 | ) 90 | 91 | # Example 1.2.5 92 | def ex_1_2_5(): 93 | ts.ranging( 94 | rx_omni=True, 95 | tx_omni=True, 96 | max_range=400, 97 | tgt_hide=True, 98 | tgt_x=75, 99 | tgt_y=-100, 100 | widgets=['dets', 'run'] 101 | ) 102 | 103 | # Example 1.2.6 104 | def ex_1_2_6(): 105 | ts.ranging( 106 | rx_omni=True, 107 | tx_omni=False, 108 | tgt_hide=False, 109 | tgt_x=50, 110 | tgt_y=50, 111 | widgets=['tx_az', 'tx_beamw', 'dets', 'run', 'x', 'y'] 112 | ) 113 | 114 | # Example 1.2.7 115 | def ex_1_2_7(): 116 | ts.ranging( 117 | rx_omni=True, 118 | tx_omni=False, 119 | tgt_hide=True, 120 | tgt_x=50, 121 | tgt_y=50, 122 | widgets=['tx_az', 'tx_beamw', 'dets', 'run'] 123 | ) 124 | 125 | # Example 1.2.8 126 | def ex_1_2_8(): 127 | ts.ranging( 128 | rx_omni=False, 129 | tx_omni=True, 130 | tgt_hide=False, 131 | tgt_x=50, 132 | tgt_y=50, 133 | widgets=['rx_az', 'rx_beamw', 'dets', 'run', 'x', 'y'] 134 | ) 135 | 136 | # Example 1.2.9 137 | def ex_1_2_9(): 138 | ts.ranging( 139 | rx_omni=False, 140 | tx_omni=True, 141 | tgt_hide=True, 142 | tgt_x=40, 143 | tgt_y=-20, 144 | widgets=['rx_az', 'rx_beamw', 'dets', 'run'] 145 | ) 146 | 147 | # Example 1.2.10 148 | def ex_1_2_10(): 149 | ts.dish_pat() 150 | 151 | # Example 1.2.11 152 | def ex_1_2_11(): 153 | ts.array( 154 | num_elem=7, 155 | dx=4, 156 | widgets=['tx_az', 'run', 'x', 'y'] 157 | ) 158 | 159 | # Example 1.2.12 160 | def ex_1_2_12(): 161 | ts.doppler() 162 | 163 | # Example 1.2.13 164 | def ex_1_2_13(): 165 | ts.radar_wave() 166 | 167 | #-------Lab 2.1: Radar Range Equation------- 168 | 169 | # Example 2.1.11 170 | def ex_2_1_1a(): 171 | ts.snr( 172 | noise_energy=-20, 173 | show_snr=False, 174 | signal_energy=10, 175 | widgets=[] 176 | ) 177 | 178 | # Example 2.1.1b 179 | def ex_2_1_1b(): 180 | ts.snr( 181 | noise_energy=0, 182 | show_snr=False, 183 | signal_energy=0, 184 | widgets=[] 185 | ) 186 | 187 | # Example 2.1.2 188 | def ex_2_1_2(): 189 | ts.snr() 190 | 191 | # Example 2.1.3 192 | def ex_2_1_3(): 193 | ts.sine_prop() 194 | 195 | # Example 2.1.4 196 | def ex_2_1_4(): 197 | ts.friis() 198 | 199 | # Example 2.1.5 200 | def ex_2_1_5(): 201 | ts.radar_range_power() 202 | 203 | # Example 2.1.6 204 | def ex_2_1_6(): 205 | ts.radar_range_energy() 206 | 207 | # Example 2.1.7 208 | def ex_2_1_7(): 209 | ts.radar_range_snr() 210 | 211 | # Example 2.1.8 212 | def ex_2_1_8(): 213 | ts.radar_range_det() 214 | 215 | #-------Lab 2.2: Basic Radar Design------- 216 | 217 | # Example 2.2.1 218 | def ex_2_2_1(): 219 | ts.radar_range_det() 220 | 221 | # Example 2.2.2 222 | def ex_2_2_2(): 223 | ts.design( 224 | max_rcs=-5, 225 | min_range=100, 226 | min_snr=15, 227 | metrics=['price', 'range', 'rcs', 'snr'], 228 | widgets=['freq', 'energy', 'noise_temp', 'r', 'radius', 'rcs'] 229 | ) 230 | 231 | # Example 2.2.3 232 | def ex_2_2_3(): 233 | ts.dish_pat(show_beamw=True) 234 | 235 | # Example 2.2.4 236 | def ex_2_2_4(): 237 | ts.rect_pat(show_beamw=True) 238 | 239 | # Example 2.2.5 240 | def ex_2_2_5(): 241 | ts.design( 242 | max_beamw=3, 243 | max_price=110000, 244 | max_rcs=-10, 245 | min_range=120, 246 | min_snr=14, 247 | metrics=['beamw', 'price', 'range', 'rcs', 'snr'], 248 | widgets=['freq', 'energy', 'noise_temp', 'r', 'radius', 'rcs'] 249 | ) 250 | 251 | # Example 2.2.6 252 | def ex_2_2_6(): 253 | 254 | test_tgt = rd.Target() 255 | test_tgt.rcs = 5 256 | r = 6E3 257 | az = math.pi/2 - math.pi/4 258 | test_tgt.pos = np.array([r*math.cos(az), r*math.sin(az)]) 259 | 260 | rby.robby(targets=[test_tgt], reset=False, widgets=['freq', 'energy', 'noise_temp', 'radius']) 261 | 262 | #-------Lab 3.1: Radar Transmissions and Receptions------- 263 | 264 | # Example 3.1.1 265 | def ex_3_1_1(): 266 | ts.sine_pulse( 267 | freq=1, 268 | prf=0.1, 269 | widgets=['energy', 'freq', 'pulsewidth'] 270 | ) 271 | 272 | # Example 3.1.2 273 | def ex_3_1_2(): 274 | ts.sine_pulse( 275 | show_duty=True, 276 | show_pri=True, 277 | widgets=['energy', 'freq', 'prf', 'pulsewidth'] 278 | ) 279 | 280 | # Example 3.1.3 281 | def ex_3_1_3(): 282 | ts.lfm() 283 | 284 | # Example 3.1.4 285 | def ex_3_1_4(): 286 | ts.dish_pat() 287 | 288 | # Example 3.1.5 289 | def ex_3_1_5(): 290 | ts.array() 291 | 292 | # Example 3.1.6 293 | def ex_3_1_6(): 294 | ts.delay_steer() 295 | 296 | # Example 3.1.7 297 | def ex_3_1_7(): 298 | ts.phase_steer() 299 | 300 | # Example 3.1.8 301 | def ex_3_1_8(): 302 | ts.pol() 303 | 304 | # Example 3.1.9 305 | def ex_3_1_9(): 306 | ts.matched_filter( 307 | start_freq=1, 308 | stop_freq=1, 309 | widgets=['delay', 'pulsewidth'] 310 | ) 311 | 312 | # Example 3.1.10 313 | def ex_3_1_10(): 314 | ts.range_res( 315 | start_freq=1, 316 | stop_freq=1, 317 | widgets=['range', 'pulsewidth'] 318 | ) 319 | 320 | # Example 3.1.11 321 | def ex_3_1_11(): 322 | ts.matched_filter() 323 | 324 | # Example 3.1.12 325 | def ex_3_1_12(): 326 | ts.range_res() 327 | 328 | # Example 3.1.13 329 | def ex_3_1_13(): 330 | test_tgt = rd.Target() 331 | test_tgt.rcs = 5 332 | r = 6E3 333 | az = math.pi/2 - math.pi/4 334 | test_tgt.pos = np.array([r*math.cos(az), r*math.sin(az)]) 335 | 336 | rby.robby(targets=[test_tgt], reset=False, widgets=['bandw', 'coherent', 'freq', 'energy', 'noise_temp', 'num_integ', 'radius']) 337 | 338 | #-------Lab 3.2: Detection------- 339 | 340 | # Example 3.2.1 341 | def ex_3_2_1(): 342 | ts.radar_range_det() 343 | 344 | # Example 3.2.2 345 | def ex_3_2_2(): 346 | ts.radar_range_det(highlight=False) 347 | 348 | # Example 3.2.3 349 | def ex_3_2_3(): 350 | ts.detect_game() 351 | 352 | # Example 3.2.4 353 | def ex_3_2_4(): 354 | ts.threshold() 355 | 356 | # Example 3.2.5 357 | def ex_3_2_5(): 358 | ts.roc() 359 | 360 | # Example 3.2.6 361 | def ex_3_2_6(): 362 | test_tgt = rd.Target() 363 | test_tgt.rcs = 5 364 | r = 6E3 365 | az = math.pi/2 - math.pi/4 366 | test_tgt.pos = np.array([r*math.cos(az), r*math.sin(az)]) 367 | 368 | rby.robby( 369 | targets=[test_tgt], 370 | dets=True, 371 | reset=False, 372 | widgets=['bandw', 'coherent', 'det_thresh', 'freq', 'energy', 'noise_temp', 'num_integ', 'radius'] 373 | ) 374 | 375 | #-------Lab 4.1: Target Parameter Estimation------- 376 | 377 | # Example 4.1.1 378 | def ex_4_1_1(): 379 | ts.dish_pat() 380 | 381 | # Example 4.1.2 382 | def ex_4_1_2(): 383 | 384 | test_tgt = rd.Target() 385 | test_tgt.rcs = 10 386 | r = 5E3 387 | az = math.pi/2 - math.pi/4 388 | test_tgt.pos = np.array([r*math.cos(az), r*math.sin(az)]) 389 | 390 | rby.robby( 391 | targets=[test_tgt], 392 | energy=0.8, 393 | freq=4E3, 394 | radius=0.8, 395 | reset=False, 396 | widgets=[] 397 | ) 398 | 399 | # Example 4.1.3 400 | def ex_4_1_3(): 401 | ts.cross_range() 402 | 403 | # Example 4.1.4 404 | def ex_4_1_4(): 405 | ts.doppler() 406 | 407 | # Example 4.1.5 408 | def ex_4_1_5(): 409 | ts.cw() 410 | 411 | # Example 4.1.6 412 | def ex_4_1_6(): 413 | ts.cw( 414 | freq=2E3, 415 | dr=55, 416 | integ_time=15, 417 | targ_line=False, 418 | widgets=[] 419 | ) 420 | 421 | # Example 4.1.7 422 | def ex_4_1_7(): 423 | ts.rdi() 424 | 425 | #-------Lab 4.2: Target Tracking------- 426 | 427 | # Example 4.2.1 428 | def ex_4_2_1(): 429 | 430 | # Test route 431 | test_route = air.Route() 432 | test_route.start = np.array([-10E3, 0]) 433 | test_route.end = np.array([0, 10E3]) 434 | test_route.lifetime = 100 435 | test_route.vel = (test_route.end - test_route.start)/test_route.lifetime 436 | test_route.speed = np.sqrt(test_route.vel[0]**2 + test_route.vel[1]**2) 437 | test_route.max_range = 10E3 438 | 439 | # Plot and return 440 | rby.robby( 441 | targets=air.to_target([test_route]), 442 | radius=1.0, 443 | freq=5E3, 444 | reset=True, 445 | dets=True, 446 | scan_rate=8, 447 | bandw=2, 448 | widgets=['det_thresh'] 449 | ) 450 | 451 | # Example 4.2.2 452 | def ex_4_2_2(): 453 | ts.propagation() 454 | 455 | # Example 4.2.3 456 | def ex_4_2_3(): 457 | ts.gnn() 458 | 459 | # Example 4.2.4 460 | def ex_4_2_4(): 461 | ts.ekf() 462 | 463 | # Example 4.2.5 464 | def ex_4_2_5(): 465 | 466 | # Test route 467 | routes = air.routes(6, 10E3, 200) 468 | targets = air.to_target(routes) 469 | 470 | # Plot and return 471 | rby.robby( 472 | targets=targets, 473 | radius=1.0, 474 | freq=5E3, 475 | reset=True, 476 | dets=True, 477 | pulses=True, 478 | scan_rate=8, 479 | bandw=2 480 | ) 481 | 482 | #-------Lab 5.1: Radar Design Revisited------- 483 | 484 | # Example 5.1.1 485 | def ex_5_1_1(): 486 | routes = air.routes(6, 10E3, 150) 487 | air.plot_routes(routes) 488 | targets = air.to_target(routes) 489 | 490 | return routes, targets 491 | 492 | # Example 5.1.2 493 | def ex_5_1_2(): 494 | ts.design( 495 | bandw=0.5, 496 | bandw_lim=[0.1, 3], 497 | energy=1, 498 | energy_lim=[0, 1], 499 | freq=500, 500 | freq_lim=[100, 3000], 501 | max_price=50E3, 502 | noise_temp=1000, 503 | noise_temp_lim=[500, 1200], 504 | num_integ=1, 505 | num_integ_lim=[1, 10], 506 | r=1, 507 | r_lim=[1, 20], 508 | radius=0.1, 509 | radius_lim=[0.1, 0.5], 510 | rcs=10, 511 | rcs_lim=[-10, 25], 512 | scan_rate=1, 513 | scan_rate_lim=[1, 10], 514 | metrics=['price'], 515 | widgets=['bandw', 'coherent', 'freq', 'energy', 'noise_temp', 'num_integ', 'radius', 'scan_rate'] 516 | ) 517 | 518 | # Example 5.1.3 519 | def ex_5_1_2b(): 520 | ts.design( 521 | bandw=0.5, 522 | bandw_lim=[0.1, 3], 523 | energy=1, 524 | energy_lim=[0, 1], 525 | freq=500, 526 | freq_lim=[100, 3000], 527 | max_price=50E3, 528 | min_snr=5, 529 | noise_temp=1000, 530 | noise_temp_lim=[500, 1200], 531 | num_integ=1, 532 | num_integ_lim=[1, 10], 533 | r=1, 534 | r_lim=[1, 20], 535 | radius=0.1, 536 | radius_lim=[0.1, 0.5], 537 | rcs=10, 538 | rcs_lim=[-10, 25], 539 | scan_rate=1, 540 | scan_rate_lim=[1, 10], 541 | metrics=['price', 'snr'], 542 | widgets=['bandw', 'coherent', 'freq', 'energy', 'min_snr', 'noise_temp', 'num_integ', 'r', 'radius', 'rcs', 'scan_rate'] 543 | ) 544 | 545 | # Example 5.1.4 546 | def ex_5_1_3a(): 547 | 548 | # Test route 549 | test_route = air.Route() 550 | test_route.start = np.array([-10E3, 0]) 551 | test_route.end = np.array([0, 10E3]) 552 | test_route.lifetime = 100 553 | test_route.vel = (test_route.end - test_route.start)/test_route.lifetime 554 | test_route.speed = np.sqrt(test_route.vel[0]**2 + test_route.vel[1]**2) 555 | test_route.max_range = 10E3 556 | 557 | # Plot and return 558 | air.plot_routes([test_route]) 559 | return air.to_target([test_route]) 560 | 561 | # Example 5.1.5 562 | def ex_5_1_3b(test_tgt): 563 | rby.robby(targets=test_tgt, max_price=50E3, reset=True, show_price=True) 564 | 565 | # Example 5.1.6 566 | def ex_5_1_4a(routes, targets): 567 | air.plot_routes(routes) 568 | 569 | # Example 5.1.7 570 | def ex_5_1_4b(routes, targets): 571 | rby.robby(targets=targets, max_price=50E3, reset=True, show_price=True) -------------------------------------------------------------------------------- /rad/plot.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | import logging 35 | 36 | import matplotlib.gridspec as gridspec 37 | import matplotlib.pyplot as pyp 38 | from matplotlib.axes import Axes 39 | from matplotlib.figure import Figure 40 | from matplotlib import rc 41 | import matplotlib.ticker as tck 42 | import numpy as np 43 | 44 | DEF_SANS = ['Segoe UI', 'Arial', 'DejaVu Sans'] 45 | 46 | def _format_ax(ax, present=False): 47 | 48 | # Check for list 49 | if isinstance(ax, np.ndarray): 50 | for axi in ax: 51 | _format_ax(axi) 52 | return 53 | 54 | # Color scheme 55 | if (present): 56 | face = [0, 0, 0] 57 | edge = [1, 1, 1] 58 | else: 59 | face = [1, 1, 1] 60 | edge = [0, 0, 0] 61 | 62 | # Title 63 | ax.set_title( 64 | "", 65 | size = 22, 66 | name = DEF_SANS, 67 | color = edge, 68 | weight = 'normal' 69 | ) 70 | 71 | # Axis labels 72 | ax.set_xlabel( 73 | "", 74 | size = 16, 75 | name = DEF_SANS, 76 | color = edge, 77 | weight = 'normal' 78 | ) 79 | ax.set_ylabel( 80 | "", 81 | size = 16, 82 | name = DEF_SANS, 83 | color = edge, 84 | weight = 'normal' 85 | ) 86 | 87 | # Spines 88 | ax.spines['bottom'].set_color(edge) 89 | ax.spines['top'].set_color(edge) 90 | ax.spines['right'].set_color(edge) 91 | ax.spines['left'].set_color(edge) 92 | ax.spines['bottom'].set_linewidth(2.0) 93 | ax.spines['top'].set_linewidth(2.0) 94 | ax.spines['right'].set_linewidth(2.0) 95 | ax.spines['left'].set_linewidth(2.0) 96 | 97 | # Tick marks 98 | ax.tick_params( 99 | axis='x', 100 | colors = edge, 101 | labelsize = 14.0, 102 | width = 2.0 103 | ) 104 | ax.tick_params( 105 | axis='y', 106 | colors = edge, 107 | labelsize = 14.0, 108 | width = 2.0 109 | ) 110 | 111 | # Tick labels 112 | formatter = tck.ScalarFormatter() 113 | ax.xaxis.set_major_formatter(formatter) 114 | ax.yaxis.set_major_formatter(formatter) 115 | 116 | def new_plot(*argv, **kwarg): 117 | 118 | axes_width = 0.4 119 | if 'axes_width' in kwarg.keys(): 120 | axes_width = kwarg['axes_width'] 121 | 122 | axes_height = 0.75 123 | if 'axes_height' in kwarg.keys(): 124 | axes_height = kwarg['axes_height'] 125 | 126 | projection = 'rectilinear' 127 | if 'projection' in kwarg.keys(): 128 | projection = kwarg['projection'] 129 | 130 | layout = 'center' 131 | if 'layout' in kwarg.keys(): 132 | layout = kwarg['layout'] 133 | 134 | # Face and edge colors 135 | face = [1, 1, 1] 136 | edge = [0, 0, 0] 137 | 138 | # Set font to available sans serif 139 | font_prop = { 140 | 'family': 'sans-serif', 141 | 'sans-serif': DEF_SANS, 142 | 'weight' : 'normal', 143 | 'size' : 14.0 144 | } 145 | rc(('font'), **font_prop) 146 | 147 | # Suppress font messages 148 | logging.getLogger('matplotlib.font_manager').setLevel(logging.ERROR) 149 | 150 | # Disable open figure warning 151 | rc('figure', max_open_warning=0) 152 | 153 | # Avoid double axes 154 | if not (layout == 'center'): 155 | pyp.ioff() 156 | 157 | # Layout 158 | if (layout == 'center'): 159 | fig_height = 500 160 | fig_width = 1050 161 | elif (layout == 'sidebar'): 162 | fig_height = 500 163 | fig_width = 600 164 | axes_height = 0.76 165 | axes_width = 0.63 166 | elif (layout == 'sidebar-colorbar'): 167 | fig_height = 500 168 | fig_width = 700 169 | axes_height = 0.76 170 | axes_width = 0.67 171 | 172 | # Build figure 173 | dpi = 100 174 | fig = pyp.figure( 175 | figsize = (fig_width/dpi, fig_height/dpi), 176 | dpi = dpi, 177 | edgecolor = edge, 178 | facecolor = face 179 | ) 180 | fig.canvas.header_visible = False 181 | ax = pyp.axes( 182 | [(1 - axes_width)/2, (1 - axes_height)*0.75, axes_width, axes_height], 183 | facecolor = face, 184 | projection = projection 185 | ) 186 | 187 | # Avoid double axes 188 | pyp.ion() 189 | 190 | # Format axes 191 | _format_ax(ax) 192 | 193 | return fig, ax 194 | 195 | def new_plot2(**kwarg): 196 | 197 | axes_width = 0.4 198 | if 'axes_width' in kwarg.keys(): 199 | axes_width = kwarg['axes_width'] 200 | 201 | present = False 202 | if (present): 203 | face = [0, 0, 0] 204 | edge = [1, 1, 1] 205 | else: 206 | face = [1, 1, 1] 207 | edge = [0, 0, 0] 208 | 209 | # Set font to Arial 210 | font_prop = { 211 | 'family': 'sans-serif', 212 | 'sans-serif': DEF_SANS, 213 | 'weight' : 'normal', 214 | 'size' : 14.0 215 | } 216 | rc(('font'), **font_prop) 217 | 218 | # Grid 219 | gs = { 220 | "left": 0.17, 221 | "bottom": 0.15, 222 | "right": 0.92, 223 | "top": 0.95, 224 | "hspace": 0.3 225 | } 226 | 227 | pyp.ioff() 228 | dpi = 100 229 | fig, axs = pyp.subplots( 230 | nrows=2, 231 | ncols=1, 232 | gridspec_kw=gs, 233 | figsize = (700/dpi, 500/dpi), 234 | dpi = dpi, 235 | edgecolor = edge, 236 | facecolor = face 237 | ) 238 | fig.canvas.header_visible = False 239 | pyp.ion() 240 | 241 | # Format axes 242 | _format_ax(axs) 243 | 244 | return fig, axs 245 | 246 | def new_plot3(*argv, **kwarg): 247 | 248 | present = False 249 | if (present): 250 | face = [0, 0, 0] 251 | edge = [1, 1, 1] 252 | else: 253 | face = [1, 1, 1] 254 | edge = [0, 0, 0] 255 | 256 | # Set font to Arial 257 | font_prop = { 258 | 'family': 'sans-serif', 259 | 'sans-serif': 'Arial', 260 | 'weight' : 'bold', 261 | 'size' : 14.0 262 | } 263 | rc(('font'), **font_prop) 264 | 265 | dpi = 100 266 | fig = pyp.figure( 267 | figsize=(600/dpi, 400/dpi), 268 | dpi=dpi, 269 | edgecolor=edge, 270 | facecolor=face 271 | ) 272 | ax = pyp.axes( 273 | [0.1667, 0.1286, 0.6667, 0.8071], 274 | facecolor=face, 275 | projection='3d' 276 | ) 277 | 278 | # Title 279 | ax.set_title( 280 | "", 281 | size = 22, 282 | name = 'Arial', 283 | color = edge, 284 | weight = 'bold' 285 | ) 286 | 287 | # Axis labels 288 | ax.set_xlabel( 289 | "", 290 | size = 16, 291 | name = 'Arial', 292 | color = edge, 293 | weight = 'bold' 294 | ) 295 | ax.set_ylabel( 296 | "", 297 | size = 16, 298 | name = 'Arial', 299 | color = edge, 300 | weight = 'bold' 301 | ) 302 | ax.set_zlabel( 303 | "", 304 | size = 16, 305 | name = 'Arial', 306 | color = edge, 307 | weight = 'bold' 308 | ) 309 | 310 | # Spines 311 | ax.spines['bottom'].set_color(edge) 312 | ax.spines['top'].set_color(edge) 313 | ax.spines['right'].set_color(edge) 314 | ax.spines['left'].set_color(edge) 315 | ax.spines['bottom'].set_linewidth(2.0) 316 | ax.spines['top'].set_linewidth(2.0) 317 | ax.spines['right'].set_linewidth(2.0) 318 | ax.spines['left'].set_linewidth(2.0) 319 | 320 | # Tick marks 321 | ax.tick_params( 322 | axis='x', 323 | colors = edge, 324 | labelsize = 14.0, 325 | width = 2.0 326 | ) 327 | ax.tick_params( 328 | axis='y', 329 | colors = edge, 330 | labelsize = 14.0, 331 | width = 2.0 332 | ) 333 | ax.tick_params( 334 | axis='z', 335 | colors = edge, 336 | labelsize = 14.0, 337 | width = 2.0 338 | ) 339 | 340 | # Tick labels 341 | formatter = tck.ScalarFormatter() 342 | ax.xaxis.set_major_formatter(formatter) 343 | ax.yaxis.set_major_formatter(formatter) 344 | ax.zaxis.set_major_formatter(formatter) 345 | 346 | # Turn on interaction 347 | pyp.ion() 348 | 349 | return fig, ax 350 | 351 | def new_plot_text(*argv, **kwarg): 352 | 353 | present = False 354 | if (present): 355 | face = [0, 0, 0] 356 | edge = [1, 1, 1] 357 | else: 358 | face = [1, 1, 1] 359 | edge = [0, 0, 0] 360 | 361 | # Set font to Arial 362 | font_prop = { 363 | 'family': 'sans-serif', 364 | 'sans-serif': 'Arial', 365 | 'weight' : 'bold', 366 | 'size' : 14.0 367 | } 368 | rc(('font'), **font_prop) 369 | 370 | dpi = 100 371 | fig = pyp.figure( 372 | figsize = (800/dpi, 400/dpi), 373 | dpi = dpi, 374 | edgecolor = edge, 375 | facecolor = face, 376 | title='' 377 | ) 378 | ax = pyp.axes( 379 | [0.1667, 0.1286, 0.5, 0.8071], 380 | facecolor = face, 381 | ) 382 | 383 | # Title 384 | ax.set_title( 385 | "", 386 | size = 22, 387 | name = 'Arial', 388 | color = edge, 389 | weight = 'bold' 390 | ) 391 | 392 | # Axis labels 393 | ax.set_xlabel( 394 | "", 395 | size = 16, 396 | name = 'Arial', 397 | color = edge, 398 | weight = 'bold' 399 | ) 400 | ax.set_ylabel( 401 | "", 402 | size = 16, 403 | name = 'Arial', 404 | color = edge, 405 | weight = 'bold' 406 | ) 407 | 408 | # Spines 409 | ax.spines['bottom'].set_color(edge) 410 | ax.spines['top'].set_color(edge) 411 | ax.spines['right'].set_color(edge) 412 | ax.spines['left'].set_color(edge) 413 | ax.spines['bottom'].set_linewidth(2.0) 414 | ax.spines['top'].set_linewidth(2.0) 415 | ax.spines['right'].set_linewidth(2.0) 416 | ax.spines['left'].set_linewidth(2.0) 417 | 418 | # Tick marks 419 | ax.tick_params( 420 | axis='x', 421 | colors = edge, 422 | labelsize = 14.0, 423 | width = 2.0 424 | ) 425 | ax.tick_params( 426 | axis='y', 427 | colors = edge, 428 | labelsize = 14.0, 429 | width = 2.0 430 | ) 431 | 432 | # Tick labels 433 | formatter = tck.ScalarFormatter() 434 | ax.xaxis.set_major_formatter(formatter) 435 | ax.yaxis.set_major_formatter(formatter) 436 | 437 | # Turn on interaction 438 | pyp.ion() 439 | 440 | return fig, ax 441 | 442 | def new_rad_plot(*argv, **kwarg): 443 | 444 | present = False 445 | if (present): 446 | face = [0, 0, 0] 447 | edge = [1, 1, 1] 448 | else: 449 | face = [1, 1, 1] 450 | edge = [0, 0, 0] 451 | 452 | # Set font to Arial 453 | font_prop = { 454 | 'family': 'sans-serif', 455 | 'sans-serif': DEF_SANS, 456 | 'weight' : 'normal', 457 | 'size' : 14.0 458 | } 459 | rc(('font'), **font_prop) 460 | 461 | pyp.ioff() 462 | 463 | dpi = 100 464 | fig_scan = pyp.figure( 465 | figsize = (9*90/dpi, 7*90/dpi), 466 | dpi = dpi, 467 | edgecolor = edge, 468 | facecolor = face 469 | ) 470 | fig_scan.canvas.header_visible = False 471 | ax_scan = fig_scan.add_axes( 472 | [0.05, 0.025, 0.93, 0.93], 473 | facecolor=face, 474 | projection='polar' 475 | ) 476 | fig_pulse = pyp.figure( 477 | figsize = (9*90/dpi, 2*110/dpi), 478 | dpi = dpi, 479 | edgecolor = edge, 480 | facecolor = face 481 | ) 482 | fig_pulse.canvas.header_visible = False 483 | ax_pulse = fig_pulse.add_axes( 484 | [0.1, 0.27, 0.85, 0.7], 485 | facecolor=face, 486 | projection='rectilinear' 487 | ) 488 | 489 | pyp.ion() 490 | 491 | # Put zero azimuth at top 492 | ax_scan.set_theta_zero_location("N") 493 | ax_scan.set_theta_direction(-1) 494 | 495 | # Title 496 | ax_scan.set_title( 497 | "", 498 | size = 22, 499 | name = DEF_SANS, 500 | color = edge, 501 | weight = 'normal' 502 | ) 503 | 504 | # Turn on interaction 505 | pyp.ion() 506 | 507 | return fig_scan, ax_scan, fig_pulse, ax_pulse -------------------------------------------------------------------------------- /rad/quiz.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | from IPython.display import display 35 | import ipywidgets as wdg 36 | import math 37 | import numpy as np 38 | from rad.const import k, c 39 | import rad.radar as rd 40 | 41 | # jupyter_intro 42 | 43 | # Simple quiz with scalar value 44 | def new_quiz(prompt, val, abs_tol=None, rel_tol=1E-2): 45 | """ 46 | Generate quiz answer box. 47 | 48 | Inputs: 49 | - prompt [str]: Question prompt 50 | - val [float]: True answer 51 | - abs_tol [float]: Absolute tolerance; if None, uses rel_tol 52 | - rel_tol [float]: Relative tolerance 53 | 54 | Outputs: 55 | (none) 56 | """ 57 | title = wdg.HTML(value = f"{prompt}") 58 | answer = wdg.FloatText() 59 | submit = wdg.Button(description="Submit") 60 | result = wdg.HTML(value = f"Ready") 61 | ansbox = wdg.VBox([wdg.HBox([title, answer, result]), submit]) 62 | display(ansbox) 63 | 64 | def check_ans(b): 65 | if abs_tol and (abs(answer.value - val) < abs_tol): 66 | result.value = f"Correct!" 67 | elif rel_tol and ((abs(answer.value - val)/abs(val)) < rel_tol): 68 | result.value = f"Correct!" 69 | else: 70 | result.value = f"Incorrect." 71 | 72 | submit.on_click(check_ans) 73 | 74 | #-------Lab 1.1: Introduction to Labs------- 75 | 76 | # Q1.1.1 77 | def quiz_1_1_1(): 78 | prompt = 'Enter answer:' 79 | val = 1.0 80 | tol = 0.01 81 | new_quiz(prompt, val, rel_tol=tol) 82 | 83 | # Q1.1.2a 84 | def quiz_1_1_2a(): 85 | prompt = 'Enter answer:' 86 | val = (3*7.1 - 5.2) 87 | tol = 0.01 88 | new_quiz(prompt, val, rel_tol=tol) 89 | 90 | # Q1.1.2b 91 | def quiz_1_1_2b(): 92 | prompt = 'Enter answer:' 93 | val = (math.log10(2.72**3) + math.cos(4*math.pi/7)) 94 | tol = 0.01 95 | new_quiz(prompt, val, rel_tol=tol) 96 | 97 | # Q1.1.3 98 | def quiz_1_1_3(): 99 | prompt = 'Enter answer:' 100 | val = 7.76E4/5.1E-3 101 | tol = 0.01 102 | new_quiz(prompt, val, rel_tol=tol) 103 | 104 | # Q1.1.4a 105 | def quiz_1_1_4a(): 106 | prompt = 'Enter value of y:' 107 | val = 15.2/-10 + 1.2 108 | tol = 0.01 109 | new_quiz(prompt, val, rel_tol=tol) 110 | 111 | # Q1.1.4b 112 | def quiz_1_1_4b(): 113 | prompt = 'Enter value of y:' 114 | val = math.cos(5*math.pi/2 + -0.1) 115 | tol = 0.01 116 | new_quiz(prompt, val, rel_tol=tol) 117 | 118 | # Q1.1.5a 119 | def quiz_1_1_5a(): 120 | prompt = 'Enter answer (in dB):' 121 | val = rd.to_db(1.5E5) 122 | tol = 0.01 123 | new_quiz(prompt, val, rel_tol=tol) 124 | 125 | # Q1.1.5b 126 | def quiz_1_1_5b(): 127 | prompt = 'Enter answer (in dB):' 128 | val = rd.to_db(7.2E-7) 129 | tol = 0.01 130 | new_quiz(prompt, val, rel_tol=tol) 131 | 132 | # Q1.1.6a 133 | def quiz_1_1_6a(): 134 | prompt = 'Enter answer:' 135 | val = rd.from_db(51.2) 136 | tol = 0.01 137 | new_quiz(prompt, val, rel_tol=tol) 138 | 139 | # Q1.1.6b 140 | def quiz_1_1_6b(): 141 | prompt = 'Enter answer:' 142 | val = rd.from_db(-20.1) 143 | tol = 0.01 144 | new_quiz(prompt, val, rel_tol=tol) 145 | 146 | # Q1.1.7 147 | def quiz_1_1_7(): 148 | prompt = 'Enter answer (in dB):' 149 | val = rd.to_db(3.7**5) 150 | tol = 0.01 151 | new_quiz(prompt, val, rel_tol=tol) 152 | 153 | # Q1.1.8a 154 | def quiz_1_1_8a(): 155 | prompt = 'Enter answer:' 156 | val = 3 157 | tol = 0.01 158 | new_quiz(prompt, val, rel_tol=tol) 159 | 160 | # Q1.1.8b 161 | def quiz_1_1_8b(): 162 | prompt = 'Enter answer (in s, within ±0.05 s):' 163 | val = 0.5 164 | tol = 0.05 165 | new_quiz(prompt, val, abs_tol=tol) 166 | 167 | # Q1.1.8c 168 | def quiz_1_1_8c(): 169 | prompt = 'Enter answer (in Hz, within ±0.2 Hz):' 170 | val = 2 171 | tol = 0.2 172 | new_quiz(prompt, val, abs_tol=tol) 173 | 174 | #-------Lab 1.2: Introduction to Radar------- 175 | 176 | # Q1.2.1a 177 | def quiz_1_2_1a(): 178 | prompt = 'Enter wavelength (in m):' 179 | val = rd.wavelen(1000, propvel=1000) 180 | tol = 0.01 181 | new_quiz(prompt, val, rel_tol=tol) 182 | 183 | # Q1.2.1b 184 | def quiz_1_2_1b(): 185 | prompt = 'Enter wavelength (in m):' 186 | val = rd.wavelen(1000, propvel=2000) 187 | tol = 0.01 188 | new_quiz(prompt, val, rel_tol=tol) 189 | 190 | # Q1.2.1c 191 | def quiz_1_2_1c(): 192 | prompt = 'Enter wavelength (in m):' 193 | val = rd.wavelen(500, propvel=1000) 194 | tol = 0.01 195 | new_quiz(prompt, val, rel_tol=tol) 196 | 197 | # Q1.2.2a 198 | def quiz_1_2_2a(): 199 | prompt = 'Enter wavelength (in m):' 200 | val = rd.wavelen(500E6, propvel=3E8) 201 | tol = 0.01 202 | new_quiz(prompt, val, rel_tol=tol) 203 | 204 | # Q1.2.2b 205 | def quiz_1_2_2b(): 206 | prompt = 'Enter frequency (in Hz):' 207 | val = 1500/5 208 | tol = 0.01 209 | new_quiz(prompt, val, rel_tol=tol) 210 | 211 | # Q1.2.3a 212 | def quiz_1_2_3a(): 213 | prompt = 'Enter time (in ms):' 214 | val = math.sqrt(75**2 + 80**2) 215 | tol = 3 216 | new_quiz(prompt, val, abs_tol=tol) 217 | 218 | # Q1.2.3b 219 | def quiz_1_2_3b(): 220 | prompt = 'Enter time (in ms):' 221 | val = 2*math.sqrt(75**2 + 80**2) 222 | tol = 3 223 | new_quiz(prompt, val, abs_tol=tol) 224 | 225 | # Q1.2.4a 226 | def quiz_1_2_4a(): 227 | prompt = 'Enter time (in s):' 228 | val = 2*1000E3/3E8 229 | tol = 0.01 230 | new_quiz(prompt, val, rel_tol=tol) 231 | 232 | # Q1.2.4b 233 | def quiz_1_2_4b(): 234 | prompt = 'Enter range (in m):' 235 | val = 3E8*0.0057/2 236 | tol = 0.01 237 | new_quiz(prompt, val, rel_tol=tol) 238 | 239 | # Q1.2.5 240 | def quiz_1_2_5(): 241 | prompt = 'Enter range (in m):' 242 | val = math.sqrt(75**2 + 100**2) 243 | tol = 0.01 244 | new_quiz(prompt, val, rel_tol=tol) 245 | 246 | # Q1.2.6a 247 | def quiz_1_2_6a(): 248 | prompt = 'Enter range (in m):' 249 | val = math.sqrt(50**2 + 50**2) 250 | tol = 0.01 251 | new_quiz(prompt, val, rel_tol=tol) 252 | 253 | # Q1.2.6b 254 | def quiz_1_2_6b(): 255 | prompt = 'Enter azimuth (in deg, within +/- 3 deg):' 256 | val = 90 - rd.rad2deg(math.atan2(50, 50)) 257 | tol = 3 258 | new_quiz(prompt, val, abs_tol=tol) 259 | 260 | # Q1.2.7a 261 | def quiz_1_2_7a(): 262 | prompt = 'Enter range (in m):' 263 | val = math.sqrt(40**2 + 20**2) 264 | tol = 0.01 265 | new_quiz(prompt, val, rel_tol=tol) 266 | 267 | # Q1.2.7b 268 | def quiz_1_2_7b(): 269 | prompt = 'Enter azimuth (in deg, within +/- 3 deg):' 270 | val = 90 - rd.rad2deg(math.atan2(-20, 40)) 271 | tol = 3 272 | new_quiz(prompt, val, abs_tol=tol) 273 | 274 | # Q1.2.8a 275 | def quiz_1_2_8a(): 276 | prompt = 'Enter beamwidth (in deg):' 277 | val = 70*0.12/3.8 278 | tol = 0.01 279 | new_quiz(prompt, val, rel_tol=tol) 280 | 281 | # Q1.2.8b 282 | def quiz_1_2_8b(): 283 | prompt = 'Enter diameter (in m):' 284 | val = 70*rd.wavelen(10E9, propvel=3E8) 285 | tol = 0.01 286 | new_quiz(prompt, val, rel_tol=tol) 287 | 288 | # Q1.2.9 289 | def quiz_1_2_9(): 290 | prompt = 'Enter transmit gain (in dB):' 291 | val = rd.to_db(4*math.pi*2.1*3.3/rd.wavelen(15E3, propvel=2E3)**2) 292 | tol = 0.01 293 | new_quiz(prompt, val, rel_tol=tol) 294 | 295 | # Q1.2.10a 296 | def quiz_1_2_10a(): 297 | prompt = 'Enter wavelength (in m):' 298 | val = rd.wavelen(5E9) 299 | tol = 0.01 300 | new_quiz(prompt, val, rel_tol=tol) 301 | 302 | # Q1.2.10b 303 | def quiz_1_2_10b(): 304 | prompt = 'Enter beamwidth (in deg):' 305 | val = 70*rd.wavelen(5E9)/4.4 306 | tol = 0.01 307 | new_quiz(prompt, val, rel_tol=tol) 308 | 309 | # Q1.2.10c 310 | def quiz_1_2_10c(): 311 | prompt = 'Enter transmit gain (in dB):' 312 | val = rd.to_db(4*math.pi*(math.pi*2.2**2)/rd.wavelen(5E9)**2) 313 | tol = 0.01 314 | new_quiz(prompt, val, rel_tol=tol) 315 | 316 | #-------Lab 2.1: Radar Range Equation------- 317 | 318 | # Q2.1.1a 319 | def quiz_2_1_1a(): 320 | prompt = 'Enter SNR (in dB):' 321 | val = rd.to_db(100/5.3) 322 | tol = 0.01 323 | new_quiz(prompt, val, rel_tol=tol) 324 | 325 | # Q2.1.1b 326 | def quiz_2_1_1b(): 327 | prompt = 'Enter signal energy (in J):' 328 | val = rd.from_db(15)*2.2 329 | tol = 0.01 330 | new_quiz(prompt, val, rel_tol=tol) 331 | 332 | # Q2.1.2a 333 | def quiz_2_1_2a(): 334 | prompt = 'Enter received power (in W):' 335 | val = rd.friis(50E3, 10E3, area=10, gain=rd.from_db(35.0)) 336 | tol = 0.01 337 | new_quiz(prompt, val, rel_tol=tol) 338 | 339 | # Q2.1.2b 340 | def quiz_2_1_2b(): 341 | prompt = 'Enter dish radius (in m):' 342 | val = np.sqrt(rd.gain2area((1/100E3/5)*4*np.pi*(10E3)**2, 3.5E9)/np.pi) 343 | tol = 0.01 344 | new_quiz(prompt, val, rel_tol=tol) 345 | 346 | # Q2.1.3a 347 | def quiz_2_1_3a(): 348 | prompt = 'Enter incident power (in W):' 349 | val = rd.friis(50E3, 150E3, area=rd.from_db(5), gain=rd.from_db(30)) 350 | tol = 0.01 351 | new_quiz(prompt, val, rel_tol=tol) 352 | 353 | # Q2.1.3b 354 | def quiz_2_1_3b(): 355 | prompt = 'Enter range (in m):' 356 | val = math.sqrt(500E3*1*rd.from_db(25)/4/math.pi/0.1E-3) 357 | tol = 0.01 358 | new_quiz(prompt, val, rel_tol=tol) 359 | 360 | # Q2.1.4a 361 | def quiz_2_1_4a(): 362 | prompt = 'Enter received power (in W):' 363 | val = rd.rx_power(100E3, 600E3, 3E9, rcs=rd.from_db(5), gain=rd.from_db(80)) 364 | tol = 0.01 365 | new_quiz(prompt, val, rel_tol=tol) 366 | 367 | # Q2.1.4b 368 | def quiz_2_1_4b(): 369 | prompt = 'Enter effective aperture area (in m^2):' 370 | val = rd.gain2area(rd.from_db(24.3), 1.3E9) 371 | tol = 0.01 372 | new_quiz(prompt, val, rel_tol=tol) 373 | 374 | # Q2.1.4c 375 | def quiz_2_1_4c(): 376 | prompt = 'Enter RCS (in dBsm):' 377 | wlen = rd.wavelen(1.5E9) 378 | val = rd.to_db(10E-12*(10E3**4)*(4*np.pi)**3/30/rd.from_db(80)/wlen**2) 379 | tol = 0.01 380 | new_quiz(prompt, val, rel_tol=tol) 381 | 382 | # Q2.1.5 383 | def quiz_2_1_5(): 384 | prompt = 'Enter noise energy (in J):' 385 | val = k*722 386 | tol = 0.01 387 | new_quiz(prompt, val, rel_tol=tol) 388 | 389 | # Q2.1.6 390 | def quiz_2_1_6(): 391 | prompt = 'Enter SNR (in dB):' 392 | val = rd.to_db(rd.rx_snr(200E3, 50, 5E9, 750, rcs=rd.from_db(-5), gain=rd.from_db(70))) 393 | tol = 0.01 394 | new_quiz(prompt, val, rel_tol=tol) 395 | 396 | # Q2.1.7 397 | def quiz_2_1_7(): 398 | prompt = 'Enter SNR (in dB):' 399 | val = rd.to_db(rd.snrconv(rd.from_db(17), 75E3, rd.from_db(-10), 50E3, rd.from_db(-15))) 400 | tol = 0.01 401 | new_quiz(prompt, val, rel_tol=tol) 402 | 403 | #-------Lab 2.2: Basic Radar Design------- 404 | 405 | #-------Lab 3.1: Radar Transmissions and Receptions------- 406 | 407 | # Q3.1.1 408 | def quiz_3_1_1(): 409 | prompt = 'Enter pulsewidth (in µs):' 410 | val = 7.2 411 | tol = 0.1 412 | new_quiz(prompt, val, abs_tol=tol) 413 | 414 | # Q3.1.2a 415 | def quiz_3_1_2a(): 416 | prompt = 'Enter PRI (in s):' 417 | val = 1/10E3 418 | tol = 0.01 419 | new_quiz(prompt, val, rel_tol=tol) 420 | 421 | # Q3.1.2b 422 | def quiz_3_1_2b(): 423 | prompt = 'Enter duty cycle:' 424 | val = 150E-6*1E3 425 | tol = 0.01 426 | new_quiz(prompt, val, rel_tol=tol) 427 | 428 | # Q3.1.3a 429 | def quiz_3_1_3a(): 430 | prompt = 'Enter delay (in ns):' 431 | val = -2.83 432 | tol = 0.04 433 | new_quiz(prompt, val, abs_tol=tol) 434 | 435 | # Q3.1.3b 436 | def quiz_3_1_3b(): 437 | prompt = 'Enter delay (in ns):' 438 | val = -2.83 439 | tol = 0.04 440 | new_quiz(prompt, val, abs_tol=tol) 441 | 442 | # Q3.1.4a 443 | def quiz_3_1_4a(): 444 | prompt = 'Enter phase (in deg):' 445 | val = 168 446 | tol = 2 447 | new_quiz(prompt, val, abs_tol=tol) 448 | 449 | # Q3.1.4b 450 | def quiz_3_1_4b(): 451 | prompt = 'Enter phase (in deg):' 452 | val = 120 453 | tol = 2 454 | new_quiz(prompt, val, abs_tol=tol) 455 | 456 | # Q3.1.5 457 | def quiz_3_1_5(): 458 | prompt = 'Enter bandwidth (in Hz):' 459 | val = c/2/10 460 | tol = 0.01 461 | new_quiz(prompt, val, rel_tol=tol) 462 | 463 | #-------Lab 3.2: Detection------- 464 | 465 | # Q3.2.1a 466 | def quiz_3_2_1a(): 467 | prompt = 'Enter prob. of detection:' 468 | val = 0.69 469 | tol = 0.02 470 | new_quiz(prompt, val, abs_tol=tol) 471 | 472 | # Q3.2.1b 473 | def quiz_3_2_1b(): 474 | prompt = 'Enter prob. of false alarm:' 475 | val = 0.17 476 | tol = 0.02 477 | new_quiz(prompt, val, abs_tol=tol) 478 | 479 | #-------Lab 4.1: Target Parameter Estimation------- 480 | 481 | # Q4.1.1a 482 | def quiz_4_1_1a(): 483 | prompt = 'Enter range (in m):' 484 | val = c*0.00333/2 485 | tol = 0.01 486 | new_quiz(prompt, val, rel_tol=tol) 487 | 488 | # Q4.1.1b 489 | def quiz_4_1_1b(): 490 | prompt = 'Enter range resolution (in m):' 491 | val = rd.range_res(30E6) 492 | tol = 0.01 493 | new_quiz(prompt, val, rel_tol=tol) 494 | 495 | # Q4.1.1c 496 | def quiz_4_1_1c(): 497 | prompt = 'Enter range accuracy (in m):' 498 | val = rd.range_res(30E6)/np.sqrt(rd.from_db(11)) 499 | tol = 0.01 500 | new_quiz(prompt, val, rel_tol=tol) 501 | 502 | # Q4.1.2a 503 | def quiz_4_1_2a(): 504 | prompt = 'Enter angle accuracy (in deg):' 505 | val = rd.dish_beamw(2, 3.5E9) 506 | tol = 0.01 507 | new_quiz(prompt, val, rel_tol=tol) 508 | 509 | # Q4.1.2b 510 | def quiz_4_1_2b(): 511 | prompt = 'Enter angle accuracy (in deg):' 512 | val = rd.dish_beamw(2, 3.5E9)/np.sqrt(rd.from_db(14)) 513 | tol = 0.01 514 | new_quiz(prompt, val, rel_tol=tol) 515 | 516 | # Q4.1.3 517 | def quiz_4_1_3(): 518 | prompt = 'Enter cross-range resolution (in m):' 519 | val = 50E3*rd.wavelen(3.5E9)/2.1 520 | tol = 0.01 521 | new_quiz(prompt, val, rel_tol=tol) 522 | 523 | # Q4.1.4a 524 | def quiz_4_1_4a(): 525 | prompt = 'Enter Doppler shift (in Hz):' 526 | val = rd.dopp_shift(500, 1.5E9) 527 | tol = 0.01 528 | new_quiz(prompt, val, rel_tol=tol) 529 | 530 | # Q4.1.4b 531 | def quiz_4_1_4b(): 532 | prompt = 'Enter range rate (in m/s):' 533 | val = -c*10E3/2/3.0E9 534 | tol = 0.01 535 | new_quiz(prompt, val, rel_tol=tol) 536 | 537 | # Q4.1.5 538 | def quiz_4_1_5(): 539 | prompt = 'Enter range rate (in m/s):' 540 | val = 55.0 541 | tol = 1.0 542 | new_quiz(prompt, val, abs_tol=tol) 543 | 544 | # Q4.1.6a 545 | def quiz_4_1_6a(): 546 | prompt = 'Enter range rate resolution (in m/s):' 547 | val = c/2/5E9/50E-3 548 | tol = 0.01 549 | new_quiz(prompt, val, rel_tol=tol) 550 | 551 | # Q4.1.6b 552 | def quiz_4_1_6b(): 553 | prompt = 'Enter range rate (in m/s):' 554 | val = c*500/4/5E9 555 | tol = 0.01 556 | new_quiz(prompt, val, rel_tol=tol) 557 | 558 | # Q4.1.7a 559 | def quiz_4_1_7a(): 560 | prompt = 'Enter RCS (in dBsm):' 561 | val = rd.to_db(rd.from_db(0)*(rd.from_db(13)/rd.from_db(15))*(65E3/50E3)**4) 562 | tol = 0.01 563 | new_quiz(prompt, val, rel_tol=tol) 564 | 565 | # Q4.1.7b 566 | def quiz_4_1_7b(): 567 | prompt = 'Enter RCS (in dBsm):' 568 | val = rd.to_db(rd.from_db(5)*(rd.from_db(13)/rd.from_db(7))*(100E3/500E3)**4) 569 | tol = 0.01 570 | new_quiz(prompt, val, rel_tol=tol) 571 | 572 | # Q5.1.1a 573 | def quiz_5_1_1a(): 574 | prompt = 'Enter wavelength (in m):' 575 | val = rd.wavelen(10E9) 576 | tol = 0.01 577 | new_quiz(prompt, val, rel_tol=tol) 578 | 579 | # Q5.1.1b 580 | def quiz_5_1_1b(): 581 | prompt = 'Enter beamwidth (in deg):' 582 | val = 70*rd.wavelen(10E9)/4 583 | tol = 0.01 584 | new_quiz(prompt, val, rel_tol=tol) 585 | 586 | # Q5.1.1c 587 | def quiz_5_1_1c(): 588 | prompt = 'Enter transmit gain (in dB):' 589 | val = rd.to_db(rd.dish_gain(2, 10E9)) 590 | tol = 0.01 591 | new_quiz(prompt, val, rel_tol=tol) 592 | 593 | # Q5.1.2a 594 | def quiz_5_1_2a(): 595 | prompt = 'Enter received energy (in J):' 596 | val = rd.rx_energy(200E3, 10, 2E9, gain=rd.area2gain(10, 2E9)**2, rcs=rd.from_db(-5)) 597 | tol = 0.01 598 | new_quiz(prompt, val, rel_tol=tol) 599 | 600 | # Q5.1.2b 601 | def quiz_5_1_2b(): 602 | prompt = 'Enter SNR (in dB):' 603 | val = rd.to_db(rd.rx_energy(200E3, 10, 2E9, gain=rd.area2gain(10, 2E9)**2, rcs=rd.from_db(-5))) - \ 604 | rd.to_db(k*500) 605 | tol = 0.01 606 | new_quiz(prompt, val, rel_tol=tol) 607 | 608 | # Q5.1.3a 609 | def quiz_5_1_3a(): 610 | prompt = 'Enter duty cycle:' 611 | val = 100E-6*2E3 612 | tol = 0.01 613 | new_quiz(prompt, val, rel_tol=tol) 614 | 615 | # Q5.1.3b 616 | def quiz_5_1_3b(): 617 | prompt = 'Enter number of pulses:' 618 | val = math.ceil(rd.from_db(12)) 619 | tol = 0.01 620 | new_quiz(prompt, val, rel_tol=tol) 621 | 622 | # Q5.1.4a 623 | def quiz_5_1_4a(): 624 | prompt = 'Enter bandwidth (in Hz):' 625 | val = c/2/1.5 626 | tol = 0.01 627 | new_quiz(prompt, val, rel_tol=tol) 628 | 629 | # Q5.1.4b 630 | def quiz_5_1_4b(): 631 | prompt = 'Enter cross range resolution (in m):' 632 | beamw = 57.3*rd.wavelen(5E9)/5.2 633 | val = 90E3*beamw/57.3 634 | tol = 0.01 635 | new_quiz(prompt, val, rel_tol=tol) 636 | 637 | # Q5.1.4c 638 | def quiz_5_1_4c(): 639 | prompt = 'Enter range rate (in m/s):' 640 | val = c*700/4/3E9 641 | tol = 0.01 642 | new_quiz(prompt, val, rel_tol=tol) 643 | 644 | # Q5.1.5 645 | def quiz_5_1_5(targets): 646 | for ii in range(len(targets)): 647 | prompt = f'Enter route number for target with RCS {targets[ii].rcs:.2f} dBsm:' 648 | val = targets[ii].route 649 | tol = 0.01 650 | new_quiz(prompt, val, abs_tol=tol) 651 | -------------------------------------------------------------------------------- /rad/radar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | # Imports 35 | import rad.const as cnst 36 | from math import pi 37 | import numpy as np 38 | from scipy.special import j1 39 | from scipy.optimize import linear_sum_assignment 40 | 41 | def area2gain(area, freq): 42 | """ 43 | Convert effective aperture area to gain. 44 | 45 | Inputs: 46 | - area [float]: Effective aperture area (m^2) 47 | - freq [float]: Transmit frequency (Hz) 48 | 49 | Outputs: 50 | - gain [float]: Gain 51 | """ 52 | 53 | wlen = wavelen(freq) 54 | return 4*pi*area/(wlen**2) 55 | 56 | def cw(freq_bins, freq=3E9, dr=0, integ_time=0.1): 57 | """ 58 | Doppler profile with continuous wave radar for two targets. 59 | 60 | Inputs: 61 | - f [ArrayLike]: Frequency bins (Hz) 62 | - obs_time [float]: Observation time (s) 63 | 64 | Outputs: 65 | - prof [ArrayLike]: Doppler profile (V) 66 | """ 67 | 68 | # Initialize output 69 | pat = np.zeros(freq_bins.shape) 70 | 71 | # Add target 72 | pat += cw_pat(freq_bins - dopp_shift(dr, freq), integ_time) 73 | 74 | return pat 75 | 76 | def cw_pat(freq_bins, obs_time): 77 | """ 78 | Doppler profile for continuous wave radar. 79 | 80 | Inputs: 81 | - f [ArrayLike]: Frequency bins (Hz) 82 | - obs_time [float]: Observation time (s) 83 | 84 | Outputs: 85 | - prof [ArrayLike]: Doppler profile (V) 86 | """ 87 | 88 | return sinc(freq_bins, obs_time) 89 | 90 | def deg2rad(x): 91 | """ 92 | Convert from degrees to radians. 93 | 94 | Inputs: 95 | - x [float]: Input data (deg) 96 | 97 | Outputs: 98 | - y [float]: Output data (rad) 99 | """ 100 | 101 | return (pi/180)*x 102 | 103 | def detect( 104 | az_mesh, 105 | range_mesh, 106 | snr, 107 | thresh: float 108 | ): 109 | """ 110 | Constant threshold detection. 111 | 112 | Inputs: 113 | - az_mesh [ArrayLike]: Mesh of azimuth points (deg) 114 | - range_mesh [ArrayLike]: Mesh of range points (m) 115 | - snr [ArrayLike]: Signal-to-noise ratio values 116 | - thresh [float]: Detection threshold 117 | 118 | Outputs: 119 | - az_det [ArrayLike]: Azimuth values of detections (deg) 120 | - range_det [ArrayLike]: Range values of detections (m) 121 | """ 122 | 123 | det_ix = (snr >= thresh) 124 | return az_mesh[det_ix], range_mesh[det_ix] 125 | 126 | def dish_beamw(radius: float, freq: float): 127 | """ 128 | Dish radar beamwidth. 129 | 130 | Inputs: 131 | - radius [float]: Dish radius (m) 132 | - freq [float]: Transmit frequency (Hz) 133 | 134 | Outputs: 135 | - beamw [float]: Beamwidth (deg) 136 | """ 137 | 138 | return np.minimum(360.0, 70.0*wavelen(freq)/2/radius) 139 | 140 | def dish_cross_range( 141 | xr_bins, 142 | freq: float = 3E9, 143 | r: float = 100E3, 144 | radius: float = 3.0, 145 | xr: float = 100 146 | ): 147 | """ 148 | Angle cut of two targets in dish radar pattern. 149 | 150 | Inputs: 151 | - xr_bins [ArrayLike]: Cross-range bins (m) 152 | - freq [float]: Transmit frequency (Hz); default 3E9 Hz 153 | - r [float]: Target range (m); default 100E3 m 154 | - radius [float]: Dish radius (m); default 3 m 155 | - xr [float]: Target cross-range separation (m); default 100 m 156 | 157 | Outputs: 158 | - pat [ArrayLike]: Radar pattern (V) 159 | """ 160 | 161 | # Initialize output 162 | xr_pat = np.zeros(xr_bins.shape) 163 | 164 | # Bins 165 | sin_theta = xr_bins/np.sqrt(r**2 + (xr_bins/2)**2) 166 | 167 | # Wavelength 168 | wlen = wavelen(freq) 169 | 170 | # First target 171 | sin_theta1 = -xr/2/np.sqrt(r**2 + (xr/2)**2) 172 | xr_pat += dish_pat((radius/wlen)*(sin_theta - sin_theta1)) 173 | 174 | # Second target 175 | sin_theta2 = xr/2/np.sqrt(r**2 + (xr/2)**2) 176 | xr_pat += dish_pat((radius/wlen)*(sin_theta - sin_theta2)) 177 | 178 | return xr_pat 179 | 180 | def dish_gain(radius, freq): 181 | """ 182 | Dish radar gain. 183 | 184 | Inputs: 185 | - radius [float]: Dish radius (m) 186 | - freq [float]: Transmit frequency (Hz) 187 | 188 | Outputs: 189 | - g: Gain 190 | """ 191 | 192 | return 4*pi**2*radius**2/wavelen(freq)**2 193 | 194 | def dish_pat(u): 195 | """ 196 | Dish radar radiation pattern. 197 | 198 | Inputs: 199 | - u [float]: Normalized direction sine, u = (a/wlen)*sin(theta) 200 | 201 | Outputs: 202 | - f: Radiation vector amplitude 203 | """ 204 | 205 | uz = np.abs(u) > 1E-4 206 | pat = np.zeros(u.shape) 207 | pat[uz] = 2*j1(pi*u[uz])/pi/u[uz] 208 | pat[np.logical_not(uz)] = 1.0 209 | return pat 210 | 211 | def dopp_shift(dr, freq): 212 | """ 213 | Doppler shift. 214 | 215 | Inputs: 216 | - dr [float]: Range rate (m/s) 217 | - freq [float]: Transmit frequency (Hz) 218 | 219 | Outputs: 220 | - fd [float]: Doppler shift (Hz) 221 | """ 222 | 223 | return -2*dr*freq/cnst.c 224 | 225 | def en2ra(x_en): 226 | """ 227 | Convert from East-North to range-azimuth. 228 | 229 | Inputs: 230 | - x_en [ArrayLike]: East-North state vector 231 | 232 | Outputs: 233 | - x_ra [ArrayLike]: Range-azimuth state vector 234 | """ 235 | 236 | x_ra = np.zeros((2)) 237 | x_ra[0] = np.sqrt(x_en[0]**2 + x_en[1]**2) 238 | x_ra[1] = np.arctan2(x_en[0], x_en[1]) 239 | 240 | return x_ra 241 | 242 | def en2ra_jac(x_en): 243 | """ 244 | Trasformation Jacobian from East-North to range-azimuth. 245 | 246 | Inputs: 247 | - x_en [ArrayLike]: East-North state vector 248 | 249 | Outputs: 250 | - jac_ra [ArrayLike]: Jacobian matrix 251 | """ 252 | 253 | r = np.sqrt(x_en[0]**2 + x_en[1]**2) 254 | rho2 = x_en[0]**2 + x_en[1]**2 255 | 256 | jac_ra = np.zeros((2, 4)) 257 | jac_ra[0, 0] = x_en[0]/r 258 | jac_ra[0, 1] = x_en[1]/r 259 | jac_ra[1, 0] = x_en[1]/rho2 260 | jac_ra[1, 1] = -x_en[0]/rho2 261 | 262 | return jac_ra 263 | 264 | def flat_prof(r, res): 265 | """ 266 | Range profile for sinc pulse. 267 | 268 | Inputs: 269 | - r [ArrayLike]: Range bins (m) 270 | - res [float]: Range resolution (m) 271 | 272 | Outputs: 273 | - prof [ArrayLike]: Range profile (V) 274 | """ 275 | 276 | return sinc(r, 1/res) 277 | 278 | def friis( 279 | r: float, 280 | power: float, 281 | area: float = 1, 282 | gain: float = 1, 283 | loss: float = 1 284 | ): 285 | """ 286 | Received power using Friis transmission equation. 287 | 288 | Inputs: 289 | - r [float]: Range (m) 290 | - power [float]: Transmit power (W) 291 | - area [float]: Receive aperture area (m^2); default 1 m^2 292 | - gain [float]: Transmit gain; default 1 293 | - loss [float]: Receive loss; default 1 294 | 295 | Outputs: 296 | - rx_power [float]: Received power (W) 297 | """ 298 | 299 | numer = power*gain*area 300 | denom = 4*pi*(r**2)*loss 301 | return numer/denom 302 | 303 | def from_db(x): 304 | """ 305 | Convert from decibels to original units. 306 | 307 | Inputs: 308 | - x [float]: Input data (dB) 309 | 310 | Outputs: 311 | - y [float]: Output data 312 | """ 313 | 314 | return 10**(0.1*x) 315 | 316 | def gain2area(gain: float, freq: float): 317 | """ 318 | Convert gain to effective aperture area. 319 | 320 | Inputs: 321 | - gain [float]: Gain 322 | - freq [float]: Transmit frequency (Hz) 323 | 324 | Outputs: 325 | - area [float]: Effective aperture area (m^2) 326 | """ 327 | 328 | wlen = wavelen(freq) 329 | return gain*(wlen**2)/4/pi 330 | 331 | def gnn(track_x, track_y, num_track, det_x, det_y, num_det): 332 | """ 333 | Global nearest neighbor data association. 334 | 335 | Inputs: 336 | - track_x [ArrayLike]: Track state x components (m) 337 | - track_y [ArrayLike]: Track state y components (m) 338 | - num_track [int]: Number of input tracks 339 | - det_x [ArrayLike]: Detection x components (m) 340 | - det_x [ArrayLike]: Detection y components (m) 341 | - num_det [int]: Number of input detections 342 | 343 | Outputs: 344 | - assoc [ArrayLike]: Assignment vector 345 | """ 346 | 347 | assoc = -1*np.ones((num_det), dtype='int') 348 | 349 | dist = np.zeros((num_det, num_track)) 350 | for ii in range(num_det): 351 | for jj in range(num_track): 352 | dist[ii, jj] = np.sqrt((det_x[ii] - track_x[jj])**2 + (det_y[ii] - track_y[jj])**2) 353 | 354 | row_ind, col_ind = linear_sum_assignment(dist) 355 | assoc[row_ind] = col_ind 356 | 357 | return assoc 358 | 359 | def lfm_prof(r, res: float): 360 | """ 361 | Range profile for linear frequency-modulated waveform (LFM). 362 | 363 | Inputs: 364 | - r [ArrayLike]: Range bins (m) 365 | - res [float]: Range resolution (m) 366 | 367 | Outputs: 368 | - prof [ArrayLike]: Range profile (V) 369 | """ 370 | 371 | return sinc(r, 1/res) 372 | 373 | def nearest(track_x, track_y, num_track, det_x, det_y, num_det): 374 | """ 375 | Global nearest neighbor data association. 376 | 377 | Inputs: 378 | - track_x [ArrayLike]: Track state x components (m) 379 | - track_y [ArrayLike]: Track state y components (m) 380 | - num_track [int]: Number of input tracks 381 | - det_x [ArrayLike]: Detection x components (m) 382 | - det_x [ArrayLike]: Detection y components (m) 383 | - num_det [int]: Number of input detections 384 | 385 | Outputs: 386 | - assoc [ArrayLike]: Assignment vector 387 | """ 388 | 389 | assoc = -1*np.ones((num_det), dtype='int') 390 | 391 | dist = np.zeros((num_track)) 392 | for ii in range(num_det): 393 | if (ii < num_track): 394 | for jj in range(num_track): 395 | if jj not in assoc: 396 | dist[jj] = np.sqrt((det_x[ii] - track_x[jj])**2 + (det_y[ii] - track_y[jj])**2) 397 | else: 398 | dist[jj] = np.Inf 399 | 400 | assoc[ii] = dist.argmin() 401 | 402 | return assoc 403 | 404 | def noise_energy( 405 | noise_temp: float 406 | ): 407 | """ 408 | Thermal noise energy. 409 | 410 | Inputs: 411 | - noise_temp [float]: System noise temperature (K) 412 | 413 | Outputs: 414 | - noise_energy [float]: System noise energy (J) 415 | """ 416 | 417 | return cnst.k*noise_temp 418 | 419 | def price( 420 | bandw: float = 100, 421 | bandw_cost: float = 100, 422 | energy: float = 100, 423 | energy_cost: float = 20, 424 | freq: float = 1E3, 425 | freq_cost: float = 10, 426 | num_integ: int = 1, 427 | integ_cost: float = 100, 428 | noise_temp: float = 800, 429 | noise_temp_cost: float = 50, 430 | radius: float = 1, 431 | radius_cost: float = 10000, 432 | scan_rate: float = 0, 433 | scan_rate_cost: float = 500 434 | ): 435 | """ 436 | Notional price model. 437 | 438 | Inputs: 439 | - bandw [float]: Transmit bandwidth (MHz); default 100 MHz 440 | - bandw_cost [float]: Transmit bandwidth cost ($/MHz); default $100/MHz 441 | - energy [float]: Transmit energy (mJ); default 100 mJ 442 | - energy_cost [float]: Transmit energy cost ($/mJ); default $20/mJ 443 | - freq [float]: Transmit frequency (MHz); default 1E3 MHz 444 | - freq_cost [float]: Transmit frequency cost ($/MHz); default $10/MHz 445 | - noise_temp [float]: System noise temperature (K); default 1000 K 446 | - noise_temp_cost [float]: System noise temperature cost ($/K); default $50/K 447 | - num_integ [int]: Number of integrated pulses; default 1 pulse 448 | - integ_cost [float]: Integration cost ($/pulse); default $100/pulse 449 | - radius [float]: Dish radius (m): default 0.3 m 450 | - radius_cost [float]: Dish radius cost ($/m): default $10,000/m 451 | - scan_rate [float]: Scan rate (scans/min); default 5 scans/min 452 | - scan_rate_cost [float]: Scan rate cost ($/scans/min); default 500 $/scans/min 453 | 454 | Outputs: 455 | (none) 456 | """ 457 | 458 | return bandw_cost*bandw + energy*energy_cost + freq*freq_cost + \ 459 | (num_integ - 1)*integ_cost + np.maximum(1500 - noise_temp, 0)*noise_temp_cost + \ 460 | radius*radius_cost + scan_rate*scan_rate_cost 461 | 462 | def prop_cv(dt): 463 | """ 464 | State transition matrix for constant velocity target. 465 | 466 | Inputs: 467 | - dt [float]: Propagation time (s). 468 | 469 | Outputs: 470 | - phi [ArrayLike]: State transition matrix 471 | """ 472 | phi = np.eye(4) 473 | phi[0, 2] = dt 474 | phi[1, 3] = dt 475 | return phi 476 | 477 | def rad2deg(x): 478 | """ 479 | Convert from radians to degrees. 480 | 481 | Inputs: 482 | - x [float]: Input data (rad) 483 | 484 | Outputs: 485 | - y [float]: Output data (deg) 486 | """ 487 | 488 | return (180/pi)*x 489 | 490 | def range_res(bandw): 491 | """ 492 | Range resolution of transmit waveform. 493 | 494 | Inputs: 495 | - bandw [float]: Transmit bandwidth (Hz) 496 | 497 | Outputs: 498 | - res [float]: Range resolution (m) 499 | """ 500 | 501 | return cnst.c/2/bandw 502 | 503 | def rdi( 504 | range_mesh, 505 | dopp_mesh, 506 | r, 507 | dr, 508 | bandw, 509 | freq, 510 | num_pulse, 511 | prf 512 | ): 513 | """ 514 | Range-Doppler image 515 | 516 | Inputs: 517 | - range_mesh [ArrayLike]: Mesh of range points (m) 518 | - dopp_mesh [ArrayLike]: Mesh of Doppler points (Hz) 519 | - r [float]: Target range (m) 520 | - dr [float]: Target range rate (m/s) 521 | - bandw [float]: Transmit bandwidth (Hz) 522 | - freq [float]: Transmit frequency (Hz) 523 | - num_pulse [int]: Number of pulses in burst 524 | - prf [float]: Pulse repetition frequency (Hz) 525 | 526 | Outputs: 527 | - img [ArrayLike]: Range-Doppler image (V) 528 | """ 529 | 530 | range_pat = flat_prof(range_mesh - r, range_res(bandw)) 531 | dopp_pat = sinc(dopp_mesh - dopp_shift(dr, freq), num_pulse/prf) 532 | return range_pat*dopp_pat 533 | 534 | def rect_beamw(h, w, freq): 535 | """ 536 | Rectangular radar beamwidth. 537 | 538 | Inputs: 539 | - h [float]: Aperture height (m) 540 | - w [float]: Aperture width (m) 541 | - freq [float]: Transmit frequency (Hz) 542 | 543 | Outputs: 544 | - beamw_h [float]: Horizontal beamwidth (deg) 545 | - beamw_v [float]: Vertical beamwidth (deg) 546 | """ 547 | wlen = wavelen(freq) 548 | beamw_h = np.minimum(360.0, rad2deg(wlen/w)) 549 | beamw_v = np.minimum(360.0, rad2deg(wlen/h)) 550 | return (beamw_h, beamw_v) 551 | 552 | def rect_pat(u, v): 553 | """ 554 | Rectangular radar radiation pattern. 555 | 556 | Inputs: 557 | - u [float]: Normalized horizontal direction sine, u = -(w/wlen)*sin(az)*cos(el) 558 | - v [float]: Normalized vertical direction sine, v = -(h/wlen)*sin(el) 559 | 560 | Outputs: 561 | - f: Radiation vector amplitude 562 | """ 563 | 564 | return 2*sinc(u, 1)*sinc(v, 1) 565 | 566 | def rx_power( 567 | r: float, 568 | power: float, 569 | freq: float, 570 | gain: float = 1, 571 | loss: float = 1, 572 | rcs: float = 1 573 | ): 574 | """ 575 | Received power from radar transmission. 576 | 577 | Inputs: 578 | - r [float]: Target range (m) 579 | - power [float]: Transmit power (W) 580 | - freq [float]: Transmit frequency (Hz) 581 | - gain [float]: Total transmit and receive gain; default 1 582 | - loss [float]: Total transmit and receive loss; default 1 583 | - rcs [float]: Target radar cross section (m^2); default 1 m^2 584 | 585 | Outputs: 586 | - power [float]: Received power (W) 587 | """ 588 | 589 | wlen = wavelen(freq) 590 | numer = power*(wlen**2)*gain*rcs 591 | denom = ((4*pi)**3)*(r**4)*loss 592 | return numer/denom 593 | 594 | def rx_energy( 595 | r: float, 596 | energy: float, 597 | freq: float, 598 | gain: float = 1, 599 | loss: float = 1, 600 | rcs: float = 1 601 | ): 602 | """ 603 | Received energy from radar transmission. 604 | 605 | Inputs: 606 | - r [float]: Target range (m) 607 | - energy [float]: Transmit energy (W) 608 | - freq [float]: Transmit frequency (Hz) 609 | - gain [float]: Total transmit and receive gain; default 1 610 | - loss [float]: Total transmit and receive loss; default 1 611 | - rcs [float]: Target radar cross section (m^2); default 1 m^2 612 | 613 | Outputs: 614 | - energy [float]: Received energy (J) 615 | """ 616 | 617 | wlen = wavelen(freq) 618 | numer = energy*(wlen**2)*gain*rcs 619 | denom = ((4*pi)**3)*(r**4)*loss 620 | return numer/denom 621 | 622 | def rx_snr( 623 | r: float, 624 | energy: float, 625 | freq: float, 626 | noise_temp: float, 627 | gain: float = 1, 628 | loss: float = 1, 629 | rcs: float = 1 630 | ): 631 | """ 632 | Signal-to-noise ratio from radar transmission. 633 | 634 | Inputs: 635 | - r [float]: Target range (m) 636 | - energy [float]: Transmit energy (W) 637 | - freq [float]: Transmit frequency (Hz) 638 | - noise_temp [float]: System noise temperature (K) 639 | - gain [float]: Total transmit and receive gain; default 1 640 | - loss [float]: Total transmit and receive loss; default 1 641 | - rcs [float]: Target radar cross section (m^2); default 1 m^2 642 | 643 | Outputs: 644 | - snr [float]: Signal-to-noise ratio 645 | """ 646 | 647 | wlen = wavelen(freq) 648 | numer = energy*(wlen**2)*gain*rcs 649 | denom = ((4*pi)**3)*(r**4)*loss*noise_energy(noise_temp) 650 | return numer/denom 651 | 652 | def sinc(x, w): 653 | """ 654 | Sinc function. 655 | 656 | Inputs: 657 | - x [ArrayLike]: Input data 658 | - w [float]: Inverse mainlobe width 659 | 660 | Outputs: 661 | - y [ArrayLike]: Output data 662 | """ 663 | 664 | xz = np.abs(x) > 1E-4 665 | pat = np.zeros(x.shape) 666 | pat[xz] = np.sin(pi*w*x[xz])/pi/x[xz]/w 667 | pat[np.logical_not(xz)] = 1.0 668 | return pat 669 | 670 | def snrconv( 671 | snr0: float, 672 | r0: float, 673 | rcs0: float, 674 | r: float, 675 | rcs: float 676 | ): 677 | """ 678 | Signal-to-noise ratio (SNR) from reference. 679 | 680 | Inputs: 681 | - snr0 [float]: Reference SNR 682 | - r0 [float]: Reference range (m) 683 | - rcs0 [float]: Reference radar cross section (m^2) 684 | - r [float]: Target range (m) 685 | - rcs [float]: Target radar cross section (m^2) 686 | 687 | Outputs: 688 | - snr [float]: SNR 689 | """ 690 | 691 | return snr0*((r0/r)**4)*(rcs/rcs0) 692 | 693 | class Target(): 694 | """ 695 | Generic target container. 696 | """ 697 | 698 | def __init__(self): 699 | self.pos = None 700 | self.rcs = None 701 | self.route = None 702 | 703 | def get_pos(self, t): 704 | if self.pos.size == 2: 705 | return self.pos 706 | elif self.pos.size == 4: 707 | return self.pos[0:2] + t*self.pos[2:4] 708 | 709 | def get_raz(self, t): 710 | pos = self.get_pos(t) 711 | raz = np.zeros((2)) 712 | raz[0] = np.sqrt(pos[0]**2 + pos[1]**2) 713 | raz[1] = np.pi/2 - np.arctan2(pos[1], pos[0]) 714 | 715 | return raz 716 | 717 | def to_db(x): 718 | """ 719 | Convert from original units to decibels. 720 | 721 | Inputs: 722 | - x [float]: Input data 723 | 724 | Outputs: 725 | - y [float]: Output data (dB) 726 | """ 727 | 728 | if np.isscalar(x): 729 | if np.abs(x) < 1E-40: 730 | return -400 731 | else: 732 | x[np.abs(x) < 1E-40] = 1E-40 733 | return 10*np.log10(np.abs(x)) 734 | 735 | def wavelen(freq, propvel=cnst.c): 736 | """ 737 | Calculate wavelength. 738 | 739 | Inputs: 740 | - freq [float]: Transmit frequency (Hz) 741 | - propvel [float]: Proagation velocity (m/s); default 3E8 m/s 742 | 743 | Outputs: 744 | - wlen [float]: Wavelength (m) 745 | """ 746 | 747 | return propvel/freq -------------------------------------------------------------------------------- /rad/robby.py: -------------------------------------------------------------------------------- 1 | """ 2 | Introduction to Radar Course 3 | 4 | Authors 5 | ======= 6 | 7 | Zachary Chance, Robert Freking, Victoria Helus 8 | MIT Lincoln Laboratory 9 | Lexington, MA 02421 10 | 11 | Distribution Statement 12 | ====================== 13 | 14 | DISTRIBUTION STATEMENT A. Approved for public release. Distribution is unlimited. 15 | 16 | This material is based upon work supported by the United States Air Force under Air 17 | Force Contract No. FA8702-15-D-0001. Any opinions, findings, conclusions or 18 | recommendations expressed in this material are those of the author(s) and do not 19 | necessarily reflect the views of the United States Air Force. 20 | 21 | © 2021 Massachusetts Institute of Technology. 22 | 23 | The software/firmware is provided to you on an As-Is basis 24 | 25 | Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS Part 26 | 252.227-7013 or 7014 (Feb 2014). Notwithstanding any copyright notice, U.S. 27 | Government rights in this work are defined by DFARS 252.227-7013 or 28 | DFARS 252.227-7014 as detailed above. Use of this work other than as specifically 29 | authorized by the U.S. Government may violate any copyrights that exist in this work. 30 | 31 | RAMS ID: 1016938 32 | """ 33 | 34 | # Imports 35 | from IPython.display import display 36 | import ipywidgets as wdg 37 | from matplotlib.axes import Axes 38 | import matplotlib.pyplot as pyp 39 | import numpy as np 40 | from numpy.random import randn 41 | from typing import Union, List 42 | import rad.plot as pl 43 | import rad.radar as rd 44 | import rad.const as cnst 45 | from rad.radar import Target 46 | 47 | def pulse( 48 | bandw: float, 49 | beamw: float, 50 | coherent: bool, 51 | energy: float, 52 | freq: float, 53 | gain: float, 54 | min_range: float, 55 | noise_power: float, 56 | num_integ: int, 57 | targets: List[Target], 58 | time: float, 59 | az: float, 60 | range_bins 61 | ): 62 | """ 63 | Generate a single pulse response from a set of Targets. 64 | 65 | Inputs: 66 | - bandw [float]: Transmit bandwidth (Hz) 67 | - beamw [float]: Transmit azimuth beamwidth (rad) 68 | - coherent [bool]: Flag for coherent integration 69 | - energy [float]: Transmit energy (J) 70 | - freq [float]: Transmit frequency (Hz) 71 | - gain [float]: Total transmit and receive gain 72 | - min_range [float]: Minimum observable range (m) 73 | - noise_power [float]: System noise power (W) 74 | - num_integ [int]: Number of integrated pulses 75 | - targets [List[Target]]: List of Targets 76 | - time [float]: Time (s) 77 | - az [float]: Transmit azimuth (rad) 78 | - range_bins [ArrayLike]: Range bins (m) 79 | 80 | Outputs: 81 | - pulse [ArrayLike]: Pulse (V) 82 | """ 83 | 84 | # Range resolution 85 | res = rd.range_res(bandw) 86 | 87 | # Generate pulse 88 | pulse = np.zeros((range_bins.size,)) 89 | for tgt in targets: 90 | 91 | # Target range/az 92 | tgt_raz = tgt.get_raz(time) 93 | 94 | # Check for blanking 95 | if (tgt_raz[0] >= min_range): 96 | 97 | # Centered range bins 98 | dr = range_bins - tgt_raz[0] 99 | 100 | # Distance from boresight (rad) 101 | daz = np.angle(np.exp(1j*(tgt_raz[1] - az))) 102 | 103 | # Beamshape 104 | beamshape = rd.dish_pat((0.61/beamw)*np.sin(daz)) 105 | 106 | # One-way 107 | beamshape[np.abs(daz) > np.pi/2] = 0.0 108 | 109 | # Received energy (J) 110 | rx_energy = rd.rx_energy(tgt_raz[0], energy, freq, gain=gain, rcs=rd.from_db(tgt.rcs)) 111 | 112 | # Integration gain 113 | if (num_integ > 1): 114 | if coherent: 115 | integ_gain = num_integ 116 | else: 117 | integ_gain = np.sqrt(num_integ) 118 | rx_energy *= integ_gain 119 | 120 | # Add to pulse 121 | pulse += np.sqrt(rx_energy)*beamshape*rd.flat_prof(dr, res) 122 | 123 | # Add noise 124 | pulse += np.sqrt(noise_power)*randn((range_bins.size)) 125 | 126 | return pulse 127 | 128 | def robby( 129 | bandw: float = 1, 130 | coherent: bool = True, 131 | det_thresh: float = 12, 132 | dets: bool = False, 133 | energy: float = 0.3, 134 | freq: float = 2E3, 135 | max_price: float = 1E5, 136 | max_range: float = 10E3, 137 | min_range: float = 2E3, 138 | noise_temp: float = 1000, 139 | num_integ: int = 1, 140 | num_targ: int = 1, 141 | pulses: bool = True, 142 | radius: float = 0.3, 143 | reset: bool = False, 144 | scan_rate: float = 5, 145 | show_price: bool = False, 146 | targets: List[Target] = None, 147 | widgets = ['bandw', 'coherent', 'det_thresh', 'freq', 'energy', 'noise_temp', 'num_integ', 'prf', 'radius', 'scan_rate'] # 'test_targ' 148 | ): 149 | """ 150 | Create test radar display. 151 | 152 | Inputs: 153 | - bandw [float]: Transmit bandwidth (MHz); default 1 MHz 154 | - coherent [bool]: Flag for coherent integration; default True 155 | - det_thresh [float]: Detection threshold (dB); default 12 dB 156 | - dets [bool]: Flag for detection display; default False 157 | - energy [float]: Transmit energy (mJ); default 0.3 mJ 158 | - freq [float]: Transmit frequency (MHz); default 2E3 MHz 159 | - max_price [float]: Maximum allowable price ($); default $100,000 160 | - max_range [float]: Maximum observable range (m); default 10E3 161 | - min_range [float]: Minimum observable range (m); default 2E3 162 | - noise_temp [float]: System noise temperature (K); default 1000 163 | - num_integ [int]: Number of integrated pulses; default 1 pulse 164 | - num_targ [int]: Number of test targets; default 1 target 165 | - pulses [bool]: Flag for pulse display; default True 166 | - radius [float]: Dish radius (m): default 0.3 m 167 | - reset [bool]: Flag for reset button; default False 168 | - scan_rate [float]: Scan rate (scans/min); default 5 scans/min 169 | - show_price [bool]: Flat for price display; default False 170 | - targets [List[Target]]: List of Targets; default None 171 | - widgets [List[str]]: List of desired widgets 172 | 173 | Outputs: 174 | (none) 175 | """ 176 | 177 | # Widget style 178 | style = {'description_width': 'initial'} 179 | box_layout = wdg.Layout(justify_items='flex-start') 180 | wdg_layout = wdg.Layout(grid_template_columns="repeat(3, 350px)") 181 | 182 | # Build axes 183 | fig_scan, ax_scan, fig_pulse, ax_pulse = pl.new_rad_plot() 184 | 185 | # Pulse axes 186 | ax_pulse.set_ylabel('SNR (dB)') 187 | ax_pulse.set_xlabel('Range (m)') 188 | 189 | # Remove radius labels 190 | ax_scan.set_yticklabels([]) 191 | 192 | # Live text 193 | text1 = fig_scan.text(0, 0, "Time: ---", 194 | family='monospace', 195 | size=9, 196 | weight='normal' 197 | ) 198 | 199 | # Azimuth bins (rad) 200 | num_az = 500 201 | az_bins = np.linspace(0, 2*np.pi, num_az) 202 | daz = az_bins[1] - az_bins[0] 203 | 204 | # Range bins 205 | num_range = 500 206 | range_bins = np.linspace(min_range, max_range, num_range) 207 | dr = range_bins[1] - range_bins[0] 208 | 209 | # Mesh for detections 210 | az_mesh, range_mesh = np.meshgrid(az_bins, range_bins) 211 | 212 | # Targets 213 | if not targets: 214 | targets = [] 215 | for ii in range(num_targ): 216 | targii = rd.Target() 217 | targii.pos = np.array([0, min_range]) 218 | targii.rcs = 0 219 | targets.append(targii) 220 | 221 | # Control widgets 222 | controls_box = [] 223 | 224 | # Radar controls 225 | rad_controls = [] 226 | 227 | # Noise temperature 228 | if ('noise_temp' in widgets): 229 | noise_temp_wdg = wdg.FloatSlider( 230 | min=600, 231 | max=1200, 232 | step=10, 233 | value=noise_temp, 234 | description="Noise Temperature (°K)", 235 | style=style, 236 | readout_format='.2f' 237 | ) 238 | rad_controls.append(noise_temp_wdg) 239 | else: 240 | noise_temp_wdg = wdg.fixed(noise_temp) 241 | 242 | # Dish radius 243 | if ('radius' in widgets): 244 | radius_wdg = wdg.FloatSlider( 245 | min=0.1, 246 | max=1, 247 | step=0.05, 248 | value=radius, 249 | description="Dish Radius (m)", 250 | style=style, 251 | readout_format='.2f' 252 | ) 253 | rad_controls.append(radius_wdg) 254 | else: 255 | radius_wdg = wdg.fixed(radius) 256 | 257 | if ('scan_rate' in widgets): 258 | scan_rate_wdg = wdg.FloatSlider( 259 | min=1, 260 | max=10, 261 | step=0.1, 262 | value=scan_rate, 263 | description="Scan Rate (scans/min)", 264 | style=style, 265 | readout_format='.2f' 266 | ) 267 | rad_controls.append(scan_rate_wdg) 268 | else: 269 | scan_rate_wdg = wdg.fixed(scan_rate) 270 | 271 | if ('freq' in widgets): 272 | freq_wdg = wdg.FloatSlider( 273 | min=500, 274 | max=5000, 275 | step=50, 276 | value=freq, 277 | description="Transmit Frequency (MHz)", 278 | style=style, 279 | readout_format='.2f' 280 | ) 281 | rad_controls.append(freq_wdg) 282 | else: 283 | freq_wdg = wdg.fixed(freq) 284 | 285 | rad_controls_box = [] 286 | if rad_controls: 287 | rad_controls_title = [wdg.HTML(value = f"Radar")] 288 | rad_controls_box = wdg.VBox(rad_controls_title + rad_controls, layout=box_layout) 289 | controls_box.append(rad_controls_box) 290 | 291 | # Transmission controls 292 | tx_controls = [] 293 | 294 | # Bandwidth 295 | if ('bandw' in widgets): 296 | bandw_wdg = wdg.FloatSlider( 297 | min=0.1, 298 | max=3, 299 | step=0.05, 300 | value=bandw, 301 | description="Bandwidth (MHz)", 302 | style=style, 303 | readout_format='.2f' 304 | ) 305 | tx_controls.append(bandw_wdg) 306 | else: 307 | bandw_wdg = wdg.fixed(bandw) 308 | 309 | # Energy 310 | if ('energy' in widgets): 311 | energy_wdg = wdg.FloatSlider( 312 | min=0.1, 313 | max=1, 314 | step=0.01, 315 | value=energy, 316 | description="Transmit Energy (mJ)", 317 | style=style, 318 | readout_format='.2f' 319 | ) 320 | tx_controls.append(energy_wdg) 321 | else: 322 | energy_wdg = wdg.fixed(energy) 323 | 324 | tx_controls_box = [] 325 | if tx_controls: 326 | tx_controls_title = [wdg.HTML(value = f"Transmission")] 327 | tx_controls_box = wdg.VBox(tx_controls_title + tx_controls, layout=box_layout) 328 | 329 | # Processing controls 330 | proc_controls = [] 331 | 332 | if dets and ('det_thresh' in widgets): 333 | det_thresh_wdg = wdg.FloatSlider( 334 | min=0, 335 | max=30, 336 | step=1, 337 | value=det_thresh, 338 | description="Detection Threshold (dB)", 339 | style=style, 340 | readout_format='d' 341 | ) 342 | proc_controls.append(det_thresh_wdg) 343 | else: 344 | det_thresh_wdg = wdg.fixed(det_thresh) 345 | 346 | if ('num_integ' in widgets): 347 | num_integ_wdg = wdg.FloatSlider( 348 | min=1, 349 | max=25, 350 | step=1, 351 | value=num_integ, 352 | description="Integrated Pulses", 353 | style=style, 354 | readout_format='d' 355 | ) 356 | proc_controls.append(num_integ_wdg) 357 | else: 358 | num_integ_wdg = wdg.fixed(num_integ) 359 | 360 | if ('coherent' in widgets): 361 | coh_wdg = wdg.Checkbox( 362 | value=False, 363 | description='Coherent Integration' 364 | ) 365 | proc_controls.append(coh_wdg) 366 | else: 367 | coh_wdg = wdg.fixed(coherent) 368 | 369 | proc_controls_box = [] 370 | if proc_controls: 371 | proc_controls_title = [wdg.HTML(value = f"Processing")] 372 | proc_controls_box = wdg.VBox(proc_controls_title + proc_controls, layout=box_layout) 373 | if tx_controls: 374 | controls_box.append(wdg.VBox([tx_controls_box, proc_controls_box])) 375 | else: 376 | controls_box.append(proc_controls_box) 377 | 378 | # Operation 379 | oper_controls = [] 380 | 381 | scan_wdg = wdg.Button(description="Scan") 382 | oper_controls.append(scan_wdg) 383 | 384 | if reset: 385 | reset_wdg = wdg.Button(description="Reset") 386 | oper_controls.append(reset_wdg) 387 | 388 | oper_controls_box = [] 389 | if oper_controls: 390 | oper_controls_title = [wdg.HTML(value = f"Operation")] 391 | oper_controls_box = wdg.VBox(oper_controls_title + oper_controls, layout=box_layout) 392 | controls_box.append(oper_controls_box) 393 | 394 | # Pulse display slider 395 | az_pulse_wdg = wdg.FloatSlider( 396 | min=0, 397 | max=360, 398 | step=360/(num_az - 1), 399 | value=0, 400 | description="Azimuth (deg)", 401 | style=style, 402 | readout_format='.2f' 403 | ) 404 | az_pulse_title = wdg.HTML(value = f"Pulse Display") 405 | pulse_disp_box = [az_pulse_title, az_pulse_wdg] 406 | 407 | # Test targets 408 | target_controls = [] 409 | targ_update = False 410 | if ('test_targ' in widgets): 411 | targ_update = True 412 | targ_r_wdg = [] 413 | targ_az_wdg = [] 414 | targ_rcs_wdg = [] 415 | for ii in range(num_targ): 416 | targ_raz = targets[ii].get_raz(0) 417 | targ_r_wdg.append(wdg.FloatSlider( 418 | min=min_range, 419 | max=max_range, 420 | step=dr, 421 | value=targ_raz[0], 422 | description="Target #" + str(ii) + " Range (m)", 423 | style=style, 424 | readout_format='.2f' 425 | )) 426 | targ_az_wdg.append(wdg.FloatSlider( 427 | min=0, 428 | max=360, 429 | step=360/(num_az - 1), 430 | value=rd.rad2deg(targ_raz[1]), 431 | description="Target #" + str(ii) + " Azimuth (deg)", 432 | style=style, 433 | readout_format='.2f' 434 | )) 435 | targ_rcs_wdg.append(wdg.FloatSlider( 436 | min=-20, 437 | max=20, 438 | step=1, 439 | value=targets[ii].rcs, 440 | description="Target #" + str(ii) + " RCS (dBsm)", 441 | style=style, 442 | readout_format='.2f' 443 | )) 444 | target_controls.append(wdg.VBox([targ_r_wdg[-1], targ_az_wdg[-1], targ_rcs_wdg[-1]])) 445 | 446 | target_controls_box = [] 447 | if target_controls: 448 | target_controls_title = [wdg.HTML(value = f"Targets")] 449 | target_controls_box = wdg.VBox(target_controls_title + target_controls, layout=box_layout) 450 | controls_box.append(target_controls_box) 451 | 452 | # Display widgets 453 | if controls_box: 454 | 455 | controls_wdg = wdg.GridBox(controls_box, layout=wdg_layout) 456 | if show_price: 457 | 458 | # Coherent processing 459 | integ_cost = 40 460 | if coherent: 461 | integ_cost = 100 462 | 463 | price0 = rd.price( 464 | bandw=bandw, 465 | energy=energy, 466 | freq=freq, 467 | integ_cost=integ_cost, 468 | noise_temp=noise_temp, 469 | num_integ=num_integ, 470 | radius=radius, 471 | scan_rate=scan_rate 472 | ) 473 | price_wdg = wdg.HTML(value=f"") 474 | if price0 <= max_price: 475 | price_wdg.value = f"

Price: ${price0:.2f}

" 476 | else: 477 | price_wdg.value = f"

Price: ${price0:.2f}

" 478 | 479 | controls_box.insert(0, price_wdg) 480 | 481 | display( 482 | wdg.VBox([ 483 | wdg.AppLayout( 484 | center=fig_scan.canvas, 485 | right_sidebar=wdg.VBox(controls_box), 486 | pane_widths=[0, '810px', '350px'] 487 | ), 488 | wdg.AppLayout( 489 | center=fig_pulse.canvas, 490 | right_sidebar=wdg.VBox(pulse_disp_box), 491 | pane_widths=[0, '810px', '350px'] 492 | ), 493 | ]) 494 | ) 495 | 496 | # Initialize frame 497 | frame_wdg = wdg.IntSlider( 498 | value=1, 499 | min=1, 500 | max=20, 501 | step=1, 502 | description="Frame", 503 | readout=False, 504 | disabled=False 505 | ) 506 | 507 | # SNR range 508 | min_scan_snr = -10 509 | max_scan_snr = 30 510 | min_pulse_snr = -5 511 | max_pulse_snr = 30 512 | 513 | # Initialize SNR 514 | snr = min_scan_snr*np.ones((num_range, num_az)) 515 | 516 | # Pulse radial plot 517 | pc = ax_scan.pcolormesh(az_bins, range_bins, snr, shading='gouraud', cmap='inferno') 518 | pc.set_clim(min_scan_snr, max_scan_snr) 519 | 520 | # Colorbar 521 | cbar = pyp.colorbar(pc, ax=ax_scan, shrink=0.5, pad=0.1) 522 | cbar.ax.set_ylabel('Signal-to-Noise Ratio (dB)', size=12, name=pl.DEF_SANS) 523 | 524 | # Detections 525 | if dets: 526 | 527 | # Detection plot 528 | dets_range = [] 529 | dets_az = [] 530 | dets_line = ax_scan.plot( 531 | dets_az, 532 | dets_range, 533 | fillstyle='full', 534 | markeredgecolor='white', 535 | markerfacecolor='white', 536 | marker='.', 537 | linestyle='None' 538 | )[0] 539 | 540 | # Initial single pulse 541 | ax_pulse.set_xlim([min_range, max_range]) 542 | ax_pulse.set_ylim([min_pulse_snr, max_pulse_snr]) 543 | line_pulse = ax_pulse.plot(range_bins, min_scan_snr*np.ones((num_range,)), linewidth=2.0, color='red')[0] 544 | 545 | # Scan 546 | def plot(btn): 547 | 548 | # Time (s) 549 | t = (frame_wdg.value - 1)*(60/scan_rate_wdg.value) 550 | 551 | # Update text 552 | text1.set_text("Time: {ti:.2f} s".format(ti=t)) 553 | 554 | # Coherent operation 555 | coherent = coh_wdg.value 556 | 557 | # Detection threshold 558 | det_thresh = det_thresh_wdg.value 559 | 560 | # Integration 561 | num_integ = num_integ_wdg.value*1.0 562 | 563 | # Frequency (Hz) 564 | freq = freq_wdg.value*1E6 565 | 566 | # Wavelength (m) 567 | wavelen = rd.wavelen(freq) 568 | 569 | # Dish radius (m) 570 | radius = radius_wdg.value 571 | 572 | # Dish beamwidth (rad) 573 | beamw = 1.22*wavelen/2/radius 574 | 575 | # Dish gain 576 | tx_gain = rd.dish_gain(radius_wdg.value, freq) 577 | rx_gain = tx_gain 578 | gain = tx_gain*rx_gain 579 | 580 | # Transmit energy 581 | tx_energy = energy_wdg.value*1E-3 582 | 583 | # Transmit bandwidth (Hz) 584 | bandw = bandw_wdg.value*1E6 585 | 586 | # Noise temperature (°K) 587 | noise_temp = noise_temp_wdg.value 588 | 589 | # Noise power (W) 590 | noise_power = cnst.k*noise_temp 591 | 592 | # Update targets 593 | if targ_update: 594 | for ii in range(num_targ): 595 | rii = targ_r_wdg[ii].value 596 | azii = np.pi/2 - rd.deg2rad(targ_az_wdg[ii].value) 597 | rcsii = targ_rcs_wdg[ii].value 598 | targets[ii].pos[0] = rii*np.cos(azii) 599 | targets[ii].pos[1] = rii*np.sin(azii) 600 | targets[ii].rcs = rcsii 601 | 602 | # Generate pulses 603 | for ii in range(num_az): 604 | snr[:, ii] = 20*np.log10(np.abs(pulse( 605 | bandw, 606 | beamw, 607 | coherent, 608 | tx_energy, 609 | freq, 610 | gain, 611 | min_range, 612 | noise_power, 613 | num_integ, 614 | targets, 615 | t, 616 | az_bins[ii], 617 | range_bins 618 | ))) - 10*np.log10(noise_power) 619 | 620 | # Update pulses 621 | if pulses: 622 | pc.set_array(snr.ravel()) 623 | 624 | # Update detections 625 | if dets: 626 | dets_az, dets_range = rd.detect(az_mesh, range_mesh, snr, det_thresh) 627 | dets_line.set_data(dets_az, dets_range) 628 | 629 | # Update pulse 630 | update_line(az_pulse_wdg.value) 631 | 632 | # Increment frame 633 | frame_wdg.value += 1 634 | 635 | # Grid 636 | ax_scan.grid(color=[0.8, 0.8, 0.8], linestyle=':', linewidth=1.0) 637 | 638 | def reset_plot(btn): 639 | frame_wdg.value = 1 640 | plot(btn) 641 | 642 | def update_line(az): 643 | 644 | # Update pulse line 645 | az = rd.deg2rad(az) 646 | az_bin = np.round(az/daz).astype('int') 647 | line_pulse.set_ydata(snr[:, az_bin]) 648 | 649 | def update_price(bandw, coherent, energy, freq, noise_temp, num_integ, radius, scan_rate): 650 | 651 | if show_price: 652 | 653 | # Coherent processing 654 | integ_cost = 40 655 | if coherent: 656 | integ_cost = 100 657 | 658 | # Calculate price 659 | price = rd.price( 660 | bandw=bandw, 661 | energy=energy, 662 | freq=freq, 663 | integ_cost=integ_cost, 664 | noise_temp=noise_temp, 665 | num_integ=num_integ, 666 | radius=radius, 667 | scan_rate=scan_rate 668 | ) 669 | 670 | # Update display price 671 | if price <= max_price: 672 | price_wdg.value = f"

Price: ${price:.2f}

" 673 | else: 674 | price_wdg.value = f"

Price: ${price:.2f}

" 675 | 676 | # Add interactions 677 | scan_wdg.on_click(plot) 678 | if reset: 679 | reset_wdg.on_click(reset_plot) 680 | wdg.interactive(update_line, az=az_pulse_wdg) 681 | wdg.interactive( 682 | update_price, 683 | bandw=bandw_wdg, 684 | coherent=coh_wdg, 685 | energy=energy_wdg, 686 | freq=freq_wdg, 687 | noise_temp=noise_temp_wdg, 688 | num_integ=num_integ_wdg, 689 | radius=radius_wdg, 690 | scan_rate=scan_rate_wdg 691 | ) 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jupyterlab 2 | jupyterlab-mathjax3 3 | jupyterlab-hide-code 4 | numpy 5 | scipy 6 | matplotlib 7 | ipympl --------------------------------------------------------------------------------