From bfc46ab850cf5cfc1a305c36a79a5f19139ec269 Mon Sep 17 00:00:00 2001 From: Kentaro Wada Date: Sat, 30 Dec 2023 16:28:04 +0900 Subject: [PATCH] Migrate to ruff --- .github/workflows/lint.yml | 11 +- Makefile | 15 ++ examples/draw.py | 8 +- examples/instances2rgb.py | 2 +- examples/label2rgb.py | 4 +- getting_started.py | 8 +- imgviz/_io/_pyglet/base.py | 4 +- imgviz/_io/_pyglet/pyglet_imshow.py | 28 +--- imgviz/color.py | 15 +- imgviz/data/arc2017/__init__.py | 4 +- imgviz/depth.py | 4 +- imgviz/draw/ellipse.py | 4 +- imgviz/draw/rectangle.py | 4 +- imgviz/draw/star.py | 4 +- imgviz/draw/text.py | 8 +- imgviz/draw/text_in_rectangle.py | 4 +- imgviz/external/transformations.py | 222 ++++++++++++++++------------ imgviz/flow.py | 4 +- imgviz/instances.py | 8 +- imgviz/label.py | 22 ++- imgviz/nchannel.py | 4 +- imgviz/resize.py | 4 +- imgviz/tile.py | 4 +- pyproject.toml | 12 -- requirements-dev.txt | 5 +- ruff.toml | 33 +++++ setup.py | 4 +- tests/draw_tests/test_text.py | 4 +- 28 files changed, 239 insertions(+), 214 deletions(-) create mode 100644 Makefile delete mode 100644 pyproject.toml create mode 100644 ruff.toml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7999bea..fe63daa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,14 +21,13 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Flake8 + - name: Install dependencies run: | - pip install hacking==4.1.0 - flake8 . - - name: Black + pip install -r requirements-dev.txt + - name: Lint run: | - pip install black==22.3.0 - black --check --diff . + ruff format --check + ruff check - name: Mypy run: | pip install mypy types-PyYAML diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2068c7b --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +all: + @echo '## Make commands ##' + @echo + @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | xargs + +lint: + ruff format --check + ruff check + +format: + ruff format + ruff check --fix + +test: + pytest tests diff --git a/examples/draw.py b/examples/draw.py index 585e075..5c20ce4 100755 --- a/examples/draw.py +++ b/examples/draw.py @@ -29,13 +29,9 @@ def draw(): yxs = [(265, 265), (265, 330), (320, 315), (350, 270), (350, 320)] # eye segment - viz = imgviz.draw.line( - viz, yx=[yxs[0], yxs[1]], fill=(255, 255, 255), width=5 - ) + viz = imgviz.draw.line(viz, yx=[yxs[0], yxs[1]], fill=(255, 255, 255), width=5) # mouse segment - viz = imgviz.draw.line( - viz, yx=[yxs[3], yxs[4]], fill=(255, 255, 255), width=5 - ) + viz = imgviz.draw.line(viz, yx=[yxs[3], yxs[4]], fill=(255, 255, 255), width=5) colors = imgviz.label_colormap(value=255)[1:] shapes = ["star", "ellipse", "rectangle", "circle", "triangle"] diff --git a/examples/instances2rgb.py b/examples/instances2rgb.py index bc00ce6..985d4cc 100755 --- a/examples/instances2rgb.py +++ b/examples/instances2rgb.py @@ -8,7 +8,7 @@ def instances2rgb(): data = imgviz.data.voc() - captions = [data["class_names"][l] for l in data["labels"]] + captions = [data["class_names"][label_id] for label_id in data["labels"]] insviz1 = imgviz.instances2rgb( image=data["rgb"], bboxes=data["bboxes"], diff --git a/examples/label2rgb.py b/examples/label2rgb.py index bf7cfd8..4f3e05d 100755 --- a/examples/label2rgb.py +++ b/examples/label2rgb.py @@ -11,9 +11,7 @@ def label2rgb(): rgb = data["rgb"] label = data["class_label"] - label_names = [ - "{}:{}".format(i, n) for i, n in enumerate(data["class_names"]) - ] + label_names = ["{}:{}".format(i, n) for i, n in enumerate(data["class_names"])] labelviz_withname1 = imgviz.label2rgb( label, label_names=label_names, font_size=25, loc="centroid" ) diff --git a/getting_started.py b/getting_started.py index da4661d..9e5f243 100644 --- a/getting_started.py +++ b/getting_started.py @@ -23,7 +23,9 @@ # colorize label image class_label = data["class_label"] -labelviz = imgviz.label2rgb(class_label, image=gray, label_names=data["class_names"], font_size=20) +labelviz = imgviz.label2rgb( + class_label, image=gray, label_names=data["class_names"], font_size=20 +) # instance bboxes bboxes = data["bboxes"].astype(int) @@ -33,7 +35,9 @@ maskviz = imgviz.instances2rgb(gray, masks=masks, labels=labels, captions=captions) # tile instance masks -insviz = [(rgb * m[:, :, None])[b[0] : b[2], b[1] : b[3]] for b, m in zip(bboxes, masks)] +insviz = [ + (rgb * m[:, :, None])[b[0] : b[2], b[1] : b[3]] for b, m in zip(bboxes, masks) +] insviz = imgviz.tile(imgs=insviz, border=(255, 255, 255)) insviz = imgviz.resize(insviz, height=rgb.shape[0]) diff --git a/imgviz/_io/_pyglet/base.py b/imgviz/_io/_pyglet/base.py index c6ff2a2..5849644 100644 --- a/imgviz/_io/_pyglet/base.py +++ b/imgviz/_io/_pyglet/base.py @@ -6,7 +6,5 @@ def check_pyglet_available(): if pyglet is None: - raise ImportError( - "pyglet is not installed, run following: pip install pyglet" - ) + raise ImportError("pyglet is not installed, run following: pip install pyglet") return pyglet diff --git a/imgviz/_io/_pyglet/pyglet_imshow.py b/imgviz/_io/_pyglet/pyglet_imshow.py index 83317a1..5ebfa90 100644 --- a/imgviz/_io/_pyglet/pyglet_imshow.py +++ b/imgviz/_io/_pyglet/pyglet_imshow.py @@ -41,9 +41,7 @@ def pyglet_imshow(image, caption=None, interval=0.5, keymap=None, hook=None): hook=hook, ) elif isinstance(image, np.ndarray): - _pyglet_imshow_ndarray( - image, caption=caption, keymap=keymap, hook=hook - ) + _pyglet_imshow_ndarray(image, caption=caption, keymap=keymap, hook=hook) else: _pyglet_imshow_list( image, @@ -54,9 +52,7 @@ def pyglet_imshow(image, caption=None, interval=0.5, keymap=None, hook=None): ) -def _pyglet_imshow_list( - images, caption=None, interval=0.5, keymap=None, hook=None -): +def _pyglet_imshow_list(images, caption=None, interval=0.5, keymap=None, hook=None): pyglet = check_pyglet_available() def usage(): @@ -88,9 +84,7 @@ def usage(): else: image = hook(images[0]) if not isinstance(image, (np.ndarray, PIL.Image.Image)): - raise TypeError( - "hook must return numpy.ndarray or PIL.Image.Image" - ) + raise TypeError("hook must return numpy.ndarray or PIL.Image.Image") max_image_height, max_image_width = image.shape[:2] aspect_ratio = max_image_width / max_image_height @@ -108,14 +102,10 @@ def usage(): def _post_image_update(): _centerize_sprite_in_window(sprite, window) window.set_caption( - "{} {}/{}".format( - images[window.index], window.index + 1, len(images) - ) + "{} {}/{}".format(images[window.index], window.index + 1, len(images)) ) print( - "{} {}/{}".format( - images[window.index], window.index + 1, len(images) - ), + "{} {}/{}".format(images[window.index], window.index + 1, len(images)), file=sys.stderr, ) @@ -152,18 +142,14 @@ def on_key_press(symbol, modifiers): if window.index + 1 <= len(images) - 1: window.index += 1 sprite.image = _convert_to_imagedata( - hook(images[window.index]) - if hook - else images[window.index] + hook(images[window.index]) if hook else images[window.index] ) _post_image_update() elif symbol == pyglet.window.key.P: if window.index - 1 >= 0: window.index -= 1 sprite.image = _convert_to_imagedata( - hook(images[window.index]) - if hook - else images[window.index] + hook(images[window.index]) if hook else images[window.index] ) _post_image_update() elif symbol == pyglet.window.key.S: diff --git a/imgviz/color.py b/imgviz/color.py index f4ba805..7d3fc22 100644 --- a/imgviz/color.py +++ b/imgviz/color.py @@ -160,8 +160,9 @@ def asgray(img): gray = rgb2gray(img) else: raise ValueError( - "Unsupported image format to convert to gray:" - "shape={}, dtype={}".format(img.shape, img.dtype) + "Unsupported image format to convert to gray:" "shape={}, dtype={}".format( + img.shape, img.dtype + ) ) return gray @@ -190,8 +191,9 @@ def asrgb(img): rgb = img else: raise ValueError( - "Unsupported image format to convert to rgb:" - "shape={}, dtype={}".format(img.shape, img.dtype) + "Unsupported image format to convert to rgb:" "shape={}, dtype={}".format( + img.shape, img.dtype + ) ) return rgb @@ -221,8 +223,9 @@ def asrgba(img): rgba = rgb2rgba(img) else: raise ValueError( - "Unsupported image format to convert to rgba:" - "shape={}, dtype={}".format(img.shape, img.dtype) + "Unsupported image format to convert to rgba:" "shape={}, dtype={}".format( + img.shape, img.dtype + ) ) return rgba diff --git a/imgviz/data/arc2017/__init__.py b/imgviz/data/arc2017/__init__.py index 95ff130..b1fcd35 100644 --- a/imgviz/data/arc2017/__init__.py +++ b/imgviz/data/arc2017/__init__.py @@ -23,9 +23,7 @@ def arc2017(): class_names = [name.strip() for name in f] data["class_names"] = class_names - data["res4"] = np.load(osp.join(here, "res4.npz"), allow_pickle=True)[ - "res4" - ] + data["res4"] = np.load(osp.join(here, "res4.npz"), allow_pickle=True)["res4"] with open(osp.join(here, "camera_info.yaml")) as f: data["camera_info"] = yaml.safe_load(f) diff --git a/imgviz/depth.py b/imgviz/depth.py index e693d9d..11a001b 100644 --- a/imgviz/depth.py +++ b/imgviz/depth.py @@ -53,9 +53,7 @@ def __call__(self, depth, dtype=np.uint8): """ assert depth.ndim == 2, "depth image must be 2 dimensional" - assert np.issubdtype( - depth.dtype, np.floating - ), "depth dtype must be float" + assert np.issubdtype(depth.dtype, np.floating), "depth dtype must be float" normalized, self._min_value, self._max_value = normalize( depth, diff --git a/imgviz/draw/ellipse.py b/imgviz/draw/ellipse.py index e03300a..5b0c94a 100644 --- a/imgviz/draw/ellipse.py +++ b/imgviz/draw/ellipse.py @@ -9,9 +9,7 @@ def ellipse(src, yx1, yx2, fill=None, outline=None, width=0): dst = utils.numpy_to_pillow(src) - ellipse_( - img=dst, yx1=yx1, yx2=yx2, fill=fill, outline=outline, width=width - ) + ellipse_(img=dst, yx1=yx1, yx2=yx2, fill=fill, outline=outline, width=width) return utils.pillow_to_numpy(dst) diff --git a/imgviz/draw/rectangle.py b/imgviz/draw/rectangle.py index 8cb7662..c0e3c20 100644 --- a/imgviz/draw/rectangle.py +++ b/imgviz/draw/rectangle.py @@ -51,6 +51,4 @@ def rectangle_(img, aabb1, aabb2, fill=None, outline=None, width=0): y1, x1 = aabb1 y2, x2 = aabb2 - draw.rectangle( - xy=(x1, y1, x2, y2), fill=fill, outline=outline, width=width - ) + draw.rectangle(xy=(x1, y1, x2, y2), fill=fill, outline=outline, width=width) diff --git a/imgviz/draw/star.py b/imgviz/draw/star.py index e628246..a8fb3ca 100644 --- a/imgviz/draw/star.py +++ b/imgviz/draw/star.py @@ -60,9 +60,7 @@ def star_(img, center, size, fill=None, outline=None): # 5 valleys angles_v = angles_m + np.pi / 5 - length = radius / ( - np.sin(np.pi / 5) / np.tan(np.pi / 10) + np.cos(np.pi / 10) - ) + length = radius / (np.sin(np.pi / 5) / np.tan(np.pi / 10) + np.cos(np.pi / 10)) x_v = cx + length * np.cos(angles_v) y_v = cy - length * np.sin(angles_v) xy_v = np.stack((x_v, y_v), axis=1) diff --git a/imgviz/draw/text.py b/imgviz/draw/text.py index f786287..2cc18ae 100644 --- a/imgviz/draw/text.py +++ b/imgviz/draw/text.py @@ -10,9 +10,7 @@ def _get_font(size, font_path=None): if font_path is None: - fonts_path = osp.join( - osp.dirname(matplotlib.__file__), "mpl-data/fonts/ttf" - ) + fonts_path = osp.join(osp.dirname(matplotlib.__file__), "mpl-data/fonts/ttf") font_path = osp.join(fonts_path, "DejaVuSansMono.ttf") font = PIL.ImageFont.truetype(font=font_path, size=size) return font @@ -80,9 +78,7 @@ def text(src, yx, text, size, color=(0, 0, 0), font_path=None): """ dst = utils.numpy_to_pillow(src) - text_( - img=dst, yx=yx, text=text, size=size, color=color, font_path=font_path - ) + text_(img=dst, yx=yx, text=text, size=size, color=color, font_path=font_path) return utils.pillow_to_numpy(dst) diff --git a/imgviz/draw/text_in_rectangle.py b/imgviz/draw/text_in_rectangle.py index 0f4890e..4d0d822 100644 --- a/imgviz/draw/text_in_rectangle.py +++ b/imgviz/draw/text_in_rectangle.py @@ -7,9 +7,7 @@ from .text import text_size -def text_in_rectangle_aabb( - img_shape, loc, text, size, aabb1, aabb2, font_path=None -): +def text_in_rectangle_aabb(img_shape, loc, text, size, aabb1, aabb2, font_path=None): height, width = img_shape[:2] y1, x1 = (0, 0) if aabb1 is None else aabb1 diff --git a/imgviz/external/transformations.py b/imgviz/external/transformations.py index 2eeb7a0..e4209e7 100644 --- a/imgviz/external/transformations.py +++ b/imgviz/external/transformations.py @@ -204,8 +204,8 @@ import numpy as np -__version__ = '2017.02.17' -__docformat__ = 'restructuredtext en' +__version__ = "2017.02.17" +__docformat__ = "restructuredtext en" __all__ = () @@ -347,8 +347,9 @@ def rotation_matrix(angle, direction, point=None): """ # special case sympy symbolic angles - if type(angle).__name__ == 'Symbol': + if type(angle).__name__ == "Symbol": import sympy as sp + sina = sp.sin(angle) cosa = sp.cos(angle) else: @@ -361,9 +362,13 @@ def rotation_matrix(angle, direction, point=None): M[:3, :3] += np.outer(direction, direction) * (1.0 - cosa) direction = direction * sina - M[:3, :3] += np.array([[0.0, -direction[2], direction[1]], - [direction[2], 0.0, -direction[0]], - [-direction[1], direction[0], 0.0]]) + M[:3, :3] += np.array( + [ + [0.0, -direction[2], direction[1]], + [direction[2], 0.0, -direction[0]], + [-direction[1], direction[0], 0.0], + ] + ) # if point is specified, rotation is not around origin if point is not None: @@ -371,7 +376,7 @@ def rotation_matrix(angle, direction, point=None): M[:3, 3] = point - np.dot(M[:3, :3], point) # return symbolic angles as sympy Matrix objects - if type(angle).__name__ == 'Symbol': + if type(angle).__name__ == "Symbol": return sp.Matrix(M) return M @@ -408,14 +413,11 @@ def rotation_from_matrix(matrix): # rotation angle depending on direction cosa = (np.trace(R33) - 1.0) / 2.0 if abs(direction[2]) > 1e-8: - sina = (R[1, 0] + (cosa - 1.0) * direction[0] - * direction[1]) / direction[2] + sina = (R[1, 0] + (cosa - 1.0) * direction[0] * direction[1]) / direction[2] elif abs(direction[1]) > 1e-8: - sina = (R[0, 2] + (cosa - 1.0) * direction[0] - * direction[2]) / direction[1] + sina = (R[0, 2] + (cosa - 1.0) * direction[0] * direction[2]) / direction[1] else: - sina = (R[2, 1] + (cosa - 1.0) * direction[1] - * direction[2]) / direction[0] + sina = (R[2, 1] + (cosa - 1.0) * direction[1] * direction[2]) / direction[0] angle = math.atan2(sina, cosa) return angle, direction, point @@ -495,8 +497,7 @@ def scale_from_matrix(matrix): return factor, origin, direction -def projection_matrix(point, normal, direction=None, - perspective=None, pseudo=False): +def projection_matrix(point, normal, direction=None, perspective=None, pseudo=False): """Return matrix to project onto plane defined by point and normal. Using either perspective point, projection direction, or none of both. @@ -532,8 +533,7 @@ def projection_matrix(point, normal, direction=None, normal = unit_vector(normal[:3]) if perspective is not None: # perspective projection - perspective = np.array(perspective[:3], dtype=np.float64, - copy=False) + perspective = np.array(perspective[:3], dtype=np.float64, copy=False) M[0, 0] = M[1, 1] = M[2, 2] = np.dot(perspective - point, normal) M[:3, :3] -= np.outer(perspective, normal) if pseudo: @@ -619,11 +619,10 @@ def projection_from_matrix(matrix, pseudo=False): # perspective projection i = np.where(abs(np.real(w)) > 1e-8)[0] if not len(i): - raise ValueError( - "no eigenvector not corresponding to eigenvalue 0") + raise ValueError("no eigenvector not corresponding to eigenvalue 0") point = np.real(V[:, i[-1]]).squeeze() point /= point[3] - normal = - M[3, :3] + normal = -M[3, :3] perspective = M[:3, 3] / np.dot(point[:3], normal) if pseudo: perspective -= normal @@ -674,15 +673,19 @@ def clip_matrix(left, right, bottom, top, near, far, perspective=False): if near <= _EPS: raise ValueError("invalid frustum: near <= 0") t = 2.0 * near - M = [[t / (left - right), 0.0, (right + left) / (right - left), 0.0], - [0.0, t / (bottom - top), (top + bottom) / (top - bottom), 0.0], - [0.0, 0.0, (far + near) / (near - far), t * far / (far - near)], - [0.0, 0.0, -1.0, 0.0]] + M = [ + [t / (left - right), 0.0, (right + left) / (right - left), 0.0], + [0.0, t / (bottom - top), (top + bottom) / (top - bottom), 0.0], + [0.0, 0.0, (far + near) / (near - far), t * far / (far - near)], + [0.0, 0.0, -1.0, 0.0], + ] else: - M = [[2.0 / (right - left), 0.0, 0.0, (right + left) / (left - right)], - [0.0, 2.0 / (top - bottom), 0.0, (top + bottom) / (bottom - top)], - [0.0, 0.0, 2.0 / (far - near), (far + near) / (near - far)], - [0.0, 0.0, 0.0, 1.0]] + M = [ + [2.0 / (right - left), 0.0, 0.0, (right + left) / (left - right)], + [0.0, 2.0 / (top - bottom), 0.0, (top + bottom) / (bottom - top)], + [0.0, 0.0, 2.0 / (far - near), (far + near) / (near - far)], + [0.0, 0.0, 0.0, 1.0], + ] return np.array(M) @@ -804,7 +807,7 @@ def decompose_matrix(matrix): if not np.linalg.det(P): raise ValueError("matrix is singular") - scale = np.zeros((3, )) + scale = np.zeros((3,)) shear = [0.0, 0.0, 0.0] angles = [0.0, 0.0, 0.0] @@ -848,8 +851,9 @@ def decompose_matrix(matrix): return scale, shear, angles, translate, perspective -def compose_matrix(scale=None, shear=None, angles=None, translate=None, - perspective=None): +def compose_matrix( + scale=None, shear=None, angles=None, translate=None, perspective=None +): """Return transformation matrix from sequence of transformations. This is the inverse of the decompose_matrix function. @@ -883,7 +887,7 @@ def compose_matrix(scale=None, shear=None, angles=None, translate=None, T[:3, 3] = translate[:3] M = np.dot(M, T) if angles is not None: - R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz') + R = euler_matrix(angles[0], angles[1], angles[2], "sxyz") M = np.dot(M, R) if shear is not None: Z = np.identity(4) @@ -921,11 +925,14 @@ def orthogonalization_matrix(lengths, angles): sina, sinb, _ = np.sin(angles) cosa, cosb, cosg = np.cos(angles) co = (cosa * cosb - cosg) / (sina * sinb) - return np.array([ - [a * sinb * math.sqrt(1.0 - co * co), 0.0, 0.0, 0.0], - [-a * sinb * co, b * sina, 0.0, 0.0], - [a * cosb, b * cosa, c, 0.0], - [0.0, 0.0, 0.0, 1.0]]) + return np.array( + [ + [a * sinb * math.sqrt(1.0 - co * co), 0.0, 0.0, 0.0], + [-a * sinb * co, b * sina, 0.0, 0.0], + [a * cosb, b * cosa, c, 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + ) def affine_matrix_from_points(v0, v1, shear=True, scale=True, usesvd=True): @@ -988,7 +995,7 @@ def affine_matrix_from_points(v0, v1, shear=True, scale=True, usesvd=True): u, s, vh = np.linalg.svd(A.T) vh = vh[:ndims].T B = vh[:ndims] - C = vh[ndims:2 * ndims] + C = vh[ndims : 2 * ndims] t = np.dot(C, np.linalg.pinv(B)) t = np.concatenate((t, np.zeros((ndims, 1))), axis=1) M = np.vstack((t, ((0.0,) * ndims) + (1.0,))) @@ -1010,10 +1017,12 @@ def affine_matrix_from_points(v0, v1, shear=True, scale=True, usesvd=True): xx, yy, zz = np.sum(v0 * v1, axis=1) xy, yz, zx = np.sum(v0 * np.roll(v1, -1, axis=0), axis=1) xz, yx, zy = np.sum(v0 * np.roll(v1, -2, axis=0), axis=1) - N = [[xx + yy + zz, 0.0, 0.0, 0.0], - [yz - zy, xx - yy - zz, 0.0, 0.0], - [zx - xz, xy + yx, yy - xx - zz, 0.0], - [xy - yx, zx + xz, yz + zy, zz - xx - yy]] + N = [ + [xx + yy + zz, 0.0, 0.0, 0.0], + [yz - zy, xx - yy - zz, 0.0, 0.0], + [zx - xz, xy + yx, yy - xx - zz, 0.0], + [xy - yx, zx + xz, yz + zy, zz - xx - yy], + ] # quaternion: eigenvector corresponding to most positive eigenvalue w, V = np.linalg.eigh(N) q = V[:, np.argmax(w)] @@ -1080,11 +1089,10 @@ def superimposition_matrix(v0, v1, scale=False, usesvd=True): """ v0 = np.array(v0, dtype=np.float64, copy=False)[:3] v1 = np.array(v1, dtype=np.float64, copy=False)[:3] - return affine_matrix_from_points(v0, v1, shear=False, - scale=scale, usesvd=usesvd) + return affine_matrix_from_points(v0, v1, shear=False, scale=scale, usesvd=usesvd) -def euler_matrix(ai, aj, ak, axes='sxyz'): +def euler_matrix(ai, aj, ak, axes="sxyz"): """Return homogeneous rotation matrix from Euler angles and axis sequence. ai, aj, ak : Euler's roll, pitch and yaw angles @@ -1147,7 +1155,7 @@ def euler_matrix(ai, aj, ak, axes='sxyz'): return M -def euler_from_matrix(matrix, axes='sxyz'): +def euler_from_matrix(matrix, axes="sxyz"): """Return Euler angles from rotation matrix for specified axis sequence. axes : One of 24 axis sequences as string or encoded tuple @@ -1205,7 +1213,7 @@ def euler_from_matrix(matrix, axes='sxyz'): return ax, ay, az -def euler_from_quaternion(quaternion, axes='sxyz'): +def euler_from_quaternion(quaternion, axes="sxyz"): """Return Euler angles from quaternion for specified axis sequence. >>> angles = euler_from_quaternion([0.99810947, 0.06146124, 0, 0]) @@ -1216,7 +1224,7 @@ def euler_from_quaternion(quaternion, axes='sxyz'): return euler_from_matrix(quaternion_matrix(quaternion), axes) -def quaternion_from_euler(ai, aj, ak, axes='sxyz'): +def quaternion_from_euler(ai, aj, ak, axes="sxyz"): """Return quaternion from Euler angles and axis sequence. ai, aj, ak : Euler's roll, pitch and yaw angles @@ -1256,7 +1264,7 @@ def quaternion_from_euler(ai, aj, ak, axes='sxyz'): sc = si * ck ss = si * sk - q = np.empty((4, )) + q = np.empty((4,)) if repetition: q[0] = cj * (cc - ss) q[i] = cj * (cs + sc) @@ -1309,12 +1317,14 @@ def quaternion_matrix(quaternion): return np.identity(4) q *= math.sqrt(2.0 / n) q = np.outer(q, q) - return np.array([ - [1.0 - q[2, 2] - q[3, 3], q[1, 2] - - q[3, 0], q[1, 3] + q[2, 0], 0.0], - [q[1, 2] + q[3, 0], 1.0 - q[1, 1] - q[3, 3], q[2, 3] - q[1, 0], 0.0], - [q[1, 3] - q[2, 0], q[2, 3] + q[1, 0], 1.0 - q[1, 1] - q[2, 2], 0.0], - [0.0, 0.0, 0.0, 1.0]]) + return np.array( + [ + [1.0 - q[2, 2] - q[3, 3], q[1, 2] - q[3, 0], q[1, 3] + q[2, 0], 0.0], + [q[1, 2] + q[3, 0], 1.0 - q[1, 1] - q[3, 3], q[2, 3] - q[1, 0], 0.0], + [q[1, 3] - q[2, 0], q[2, 3] + q[1, 0], 1.0 - q[1, 1] - q[2, 2], 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + ) def quaternion_from_matrix(matrix, isprecise=False): @@ -1358,7 +1368,7 @@ def quaternion_from_matrix(matrix, isprecise=False): """ M = np.array(matrix, dtype=np.float64, copy=False)[:4, :4] if isprecise: - q = np.empty((4, )) + q = np.empty((4,)) t = np.trace(M) if t > M[3, 3]: q[0] = t @@ -1389,10 +1399,14 @@ def quaternion_from_matrix(matrix, isprecise=False): m21 = M[2, 1] m22 = M[2, 2] # symmetric matrix K - K = np.array([[m00 - m11 - m22, 0.0, 0.0, 0.0], - [m01 + m10, m11 - m00 - m22, 0.0, 0.0], - [m02 + m20, m12 + m21, m22 - m00 - m11, 0.0], - [m21 - m12, m02 - m20, m10 - m01, m00 + m11 + m22]]) + K = np.array( + [ + [m00 - m11 - m22, 0.0, 0.0, 0.0], + [m01 + m10, m11 - m00 - m22, 0.0, 0.0], + [m02 + m20, m12 + m21, m22 - m00 - m11, 0.0], + [m21 - m12, m02 - m20, m10 - m01, m00 + m11 + m22], + ] + ) K /= 3.0 # quaternion is eigenvector of K that corresponds to largest eigenvalue w, V = np.linalg.eigh(K) @@ -1412,10 +1426,15 @@ def quaternion_multiply(quaternion1, quaternion0): """ w0, x0, y0, z0 = quaternion0 w1, x1, y1, z1 = quaternion1 - return np.array([-x1 * x0 - y1 * y0 - z1 * z0 + w1 * w0, - x1 * w0 + y1 * z0 - z1 * y0 + w1 * x0, - -x1 * z0 + y1 * w0 + z1 * x0 + w1 * y0, - x1 * y0 - y1 * x0 + z1 * w0 + w1 * z0], dtype=np.float64) + return np.array( + [ + -x1 * x0 - y1 * y0 - z1 * z0 + w1 * w0, + x1 * w0 + y1 * z0 - z1 * y0 + w1 * x0, + -x1 * z0 + y1 * w0 + z1 * x0 + w1 * y0, + x1 * y0 - y1 * x0 + z1 * w0 + w1 * z0, + ], + dtype=np.float64, + ) def quaternion_conjugate(quaternion): @@ -1531,8 +1550,9 @@ def random_quaternion(rand=None): pi2 = math.pi * 2.0 t1 = pi2 * rand[1] t2 = pi2 * rand[2] - return np.array([np.cos(t2) * r2, np.sin(t1) * r1, - np.cos(t1) * r1, np.sin(t2) * r2]) + return np.array( + [np.cos(t2) * r2, np.sin(t1) * r1, np.cos(t1) * r1, np.sin(t2) * r2] + ) def random_rotation_matrix(rand=None): @@ -1592,7 +1612,7 @@ def __init__(self, initial=None): initial = np.array(initial, dtype=np.float64) if initial.shape == (4, 4): self._qdown = quaternion_from_matrix(initial) - elif initial.shape == (4, ): + elif initial.shape == (4,): initial /= vector_norm(initial) self._qdown = initial else: @@ -1712,14 +1732,31 @@ def arcball_nearest_axis(point, axes): # map axes strings to/from tuples of inner axis, parity, repetition, frame _AXES2TUPLE = { - 'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0), - 'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0), - 'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0), - 'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0), - 'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1), - 'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1), - 'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1), - 'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)} + "sxyz": (0, 0, 0, 0), + "sxyx": (0, 0, 1, 0), + "sxzy": (0, 1, 0, 0), + "sxzx": (0, 1, 1, 0), + "syzx": (1, 0, 0, 0), + "syzy": (1, 0, 1, 0), + "syxz": (1, 1, 0, 0), + "syxy": (1, 1, 1, 0), + "szxy": (2, 0, 0, 0), + "szxz": (2, 0, 1, 0), + "szyx": (2, 1, 0, 0), + "szyz": (2, 1, 1, 0), + "rzyx": (0, 0, 0, 1), + "rxyx": (0, 0, 1, 1), + "ryzx": (0, 1, 0, 1), + "rxzx": (0, 1, 1, 1), + "rxzy": (1, 0, 0, 1), + "ryzy": (1, 0, 1, 1), + "rzxy": (1, 1, 0, 1), + "ryxy": (1, 1, 1, 1), + "ryxz": (2, 0, 0, 1), + "rzxz": (2, 0, 1, 1), + "rxyz": (2, 1, 0, 1), + "rzyz": (2, 1, 1, 1), +} _TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items()) @@ -1946,9 +1983,8 @@ def transform_around(matrix, point): point = np.asanyarray(point) matrix = np.asanyarray(matrix) dim = len(point) - if matrix.shape != (dim + 1, - dim + 1): - raise ValueError('matrix must be (d+1, d+1)') + if matrix.shape != (dim + 1, dim + 1): + raise ValueError("matrix must be (d+1, d+1)") translate = np.eye(dim + 1) translate[:dim, dim] = -point @@ -1959,9 +1995,7 @@ def transform_around(matrix, point): return result -def planar_matrix(offset=[0.0, 0.0], - theta=0.0, - point=None): +def planar_matrix(offset=[0.0, 0.0], theta=0.0, point=None): """ 2D homogeonous transformation matrix @@ -1977,9 +2011,9 @@ def planar_matrix(offset=[0.0, 0.0], offset = np.asanyarray(offset, dtype=np.float64) theta = float(theta) if not np.isfinite(theta): - raise ValueError('theta must be finite angle!') + raise ValueError("theta must be finite angle!") if offset.shape != (2,): - raise ValueError('offset must be length 2!') + raise ValueError("offset must be length 2!") T = np.eye(3, dtype=np.float64) s = np.sin(theta) @@ -2011,7 +2045,7 @@ def planar_matrix_to_3D(matrix_2D): matrix_2D = np.asanyarray(matrix_2D, dtype=np.float64) if matrix_2D.shape != (3, 3): - raise ValueError('Homogenous 2D transformation matrix required!') + raise ValueError("Homogenous 2D transformation matrix required!") matrix_3D = np.eye(4) # translation @@ -2022,7 +2056,7 @@ def planar_matrix_to_3D(matrix_2D): return matrix_3D -def spherical_matrix(theta, phi, axes='sxyz'): +def spherical_matrix(theta, phi, axes="sxyz"): """ Give a spherical coordinate vector, find the rotation that will transform a [0,0,1] vector to those coordinates @@ -2044,9 +2078,7 @@ def spherical_matrix(theta, phi, axes='sxyz'): return result -def transform_points(points, - matrix, - translate=True): +def transform_points(points, matrix, translate=True): """ Returns points, rotated by transformation matrix @@ -2069,11 +2101,12 @@ def transform_points(points, """ points = np.asanyarray(points, dtype=np.float64) matrix = np.asanyarray(matrix, dtype=np.float64) - if (len(points.shape) != 2 or - (points.shape[1] + 1 != matrix.shape[1])): - raise ValueError('matrix shape ({}) doesn\'t match points ({})'.format( - matrix.shape, - points.shape)) + if len(points.shape) != 2 or (points.shape[1] + 1 != matrix.shape[1]): + raise ValueError( + "matrix shape ({}) doesn't match points ({})".format( + matrix.shape, points.shape + ) + ) # check to see if we've been passed an identity matrix identity = np.abs(matrix - np.eye(matrix.shape[0])).max() @@ -2110,7 +2143,6 @@ def is_rigid(matrix): if not np.allclose(matrix[-1], [0, 0, 0, 1]): return False - check = np.dot(matrix[:3, :3], - matrix[:3, :3].T) + check = np.dot(matrix[:3, :3], matrix[:3, :3].T) return np.allclose(check, np.eye(3)) diff --git a/imgviz/flow.py b/imgviz/flow.py index cc83565..a810102 100644 --- a/imgviz/flow.py +++ b/imgviz/flow.py @@ -92,9 +92,7 @@ def flow2rgb(flow_uv): """ assert flow_uv.ndim == 3, "flow must be 3 dimensional" assert flow_uv.shape[2] == 2, "flow must have shape (H, W, 2)" - assert np.issubdtype( - flow_uv.dtype, np.floating - ), "float must be float type" + assert np.issubdtype(flow_uv.dtype, np.floating), "float must be float type" flow_u = flow_uv[:, :, 0] flow_v = flow_uv[:, :, 1] diff --git a/imgviz/instances.py b/imgviz/instances.py index 9754d4a..a488d77 100644 --- a/imgviz/instances.py +++ b/imgviz/instances.py @@ -122,16 +122,12 @@ def instances2rgb( maskviz = mask[:, :, None] * color_ins.astype(float) dst = dst.copy() - dst[mask] = (1 - alpha) * image[mask].astype(float) + alpha * maskviz[ - mask - ] + dst[mask] = (1 - alpha) * image[mask].astype(float) + alpha * maskviz[mask] try: import skimage.segmentation - boundary = skimage.segmentation.find_boundaries( - mask, connectivity=2 - ) + boundary = skimage.segmentation.find_boundaries(mask, connectivity=2) for _ in range(boundary_width - 1): boundary = skimage.morphology.binary_dilation(boundary) dst[boundary] = (200, 200, 200) diff --git a/imgviz/label.py b/imgviz/label.py index 358a38d..79aa36f 100644 --- a/imgviz/label.py +++ b/imgviz/label.py @@ -116,7 +116,9 @@ def label2rgb( if isinstance(alpha, numbers.Number): alpha = np.array([alpha for _ in range(max_label_id + 1)]) elif isinstance(alpha, dict): - alpha = np.array([alpha.get(l, 0.5) for l in range(max_label_id + 1)]) + alpha = np.array( + [alpha.get(label_id, 0.5) for label_id in range(max_label_id + 1)] + ) else: alpha = np.asarray(alpha) assert alpha.ndim == 1 @@ -134,9 +136,13 @@ def label2rgb( unique_labels = unique_labels[unique_labels != -1] if isinstance(label_names, dict): - unique_labels = [l for l in unique_labels if label_names.get(l)] + unique_labels = [ + label_id for label_id in unique_labels if label_names.get(label_id) + ] else: - unique_labels = [l for l in unique_labels if label_names[l]] + unique_labels = [ + label_id for label_id in unique_labels if label_names[label_id] + ] if len(unique_labels) == 0: return res @@ -170,9 +176,9 @@ def label2rgb( text_sizes = np.array( [ draw_module.text_size( - label_names[l], font_size, font_path=font_path + label_names[label_id], font_size, font_path=font_path ) - for l in unique_labels + for label_id in unique_labels ] ) text_height, text_width = text_sizes.max(axis=0) @@ -195,16 +201,16 @@ def label2rgb( res[y1:y2, x1:x2] = alpha * res[y1:y2, x1:x2] + alpha * 255 res = utils.numpy_to_pillow(res) - for i, l in enumerate(unique_labels): + for i, label_id in enumerate(unique_labels): box_aabb1 = aabb1 + (i * text_height + 5, 5) box_aabb2 = box_aabb1 + (text_height - 10, text_height - 10) draw_module.rectangle_( - res, aabb1=box_aabb1, aabb2=box_aabb2, fill=colormap[l] + res, aabb1=box_aabb1, aabb2=box_aabb2, fill=colormap[label_id] ) draw_module.text_( res, yx=aabb1 + (i * text_height, 10 + (text_height - 10)), - text=label_names[l], + text=label_names[label_id], size=font_size, font_path=font_path, ) diff --git a/imgviz/nchannel.py b/imgviz/nchannel.py index ad7cfcb..0f3fef5 100644 --- a/imgviz/nchannel.py +++ b/imgviz/nchannel.py @@ -50,9 +50,7 @@ def __call__(self, nchannel, dtype=np.uint8): dst = nchannel.reshape(-1, D) if self._pca is None: - self._pca = sklearn.decomposition.PCA( - n_components=3, random_state=1234 - ) + self._pca = sklearn.decomposition.PCA(n_components=3, random_state=1234) dst = self._pca.fit_transform(dst) else: dst = self._pca.transform(dst) diff --git a/imgviz/resize.py b/imgviz/resize.py index d4c433d..487241b 100644 --- a/imgviz/resize.py +++ b/imgviz/resize.py @@ -32,9 +32,7 @@ def _resize_pillow(src, height, width, interpolation): for c in range(C): src_c = src[:, :, c] src_c = utils.numpy_to_pillow(src_c) - dst[:, :, c] = src_c.resize( - (width, height), resample=interpolation - ) + dst[:, :, c] = src_c.resize((width, height), resample=interpolation) if ndim == 2: dst = dst[:, :, 0] diff --git a/imgviz/tile.py b/imgviz/tile.py index 6437397..415c568 100644 --- a/imgviz/tile.py +++ b/imgviz/tile.py @@ -131,6 +131,4 @@ def tile( img = np.full((max_h, max_w, channel), cval, dtype=np.uint8) imgs.append(img) - return _tile( - imgs=imgs, shape=shape, border=border, border_width=border_width - ) + return _tile(imgs=imgs, shape=shape, border=border, border_width=border_width) diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 8355db9..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[tool.black] -line-length = 79 -exclude = ''' -( - ^/\..* - | ^/docs/ - | ^/build/ - | ^/github2pypi/ - | ^/imgviz/external/ - | ^/getting_started\.py -) -''' diff --git a/requirements-dev.txt b/requirements-dev.txt index 7b2a251..5f6ccbe 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,5 @@ -r requirements.txt -black==22.3.0 -hacking==4.1.0 +github2pypi==1.0.0 mypy pytest -github2pypi==1.0.0 +ruff==0.1.9 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..1e3f95b --- /dev/null +++ b/ruff.toml @@ -0,0 +1,33 @@ +exclude = [ + ".conda", + ".git", + "src", +] + +line-length = 88 +indent-width = 4 + +[lint] +# Enable Pyflakes (`F`), pycodestyle (`E`), isort (`I`). +select = ["E", "F", "I"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +[format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +[isort] +force-single-line = true diff --git a/setup.py b/setup.py index 553b7c1..d28100c 100644 --- a/setup.py +++ b/setup.py @@ -15,9 +15,7 @@ def get_version(): filename = "imgviz/__init__.py" with open(filename) as f: - match = re.search( - r"""^__version__ = ['"]([^'"]*)['"]""", f.read(), re.M - ) + match = re.search(r"""^__version__ = ['"]([^'"]*)['"]""", f.read(), re.M) if not match: raise RuntimeError("{} doesn't contain __version__".format(filename)) version = match.groups()[0] diff --git a/tests/draw_tests/test_text.py b/tests/draw_tests/test_text.py index bd3e209..fbf701f 100644 --- a/tests/draw_tests/test_text.py +++ b/tests/draw_tests/test_text.py @@ -5,9 +5,7 @@ def test_text(): img = np.full((100, 100, 3), 255, dtype=np.uint8) - res = imgviz.draw.text( - img, yx=(0, 0), text="TEST", color=(0, 0, 0), size=30 - ) + res = imgviz.draw.text(img, yx=(0, 0), text="TEST", color=(0, 0, 0), size=30) assert res.shape == img.shape assert res.dtype == img.dtype assert not np.allclose(img, res)