git: 62fd95493e22 - main - math/py-isosurfaces: New port: Construct isolines/isosurfaces over a 2D/3D scalar field

From: Yuri Victorovich <yuri_at_FreeBSD.org>
Date: Fri, 20 Jan 2023 18:43:35 UTC
The branch main has been updated by yuri:

URL: https://cgit.FreeBSD.org/ports/commit/?id=62fd95493e22bd6cb7dc9884e92a479cd76ea8d6

commit 62fd95493e22bd6cb7dc9884e92a479cd76ea8d6
Author:     Yuri Victorovich <yuri@FreeBSD.org>
AuthorDate: 2023-01-20 16:09:03 +0000
Commit:     Yuri Victorovich <yuri@FreeBSD.org>
CommitDate: 2023-01-20 18:43:32 +0000

    math/py-isosurfaces: New port: Construct isolines/isosurfaces over a 2D/3D scalar field
---
 math/Makefile                             |   1 +
 math/py-isosurfaces/Makefile              |  30 +++++++
 math/py-isosurfaces/distinfo              |   3 +
 math/py-isosurfaces/files/isoline_demo.py | 133 ++++++++++++++++++++++++++++++
 math/py-isosurfaces/pkg-descr             |   5 ++
 5 files changed, 172 insertions(+)

diff --git a/math/Makefile b/math/Makefile
index 6e6adc82c966..902b5e115691 100644
--- a/math/Makefile
+++ b/math/Makefile
@@ -925,6 +925,7 @@
     SUBDIR += py-intspan
     SUBDIR += py-iohexperimenter
     SUBDIR += py-ipyopt
+    SUBDIR += py-isosurfaces
     SUBDIR += py-jax
     SUBDIR += py-kahip
     SUBDIR += py-keras
diff --git a/math/py-isosurfaces/Makefile b/math/py-isosurfaces/Makefile
new file mode 100644
index 000000000000..63ef30cdb9c4
--- /dev/null
+++ b/math/py-isosurfaces/Makefile
@@ -0,0 +1,30 @@
+PORTNAME=	isosurfaces
+DISTVERSION=	0.1.0
+CATEGORIES=	math
+MASTER_SITES=	PYPI
+PKGNAMEPREFIX=	${PYTHON_PKGNAMEPREFIX}
+
+MAINTAINER=	yuri@FreeBSD.org
+COMMENT=	Construct isolines/isosurfaces over a 2D/3D scalar field
+WWW=		https://github.com/jared-hughes/isosurfaces
+
+LICENSE=	MIT
+
+RUN_DEPENDS=	${PYNUMPY}
+TEST_DEPENDS=	${PYTHON_PKGNAMEPREFIX}cairo>0:graphics/py-cairo@${PY_FLAVOR} \
+		${PYNUMPY} \
+		xdg-open:devel/xdg-utils
+
+USES=		python
+USE_PYTHON=	distutils autoplist
+
+NO_ARCH=	yes
+
+TEST_ENV=	${MAKE_ENV} PYTHONPATH=${STAGEDIR}${PYTHONPREFIX_SITELIBDIR}
+
+do-test:
+	@cd ${TEST_WRKSRC} && \
+		${SETENV} ${TEST_ENV} ${PYTHON_CMD} ${FILESDIR}/isoline_demo.py && \
+		xdg-open ${TEST_WRKSRC}/demo.svg
+
+.include <bsd.port.mk>
diff --git a/math/py-isosurfaces/distinfo b/math/py-isosurfaces/distinfo
new file mode 100644
index 000000000000..f4588b9ff108
--- /dev/null
+++ b/math/py-isosurfaces/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1674236137
+SHA256 (isosurfaces-0.1.0.tar.gz) = fa1b44e5e59d2f429add49289ab89e36f8dcda49b7badd99e0beea273be331f4
+SIZE (isosurfaces-0.1.0.tar.gz) = 10122
diff --git a/math/py-isosurfaces/files/isoline_demo.py b/math/py-isosurfaces/files/isoline_demo.py
new file mode 100644
index 000000000000..53bd1a51da61
--- /dev/null
+++ b/math/py-isosurfaces/files/isoline_demo.py
@@ -0,0 +1,133 @@
+# from examples/isoline_demo.py
+
+""" Code for demo-ing and experimentation. Prepare for a mess """
+from isosurfaces import plot_isoline
+from isosurfaces.isoline import (
+    Cell,
+    build_tree,
+    Triangulator,
+    CurveTracer,
+)
+import numpy as np
+import cairo
+
+min_depth = 5
+pmin = np.array([-8, -6])
+pmax = np.array([8, 6])
+
+
+def f(x, y):
+    return y * (x - y) ** 2 - 4 * x - 8
+
+
+# Here we directly use plot_implicit internals in order to see the quadtree
+fn = lambda u: f(u[0], u[1])
+tol = (pmax - pmin) / 1000
+quadtree = build_tree(2, fn, pmin, pmax, min_depth, 5000, tol)
+triangles = Triangulator(quadtree, fn).triangulate()
+curves = CurveTracer(triangles, fn, tol).trace()
+
+
+def g(x, y):
+    return x ** 3 - x - y ** 2
+
+
+# Typical usage
+curves1 = plot_isoline(
+    lambda u: g(u[0], u[1]),
+    pmin,
+    pmax,
+    min_depth=4,
+    max_quads=1000,
+)
+
+
+def h(x, y):
+    return x ** 4 + y ** 4 - np.sin(x) - np.sin(4 * y)
+
+
+curves2 = plot_isoline(lambda u: h(u[0], u[1]), pmin, pmax, 4, 1000)
+
+
+WIDTH = 640
+HEIGHT = 480
+
+
+def setup_context(c):
+    # reflection to change math units to screen units
+    scale = min(WIDTH / (pmax[0] - pmin[0]), HEIGHT / (pmax[1] - pmin[1]))
+    c.scale(scale, -scale)
+    c.translate(WIDTH / scale / 2, -HEIGHT / scale / 2)
+    c.set_line_join(cairo.LINE_JOIN_BEVEL)
+
+
+def draw_axes(c):
+    c.save()
+    c.set_line_width(0.1)
+    c.move_to(0, -100)
+    c.line_to(0, 100)
+    c.stroke()
+    c.move_to(-100, 0)
+    c.line_to(100, 0)
+    c.stroke()
+    c.restore()
+
+
+def draw_quad(c, quad: Cell):
+    width = 0
+    if quad.depth <= min_depth:
+        width = 0.02
+    elif quad.depth == min_depth + 1:
+        width = 0.01
+    else:
+        width = 0.005
+    c.set_line_width(0.5 * width)
+
+    if quad.children:
+        c.move_to(*((quad.vertices[0].pos + quad.vertices[1].pos) / 2))
+        c.line_to(*((quad.vertices[2].pos + quad.vertices[3].pos) / 2))
+        c.move_to(*((quad.vertices[0].pos + quad.vertices[2].pos) / 2))
+        c.line_to(*((quad.vertices[1].pos + quad.vertices[3].pos) / 2))
+        c.stroke()
+        for child in quad.children:
+            draw_quad(c, child)
+
+
+def draw_quads(c):
+    c.save()
+    draw_quad(c, quadtree)
+    c.restore()
+
+
+def draw_bg(c):
+    c.save()
+    c.set_source_rgb(1, 1, 1)
+    c.paint()
+    c.restore()
+
+
+def draw_curves(c, curves_list, rgb):
+    print(
+        "drawing", sum(map(len, curves_list)), "segments in", len(curves_list), "curves"
+    )
+    c.set_source_rgb(*rgb)
+    # draw curves
+    c.save()
+    c.set_line_width(0.03)
+    for curve in curves_list:
+        c.move_to(*curve[0])
+        for v in curve:
+            c.line_to(*v)
+        c.stroke()
+    c.restore()
+
+
+with cairo.SVGSurface("demo.svg", WIDTH, HEIGHT) as surface:
+    c = cairo.Context(surface)
+    setup_context(c)
+    draw_bg(c)
+    draw_axes(c)
+    # draw_quads(c)
+    draw_curves(c, curves, [0.1, 0.1, 0.8])
+    draw_curves(c, curves1, [0.8, 0.1, 0.1])
+    draw_curves(c, curves2, [0.1, 0.6, 0.1])
diff --git a/math/py-isosurfaces/pkg-descr b/math/py-isosurfaces/pkg-descr
new file mode 100644
index 000000000000..d1ca20c3de04
--- /dev/null
+++ b/math/py-isosurfaces/pkg-descr
@@ -0,0 +1,5 @@
+isosurfaces allows to construct isolines/isosurfaces of a 2D/3D scalar field
+defined by a function, i.e. curves over which f(x,y)=0 or surfaces over which
+f(x,y,z)=0. Most similar libraries use marching squares or similar over a
+uniform grid, but this uses a quadtree to avoid wasting time sampling many far
+from the implicit surface.