ANN-route-predition/experiemnts/Experiments.ipynb

552 lines
72 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"source": [
"# A suggestion of curse changes for a robot sailboat\n",
"\n",
"## Motivation\n",
"\n",
"The goal of this project is to suggest good points to change the curse of a sailboat while going from point $A$ to point $B$.\n",
"\n",
"This project is done as part of the curse \"Maschienen Learning\" at the University of Applied Sciences South Westphalia. The code labeling the was writen by the team of the [Sailing Team Darmstadt e.V.](https://www.st-darmstadt.de/). A society of stundens whose goal it is to build the [\"roBOOTer\"](https://www.st-darmstadt.de/ueber-uns/boote/prototyp-ii/) a fully autonomous sailboat able to cross the atlantic ocean. A technical challenge that was mastered the first time only a few years ago by [a Norwegian team](http://sailbuoy.no/). I myself am part of the Sailing Team Darmstadt e.V. for nearly 10 years.\n",
"\n",
"One of the challenges to solve is a highly efficient way to find a path over the Ocean. The boot is only 2 meters long and powered by solar energy. That makes power a relatively spares commodity.\n",
"\n",
"## Situation as is\n",
"At the moment the pathfinding algorithm generates a set of more or less random routes to the goal. Each route than gets optimized by a gradient decent moving the curse change points over the ocean to find a path with the lowest cost that can be found by following the highest gradient. This is relatively inefficient since only local minima can be found for each of the randomly generated route. The route with the lowest cost for the so optimized route will be chosen as the final route.\n",
"The idea of this project is to ascertain if it is possible to generate a better initial route through a neural network to give the system a kind of good instinct for the initial routes to reduce optimization steps and the number fo routes that need to be calculated to find a good route. Even tough the initial calculation effort could be high the parallel calculation of 40 routes and lots of optimization steps make it possible that some calculation time and therefore energy can be saved this way.\n",
"The idea of this project is to ascertain if it is possible to generate a better initial route through a neural network to give the system a kind of good instinct for the initial routes to reduce optimization steps and the number fo routes that need to be calculated to find a good route. Even tough the initial calculation effort could be high the parallel calculation of 40 routes and lots of optimization steps make it possible that some calculation time and energy can be saved this way.\n",
"\n",
"## The Project\n",
"\n",
"The goal of this project is to calculate a good first route. That allows for some simplifications of this problem.\n",
"\n",
"Some solutions and assumptions can be made.\n",
"1. The route proposed by this network will not be the final route. This make a somewhat accurate solution good enough.\n",
"2. Since the neural network should not learn how to interpret a specific map but the concept of a map the map can be rotated.\n",
"This allows the wind to come always from north.\n",
"3. Since curse speed is only somewhat proportional to the wind speed a final course may change depending on wind speed not only direction.\n",
"These changes are however somewhat small compared to other influences and can hopefully be ignored since later processing of a proposed route should strait these details out.\n",
"4. When the wind comes always from the same direction (After map orientation by wind) map and route can be mirrored allowing to use all data twice for each route.\n",
"5. Scale does only matter when the curvature of the earth has a significant influence. Allowing for different scaling of the problem for additional training data.\n",
"\n",
"Since there is a solution for this project that only needs some optimisation we can used labeled data to train the network.\n",
"\n",
"### The generell structure\n",
"\n",
"Since"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%% md\n"
}
}
},
{
"cell_type": "code",
"execution_count": 382,
"metadata": {
"collapsed": true,
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"from typing import Optional, Union, Any\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import pandas as pd\n",
"from PIL import ImageDraw, Image\n",
"from shapely.geometry import Polygon, Point\n",
"from shapely.ops import unary_union"
]
},
{
"cell_type": "code",
"execution_count": 383,
"outputs": [],
"source": [
"SIZE_INNER = 50\n",
"SIZE_ROUTE = 100\n",
"MIN_DESTINATION_DISTANCE = 25"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 384,
"outputs": [],
"source": [
"# https://stackoverflow.com/questions/16444719/python-numpy-complex-numbers-is-there-a-function-for-polar-to-rectangular-co\n",
"def polar_to_cartesian(\n",
" radii: np.ndarray,\n",
" angles: np.ndarray,\n",
"):\n",
" \"\"\"Transforms polar coordinates into cartesian coordinates.\n",
"\n",
" Args:\n",
" radii: A array of radii.\n",
" angles: A array of angles.\n",
"\n",
" Returns:\n",
" An array of cartesian coordinates.\n",
" \"\"\"\n",
" return radii * np.exp(2j * angles * np.pi)\n",
"\n",
"\n",
"def cartesian_to_polar(\n",
" x: np.ndarray,\n",
"):\n",
" \"\"\"Transforms cartesian coordinates into polar coordinates.\n",
"\n",
" Args:\n",
" x: A set of complex number to be separated into polar coordinates.\n",
"\n",
" Returns:\n",
" An distance array and an angle array.\n",
" \"\"\"\n",
" return abs(x), np.angle(x)"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 385,
"outputs": [
{
"data": {
"text/plain": "(array('d', [7.834251210977271, 48.43780293223563, 22.372740082639517, 7.834251210977271]),\n array('d', [-60.57486395322814, -63.49031084137326, -27.286653804050623, -60.57486395322814]))"
},
"execution_count": 385,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def random_polygon(\n",
" radius_mean: float = 2,\n",
" radius_sigma: float = 1.5,\n",
"):\n",
" \"\"\"Generates the simplest of polygons, a triangle with a size described by a random polygon.\n",
"\n",
" Args:\n",
" radius_mean: The average radius defining a circumcircle of a triangle.\n",
" radius_sigma: The variance of a radius defining a circumcircle of a triangle.\n",
"\n",
" Returns:\n",
" A single triangle.\n",
" \"\"\"\n",
" array = polar_to_cartesian(\n",
" np.random.lognormal(radius_mean, radius_sigma), np.random.rand(3)\n",
" )\n",
" offset = np.random.randint(low=-SIZE_ROUTE, high=SIZE_ROUTE, size=(2,))\n",
" return_values = np.zeros((3, 2), dtype=float)\n",
" # return_values[1, :] = np.real(offset)\n",
" return_values[:] = offset\n",
" return_values[:, :] += np.array((np.real(array), np.imag(array))).T\n",
" return Polygon(return_values)\n",
" # return np.array( + offset[0], np.imag(array) + offset[1])\n",
"\n",
"\n",
"random_polygon().exterior.xy"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 386,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"POLYGON ((-27.951979105071 -66.53134677013938, -45.511910934378555 -68.64134887839623, -37.309218213928624 -98.84106120532472, -27.951979105071 -66.53134677013938))\n"
]
}
],
"source": [
"print(random_polygon())"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 387,
"outputs": [],
"source": [
"def generate_obstacles(\n",
" seed=None,\n",
" number_of_polygons: int = 50,\n",
" radius_mean: float = 2,\n",
" radius_sigma: float = 1,\n",
"):\n",
" \"\"\"Generates a set of obstacles from a union of triangles.\n",
"\n",
" The union of triangles meas that if polygons overlap o polygon containing the union of those polygons is returned.\n",
" Args:\n",
" seed: A seed to generate a set of obstacles from.\n",
" number_of_polygons: The number of polygons that should be drawn.\n",
" radius_mean: The average radius defining a circumcircle of an obstacle triangle.\n",
" radius_sigma: The variance of a radius defining a circumcircle of an obstacle triangle.\n",
"\n",
" Returns:\n",
" A list of unified obstacles.\n",
" \"\"\"\n",
" if seed is not None:\n",
" np.random.seed(seed)\n",
" polygons = []\n",
" for _ in range(number_of_polygons):\n",
" poly = random_polygon(radius_mean, radius_sigma)\n",
" if poly.contains(Point(0, 0)):\n",
" continue\n",
" polygons.append(poly)\n",
" polygon_list = list(unary_union(polygons).geoms)\n",
" return polygon_list"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 390,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"POINT (32 36)\n"
]
}
],
"source": [
"def generate_destination(obstacles: list[Polygon], seed: Optional[int] = None) -> Point:\n",
" \"\"\"Generates for a map.\n",
"\n",
" Can be used to generate a valid destination for list of obstacles.\n",
" Args:\n",
" obstacles: A list of obstacles.\n",
" seed: The seed determining the point.\n",
"\n",
" Returns:\n",
" A goal that should be reached by the ship.\n",
" \"\"\"\n",
" # sets the seed\n",
" if seed is not None:\n",
" np.random.seed(seed)\n",
"\n",
" # generates the point\n",
" point: Optional[Point] = None\n",
" while (\n",
" point is None\n",
" or abs(point.x) < MIN_DESTINATION_DISTANCE\n",
" or abs(point.y) < MIN_DESTINATION_DISTANCE\n",
" or any(obstacle.contains(point) for obstacle in obstacles)\n",
" ):\n",
" point = Point(np.random.randint(-SIZE_INNER, SIZE_INNER, size=(2,), dtype=int))\n",
" return point\n",
"\n",
"\n",
"print(generate_destination(generate_obstacles(42), 42))"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 419,
"outputs": [
{
"data": {
"text/plain": "<Figure size 576x576 with 1 Axes>",
"image/png": "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MaavKb9aXHn27duHGjVqnKoVX6NGDcyePRt79+5Fnz590KdPHwDAzTffjISEBLRu3RqPPfbYqds3btwYDzzwADp16oSZM2cW2+Y1INWtyxrpQgghTBUUn7TT10zHuO/GYefxndDQ2Hl8J8Z9N87nIH/hhRdi9+7daN68OW655RYsXLgQd9xxB04//XT89ttv+O233wBwGD8xMRGrV6/GwoULsXr16lPHqF69OlasWIExY8YgISEB06dPx6pVqxAdHe1T24QQQgS3oAjwExdMhDur8Jy4O8uNiQsm+nTc2NhYJCUlYdq0aahZsyauuOIKfPjhh8X+btasWejUqRM6duyIdevWYf369ad+d8UVV/jUBiGEEKIkQTEHv+v4Lq+u90ZoaCh69+6N3r17o23btvjoo48K/X779u2YMmUKli9fjmrVqmHs2LGFtnWNiYnxuQ1CCCFEUUHRg29YpaFX11fUxo0bsXnz5lM/r1q1Co0aNSq0XeuJEycQExODKlWqYP/+/fjxx9Lr4Je2zasQQgjhraDowU/uOxnjvhtXaJjeFe7C5L6TfTpuSkoKbr/9dhw7dgxhYWE488wzMW3aNMycORP9+/c/NRffsWNHtGzZEg0aNEDPnj1LPV7RbV5lHl4IIURlBcV2sQAT7SYumIhdx3ehYZWGmNx3Mka3HW10UwOebBcrhBD+IdvFVtDotqMloAshhAgaQTEHL4QQQWPnTuD4catbIWxAArwQQjjdrl0sAd2qFdCkCZBXg0MEN0cP0WutoZSyuhlBwwn5GkIEjV27gFmzgPffBzZs4HVdugArVgDt21vbNmELjg3wUVFROHz4MKpXry5B3g+01jh8+DCioqKsbooQwcsT1D/4ANi2DcjJAbKzgTp1gDffBIYO5b4PQsDBAb5+/fpITk7GwYMHrW5K0IiKikL9+vWtboYQwWX37vye+rZtvC49HYiIAMLDgSeeAP7v/4C8PTGE8PApwCulRgCYBOAsAF211okFfvcggOsB5AC4Q2v9c971/QG8CiAUwLta62crc9/h4eE444wzfGm+EELYkyeof/ABsGULe+WeCphKAdHRwIgRnHevVcvatgrb8rUHvxbAMABvF7xSKdUKwEgArQGcDuAXpVTzvF//D0A/AMkAliulvtVar4cQQgSz5OT8nvqWLdx1seiukjExQJs2wLRpQLt21rRTOIZPAV5rvQFASXPgQwB8prXOALBdKbUFQNe8323RWm/Lu91neX8rAV4IEXw8Qf2DD4DNm0sO6gDgcgFVqgBTpwKDB8s8u6gQs+bg6wFYUuDn5LzrAGB3keu7mdQGIYSwnz178nvqZQV1IH+e/dFHgbvu4s9CVFC5AV4p9QuAOiX8aqLWeo7xTTp1v+MAjMv7MUMptdas+7KBGgAOWd0IE8njc7ZAfnz2fmyZmbw88AAv3rP34/NdoD++Fr7cuNwAr7W+oBLH3QOgQYGf6+ddhzKuL3q/0wBMAwClVKIv9XjtTh6fs8njc65AfmyAPD6nU0ollv9XpTOrkt23AEYqpSKVUmcAaAZgGYDlAJoppc5QSkWAiXjfmtQGIYQQImj5ukzuUgCvA6gJ4Hul1Cqt9UVa63VKqVlg8lw2gFu11jl5t7kNwM/gMrn3tdbrfHoEQgghhCjG1yz6rwF8XcrvJgMotuG61voHAD94eVfTvG+do8jjczZ5fM4VyI8NkMfndD49PkfsBy+EEEII78huckIIIUQAsl2AV0qNUEqtU0rlKqUSivzuQaXUFqXURqXURQWu75933Ral1AT/t7pylFKfK6VW5V12KKVW5V3fWCmVVuB3b1nc1EpRSk1SSu0p8DguLvC7Ep9Lp1BKvaCU+lcptVop9bVSqmre9QHx3AHOfV+VRinVQCn1m1Jqfd5nzJ1515f6OnWavM+RNXmPIzHvutOUUvOVUpvzvlazup3eUkq1KPD8rFJKnVBK3eX0504p9b5S6kDBZeClPV+KXst7P65WSnUq9w601ra6gHXtWwD4HUBCgetbAfgHQCSAMwBsBRP1QvO+bwIgIu9vWln9OCrxuF8E8Gje940BrLW6TQY8pkkA7i3h+hKfS6vb6+VjuxBAWN73zwF4LsCeu4B4XxV5THUBdMr7Pg7AprzXYomvUydeAOwAUKPIdc8DmJD3/QTPa9Wpl7zX5n8AGjn9uQPQC0Cngp8ZpT1fAC4G8CMABaA7gKXlHd92PXit9Qat9cYSfnWq/K3WejsAT/nbrsgrf6u1zgTgKX/rGIq1fi8HMNPqtvhJac+lY2it52mts/N+XALWdAgkjn9fFaW13qe1XpH3/UkAG5BfYTOQDQHwUd73HwEYal1TDNEXwFat9U6rG+IrrfUfAI4Uubq052sIgI81LQFQVSlVt6zj2y7Al6Eeipe5rVfG9U5yLoD9WuvNBa47Qym1Uim1UCl1rlUNM8BtecNJ7xcYGgyE56yg68Aza49AeO4C7TkqRCnVGEBHAEvzrirpdepEGsA8pVSSYjVQAKittd6X9/1/AGpb0zTDjEThzlCgPHcepT1fXr8nLQnwSqlflFJrS7g4uodQkgo+1lEo/ILdB6Ch1rojgLsBzFBKxfuz3RVVzuObCqApgA7gY3rRyrZ6qyLPnVJqIljrYXreVY557oKVUioWwJcA7tJan4DDX6dFnKO17gRgAIBblVK9Cv5Sc6zXsUunFAukDQbwRd5VgfTcFePr82XWZjNl0haVv7VCeY9VKRUGbrnbucBtMgBk5H2fpJTaCqA5AJ/KFpqhos+lUuodAHPzfizrubSNCjx3YwEMAtA3743oqOeuHI54jryllAoHg/t0rfVXAKC13l/g9wVfp46jtd6T9/WAUuprcKplv1KqrtZ6X96Q7gFLG+mbAQBWeJ6zQHruCijt+fL6PemkIfpALX97AYB/tdbJniuUUjWVUqF53zcBH+s2i9pXaUXmhy4F4MkULe25dAylVH8A9wMYrLV2F7g+IJ47OP99VUxerst7ADZorV8qcH1pr1NHUUrFKKXiPN+DiaBrweftmrw/uwaAaZuE+UGh0c5Aee6KKO35+hbA1XnZ9N0BHC8wlF8iS3rwZVHBV/626HwSwMzKJ5RSWQByAYzXWhdNxHCC55VSHcAhph0AbgKAsp5LB3kDXAUwn3EDS7TW4xEgz53WOtvh76uS9ARwFYA1Km9JKoCHAIwq6XXqQLUBfJ33egwDMENr/ZNSajmAWUqp6wHsBBN6HSfvpKUfCj8/JX7GOIVSaiaA3gBqKKWSATwG4FmU/Hz9AGbSbwHgBnBtucfPG1kUQgghRABx0hC9EEIIISpIArwQQggRgCTACyGEEAFIArwQQggRgCTACyGEEAFIArwQQggRgCTACyGEEAFIArwQQggRgP4fOXPCvAIPz1sAAAAASUVORK5CYII=\n"
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"def plot_obstacles(\n",
" obstacles: list[Polygon],\n",
" destination: Optional[Point] = None,\n",
" obstacle_color: Optional[str] = None,\n",
" legend: bool = True,\n",
") -> None:\n",
" \"\"\"PLots the obstacles into a matplotlib plot.\n",
"\n",
" Args:\n",
" obstacles: A list of obstacles.\n",
" obstacle_color: The color the obstacles should have. Can be None.\n",
" If none all obstacles will have different colors.\n",
" legend: If true plots a legend.\n",
" Returns:\n",
" None\n",
" \"\"\"\n",
" plt.figure(figsize=(8, 8))\n",
" plt.axis([-SIZE_ROUTE, SIZE_ROUTE, -SIZE_ROUTE, SIZE_ROUTE])\n",
" plt.title(\"Test\")\n",
" for polygon in obstacles:\n",
" if obstacle_color is not None:\n",
" plt.fill(*polygon.exterior.xy, color=obstacle_color, label=\"Obstacle\")\n",
" else:\n",
" plt.fill(*polygon.exterior.xy)\n",
" plt.scatter(*destination.xy, marker=\"X\", color=\"green\", label=\"Destination\")\n",
" plt.scatter(0, 0, marker=\"o\", color=\"green\", label=\"Start\")\n",
" if legend:\n",
" # https://stackoverflow.com/questions/13588920/stop-matplotlib-repeating-labels-in-legend\n",
" handles, labels = plt.gca().get_legend_handles_labels()\n",
" by_label = dict(zip(labels, handles))\n",
" plt.legend(by_label.values(), by_label.keys())\n",
" plt.show()\n",
"\n",
"\n",
"o = generate_obstacles(4)\n",
"plot_obstacles(o, generate_destination(o, 4), \"RED\")"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 391,
"outputs": [
{
"data": {
"text/plain": "<PIL.Image.Image image mode=RGB size=200x200>",
"image/png": "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\n"
},
"execution_count": 391,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def generate_image_from_map(\n",
" obstacles: list[Polygon], destination: Point, solution\n",
") -> Image:\n",
" \"\"\"Generate an image from the map.\n",
"\n",
" Can be used to feed an ANN.\n",
" - Obstacles are marked as reed.\n",
" - The destination is marked as green.\n",
" - The points where the route will likely change are blue.\n",
"\n",
" Args:\n",
" obstacles: A list of obstacles as shapely Polygons.\n",
" destination: A destination that should be navigated to.\n",
" \"\"\"\n",
" img = Image.new(\n",
" \"RGB\",\n",
" (SIZE_ROUTE * 2, SIZE_ROUTE * 2),\n",
" \"#ffffff\",\n",
" )\n",
" draw = ImageDraw.Draw(img)\n",
" for polygon in obstacles:\n",
" draw.polygon(\n",
" list(np.dstack(polygon.exterior.xy).reshape((-1)) + SIZE_ROUTE),\n",
" fill=\"#FF0000\",\n",
" outline=\"#FF0000\",\n",
" )\n",
" img.putpixel((int(destination.x) + 100, int(destination.y) + 100), (0, 0xFF, 0))\n",
" return img\n",
"\n",
"\n",
"o = generate_obstacles(42)\n",
"g = generate_destination(o, 42)\n",
"og_img = generate_image_from_map(o, g, None)\n",
"og_img"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 392,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7.15 ms ± 166 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
"source": [
"def generate_all_to_series(\n",
" seed: Optional[int] = None, image: bool = False\n",
") -> pd.Series:\n",
" \"\"\"Generates everything and aggregates all data into a `pd:Series`.\n",
"\n",
" Args:\n",
" seed:The seed that should be used to generate map and destination.\n",
" image: If an image should be generated or if that should be postponed to save memory.\n",
" Returns:\n",
" Contains a `pd.Series`containing the following.\n",
" - The seed tha generated the map.\n",
" - The destination in x\n",
" - The destination in y\n",
" - A list of Obstacle polygons.\n",
" - The route generated for this map by the roBOOTer navigation system.\n",
" - Optionally the image containing all the information. Can be generated at a later date without the fear for a loss of accuracy.\n",
" \"\"\"\n",
" obstacles = generate_obstacles(seed)\n",
" destination = generate_destination(obstacles, seed)\n",
"\n",
" return pd.Series(\n",
" data={\n",
" \"seed\": str(seed),\n",
" \"obstacles\": obstacles,\n",
" \"destination_x\": destination.x,\n",
" \"destination_y\": destination.y,\n",
" \"image\": generate_image_from_map(obstacles, destination, None)\n",
" if image\n",
" else pd.NA,\n",
" }\n",
" )\n",
"\n",
"\n",
"%timeit generate_all_to_series()"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 393,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7.04 s ± 129 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
},
{
"data": {
"text/plain": " obstacles destination_x \\\nseed \n0 [POLYGON ((-93.57082194649087 -66.947987633875... -6.0 \n1 [POLYGON ((-81.7284773501737 -78.1366517664776... -13.0 \n2 [POLYGON ((-84.97456253498679 -96.322348952267... -10.0 \n3 [POLYGON ((-72.52222134370759 -84.370870527173... -26.0 \n4 [POLYGON ((-49.01395292839513 -91.236864062937... -4.0 \n... ... ... \n995 [POLYGON ((-51.07604481480806 -110.26281808589... -41.0 \n996 [POLYGON ((-73.53136121570428 -96.869509434739... 28.0 \n997 [POLYGON ((-40.84353887456576 -93.533626866125... 20.0 \n998 [POLYGON ((-103.97610620795356 -105.6733356239... 45.0 \n999 [POLYGON ((-63.84493689800847 -95.735428561017... 14.0 \n\n destination_y image \nseed \n0 -3.0 <NA> \n1 -38.0 <NA> \n2 -35.0 <NA> \n3 -47.0 <NA> \n4 5.0 <NA> \n... ... ... \n995 -20.0 <NA> \n996 39.0 <NA> \n997 -41.0 <NA> \n998 -12.0 <NA> \n999 42.0 <NA> \n\n[1000 rows x 4 columns]",
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>obstacles</th>\n <th>destination_x</th>\n <th>destination_y</th>\n <th>image</th>\n </tr>\n <tr>\n <th>seed</th>\n <th></th>\n <th></th>\n <th></th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>[POLYGON ((-93.57082194649087 -66.947987633875...</td>\n <td>-6.0</td>\n <td>-3.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>1</th>\n <td>[POLYGON ((-81.7284773501737 -78.1366517664776...</td>\n <td>-13.0</td>\n <td>-38.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>2</th>\n <td>[POLYGON ((-84.97456253498679 -96.322348952267...</td>\n <td>-10.0</td>\n <td>-35.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>3</th>\n <td>[POLYGON ((-72.52222134370759 -84.370870527173...</td>\n <td>-26.0</td>\n <td>-47.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>4</th>\n <td>[POLYGON ((-49.01395292839513 -91.236864062937...</td>\n <td>-4.0</td>\n <td>5.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>...</th>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n </tr>\n <tr>\n <th>995</th>\n <td>[POLYGON ((-51.07604481480806 -110.26281808589...</td>\n <td>-41.0</td>\n <td>-20.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>996</th>\n <td>[POLYGON ((-73.53136121570428 -96.869509434739...</td>\n <td>28.0</td>\n <td>39.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>997</th>\n <td>[POLYGON ((-40.84353887456576 -93.533626866125...</td>\n <td>20.0</td>\n <td>-41.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>998</th>\n <td>[POLYGON ((-103.97610620795356 -105.6733356239...</td>\n <td>45.0</td>\n <td>-12.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>999</th>\n <td>[POLYGON ((-63.84493689800847 -95.735428561017...</td>\n <td>14.0</td>\n <td>42.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n </tbody>\n</table>\n<p>1000 rows × 4 columns</p>\n</div>"
},
"execution_count": 393,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%timeit pd.DataFrame([generate_all_to_series(i) for i in range(1000)]).set_index(\"seed\")\n",
"df = pd.DataFrame([generate_all_to_series(i) for i in range(1000)]).set_index(\"seed\")\n",
"df.to_pickle(\"test.pickle\")\n",
"df"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "code",
"execution_count": 395,
"outputs": [
{
"data": {
"text/plain": " obstacles destination_x \\\nseed \n0 [POLYGON ((-93.57082194649087 -66.947987633875... -6.0 \n1 [POLYGON ((-81.7284773501737 -78.1366517664776... -13.0 \n2 [POLYGON ((-84.97456253498679 -96.322348952267... -10.0 \n3 [POLYGON ((-72.52222134370759 -84.370870527173... -26.0 \n4 [POLYGON ((-49.01395292839513 -91.236864062937... -4.0 \n... ... ... \n995 [POLYGON ((-51.07604481480806 -110.26281808589... -41.0 \n996 [POLYGON ((-73.53136121570428 -96.869509434739... 28.0 \n997 [POLYGON ((-40.84353887456576 -93.533626866125... 20.0 \n998 [POLYGON ((-103.97610620795356 -105.6733356239... 45.0 \n999 [POLYGON ((-63.84493689800847 -95.735428561017... 14.0 \n\n destination_y image \nseed \n0 -3.0 <NA> \n1 -38.0 <NA> \n2 -35.0 <NA> \n3 -47.0 <NA> \n4 5.0 <NA> \n... ... ... \n995 -20.0 <NA> \n996 39.0 <NA> \n997 -41.0 <NA> \n998 -12.0 <NA> \n999 42.0 <NA> \n\n[1000 rows x 4 columns]",
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>obstacles</th>\n <th>destination_x</th>\n <th>destination_y</th>\n <th>image</th>\n </tr>\n <tr>\n <th>seed</th>\n <th></th>\n <th></th>\n <th></th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>[POLYGON ((-93.57082194649087 -66.947987633875...</td>\n <td>-6.0</td>\n <td>-3.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>1</th>\n <td>[POLYGON ((-81.7284773501737 -78.1366517664776...</td>\n <td>-13.0</td>\n <td>-38.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>2</th>\n <td>[POLYGON ((-84.97456253498679 -96.322348952267...</td>\n <td>-10.0</td>\n <td>-35.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>3</th>\n <td>[POLYGON ((-72.52222134370759 -84.370870527173...</td>\n <td>-26.0</td>\n <td>-47.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>4</th>\n <td>[POLYGON ((-49.01395292839513 -91.236864062937...</td>\n <td>-4.0</td>\n <td>5.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>...</th>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n </tr>\n <tr>\n <th>995</th>\n <td>[POLYGON ((-51.07604481480806 -110.26281808589...</td>\n <td>-41.0</td>\n <td>-20.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>996</th>\n <td>[POLYGON ((-73.53136121570428 -96.869509434739...</td>\n <td>28.0</td>\n <td>39.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>997</th>\n <td>[POLYGON ((-40.84353887456576 -93.533626866125...</td>\n <td>20.0</td>\n <td>-41.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>998</th>\n <td>[POLYGON ((-103.97610620795356 -105.6733356239...</td>\n <td>45.0</td>\n <td>-12.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n <tr>\n <th>999</th>\n <td>[POLYGON ((-63.84493689800847 -95.735428561017...</td>\n <td>14.0</td>\n <td>42.0</td>\n <td>&lt;NA&gt;</td>\n </tr>\n </tbody>\n</table>\n<p>1000 rows × 4 columns</p>\n</div>"
},
"execution_count": 395,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df2 = pd.read_pickle(\"test.pickle\")\n",
"df2"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "markdown",
"source": [
"https://programtalk.com/python-examples/PIL.ImageDraw.Draw.polygon/)\n",
"https://stackoverflow.com/questions/3654289/scipy-create-2d-polygon-mask"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%% md\n"
}
}
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 0
}