diff --git a/nbs/01d_polygon_canvas.ipynb b/nbs/01d_polygon_canvas.ipynb new file mode 100644 index 0000000..e31fd5e --- /dev/null +++ b/nbs/01d_polygon_canvas.ipynb @@ -0,0 +1,893 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c99844dd", + "metadata": {}, + "outputs": [], + "source": [ + "# default_exp polygon_canvas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b17564e8", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53a3307c", + "metadata": {}, + "outputs": [], + "source": [ + "from nbdev import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5017c386", + "metadata": {}, + "outputs": [], + "source": [ + "# exporti\n", + "from collections import deque\n", + "import numpy as np\n", + "import traitlets\n", + "import ipywidgets as widgets\n", + "from pathlib import Path\n", + "from math import log, sqrt, atan2, pi\n", + "from ipyevents import Event\n", + "from ipycanvas import MultiCanvas, hold_canvas\n", + "from ipywidgets import Image, Label, Layout, HBox, VBox, Output\n", + "from abc import ABC, abstractmethod\n", + "from ipycanvas import Canvas\n", + "from ipyannotator.bbox_canvas import draw_bg\n", + "from random import randrange" + ] + }, + { + "cell_type": "markdown", + "id": "3004e4ee", + "metadata": {}, + "source": [ + "# Polygon Algorithms Helpers" + ] + }, + { + "cell_type": "markdown", + "id": "dd61c31a", + "metadata": {}, + "source": [ + "### Convex Hull\n", + "\n", + "The following class implements the Monothone Chain Algorithm to get the boundaries from a set of points, the present algorithm was inspired from [the wikibooks](https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain#Python)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6fa5daef", + "metadata": {}, + "outputs": [], + "source": [ + "# exporti\n", + "class ConvexHull:\n", + " def _cross(self, o, a, b):\n", + " return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])\n", + " \n", + " def execute(self, points, k):\n", + " points = sorted(set(points))\n", + "\n", + " if len(points) <= 1:\n", + " return points\n", + "\n", + " lower = []\n", + " for p in points:\n", + " while len(lower) >= 2 and self._cross(lower[-2], lower[-1], p) <= 0:\n", + " lower.pop()\n", + " lower.append(p)\n", + "\n", + " upper = []\n", + " for p in reversed(points):\n", + " while len(upper) >= 2 and self._cross(upper[-2], upper[-1], p) <= 0:\n", + " upper.pop()\n", + " upper.append(p)\n", + "\n", + " return lower[:-1] + upper[:-1]" + ] + }, + { + "cell_type": "markdown", + "id": "1db73299", + "metadata": {}, + "source": [ + "### Concave Hull\n", + "\n", + "The following class implements the Concave Hull Algorithm, based on Moreira and Santos (2007), to get the edge boundaries from a set of points. This particular algorithm is based on the Jarvi's March (gift wrapping) to calculate the concave hull, but uses a limited k-nearest neighbourhoods to do it. The code is similar to the paper.\n", + "\n", + "#### Reference\n", + "\n", + "Moreira, A. and Santos, M.Y., 2007, Concave Hull: A K-nearest neighbors approach for the computation of the region occupied by a set of points [PDF](https://repositorium.sdum.uminho.pt/bitstream/1822/6429/1/ConcaveHull_ACM_MYS.pdf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be2dff13", + "metadata": {}, + "outputs": [], + "source": [ + "# exporti\n", + "\n", + "class PointDistance:\n", + " def _euclidian_distance(self, a, b):\n", + " result = abs(a[0] - b[0])**2 + abs(a[1] - b[1])**2\n", + " \n", + " return sqrt(result)\n", + "\n", + "class ConcaveHull(PointDistance):\n", + " def _clean_list(self, points):\n", + " return list(set(points))\n", + " \n", + " def _find_min_y_point(self, points):\n", + " return min(points, key=lambda p: p[1])\n", + " \n", + " def _remove_point(self, points, point):\n", + " points.remove(point)\n", + " return points\n", + " \n", + " def _add_point(self, points, point):\n", + " points.append(point)\n", + " return points\n", + " \n", + " def _nearest_point(self, points, point, k):\n", + " nearest = []\n", + " for p in points:\n", + " nearest.append(\n", + " (self._euclidian_distance(point, p), p)\n", + " )\n", + " \n", + " nearest = sorted(nearest, key=lambda p: p[0])\n", + " result = []\n", + " \n", + " for i in range(min(k, len(points))):\n", + " result.append(nearest[i][1])\n", + " \n", + " return result\n", + " \n", + " def _sort_by_angle(self, points, point, angle):\n", + " \"\"\"Sort in descending order of right-hand turn\"\"\"\n", + " def compare(a):\n", + " return self._angle_difference(angle, self._angle(point, a))\n", + " \n", + " return sorted(points, key=compare, reverse=True)\n", + "\n", + " def _intersects_q(self, line1, line2):\n", + " a1 = line1[1][1] - line1[0][1]\n", + " b1 = line1[0][0] - line1[1][0]\n", + " c1 = a1 * line1[0][0] + b1 * line1[0][1]\n", + " a2 = line2[1][1] - line2[0][1]\n", + " b2 = line2[0][0] - line2[1][0]\n", + " c2 = a2 * line2[0][0] + b2 * line2[0][1]\n", + " tmp = (a1 * b2 - a2 * b1)\n", + " if tmp == 0:\n", + " return False\n", + " sx = (c1 * b2 - c2 * b1) / tmp\n", + " if (sx > line1[0][0] and sx > line1[1][0]) or (sx > line2[0][0] and sx > line2[1][0]) or\\\n", + " (sx < line1[0][0] and sx < line1[1][0]) or (sx < line2[0][0] and sx < line2[1][0]):\n", + " return False\n", + " sy = (a1 * c2 - a2 * c1) / tmp\n", + " if (sy > line1[0][1] and sy > line1[1][1]) or (sy > line2[0][1] and sy > line2[1][1]) or\\\n", + " (sy < line1[0][1] and sy < line1[1][1]) or (sy < line2[0][1] and sy < line2[1][1]):\n", + " return False\n", + " return True\n", + " \n", + " def _angle(self, a, b):\n", + " \"\"\"Return the angle in radians between two points\"\"\"\n", + " return atan2(b[1] - a[1], b[0] - a[0])\n", + " \n", + " def _angle_difference(self, a, b):\n", + " if (a > 0 and b >= 0) and a > b:\n", + " return abs(a - b)\n", + " elif (a >= 0 and b > 0) and a < b:\n", + " return 2 * pi + a - b\n", + " elif ((a < 0 and b <= 0) and a < b):\n", + " return 2 * pi + a + abs(b)\n", + " elif ((a <= 0 and b < 0) and a > b):\n", + " return abs(a - b)\n", + " elif (a <= 0 and 0 < b):\n", + " return 2 * pi + a - b\n", + " elif (a >= 0 and 0 >= b):\n", + " return a + abs(b)\n", + " \n", + " return 0.0\n", + " \n", + " def _point_in_polygon_q(self, point, points):\n", + " x = point[0]\n", + " y = point[1]\n", + " poly = [(pt[0], pt[1]) for pt in points]\n", + " n = len(poly)\n", + " inside = False\n", + " xints = 0.0\n", + "\n", + " p1x, p1y = poly[0]\n", + " for i in range(n + 1):\n", + " p2x, p2y = poly[i % n]\n", + " if y > min(p1y, p2y):\n", + " if y <= max(p1y, p2y):\n", + " if x <= max(p1x, p2x):\n", + " if p1y != p2y:\n", + " xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x\n", + " if p1x == p2x or x <= xints:\n", + " inside = not inside\n", + " p1x, p1y = p2x, p2y\n", + "\n", + " return inside\n", + " \n", + " def execute(self, points, k):\n", + " kk = max(k, 2)\n", + " dataset = self._clean_list(points)\n", + "\n", + " if len(dataset) < 3:\n", + " return None\n", + " \n", + " if len(dataset) == 3:\n", + " return dataset\n", + " \n", + " kk = min(kk, len(dataset))\n", + " first_point = self._find_min_y_point(dataset)\n", + " hull = [first_point]\n", + " current_point = first_point\n", + " dataset = self._remove_point(dataset, first_point)\n", + " prev_angle = pi\n", + " step = 2\n", + " \n", + " while ((current_point != first_point) or step == 2) and len(dataset) > 0:\n", + " if step == 5:\n", + " dataset = self._add_point(dataset, first_point)\n", + " \n", + " k_nearest_point = self._nearest_point(dataset, current_point, kk)\n", + " c_points = self._sort_by_angle(k_nearest_point, current_point, prev_angle)\n", + " its = True\n", + " i = -1\n", + "\n", + " while its is True and i < (len(c_points)-1):\n", + " i += 1\n", + " \n", + " if c_points[i] == first_point:\n", + " last_point = 1\n", + " else:\n", + " last_point = 0\n", + " \n", + " j = 2\n", + " its = False\n", + " \n", + " while (its is False) and (j < (len(hull) - last_point)):\n", + " its = self._intersects_q((hull[step-2], c_points[i]), (hull[step-2-j], hull[step-1-j]))\n", + " j += 1\n", + " \n", + " if its is True:\n", + " \"\"\"All candidates intersect restart with a new k\"\"\"\n", + " return self.execute(points, kk+1)\n", + " \n", + " current_point = c_points[i]\n", + " hull = self._add_point(hull, current_point)\n", + " prev_angle = self._angle(hull[step-1], hull[step-2])\n", + " dataset = self._remove_point(dataset, current_point)\n", + " step += 1\n", + " \n", + " all_inside = True\n", + " i = len(dataset)-1\n", + " \n", + " \"\"\" check if all the given points are inside the computed polygon \"\"\"\n", + " while (all_inside is True) and i >= 0:\n", + " all_inside = self._point_in_polygon_q(dataset[i], hull)\n", + " i -= 1\n", + " \n", + " \"\"\"Since at least one point is out of the computed polygon, try again with a higher number of neighbours \"\"\"\n", + " if all_inside is False:\n", + " return self.execute(points, kk+1)\n", + " \n", + " return hull" + ] + }, + { + "cell_type": "markdown", + "id": "fd25764f", + "metadata": {}, + "source": [ + "## Testing \n", + "\n", + "The following methods tests the hull algorithms" + ] + }, + { + "cell_type": "markdown", + "id": "705aefe9", + "metadata": {}, + "source": [ + "### Concave Hull" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "718a26b2", + "metadata": {}, + "outputs": [], + "source": [ + "p = ConcaveHull()\n", + "\n", + "\n", + "assert p._euclidian_distance((1, 1), (3,4)), sqrt(13)\n", + "\n", + "assert p._clean_list([(1,2), (1,2)]), (1,2)\n", + "\n", + "assert p._clean_list([(2,7), (3,4), (5,6)]), (3,4)\n", + "\n", + "assert p._nearest_point([(2,7), (3,4), (5,6)], (1,1), 1), (3,4)\n", + " \n", + "assert p._angle_difference(pi, 2*pi), pi\n", + "\n", + "assert p._angle((20,20), (40,40)), 0.7853981633974483\n", + " \n", + "assert p._sort_by_angle([(1,2), (3,4), (2,1)], (1,1), pi), [(2, 1), (3, 4), (1, 2)]\n", + "\n", + "assert p._remove_point([(1,2), (3,4)], (1,2)), [(3,4)]\n", + " \n", + "assert p._add_point([(1,2)], (3,4)), [(1,2), (3,4)]\n", + "\n", + "assert p.execute([(1,2), (1,1), (3,4), (2,1)], 3), [(1, 1), (2, 1), (3, 4), (1, 2)]" + ] + }, + { + "cell_type": "markdown", + "id": "ae0de8d1", + "metadata": {}, + "source": [ + "# Draw Polygon on Canvas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3658d88", + "metadata": {}, + "outputs": [], + "source": [ + "#exporti\n", + "\n", + "from typing import List\n", + "\n", + "def draw_polygon(canvas, coords: List[tuple], color='blue', line_fill_color='white', line_width=None, clear=False):\n", + " with hold_canvas(canvas):\n", + " if clear:\n", + " canvas.clear()\n", + " \n", + " line_width = line_width or log(canvas.height)/5\n", + "\n", + " canvas.line_width = line_width*3 # we will have 1 inner and 2 outer lines, so multiply by 3\n", + " canvas.line_cap = 'round'\n", + " \n", + " canvas.stroke_style = color\n", + " canvas.stroke_polygon(coords)\n", + "\n", + " canvas.stroke_style = line_fill_color\n", + " canvas.line_width = line_width\n", + " canvas.stroke_polygon(coords)\n", + "\n", + " \n", + "def draw_points(canvas, coords: List[tuple], color='white', size=2, clear=False):\n", + " with hold_canvas(canvas):\n", + " if clear:\n", + " canvas.clear()\n", + " \n", + " canvas.fill_style = color\n", + " for point in coords:\n", + " if point:\n", + " canvas.fill_circle(point[0], point[1], size)" + ] + }, + { + "cell_type": "markdown", + "id": "a4db5b2d", + "metadata": {}, + "source": [ + "## Drawing with concave hull\n", + "\n", + "The following section will show up draw using the concave hull" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc2ddb27", + "metadata": {}, + "outputs": [], + "source": [ + "### Ploting a five points concave hull\n", + "p = ConcaveHull()\n", + "\n", + "points = [(20, 20), (80, 20), (45, 30), (50,70), (80, 100)]\n", + "polygon = p.execute(points, 3)\n", + "\n", + "canvas = Canvas(width=125, height=125)\n", + "draw_bg(canvas)\n", + "\n", + "draw_polygon(canvas, polygon, color='red', line_width=1.5)\n", + "draw_points(canvas, points, color='blue', size=5)\n", + "\n", + "canvas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75c7730d", + "metadata": {}, + "outputs": [], + "source": [ + "### Ploting n points concave hull\n", + "\n", + "n = 1000\n", + "points = []\n", + "\n", + "for i in range(n):\n", + " points.append((randrange(1,900), randrange(1,900)))\n", + "\n", + "polygon = p.execute(points, 3)\n", + "\n", + "canvas = Canvas(width=1000, height=1000)\n", + "draw_bg(canvas)\n", + "\n", + "draw_polygon(canvas, polygon, color='red', line_width=1.5)\n", + "draw_points(canvas, points, color='blue', size=5)\n", + "\n", + "canvas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d23e82ac", + "metadata": {}, + "outputs": [], + "source": [ + "# test how draw_polygon works\n", + "canvas = Canvas(width=125, height=125)\n", + "draw_bg(canvas)\n", + "\n", + "draw_polygon(canvas, [(10, 40), (10, 100)], color='black', line_width=2)\n", + "draw_polygon(canvas, np.array([(20, 20), (80, 20), (80, 100)]), color='red', line_width=1.5)\n", + "draw_polygon(canvas, np.array([(10, 10), (90, 10), (90, 110), (60, 110)]), color='green', line_width=2)\n", + "canvas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dba06929", + "metadata": {}, + "outputs": [], + "source": [ + "# test how draw_polygon works\n", + "canvas = Canvas(width=125, height=125)\n", + "draw_bg(canvas)\n", + "\n", + "draw_polygon(canvas, [(20, 20), (80, 20), (80, 100)], color='red', line_width=1.5)\n", + "draw_points(canvas, [(20, 20), (80, 20), (80, 100)], color='blue', size=5)\n", + "\n", + "canvas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87100571", + "metadata": {}, + "outputs": [], + "source": [ + "# testing how capture the delete keyboard \n", + "\n", + "# from IPython.display import display\n", + "# from ipywidgets import Label, HTML, HBox, Image, VBox, Box, HBox\n", + "\n", + "\n", + "# canvas = Canvas(width=125, height=125)\n", + "# draw_bg(canvas)\n", + "# draw_polygon(canvas, [(20, 20), (80, 20), (80, 100)], color='red', line_width=1.5)\n", + "\n", + "# d = Event(source=canvas, watched_events=['keydown'])\n", + "\n", + "# def handle_event(event):\n", + "# draw_points(canvas, [(10, 10)], color='blue', size=5)\n", + "\n", + "# d.on_dom_event(handle_event)\n", + "# canvas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43328fdf", + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "from ipywidgets import Label, HTML, HBox, Image, VBox, Box, HBox\n", + "\n", + "\n", + "canvas = Canvas(width=125, height=125)\n", + "draw_bg(canvas)\n", + "draw_polygon(canvas, [(20, 20), (80, 20), (80, 100)], color='red', line_width=1.5)\n", + "\n", + "\n", + "h = HTML('Event info')\n", + "d = Event(source=canvas, watched_events=['keydown'])\n", + "\n", + "def handle_event(event):\n", + " lines = {k:v for k, v in event.items()}\n", + " content = str(lines['key'])\n", + " h.value = content\n", + " print(content)\n", + "\n", + "d.on_dom_event(handle_event)\n", + "canvas\n", + "\n", + "display(canvas, h)" + ] + }, + { + "cell_type": "markdown", + "id": "5d0089d3", + "metadata": {}, + "source": [ + "# Create PolygoneCanvas" + ] + }, + { + "cell_type": "markdown", + "id": "2924cf52", + "metadata": {}, + "source": [ + "You can interactively use the widget in the following ways:\n", + "\n", + "- Click in the desired location to add a new point to a polygon\n", + "\n", + "- Press \"Delete\" or \"Backspace\" to remove the last point\n", + "\n", + "- Press \"Space\" to stop drawing and adding new points\n", + "\n", + "- Press \"LeftArrow\" or \"RightArrow\" to stop change last added point\n", + "\n", + "- Move a cursor away from a widget to stop drawing and adding new points\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8cd3217", + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "from ipyannotator.bbox_canvas import draw_bounding_box, draw_img, get_image_size, points2bbox_coords\n", + "from IPython.display import display\n", + "from ipywidgets import Label, HTML, HBox, Image, VBox, Box, HBox\n", + "\n", + "\n", + "class PolygonCanvas(HBox, PointDistance, traitlets.HasTraits):\n", + " \"\"\"\n", + " Represents canvas holding image and a polygon ontop. \n", + " Gives user an ability to draw a polygon with mouse.\n", + " \n", + " \"\"\"\n", + " debug_output = widgets.Output(layout={'border': '1px solid black'})\n", + " image_path = traitlets.Unicode()\n", + " _canvas_polygon_coords = traitlets.List()\n", + " _image_scale = traitlets.Float()\n", + " \n", + " def __init__(self, width, height, k = 3):\n", + " super().__init__()\n", + " \n", + " # used by the hull algorithm\n", + " self._k = k\n", + " self._hull = ConcaveHull()\n", + "\n", + " self._is_drawing = False\n", + " self._current_point = None\n", + " self._image_scale = 1.0\n", + " \n", + " self._bg_layer = 0\n", + " self._image_layer = 1\n", + " self._box_layer = 2\n", + " # do not stick bbox to borders\n", + " self.padding = 2\n", + "\n", + " # Define each of the children...\n", + " self._image = Image(layout=Layout(display='flex',\n", + " justify_content='center',\n", + " align_items='center',\n", + " align_content='center'))\n", + " self._multi_canvas = MultiCanvas(3, width=width, height=height)\n", + " self._im_name_box = Label()\n", + " \n", + " children = [VBox([self._multi_canvas, self._im_name_box])]\n", + " self.children = children\n", + " \n", + " draw_bg(self._multi_canvas[self._bg_layer])\n", + " \n", + " self.d = Event(source=self, watched_events=['keydown'])\n", + " \n", + " # link drawing events \n", + " self._multi_canvas[self._box_layer].on_mouse_move(self._update_pos)\n", + " self._multi_canvas[self._box_layer].on_mouse_up(self._add_point)\n", + " self._multi_canvas[self._box_layer].on_mouse_out(self._stop_drawing)\n", + " self.d.on_dom_event(self._del_nearest_point)\n", + " \n", + " @debug_output.capture(clear_output=False) \n", + " def _add_point(self, x, y):\n", + "# print(\"-> ADD POINT\")\n", + " self._is_drawing = True\n", + " self._canvas_polygon_coords.append((x, y))\n", + " self._draw_polygon()\n", + "# print(\"<- ADD POINT\")\n", + " \n", + " def _remove_point(self, point):\n", + " self._is_drawing = True\n", + " self._canvas_polygon_coords.remove(point)\n", + " self._draw_polygon()\n", + " \n", + " @debug_output.capture(clear_output=False)\n", + " def _update_pos(self, x, y):\n", + "# print(\"-> UPDATE POS\")\n", + " self._is_drawing = True\n", + " self._current_point = (x, y)\n", + " self._draw_polygon()\n", + "# print(\"<- UPDATE POS\")\n", + " \n", + " @debug_output.capture(clear_output=False)\n", + " def _stop_drawing(self, x, y):\n", + "# print(\"-> STOP DRAWING\")\n", + " self._is_drawing = False\n", + " self._draw_polygon()\n", + "# print(\"<- STOP DRAWING\")\n", + "\n", + " @debug_output.capture(clear_output=False)\n", + " @traitlets.observe('_canvas_polygon_coords', '_current_point')\n", + " def _draw_polygon(self, change=None):\n", + "# print('-> Observe _canvas_polygon_coords: ') \n", + " if not self._canvas_polygon_coords:\n", + " self._clear_polygon()\n", + " self._canvas_polygon_coords = []\n", + "\n", + " return\n", + "\n", + " if self._is_drawing:\n", + " coords = (self._canvas_polygon_coords + [self._current_point])\n", + " else: \n", + " coords = self._canvas_polygon_coords\n", + " \n", + " if len(coords) > 3:\n", + " alpha_coords = self._hull.execute(coords, self._k)\n", + "\n", + " draw_polygon(self._multi_canvas[self._box_layer], alpha_coords, \n", + " color='black', clear=True, line_width=1.5)\n", + " else:\n", + " draw_polygon(self._multi_canvas[self._box_layer], [], \n", + " color='black', clear=True, line_width=1.5)\n", + " \n", + " draw_points(self._multi_canvas[self._box_layer], coords, \n", + " color='green', size=3)\n", + " \n", + " self._select_nearest_point()\n", + "# print('<- Observe canvas_coords')\n", + " \n", + " def _nearest_point(self):\n", + " coords = self._canvas_polygon_coords\n", + " nearest_point = None\n", + " nearest_distance = None\n", + " \n", + " for coord in coords:\n", + " distance = self._euclidian_distance(coord, self._current_point)\n", + " \n", + " if nearest_distance is None or nearest_distance > distance:\n", + " nearest_distance = distance\n", + " nearest_point = coord\n", + "\n", + " return nearest_point\n", + " \n", + " def _select_nearest_point(self):\n", + " nearest = self._nearest_point()\n", + "\n", + " if nearest:\n", + " draw_points(self._multi_canvas[self._box_layer], [nearest], \n", + " color='yellow', size=3)\n", + " \n", + " def _del_nearest_point(self, event):\n", + " event_as_dict = {k:v for k, v in event.items()}\n", + " \n", + " if event_as_dict['key'] == 'Delete':\n", + " nearest = self._nearest_point()\n", + "\n", + " if nearest:\n", + " self._remove_point(nearest)\n", + "\n", + " def _clear_polygon(self):\n", + " self._multi_canvas[self._box_layer].clear()\n", + " \n", + " @traitlets.observe('image_path')\n", + " def _draw_image(self, image):\n", + " self._image_scale = draw_img(self._multi_canvas[self._image_layer], self.image_path, clear=True)\n", + " self._im_name_box.value = Path(self.image_path).name\n", + "\n", + " @property\n", + " def image_scale(self):\n", + " return self._image_scale\n", + " \n", + " def _clear_image(self):\n", + " self._multi_canvas[self._image_layer].clear()\n", + " \n", + " # needed to support voila\n", + " # https://ipycanvas.readthedocs.io/en/latest/advanced.html#ipycanvas-in-voila\n", + " def observe_client_ready(self, cb=None):\n", + " self._multi_canvas.on_client_ready(cb)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b366ec71", + "metadata": {}, + "outputs": [], + "source": [ + "gui = PolygonCanvas(width=250, height=250)\n", + "# gui.image_path = '../data/projects/im2im1/class_images/blocks_1.png'\n", + "\n", + "gui" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d6caba4", + "metadata": {}, + "outputs": [], + "source": [ + "gui.debug_output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60b4f51a", + "metadata": {}, + "outputs": [], + "source": [ + "gui._canvas_polygon_coords" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "031a1d7c", + "metadata": {}, + "outputs": [], + "source": [ + "gui._canvas_polygon_coords = [(137.5625, 74.75), (44.5625, 167.75), (312.5625, 250.75)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a18f8d8", + "metadata": {}, + "outputs": [], + "source": [ + "gui.image_path = '../data/projects/im2im1/class_images/blocks_1.png'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c00012b", + "metadata": {}, + "outputs": [], + "source": [ + "gui.image_scale" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ac7df60", + "metadata": {}, + "outputs": [], + "source": [ + "gui._clear_polygon()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71c26c22", + "metadata": {}, + "outputs": [], + "source": [ + "#hide\n", + "from nbdev.export import *\n", + "notebook2script()" + ] + }, + { + "cell_type": "markdown", + "id": "03d0fd5c", + "metadata": {}, + "source": [ + "## User interaction with the canvas\n", + "\n", + "As is right now" + ] + }, + { + "cell_type": "markdown", + "id": "0c3b6ea8", + "metadata": {}, + "source": [ + "\n", + "\n", + "![](http://www.plantuml.com/plantuml/png/bOynReOm38Ltdy9Iv_y27H1Ipz0vif80KPCuIXnGRry34WCjGrVxy_FtnYPKfQS8P8KhVZPVyMrRWdYmdALon0_AApM0o5nmKiYJNR1moA9mujK38Xwdh-64tz5mDebxy-O2pXM-1fmgwsrsYlNYIBmft7RcsjmeLsbJ9dxFd457Tvc-QziOxDUbiYVybkdbGGML8kVCKUj_Ai_Vk0lysRe9boCfv1b6dVKKVm00)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.7.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}