Correctness fixes from the figureextract ecosystem review (2026-07-03).
All are boundary conditions the published algorithms assume away; see the
new vignette("correctness-properties") for the guarantees they restore.
thin() now skeletonises shapes that touch the matrix border correctly.
The kernels inspect an 8-neighbourhood and so never deleted pixels in the
outermost row or column, leaving edge-touching shapes two to three pixels
thick; thin() now pads the image with a one-pixel background margin and
crops it back, so a shape thins identically whether or not it touches the
frame. Applies to all seven methods (#F012).
thin(method = "opta") now keeps two-pixel-wide strokes connected. The
SPTA kernel evaluated all four contour directions against the pre-cycle
snapshot and deleted them in one batch, so both sides of a 2px stroke were
removed together and the skeleton fragmented (a 2×9 bar collapsed to its
four corner pixels). Deletion is now sequential per direction, as
Naccache & Shinghal's two-scan formulation requires (#F011).
distance_transform(metric = "manhattan" | "chessboard") now returns
Inf for a fully-foreground image (no background pixel exists), matching
the "euclidean" metric and EBImage::distmap(). The two-pass metrics
previously seeded foreground with a finite sentinel that leaked out as a
plausible-looking but meaningless finite distance (#F015).
thin(), distance_transform(), and medial_axis() now reject NA
input with a clear error at the coercion boundary instead of silently
turning it into NA_integer_ (INT_MIN) in the C++ kernels (#F016).
thin(method = "hilditch") now thins junctions to the published parallel
form. The look-ahead conditions 3 and 4 skip deleting a pixel only when a
cardinal neighbour has crossing number A == 1 on the current image; the
kernel had compared the look-ahead crossing number (computed with the
centre already removed) against 1, which is strictly stronger and also
spared junction neighbours where A >= 2, leaving a redundant pixel beside
the junction. Skeletons are now equal to or thinner than before, never
thicker (verified against a reference implementation of the published form
over random images) (#F014).
Added a connectivity-preservation property test across all seven methods and tightened the Holt straight-line test (#F013).
Documented and pinned the isolated-2×2-block behaviour: the default
zhang_suen erases an isolated 2×2 block entirely while guo_hall keeps
one pixel. See vignette("choosing-a-method") for guidance on small-blob
masks (#F017).
Removed thinImage(). Use thin() (Zhang-Suen is the default method).
Initial CRAN release.