# Copyright 2019-2020 Stanislav Pidhorskyi
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import anntoolkit
[docs]class App:
r"""A base class for the application.
.. note::
You need to implement your own application class that derives from this class.
.. warning::
You will need to call `set_image` before you can run it
Arguments:
width (int): width of the window. Default: 600.
height (int): height of the window. Default: 600.
title (str): title of the window. Default: "Hello".
Example:
>>> import anntoolkit
>>> import imageio
>>>
>>> class App(anntoolkit.App):
>>> def __init__(self):
>>> # Calling constructor of base class
>>> super(App, self).__init__(title='Test')
>>>
>>> # set image to view
>>> im = imageio.imread('test_image.jpg')
>>> self.set_image(im)
>>>
>>> # Run app
>>> app = App()
>>> app.run()
"""
def __init__(self, width=600, height=600, title="Hello"):
self._ctx = anntoolkit.Context()
self._ctx.init(width, height, title)
def mouse_button(down, x, y, lx, ly):
self.on_mouse_button(down, x, y, lx, ly)
self._ctx.set_mouse_button_callback(mouse_button)
def mouse_pos(x, y, lx, ly):
self.on_mouse_position(x, y, lx, ly)
self._ctx.set_mouse_position_callback(mouse_pos)
def keyboard(key, action, mods):
if key < 255:
key = chr(key)
if action == 1:
self.keys[key] = 1
elif action == 0:
if key in self.keys:
del self.keys[key]
self.on_keyboard(key, action == 1, mods)
self._ctx.set_keyboard_callback(keyboard)
self.keys = {}
self.image = None
[docs] def run(self):
"""Runs the application.
.. note::
This is a blocking call. It won't return until the window is closed
Runs an event loop, where it process user input and updates window.
Call callback method :meth:`on_update`.
Example:
>>> app = App()
>>> app.run()
"""
while not self._ctx.should_close():
with self._ctx:
for k, v in self.keys.items():
self.keys[k] += 1
if v > 50:
self.on_keyboard(k, True, 0)
self.keys[k] = 45
self.on_update()
[docs] def on_update(self):
"""Is called each frame from the event loop that is run in :meth:`run` method
Empty method, you need to overwrite it.
Do all drawing from here.
.. note::
* Calls to draw API are valid only from within this method. Draw API: :meth:`point`, :meth:`box`, :meth:`text`, etc.
.. warning::
* Don't call it, this is callback
"""
pass
[docs] def on_mouse_position(self, x, y, lx, ly):
"""Is called on mouse move event from event loop that is run in :meth:`run` method
If the user moves mouse, this method is called
Empty method, you need to overwrite it
.. warning::
* Don't call it, this is callback
Arguments:
x (float): x coordinate of mouse cursor in window coordinate system
y (float): y coordinate of mouse cursor in window coordinate system
lx (float): x coordinate of mouse cursor in image coordinate system
ly (float): y coordinate of mouse cursor in image coordinate system
"""
pass
[docs] def on_keyboard(self, key, down, mods):
"""Is called on keybord key event from event loop that is run in :meth:`run` method
Empty method, you need to overwrite it
.. warning::
* Don't call it, this is callback
Arguments:
key (int): The key that generated even. Can be `anntoolkit.KeyEscape`, `anntoolkit.KeyTab`,
`anntoolkit.KeyBackspace`, `anntoolkit.KeyInsert`, `anntoolkit.KeyDelete`, `anntoolkit.KeyRight`, `anntoolkit.KeyLeft`, `anntoolkit.KeyDown`, `anntoolkit.KeyUp` or any char from 'A' to 'Z'
down (bool): True if key is pressed. False if key is released.
mods (int): Indicates additional modifing keys, such as Ctrl, Shift, Alt
Example:
>>> def on_keyboard(self, key, down, mods):
>>> if down:
>>> if key == anntoolkit.KeyLeft:
>>> ...
>>> if key == anntoolkit.KeyRight:
>>> ...
>>> if key == anntoolkit.KeyUp:
>>> ...
>>> if key == anntoolkit.KeyDown:
>>> ...
>>> if key == anntoolkit.KeyDelete:
>>> ...
>>> if key == anntoolkit.KeyBackspace:
>>> ...
>>> if key == 'R':
>>> ...
>>> if key == 'A':
>>> ...
>>> if key == 'Q':
>>> ...
"""
pass
[docs] def set_image(self, image, recenter=True):
""" Sets the image to annotate
.. note::
Must be numpy ndarray of type numpy.uint8
Should have 2 dims (grayscale) or 3 dims (colored) with the last dim of size 3 for RGB case or 4 for RGBA case.
Example:
>>> im = imageio.imread('test_image.jpg')
>>> app.set_image(im)
"""
# m = anntoolkit.generate_mipmaps(image)
# self._ctx.set(anntoolkit.Image(m))
self.image = image
if recenter:
self._ctx.set(anntoolkit.Image([image]))
else:
self._ctx.set_without_recenter(anntoolkit.Image([image]))
[docs] def recenter(self):
""" Resets zoom and recenters the image to fit in the window
"""
self._ctx.recenter()
[docs] def set_roi(self, roi, scale=0):
""" Zooms and moves the image to view the requested roi
"""
scale = 1.0 / scale
x0, y0 = roi.left(), roi.top()
x1, y1 = roi.left() + roi.width(), roi.top() + roi.height()
self._ctx.set_roi(x0 * scale, y0 * scale, x1 * scale, y1 * scale)
[docs] def text(self, s, x, y, color=None, color_bg=None, alignment=anntoolkit.Alignment.Left):
"""Draw text in window space
Arguments:
s (str): Text
x (int): x coordinate of the text in window space
y (int): y coordinate of the text in window space
color (tuple[int, int, int, int]): RGBA color of text. Default None, results in white
color_bg (tuple[int, int, int, int]): RGBA color of background. Default None, results in black
alignment (anntoolkit.Alignment): Alignment. Can be one of: `anntoolkit.Alignment.Left`,
`anntoolkit.Alignment.Center`, `anntoolkit.Alignment.Right`. Default `anntoolkit.Alignment.Left`.
"""
if color is None and color_bg is None:
self._ctx.text(s, x, y, alignment)
else:
if color is None:
raise ValueError
if color_bg is None:
color_bg = (0, 0, 0, 255)
self._ctx.text(s, x, y, color, color_bg, alignment)
[docs] def text_loc(self, s, lx, ly, color=None, color_bg=None, alignment=anntoolkit.Alignment.Left):
"""Draw text in image space
Arguments:
s (str): Text
x (int): x coordinate of the text in image space
y (int): y coordinate of the text in image space
color (tuple[int, int, int, int]): RGBA color of text
color_bg (tuple[int, int, int, int]): RGBA color of background
alignment (anntoolkit.Alignment): Alignment. Can be one of: `anntoolkit.Alignment.Left`,
`anntoolkit.Alignment.Center`, `anntoolkit.Alignment.Right`. Default `anntoolkit.Alignment.Left`.
"""
if color is None and color_bg is None:
self._ctx.text_loc(s, lx, ly, alignment)
else:
if color is None:
raise ValueError
if color_bg is None:
color_bg = (0, 0, 0, 255)
self._ctx.text_loc(s, lx, ly, color, color_bg, alignment)
[docs] def point(self, x, y, color, radius=5.0):
"""Draw point in image space
Arguments:
x (int): x coordinate of the text in image space
y (int): y coordinate of the text in image space
color (tuple[int, int, int, int]): RGBA color of text
radius (float):radius of the point. Default 5.0.
"""
self._ctx.point(x, y, color, radius)
[docs] def win_2_loc(self, x, y):
"""Convert window space to image space
Arguments:
x (float): x coordinate of the text in window space
y (float): y coordinate of the text in window space
Returns:
tuple[float, float] - x, y coordinates in image space
"""
return self._ctx.win_2_loc(x, y)
[docs] def loc_2_win(self, x, y):
"""Convert image space to window space
Arguments:
x (float): x coordinate of the text in image space
y (float): y coordinate of the text in image space
Returns:
tuple[float, float] - x, y coordinates in window space
"""
return self._ctx.loc_2_win(x, y)
@property
def scale(self):
"""Returns `scale` - change of length when transform from image space to window space
If `scale` is 2, that means any line segment in window space will be twice longer than corresponding in image space
Returns:
float - scale
"""
return self._ctx.get_scale()
[docs] def box(self, box, color_stroke, color_fill):
"""Draw a box
Arguments:
box (list[tuple[float, float], tuple[float, float]]): coordinates of the box. List should be size of two
contain x,y coordinats of two opposite corners of the box
color_stroke (tuple[int, int, int, int]): color of the stroke
color_fill (tuple[int, int, int, int]): color of the fill
Returns:
tuple[float, float] - x, y coordinates in window space
"""
minx, miny = box[0]
maxx, maxy = box[1]
self._ctx.box(minx, miny, maxx, maxy, color_stroke, color_fill)
@property
def width(self):
"""Width of the window
"""
return self._ctx.width()
@property
def height(self):
"""Height of the window
"""
return self._ctx.height()