From 3b198a6065179d3f27621f27b8aea7b4b361a03a Mon Sep 17 00:00:00 2001 From: Zhao Liang Date: Mon, 19 Dec 2022 14:32:00 +0800 Subject: [PATCH] [example] Add circle-packing example (#6870) This PR adds a new example. ![Figure_1](https://user-images.githubusercontent.com/23307174/206941350-03f6b7f6-9d6c-44a3-9f29-ea44aff229a9.png) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .gitignore | 1 + .../circle-packing/circle_packing_image.py | 133 ++++++++++++++++++ .../algorithm/circle-packing/taichi_logo.png | Bin 0 -> 6390 bytes requirements_dev.txt | 2 + 4 files changed, 136 insertions(+) create mode 100644 python/taichi/examples/algorithm/circle-packing/circle_packing_image.py create mode 100644 python/taichi/examples/algorithm/circle-packing/taichi_logo.png diff --git a/.gitignore b/.gitignore index 868b1ae350d69..62baf3ae6c002 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ __pycache__ !docs/**/*.jpg !docs/**/*.png !tests/python/expected/*.png +!python/taichi/examples/algorithm/circle-packing/*.png *.egg-info .tlang_cache /taichi/common/version.h diff --git a/python/taichi/examples/algorithm/circle-packing/circle_packing_image.py b/python/taichi/examples/algorithm/circle-packing/circle_packing_image.py new file mode 100644 index 0000000000000..d81cf28030d22 --- /dev/null +++ b/python/taichi/examples/algorithm/circle-packing/circle_packing_image.py @@ -0,0 +1,133 @@ +""" +Given an input image, redraw it with circle packings. +""" +try: + import cairocffi as cairo + import cv2 +except: + raise ImportError( + "This example depends on opencv and cairocffi, please run 'pip install opencv-python cairocffi' to install." + ) + +import os + +import matplotlib.pyplot as plt +import numpy as np + +import taichi as ti + +ti.init(arch=ti.cpu) + +_internal_scale = 5 # internally we need a large image to paint + + +@ti.dataclass +class Circle: + x: int + y: int + r: int + + +circles = Circle.field() # pylint: disable=no-member +ti.root.dynamic(ti.i, 100000, chunk_size=64).place(circles) + + +def load_image(imgfile): + image = cv2.imread(imgfile) + h, w = image.shape[:2] + image = cv2.resize(image, + (int(_internal_scale * w), int(_internal_scale * h)), + interpolation=cv2.INTER_AREA) + return cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + + +def get_dist_transform_image(image): + canny = cv2.Canny(image, 100, 200) + edges_inv = 255 - canny + dist_image = cv2.distanceTransform(edges_inv, cv2.DIST_L2, 0) + return dist_image + + +@ti.kernel +def add_new_circles(filled: ti.types.ndarray(), dist_image: ti.types.ndarray(), + min_radius: int, max_radius: int) -> int: + H, W = dist_image.shape[0], dist_image.shape[1] + ti.loop_config(serialize=True) + for x in range(min_radius, W - min_radius): + for y in range(min_radius, H - min_radius): + valid = True + if dist_image[y, x] > min_radius: + r = int((dist_image[y, x] + 1) / 2) + r = ti.min(r, max_radius) + if not filled[y, x] and r <= x < W - r and r <= y < H - r: + for ii in range(x - r, x + r + 1): + for jj in range(y - r, y + r + 1): + if (ii - x)**2 + (jj - y)**2 < (r + 1)**2: + if filled[jj, ii]: + valid = False + break + if not valid: + break + + if valid: + circles.append(Circle(x, y, r)) + for ii in range(x - r, x + r + 1): + for jj in range(y - r, y + r + 1): + if (ii - x)**2 + (jj - y)**2 < (r + 1)**2: + filled[jj, ii] = 1 + + return circles.length() + + +def plot_cirlces(image, ctx, n): + for i in range(n): + c = circles[i] + fc = image[c.y, c.x] / 255 + if all(fc < 0.1): + ec = (0.5, 0.5, 0.5) + else: + ec = (0, 0, 0) + ctx.arc(c.x, c.y, c.r, 0, 2 * np.pi) + ctx.set_source_rgb(*fc) + ctx.fill_preserve() + ctx.set_source_rgba(*ec) + ctx.stroke() + + +def process(imgfile, scale): + image = load_image(imgfile) + dist_image = get_dist_transform_image(image) + image = cv2.GaussianBlur(image, (5, 5), 0) + H, W = image.shape[:2] + surface = cairo.ImageSurface(cairo.FORMAT_RGB24, W, H) + ctx = cairo.Context(surface) + ctx.set_source_rgb(0, 0, 0) + ctx.paint() + + filled = np.zeros([H, W], dtype=np.int32) + R = [150, 120, 100, 80, 50, 30, 25, 20, 15, 10, 7, 5, 3, 2] + for i in range(1, len(R)): + n = add_new_circles(filled, dist_image, R[i], R[i - 1]) + + ctx.set_line_width(1) + plot_cirlces(image, ctx, n) + data = surface.get_data() + result = np.frombuffer(data, dtype=np.uint8).reshape(H, W, 4) + w = int(W / _internal_scale * scale) + h = int(H / _internal_scale * scale) + result = cv2.resize(result, (w, h), interpolation=cv2.INTER_CUBIC) + plt.tight_layout() + plt.axis('off') + plt.imshow(result) + plt.show() + + +def main(imgfile=None, scale=2): + if imgfile is None: + imgfile = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'taichi_logo.png') + process(imgfile, scale) + + +if __name__ == '__main__': + main() diff --git a/python/taichi/examples/algorithm/circle-packing/taichi_logo.png b/python/taichi/examples/algorithm/circle-packing/taichi_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ba98df7616ed55d8e6622329b6fab6e51df6b4b3 GIT binary patch literal 6390 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 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 Ef~8K8M*si- literal 0 HcmV?d00001 diff --git a/requirements_dev.txt b/requirements_dev.txt index 2cc165f61719d..146ea74ff3e69 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -17,3 +17,5 @@ pre-commit scikit-build numpy ninja; platform_system != 'Windows' +cairocffi +opencv-python