From 85ca6a6b0b0895d7bb7a85a047df6630b268c60b Mon Sep 17 00:00:00 2001 From: Chaskin Saroff Date: Sat, 4 May 2019 22:11:34 -0700 Subject: [PATCH 1/2] Add back button w/ selected color overlay --- .gitignore | 1 - examples/back_button.ipynb | 437 +++++++++++++++++++++++++++++++++++++ pigeon/annotate.py | 34 ++- 3 files changed, 464 insertions(+), 8 deletions(-) create mode 100644 examples/back_button.ipynb diff --git a/.gitignore b/.gitignore index 9a20f3c..9988671 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .DS_Store -*ipynb # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/examples/back_button.ipynb b/examples/back_button.ipynb new file mode 100644 index 0000000..2fa9f5d --- /dev/null +++ b/examples/back_button.ipynb @@ -0,0 +1,437 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Back Button\n", + "\n", + "During image labeling, it is quite common to make a mistake and click on wrong label. In order help improve the labeling process, we've added a back button.\n", + "\n", + "To demonstrate how this works, let's grab a small dataset using the fastai library" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "# Load Libraries and \"Notebook Parameters\"\n", + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "from fastai.vision import *\n", + "from pigeon import annotate\n", + "from IPython.display import display\n", + "from IPython.display import Image as IPyImage" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "path = untar_data(URLs.MNIST)\n", + "path_tr = path/'training'" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[PosixPath('/home/csaroff/.fastai/data/mnist_png/training/5/30817.png'),\n", + " PosixPath('/home/csaroff/.fastai/data/mnist_png/training/5/36588.png'),\n", + " PosixPath('/home/csaroff/.fastai/data/mnist_png/training/5/57593.png'),\n", + " PosixPath('/home/csaroff/.fastai/data/mnist_png/training/5/6027.png'),\n", + " PosixPath('/home/csaroff/.fastai/data/mnist_png/training/5/12021.png')]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "img_fps = list(path_tr.rglob('*.png'))\n", + "img_fps[:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Widget\n", + "Try labeling one as valid and then going back. The existing label should show up in green" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9ace655b12d74ec6ab9a375cf5b88bfe", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8acf69355dd44bf5bc566bd7e09ab2d4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1637daeb32064f2b9a57d85869d519d9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "annotations = annotate(\n", + " img_fps,\n", + " options=['valid', 'invalid'],\n", + " display_fn=lambda fn: display(IPyImage(str(fn)))\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you prefer to use fastai's Image library, that's cool too." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "356350871773452ba931b5b326fbefa1", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8d2efbd723e64efc8ca64be97e046c31", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3ccfdeeb87c64a52a4a00567a1e9609b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "annotations = annotate(\n", + " img_fps,\n", + " options=['valid', 'invalid'],\n", + " display_fn=lambda fn: display(open_image(str(fn)))\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you want to use fastai's Image.view method, things get a little a little tricky." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b6315a1e32334dc7ac330a1e6f9dca75", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "411cb6cb9e9246afa710bb39bb7605da", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ebf8621f4cfe4ea7b8a9dda310980adc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "points = ImagePoints(FlowField([28, 28], Tensor([[14, 14], [16, 16]])))\n", + "annotations = annotate(\n", + " img_fps,\n", + " options=['valid', 'invalid'],\n", + " display_fn=lambda fn: display(open_image(str(fn)).show(y=points))\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Unfortunately, this technique doesn't refresh the image, but instead creates a new matplotlib axis to plot against with every call. Digging deeper, it turns out that display_fn needs to return something that pigeon knows how to plot like matplotlib's axis's figure." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def get_labeled_figure(fp, ax):\n", + " tagged_img = open_image(fp)\n", + " ax.clear()\n", + " tagged_img.show(ax=ax, y=points, title=os.path.basename(fp))\n", + "\n", + " # This is a bit of a hack to make this annotation library work\n", + " # with fastai. Because fastai images rely on the use of\n", + " # matplotlib's imshow api, we'll grab the resulting figure and\n", + " # render it\n", + " return display(ax.figure)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Ugly hack\n", + "ax = subplots(1, 1)[0,0]" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cc2a509d58b045bca911e7f626e427cc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7d4b3cf3e5a74b768aa6e88c3c5510b7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "79793045007f417bac90ea2bc13b6975", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "A Jupyter Widget" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "points = ImagePoints(FlowField([28, 28], Tensor([[14, 14], [16, 16]])))\n", + "annotations = annotate(\n", + " img_fps,\n", + " options=['valid', 'invalid'],\n", + " display_fn=partial(get_labeled_figure, ax=ax)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Yay! Our annotation library works with fastai's image overlay features." + ] + } + ], + "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.6.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pigeon/annotate.py b/pigeon/annotate.py index ef5dab2..12c1aaa 100644 --- a/pigeon/annotate.py +++ b/pigeon/annotate.py @@ -6,6 +6,7 @@ def annotate(examples, options=None, shuffle=False, + include_back=True, include_skip=True, display_fn=display): """ @@ -30,19 +31,29 @@ def annotate(examples, if shuffle: random.shuffle(examples) - annotations = [] + annotations = [(ex, None) for ex in examples] current_index = -1 def set_label_text(): nonlocal count_label - count_label.value = '{} examples annotated, {} examples left'.format( - len(annotations), len(examples) - current_index + label_count = len([(x, y) for (x, y) in annotations if y]) + count_label.value = 'Image #{}. {}/{} examples annotated. {} examples left.'.format( + current_index, label_count, len(examples), len(examples) - current_index ) + # Sync's button style according to the current label + def update_button_style(): + for btn in buttons: + if btn.description == annotations[current_index][1]: + btn.button_style = 'success' + else: + btn.button_style = '' + def show_next(): nonlocal current_index current_index += 1 set_label_text() + update_button_style() if current_index >= len(examples): for btn in buttons: btn.disabled = True @@ -53,12 +64,18 @@ def show_next(): display_fn(examples[current_index]) def add_annotation(annotation): - annotations.append((examples[current_index], annotation)) + annotations[current_index] = (examples[current_index], annotation) show_next() def skip(btn): show_next() + def back(btn): + nonlocal current_index + if current_index is not 0: + current_index-=2 + show_next() + count_label = HTML() set_label_text() display(count_label) @@ -73,7 +90,12 @@ def skip(btn): raise Exception('Invalid options') buttons = [] - + + if include_back: + btn = Button(description='back') + btn.on_click(back) + buttons.append(btn) + if task_type == 'classification': use_dropdown = len(options) > 5 @@ -85,7 +107,6 @@ def on_click(btn): add_annotation(dd.value) btn.on_click(on_click) buttons.append(btn) - else: for label in options: btn = Button(description=label) @@ -93,7 +114,6 @@ def on_click(label, btn): add_annotation(label) btn.on_click(functools.partial(on_click, label)) buttons.append(btn) - elif task_type == 'regression': target_type = type(options[0]) if target_type == int: From 9a263bbbef435f370a56531949252a7aa6aae8a1 Mon Sep 17 00:00:00 2001 From: Ygor Gallina Date: Tue, 27 Aug 2019 14:46:38 +0900 Subject: [PATCH 2/2] An IndexError was thrown at the end of the anotation, this commit fix it. --- pigeon/annotate.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pigeon/annotate.py b/pigeon/annotate.py index 12c1aaa..4c4e86a 100644 --- a/pigeon/annotate.py +++ b/pigeon/annotate.py @@ -53,9 +53,13 @@ def show_next(): nonlocal current_index current_index += 1 set_label_text() - update_button_style() + # If we reach the end, don't update the buttons. + if current_index < len(examples): + update_button_style() if current_index >= len(examples): for btn in buttons: + if btn.description == 'back': + continue btn.disabled = True print('Annotation done.') return @@ -72,6 +76,10 @@ def skip(btn): def back(btn): nonlocal current_index + # If the annotation was finished. Buttons needs to be re-enabled. + if current_index >= len(examples): + for btn in buttons: + btn.disabled = False if current_index is not 0: current_index-=2 show_next()