Skip to content

Commit

Permalink
colorspace param added to ImageInnotation; Open CV2 no longer (someti…
Browse files Browse the repository at this point in the history
…mes) required
  • Loading branch information
danlester committed Jun 12, 2019
1 parent 36f8386 commit 82b5889
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 5 deletions.
278 changes: 278 additions & 0 deletions Example/Tests-Images.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from jupyter_innotater import *\n",
"import numpy as np, os"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Image Filenames and Bounding Boxes"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d80008144bff46a7943c0303406d05eb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Innotater(children=(HBox(children=(VBox(children=(ImagePad(value=b'\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x0…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"foodfns = sorted(os.listdir('./foods/'))\n",
"targets = np.zeros((len(foodfns), 4), dtype='int') # (x,y,w,h) for each data row\n",
"\n",
"Innotater( ImageInnotation(foodfns, path='./foods'), BoundingBoxInnotation(targets) )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Press 'n' or 'p' to move to next or previous image in the Innotater above."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0, 0, 0, 0],\n",
" [0, 0, 0, 0],\n",
" [0, 0, 0, 0],\n",
" [0, 0, 0, 0],\n",
" [0, 0, 0, 0],\n",
" [0, 0, 0, 0],\n",
" [0, 0, 0, 0],\n",
" [0, 0, 0, 0]])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"targets"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Numpy Image Data and Multi-classification"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(300, 400, 3)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import cv2\n",
"classes = ['vegetable', 'biscuit', 'fruit']\n",
"foods = [cv2.imread('./foods/'+f) for f in foodfns]\n",
"targets = [0] * len(foodfns)\n",
"foods[0].shape"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "75f10293e36d49bc8fa4e4f203b23233",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Innotater(children=(HBox(children=(VBox(children=(ImagePad(value=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"w2 = Innotater(\n",
" ImageInnotation(foods, name='Food'), \n",
" MultiClassInnotation(targets, name='FoodType', classes=classes, desc='Food Type')\n",
")\n",
"display(w2)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(300, 400, 3)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"foodsmpl = [plt.imread('./foods/'+f) for f in foodfns]\n",
"foodsmpl[0].shape"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "e3bbc452a73a44a2b3eb6492986e79eb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Innotater(children=(HBox(children=(VBox(children=(ImagePad(value=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"w3 = Innotater(\n",
" ImageInnotation(foodsmpl, name='Food', colorspace='RGB'), \n",
" MultiClassInnotation(targets, name='FoodType', classes=classes, desc='Food Type')\n",
")\n",
"display(w3)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b98bdf456d4741e7a86c28b4d9ef72c9",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Innotater(children=(HBox(children=(VBox(children=(ImagePad(value=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"foodsbw = [f[:,:,0] for f in foods]\n",
"w4 = Innotater(\n",
" ImageInnotation(foodsbw, name='Food', colorspace='RGB'), \n",
" MultiClassInnotation(targets, name='FoodType', classes=classes, desc='Food Type')\n",
")\n",
"display(w4)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "e05e07b678c54dbc96a13fb51f68b196",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Innotater(children=(HBox(children=(VBox(children=(ImagePad(value=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"foodsbw2 = [np.expand_dims(f, axis=-1) for f in foodsbw]\n",
"\n",
"w5 = Innotater(\n",
" ImageInnotation(foodsbw2, name='Food'), \n",
" MultiClassInnotation(targets, name='FoodType', classes=classes, desc='Food Type')\n",
")\n",
"display(w5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
5 changes: 5 additions & 0 deletions docs/chapters/requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ Requirements
Innotater has been developed under Jupyter 5.7 with a Python 3.7 kernel.
I will be pleased to look into any problems running under older versions
of each.

The minimum supported Python version is considered to be 3.3.

Open CV2 is used if available and will improve speed and reliability of
image handling, but it is not currently required.
17 changes: 15 additions & 2 deletions docs/chapters/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ ImageInnotation
^^^^^^^^^^^^^^^

data is expected to be an array of filenames, blobs, or numpy arrays
containing image data directly (RGB format).
(or similar, e.g. PyTorch Tensor) containing image data directly.

Extra optional parameters:

``path`` - a path to be prefixed to all filenames provided in data.
``path`` - a path to be prefixed to all filenames provided in ``data``
(this parameter is ignored if ``data`` does not contain filenames).

``width`` and/or ``height`` to specify the maximum size of image to
display as an integer number of pixels. For example, if you specify only
Expand All @@ -84,6 +85,18 @@ before it is processed to be displayed on the screen. For example, you
might set ``transform`` to a denormalization function because all images
in data have been normalized for training purposes.

``colorspace`` is a string containing either 'RGB' or 'BGR' (default is
'BGR'). This only has an effect if you pass numpy arrays or similar as
the ``data`` attribute. It specifies the meaning of the color channels
of the input data. For example, if you load images using Open CV2
(cv2.imread) then the default of 'BGR' will normally be correct; if you
load images via matplotlib imread, you will likely need colorspace to be
'RGB'.

Note that if a numpy array is provided channel-first, the Innotater
should detect this and automatically switch the channel to be the last
axis internally.

BoundingBoxInnotation
^^^^^^^^^^^^^^^^^^^^^

Expand Down
37 changes: 35 additions & 2 deletions jupyter-innotater/jupyter_innotater/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
from ipywidgets import Checkbox, Select, Textarea, Dropdown, Text
import re
from pathlib import Path
import numpy as np # Required to manipulate numpy or pytorch image matrix
try:
import cv2 # Prefer Open CV2 but don't put in requirements.txt because it can be difficult to install
usecv2 = True
except ImportError:
import png, io # PyPNG is a pure-Python PNG manipulator
usecv2 = False


class Innotation:
Expand Down Expand Up @@ -113,6 +120,12 @@ def __init__(self, *args, **kwargs):

self.transform = kwargs.get('transform', None)

self.colorspace = 'BGR'
if 'colorspace' in kwargs:
self.colorspace = kwargs['colorspace']
if self.colorspace not in ('BGR', 'RGB'):
raise Exception("Parameter colorspace must be either 'RGB' or 'BGR'")

self.max_repeats = 0

self.watchlist = WatchList()
Expand All @@ -133,15 +146,35 @@ def update_ui(self, uindex):
p = Path(self.path) / p
self.get_widget().set_value_from_file(p)
elif 'numpy' in str(type(it)) or 'Tensor' in str(type(it)):
import cv2, numpy as np # Required to manipulate numpy or pytorch image matrix
npim = it.numpy() if hasattr(it, 'numpy') else it
if len(npim.shape) == 3 and npim.shape[2] not in (1,3,4):
# Channels dim needs to be moved to back
npim = npim.transpose((1,2,0))
if not np.issubdtype(npim.dtype, np.integer):
# Float so scale
npim = (npim * 255).astype('int')
self.get_widget().value = cv2.imencode('.png', npim)[1].tostring()

if usecv2 and self.colorspace == 'RGB':
npim = cv2.cvtColor(npim, cv2.COLOR_RGB2BGR)
elif not usecv2 and self.colorspace == 'BGR' and len(npim.shape) == 3:
npim = np.flip(npim, axis=2)

if usecv2:
self.get_widget().value = cv2.imencode('.png', npim)[1].tostring()
else:
pngbytes = io.BytesIO()
pngmode = 'L' # Greyscale
if len(npim.shape) == 3:
if npim.shape[2] > 4:
raise Exception("Image numpy array appears to have more than 4 channels")
pngmode = ('L','LA','RGB','RGBA')[npim.shape[2]-1]
else:
npim = np.expand_dims(npim, axis=-1) # Need a third axis for channel
pngim = png.from_array(npim, mode=pngmode) # Don't have BGR available so flipped to RGB above
pngim.write(pngbytes) if hasattr(pngim, 'write') else pngim.save(pngbytes) # PyPNG API due to change after v0.0.19
self.get_widget().value = pngbytes.getvalue()
pngbytes.close()

else:
# Actual raw image data
self.get_widget().value = it
Expand Down
3 changes: 2 additions & 1 deletion jupyter-innotater/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ipywidgets>=7.1.0
widgetsnbextension>=3.1.0
notebook>=5.3.0
numpy
numpy>=1.4.0
pypng>=0.0.19

0 comments on commit 82b5889

Please sign in to comment.