Working rules for Claude Code (and any human collaborator) when editing this package. The figureextract meta-repo's
ARCHITECTURE.mdandDECISIONS.mddescribe the wider ecosystem; this file is the per-package complement.
thinr provides binary image thinning (skeletonization) algorithms — Zhang-Suen, Guo-Hall, Lee (2-D), K3M, the parallel form commonly attributed to Hilditch, OPTA / SPTA, and Holt — behind a single dispatching API. Also provides the medial axis transform (Blum 1967) and a fast distance transform (Felzenszwalb-Huttenlocher 2012; classic two-pass sweep). EBImage provides binary morphology but no thinning operator, so thinr complements it rather than replacing any of its functions. Per ADR-007 this is the one public CRAN package in the figureextract ecosystem; LGPL-3 is chosen for EBImage compatibility.
- Slice: 0 — Infrastructure
- Version: 0.3.0.9000 (as of 2026-07-28, from
DESCRIPTION) — a development version on top of the published CRAN release 0.3.0. The.9000suffix was added because 0.3.0 is live on CRAN: before the bump, a localrcmdcheck(args = "--as-cran")reported "Insufficient package version (submitted: 0.3.0, existing: 0.3.0)" from the live CRAN incoming-feasibility check — correct behaviour for a repeat submission of a published version, but a confusing WARNING for anyone checking locally. Bump the version again (to0.4.0or0.3.1) at the next CRAN submission. - Status: Published CRAN release (0.3.0) plus unreleased development work. Seven thinning algorithms fully implemented in Rcpp (as of 2026-07-28:
zhang_suen,guo_hall,lee,k3m,hilditch,opta,holt— thetests/testthat/test-thin.Rmethodsvector), all verified against original papers (or the Lam-Lee-Suen 1992 survey for Hilditch's parallel form). Medial axis and distance transform shipped as standalone exported utilities. Tests pass (510 assertions,devtools::test()); lintr clean. GitHub Actions: R-CMD-check (matrix), pkgdown, test-coverage, lint, pr-commands. - Recent shipments:
- thinr 0.3.0.9000 — F018 kernel de-duplication (2026-07-28) —
src/zhang_suen.cpp,src/lee.cpp, andsrc/holt.cppnow#include "thinr_common.h"and callthinr::crossing_number()/thinr::neighbour_count()instead of re-implementing them inline, joiningsrc/hilditch.cpp. The de-duplication is the gate: before it, mutating the shared definition inthinr_common.hchanged onlyhilditch's output; after it, the same mutation reacheszhang_suen,lee,holt, andhilditch— so a future correctness fix to the shared helpers can no longer silently miss three of the four kernels that need it. Behaviour-identical, proven rather than argued: the pre- and post-change builds were installed into separate libraries and compared over 257 binary images × 7 methods × 5max_itervalues × {thin(), raw kernel} plusmedial_axis()and all 3distance_transform()metrics — 19,172 result slots, allidentical(), same serialisation MD5.guo_hall.cppandk3m.cppare deliberately not included: they implement different connectivity criteria (Guo-Hall'sC(p)/N1/N2, K3M's lookup-table weights), not the Zhang-Suen crossing number. - thinr 0.3.0 — 2026-07-03 ecosystem review fix wave (as of 2026-07-28; see
NEWS.mdandREVIEW-2026-07-28.mdfor detail) — F012 (edge/border pixels now thinned via zero-pad-then-crop, all 7 methods), F011 (OPTA no longer disconnects a 2px-thick stroke via sequential per-direction sub-iterations), F015 (distance_transform()returnsInf, not a finite sentinel, on an all-foreground image, formanhattan/chessboardmatchingeuclidean), F016 (NArejected with a clear error at theas_binary_matrix()coercion boundary instead of silently becomingINT_MIN), F014 (Hilditch look-ahead junction condition corrected to the published parallel form), F013 (added a connectivity-preservation property test across all 7 methods), F017 (documented + pinned the isolated-2x2-block method-dependent survival), and removedthinImage()entirely (it had described itself as anEBImage::thinImage()-equivalent, a function that has never existed inEBImage;thin()is now the sole entry point) . F018 (zhang_suen.cpp/lee.cpp/holt.cppduplicating the sharedthinr::crossing_number()/neighbour_count()) was confirmed still present by the 2026-07-28 review and deliberately held back from that documentation branch; it shipped separately as 0.3.0.9000 — see the entry above andREVIEW-2026-07-28.md. - R-side coverage to its practical maximum (2026-05-21, PR #2,
1870e25) — added tests covering the remaining branches in thethin()dispatcher and the storage-restoration helpers. - release-prep merged (2026-05-20, PR #1,
98f4a80) — the CRAN-prep branch (with all reviewer-feedback fixes, the K3M / OPTA / Holt verifications, the Stentiford / Pavlidis drop, and pkgdown reference-index fix) landed onmain. - Dropped
stentiford(folk misattribution; the 1983 paper is preprocessing not thinning) andpavlidis(the implementation didn't match the contour-following algorithm of the 1980 paper). The dropped algorithms are not implemented in any major image-processing library (scikit-image, OpenCV, MATLAB, ImageJ, mahotas); per the package's "widely used elsewhere" inclusion criterion, removing them keeps the package focused. (2026-05-20) - Algorithm verification pass against papers in
references/: K3M lookup tables corrected against Saeed et al. 2010; OPTA rewritten per survey's safe-point expression; Holt rewritten per the original CACM paper (correcting a survey transcription error in the middle clause). (2026-05-20) - Tier-1 + Tier-2 algorithm expansion: Hilditch, OPTA, Holt; plus
medial_axis()anddistance_transform(). (2026-05-16) - CRAN reviewer feedback addressed (function-name quotes + DOIs). (2026-05-16)
- v0.2.0 stub-replacement: Lee 2-D and K3M fully implemented. (2026-05-16)
- v0.1.0 skeleton with Rcpp setup, 2 algorithms, vignette, README, NEWS, GitHub Actions CI, pkgdown. (2026-05-16)
- thinr 0.3.0.9000 — F018 kernel de-duplication (2026-07-28) —
- Style: tidyverse style guide.
styler::style_pkg()clean.lintr::lint_package()clean per.lintr(line length 100, snake_case + CamelCase, no commented-code linter). - C++: ISO C++17 baseline (Rcpp default for current R). Two-space indent. No
using namespace std. Avoid Rcpp sugar in tight loops where it costs measurable performance. - R version: ≥ 4.2.
- Pipe: native
|>preferred. - Imports: Rcpp only. Don't add dependencies without a strong reason — this is a small, focused, CRAN-friendly package.
- Framework:
testthat3rd edition with thedescribe/itBDD style (legible test reports for a public package). - Coverage: every algorithm in
methods <- c(...)(currently seven, as of 2026-07-28, fromtests/testthat/test-thin.R—stentiford/pavlidiswere dropped 2026-05-20, see below) is exercised against the same property suite (solid square thins, line collapse, idempotence, empty input, isolated-pixel preservation, ring topology, connectivity-preservation, edge-handling). New methods are added to the vector and inherit all tests automatically. distance_transformandmedial_axiseach have their own test files (tests/testthat/test-distance-transform.R,test-medial-axis.R).- Acceptance:
rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning")locally reports 0 errors and 0 warnings (verified 2026-07-28 on0.3.0.9000). Two NOTEs are expected and acceptable: the Ubuntu system compilation-flags note (-mno-omit-leaf-frame-pointer, from the R installation, not from this package) and CRAN incoming feasibility's "Version contains large components" for the.9000development suffix. ANon-standard file/directory found at top levelNOTE means a new root-level doc needs a.Rbuildignoreentry — add it rather than accepting the NOTE. The GitHub ActionsR-CMD-check.yamlgate is the authoritative signal and is green onmain. - Checking from a
git worktree:R CMD buildexcludes a.gitdirectory but not the.gitfile a worktree uses, so--as-cranused to emit a spurious "hidden files and directories: .git" NOTE from a worktree that a normal clone never showed..Rbuildignorenow carries^\.git$so worktree checks match clone checks.
C++ sources in src/:
thinr_common.h— shared inline helpers (crossing_number,neighbour_count).zhang_suen.cpp— Zhang & Suen (1984).guo_hall.cpp— Guo & Hall (1989).lee.cpp— Lee, Kashyap & Chu (1994), 2-D adaptation.k3m.cpp— Saeed et al. (2010); paper lookup tables reproduced verbatim.hilditch.cpp— parallel form commonly attributed to Hilditch (1969); the actual implementation follows the Rutovitz-style R1–R4 conditions as documented in Lam, Lee & Suen (1992).opta.cpp— Naccache & Shinghal (1984), Safe Point Thinning Algorithm; boolean safe-point expression follows the survey.holt.cpp— Holt, Stewart, Clint & Perrott (1987); condition H follows the original CACM paper (a survey transcription error was caught and corrected against the original).distance_transform.h+distance_transform.cpp— Felzenszwalb-Huttenlocher 2012 squared Euclidean + Rosenfeld-Pfaltz two-pass sweep for L1 and L∞.medial_axis.cpp— ridge detection on the squared Euclidean DT.RcppExports.cpp— auto-generated; do not edit (regenerated byRcpp::compileAttributes()).
R sources in R/:
thin.R—thin()dispatching function and theas_binary_matrix()/restore_storage()coercion helpers.distance_transform.R— exported wrapper for.distance_transform_cpp.medial_axis.R— exported wrapper for.medial_axis_cpp.thinr-package.R— package-level Roxygen doc.RcppExports.R— auto-generated; do not edit.
- Exported:
thin(),medial_axis(),distance_transform(). - Internal:
as_binary_matrix(),restore_storage()(helpers; not exported).
To add a new thinning algorithm:
- Add
src/<algorithm>.cppexporting.<algorithm>_cpp(IntegerMatrix, int). Use the helpers inthinr_common.h. - Run
Rcpp::compileAttributes(".")soR/RcppExports.Ris regenerated. - Add the method name to the
match.arglist and theswitch()inR/thin.R. - Add the method to the
methodsvector at the top oftests/testthat/test-thin.R— all property tests apply automatically. - Update
NEWS.md, the algorithms table inREADME.md, the algorithms section inR/thinr-package.R, and the algorithms table invignettes/choosing-a-method.Rmd. - Add the published reference to
DESCRIPTIONDescription field if it has a DOI.
CRAN package documentation conventions:
- Every exported function has Roxygen
@param,@return,@examples. - Examples are runnable (
R CMD checkruns them with--run-donttest). - The vignette is non-trivial; it shows a real comparison of algorithms.
- The README has install instructions and a usage snippet.
- NEWS.md follows the standard "version - bullet list" format.
cran-comments.mdis updated on every CRAN submission.
R CMD check,devtools::check(),devtools::test(),devtools::document().Rcpp::compileAttributes(),roxygen2::roxygenize().lintr::lint_package(),styler::style_pkg(),covr::package_coverage().pkgdown::build_site(),usethis::use_*()helpers that only edit local files.- Reading any file;
ls,git status,git log,git diff.
- Adding / removing dependencies (this package wants to stay small).
- Renaming / removing exported functions (breaking change).
- Bumping the major version.
- Modifying CI configuration.
- Any
gitcommit, tag, branch, or push. - Submitting to CRAN (
devtools::submit_cran(),devtools::release()). - Modifying the LGPL-3 license declaration.
- Force-push.
- Commit secrets.
- Modify git config.
- Skip git hooks.
- Destructive resets without per-instance approval.
- Multi-step work: propose a plan, wait for explicit approval.
- Surface suggestions; don't implement without approval.
- Convert relative dates to absolute when committing or writing docs.
LGPL-3, per ADR-007 and ADR-017. Public CRAN release.
- The LGPL-3 license is the same as EBImage's, so EBImage can depend on
thinrwithout relicensing concerns. - Source files do not carry per-file copyright headers (CRAN convention); the LGPL-3 text is included by R's standard license machinery via
License: LGPL-3inDESCRIPTION. - Contributors retain copyright in their contributions, licensed under LGPL-3.