{ "cells": [ { "cell_type": "markdown", "id": "19dd0806", "metadata": {}, "source": [ "# Lidar North Alignment by Hard Target Mapping\n", "To align the north orientation of a lidar, you can use surrounding obstacles (if available) with known coordinates to calibrate your lidar north orientation. Lidar observations are taken from PPI scans, alowing to detect surrounding wind turbines. \n", "\n", "This example is redundant to the example by [Rott et al. 2022](https://zenodo.org/records/5654919) but is shown here for reasons of completeness. \n", "We will use the data provided by Rott et al 2022 here:\n", "> Andreas Rott, Jörge Schneemann, & Frauke Theuer. (2021). AndreasRott/Alignment_of_scanning_lidars_in_offshore_wind_farms: Version1.0 (Release1.0.0). Zenodo. https://doi.org/10.5281/zenodo.5654919\n", "\n", "lets read the provided data first:" ] }, { "cell_type": "code", "execution_count": null, "id": "05b48a7f", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset> Size: 4GB\n",
       "Dimensions:      (time: 23335, range: 3760)\n",
       "Coordinates:\n",
       "  * time         (time) datetime64[ns] 187kB 2018-11-26T09:25:53.473991900 .....\n",
       "  * range        (range) float64 30kB 800.0 802.0 804.0 ... 1.079e+04 1.08e+04\n",
       "Data variables:\n",
       "    radialspeed  (time, range) float64 702MB 13.46 13.42 13.4 ... nan nan nan\n",
       "    cnr          (time, range) float64 702MB -32.25 -32.26 ... -30.0 -30.0\n",
       "    azimuth      (time) float64 187kB -57.9 -57.8 -57.7 ... 189.8 189.9 190.0\n",
       "    datetime     (time, range) float64 702MB 7.374e+05 7.374e+05 ... nan nan\n",
       "    elevation    (time) float64 187kB -0.1 -0.1 -0.099 -0.1 ... -0.1 -0.1 -0.1\n",
       "    x            (time, range) float64 702MB 741.2 743.1 744.9 ... nan nan nan\n",
       "    y            (time, range) float64 702MB -301.0 -301.7 -302.5 ... nan nan
" ], "text/plain": [ " Size: 4GB\n", "Dimensions: (time: 23335, range: 3760)\n", "Coordinates:\n", " * time (time) datetime64[ns] 187kB 2018-11-26T09:25:53.473991900 .....\n", " * range (range) float64 30kB 800.0 802.0 804.0 ... 1.079e+04 1.08e+04\n", "Data variables:\n", " radialspeed (time, range) float64 702MB 13.46 13.42 13.4 ... nan nan nan\n", " cnr (time, range) float64 702MB -32.25 -32.26 ... -30.0 -30.0\n", " azimuth (time) float64 187kB -57.9 -57.8 -57.7 ... 189.8 189.9 190.0\n", " datetime (time, range) float64 702MB 7.374e+05 7.374e+05 ... nan nan\n", " elevation (time) float64 187kB -0.1 -0.1 -0.099 -0.1 ... -0.1 -0.1 -0.1\n", " x (time, range) float64 702MB 741.2 743.1 744.9 ... nan nan nan\n", " y (time, range) float64 702MB -301.0 -301.7 -302.5 ... nan nan" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd \n", "import numpy as np\n", "from lidalign.north_alignment import Northalignment\n", "\n", "HardTargets_file_path = 'https://zenodo.org/record/5654866/files/HardTargets_coordinates_GTI.csv'\n", "HardTarget_Scan_path = 'https://zenodo.org/record/5654866/files/HT_scans_GTI.zip'\n", "\n", "\n", "\n", "HardTargets = pd.read_csv(HardTargets_file_path,index_col=0)\n", "\n", "## Set the location of your Lidar here using the cartesian coordinate system. In this example case the turbine was located at the Turbine 58 i.e. x=HardTargets.x[57], y = HardTargets.y[57].\n", "LidarLocation = pd.DataFrame({'x':HardTargets.x[57], 'y':HardTargets.y[57]}, index=[0])\n", "\n", "## Set the initial guess for your lidar orientation (deviation from North direction clockwise)\n", "initial_guess_Lidar_orientation = 170\n", "\n", "## Set the location of your lidar data here. The columns 'cnr', 'range' and 'azi' are used. Please make sure that all scans were performed with a scanning elevation of 0 deg.\n", "Lidar = pd.read_pickle(HardTarget_Scan_path)\n", "\n", "# conversion to xarray:\n", "Lidar['time'] = pd.to_datetime(Lidar[\"datetime\"] - 719529, unit=\"D\")\n", "Lidar['x'] = np.sin((Lidar.azi+initial_guess_Lidar_orientation)*np.pi/180)*Lidar.range\n", "Lidar['y'] = np.cos((Lidar.azi+initial_guess_Lidar_orientation)*np.pi/180)*Lidar.range\n", "\n", "ds = Lidar.set_index(['time','range']).to_xarray()\n", "del Lidar\n", "ds = ds.rename_vars(dict(azi = \"azimuth\",ele = \"elevation\"))\n", "ds['cnr'] = ds['cnr'].fillna(-30)\n", "# just keep first value of azimuth and elevation for each time step (since they are constant across range)\n", "ds['elevation'] = ds['elevation'].max(dim = 'range')\n", "ds['azimuth'] = ds['azimuth'].max(dim = 'range')\n", "ds = ds.dropna(dim = 'range', how = 'all', subset = ['cnr']).dropna(dim = 'time', how = 'all', subset = ['cnr'])\n", "ds" ] }, { "cell_type": "code", "execution_count": 12, "id": "27760f97", "metadata": {}, "outputs": [], "source": [ "ds['cnr'] = ds['cnr'].where((ds.range <7000) &(ds.y>-7/6*ds.x-200))" ] }, { "cell_type": "markdown", "id": "40b5da02", "metadata": {}, "source": [ "### Perform north alignment from hard targets\n", "As explained in Rott et al. 2022, the hard targets are identified by high CNR values. Then, together with the azimuth and range of the measurement, we can determine the x and y positions of the hard target in the lidar coordinate system. The opimization then fits the coordinates in the lidar coordinate system to the global coordinate system, allowing for the determination of the lidar azimuth offset to actual north. \n", "\n", "\n", "### Adjustments to the original\n", "Note, that some minor adjustments can be found, if \"V2\" is used as method, compared to the work by [Rott et al. 2022](https://wes.copernicus.org/articles/7/283/2022/):\n", "- we fit a gaussian to the CNR data to obtain only a single range from the CNR return signal \n", "- on this range, we add the distance of 5 meters for the tower radius \n", "- Obstacles observed with the lidar but not found in the coordinate list (e.g., vessels or other offshore platforms) that are too far away (>*max_distance*) are removed from the fitting procedure\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9d847612", "metadata": {}, "outputs": [], "source": [ "NAL = Northalignment(HardTargets)\n", "\n", "initial_guess = [*LidarLocation.values[0], -initial_guess_Lidar_orientation]\n", "results = NAL.fit(ds, initial_guess, 5, 'V2', plot = False)" ] }, { "cell_type": "markdown", "id": "464a8e21", "metadata": {}, "source": [ "### Visualisation\n", "\n", "The following provides an interactive illustration of the results:" ] }, { "cell_type": "code", "execution_count": 33, "id": "cdb611f4", "metadata": {}, "outputs": [ { "data": { "text/html": [ " \n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = Northalignment.plot(results.x, NAL.lidar_data_df, NAL.HardTargets, interactive=True, all_hard_targets_lidar=NAL.all_hard_targets_lidar)\n", "fig.show(renderer = 'notebook')" ] }, { "cell_type": "markdown", "id": "c07cde42", "metadata": {}, "source": [ "For more explanation see the Hard Targetting example by Rott et al. in the legacy directory." ] } ], "metadata": { "kernelspec": { "display_name": ".general_venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }