11# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/01_bbox_canvas.ipynb (unless otherwise specified).
22
3- __all__ = ['draw_bg' , 'draw_bounding_box' , 'get_image_size' , 'draw_img' , 'points2bbox_coords' , ' BBoxCanvas' ]
3+ __all__ = ['BBoxCanvas' ]
44
5- # Cell
5+ # Internal Cell
66import numpy as np
77import traitlets
88import ipywidgets as widgets
1111from ipycanvas import MultiCanvas , hold_canvas
1212from ipywidgets import Image , Label , Layout , HBox , VBox , Output
1313
14- # Cell
14+ # Internal Cell
1515
1616def draw_bg (canvas , color = 'rgb(236,240,241)' ):
1717 with hold_canvas (canvas ):
1818 canvas .fill_style = color
1919 canvas .fill_rect (0 , 0 , canvas .size [0 ], canvas .size [1 ])
2020
21- # Cell
21+ # Internal Cell
2222
2323def draw_bounding_box (canvas , coords , color = 'white' , line_width = None ,
2424 border_ratio = 2 , clear = False ):
@@ -41,15 +41,15 @@ def draw_bounding_box(canvas, coords, color='white', line_width=None,
4141 canvas .stroke_rect (pos_x + gap , pos_y + gap , rect_x - 2 * gap ,
4242 rect_y - 2 * gap )
4343
44- # Cell
44+ # Internal Cell
4545from PIL import Image as pilImage
4646
4747# can we do this without reading image?
4848def get_image_size (path ):
4949 pil_im = pilImage .open (path )
5050 return pil_im .width , pil_im .height
5151
52- # Cell
52+ # Internal Cell
5353
5454def draw_img (canvas , file , clear = False ):
5555 # draws resized image on canvas and returns scale used
@@ -77,7 +77,7 @@ def draw_img(canvas, file, clear=False):
7777 height = height_img * min (1 , scale ))
7878 return scale
7979
80- # Cell
80+ # Internal Cell
8181
8282def points2bbox_coords (start_x , start_y , end_x , end_y ):
8383 min_x , max_x = sorted ((start_x , end_x ))
@@ -88,6 +88,11 @@ def points2bbox_coords(start_x, start_y, end_x, end_y):
8888# Cell
8989
9090class BBoxCanvas (HBox , traitlets .HasTraits ):
91+ """
92+ Represents canvas holding image and bbox ontop.
93+ Gives user an ability to draw a bbox with mouse.
94+
95+ """
9196 debug_output = widgets .Output (layout = {'border' : '1px solid black' })
9297 image_path = traitlets .Unicode ()
9398 bbox_coords = traitlets .Dict ()
@@ -104,6 +109,8 @@ def __init__(self, width, height):
104109 self ._bg_layer = 0
105110 self ._image_layer = 1
106111 self ._box_layer = 2
112+ # do not stick bbox to borders
113+ self .padding = 2
107114
108115 # Define each of the children...
109116 self ._image = Image (layout = Layout (display = 'flex' ,
@@ -122,40 +129,79 @@ def __init__(self, width, height):
122129 self ._multi_canvas [self ._box_layer ].on_mouse_down (self ._start_drawing )
123130 self ._multi_canvas [self ._box_layer ].on_mouse_up (self ._stop_drawing )
124131
132+
125133 @debug_output .capture (clear_output = False )
126134 def _update_pos (self , x , y ):
127135 if self .is_drawing :
128136 self ._canvas_bbox_coords = points2bbox_coords (* self ._start_point , x , y )
137+ # bbox should not cross the canvas border:
138+ if self ._invalid_coords (x , y ):
139+ print (' !! Out of canvas border !!' )
140+ self ._stop_drawing (x , y )
141+
142+ def _invalid_coords (self , x , y ):
143+ return (self ._canvas_bbox_coords ["x" ] + self ._canvas_bbox_coords ["width" ] > self ._multi_canvas .width - self .padding or
144+ self ._canvas_bbox_coords ["y" ] + self ._canvas_bbox_coords ["height" ] > self ._multi_canvas .height - self .padding or
145+ self ._canvas_bbox_coords ["x" ] < self .padding or
146+ self ._canvas_bbox_coords ["y" ] < self .padding )
147+
129148
130149 @debug_output .capture (clear_output = True )
131150 def _start_drawing (self , x , y ):
151+ # print("-> START DRAWING")
132152 self ._start_point = (x , y )
133153 self .is_drawing = True
154+ # print("<- START DRAWING")
134155
135156 @debug_output .capture (clear_output = False )
136157 def _stop_drawing (self , x , y ):
158+ # print("-> STOP DRAWING")
137159 self .is_drawing = False
138- self .bbox_coords = {k : v / self ._image_scale for k , v in self ._canvas_bbox_coords .items ()}
160+
161+ # if something is drawn
162+ if self ._canvas_bbox_coords :
163+ # if bbox is not human visible, clean:
164+ if (self ._canvas_bbox_coords ['width' ] < 10 or
165+ self ._canvas_bbox_coords ['height' ] < 10 ):
166+ self ._canvas_bbox_coords = {}
167+ print (" !! too small bbox drawn !!" )
168+ else : # otherwise, save bbox values to backend
169+ self .bbox_coords = dict ({ k : v / self ._image_scale for k , v in self ._canvas_bbox_coords .items () })
170+ # print("<- STOP DRAWING")
171+
139172
140173 @traitlets .observe ('bbox_coords' )
141174 def _update_canvas_bbox_coords (self , change ):
142- self ._canvas_bbox_coords = {k : v * self ._image_scale for k , v in self .bbox_coords .items ()}
175+ # print('-> Observe bbox_coords: ', change)
176+
177+ if change ['new' ] == self ._canvas_bbox_coords : # change event from gui, do nothing
178+ print ('-> GUI' )
179+ else : # recalculate canvas coordinates as bbox was set by backend:
180+ self ._canvas_bbox_coords = {k : v * self ._image_scale for k , v in self .bbox_coords .items ()}
181+ print ('-> Backend' )
182+ # print('<- Observe bbox_coords')
183+
143184
144185 @traitlets .observe ('_canvas_bbox_coords' )
145186 def _draw_bbox (self , change ):
187+ # print('-> Observe canvas_coords: ', change)
146188 if not self ._canvas_bbox_coords :
147189 self ._clear_bbox ()
190+ self .bbox_coords = {}
148191 return
149192 coords = [self ._canvas_bbox_coords ['x' ],
150193 self ._canvas_bbox_coords ['y' ],
151194 self ._canvas_bbox_coords ['width' ],
152195 self ._canvas_bbox_coords ['height' ]]
153196 draw_bounding_box (self ._multi_canvas [self ._box_layer ], coords ,
154197 color = 'white' , border_ratio = 2 , clear = True )
198+ # print('<- Observe canvas_coords')
199+
155200
156201 def _clear_bbox (self ):
157202 self ._multi_canvas [self ._box_layer ].clear ()
158203
204+
159205 @traitlets .observe ('image_path' )
160206 def _draw_image (self , image ):
161207 self ._image_scale = draw_img (self ._multi_canvas [self ._image_layer ], self .image_path , clear = True )
0 commit comments