Summary
Bermuda panics while triangulating a valid Napari polygon-with-holes path.
The panic occurs in face_triangulation.rs at an unchecked Option::unwrap(). The issue reproduces by calling Bermuda directly, without Napari being involved.
Reproducer
The attached file, create_holes_triangulation_failure.txt, contains the exact (570, 2) float32 array passed to Bermuda.
from pathlib import Path
import bermuda
import numpy as np
vertices = np.loadtxt(
Path("create_holes_triangulation_failure.txt"),
dtype=np.float32,
)
assert vertices.shape == (570, 2)
assert np.isfinite(vertices).all()
bermuda.triangulate_polygons_face([vertices])
The same panic also occurs through:
bermuda.triangulate_polygons_with_edge([vertices])
Observed result
thread '<unnamed>' panicked at crates/triangulation/src/face_triangulation.rs:531:60:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Traceback (most recent call last):
File "<stdin>", line 10, in <module>
pyo3_runtime.PanicException: called `Option::unwrap()` on a `None` value
Expected result
The valid polygon should triangulate successfully.
At minimum, if Bermuda considers the input unsupported, the Python API should return a regular, structured exception rather than allowing the Rust implementation to panic.
Input topology
The array uses Napari's single-path encoding for a polygon with two holes:
- indices
0:546: exterior ring, including closure;
- indices
546:557: first hole, including closure;
- index
557: repeated exterior anchor separating the holes;
- indices
558:569: second hole, including closure;
- index
569: repeated exterior anchor/final closure.
Coordinates are in Napari (y, x) order.
The fixture has:
- no non-finite values;
- no consecutive duplicate coordinates;
- closed exterior and interior rings;
- a valid, non-empty decoded Shapely polygon.
The decoded geometry can be validated with:
from shapely.geometry import Polygon
exterior = vertices[:546, ::-1]
hole_1 = vertices[546:557, ::-1]
hole_2 = vertices[558:569, ::-1]
polygon = Polygon(exterior, [hole_1, hole_2])
assert polygon.is_valid
assert not polygon.is_empty
Additional diagnostics:
Exterior area: 140075382.17486215
First-hole area: 277656.0446577072
Second-hole area: 377113.79895591736
Final polygon area: 139420612.33124852
Minimum clearance: 17.5421233974749
Distance between holes: 630.1430630856535
Both holes are strictly contained by the exterior.
Failure isolation
The problem appears to be inside face triangulation:
- the exterior alone succeeds;
- each hole alone succeeds;
- the exterior with the second hole succeeds;
- the exterior with the first hole panics;
- the exterior with both holes panics, independently of the order of the holes.
The result is also sensitive to geometrically equivalent representations:
- cyclically changing the first vertex of the first hole can make the panic disappear;
- translating the coordinates can change the result;
- scaling the coordinates can change the result.
This may indicate sensitivity to floating-point comparisons or sweep-line event ordering, but I have not investigated the Rust implementation sufficiently to identify the appropriate fix.
Environment
Bermuda: 0.1.7
Napari: 0.7.1
NumPy: 2.3.5
Shapely: 2.1.2
Python: 3.13.11
OS: macOS 26.4.1
Arch: arm64
Fixture integrity
File SHA-256:
7f09c0043050e518052e52736fd68d7cd400611697675b1e3dc6c4977f4639d2
Loaded float32 array byte SHA-256:
21df8806d99580c208b26e075297fdbefeac4ce8f134ac370328203af55975ec
create_holes_triangulation_failure.txt
Summary
Bermuda panics while triangulating a valid Napari polygon-with-holes path.
The panic occurs in
face_triangulation.rsat an uncheckedOption::unwrap(). The issue reproduces by calling Bermuda directly, without Napari being involved.Reproducer
The attached file,
create_holes_triangulation_failure.txt, contains the exact(570, 2)float32array passed to Bermuda.The same panic also occurs through:
Observed result
Expected result
The valid polygon should triangulate successfully.
At minimum, if Bermuda considers the input unsupported, the Python API should return a regular, structured exception rather than allowing the Rust implementation to panic.
Input topology
The array uses Napari's single-path encoding for a polygon with two holes:
0:546: exterior ring, including closure;546:557: first hole, including closure;557: repeated exterior anchor separating the holes;558:569: second hole, including closure;569: repeated exterior anchor/final closure.Coordinates are in Napari
(y, x)order.The fixture has:
The decoded geometry can be validated with:
Additional diagnostics:
Both holes are strictly contained by the exterior.
Failure isolation
The problem appears to be inside face triangulation:
The result is also sensitive to geometrically equivalent representations:
This may indicate sensitivity to floating-point comparisons or sweep-line event ordering, but I have not investigated the Rust implementation sufficiently to identify the appropriate fix.
Environment
Fixture integrity
create_holes_triangulation_failure.txt