smoothify 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. {smoothify-0.3.2 → smoothify-0.3.3}/.github/workflows/ci.yml +5 -2
  2. {smoothify-0.3.2 → smoothify-0.3.3}/CHANGELOG.md +9 -0
  3. {smoothify-0.3.2/smoothify.egg-info → smoothify-0.3.3}/PKG-INFO +14 -15
  4. {smoothify-0.3.2 → smoothify-0.3.3}/README.md +13 -14
  5. {smoothify-0.3.2 → smoothify-0.3.3}/examples/Water_Smoothed.gpkg +0 -0
  6. smoothify-0.3.3/examples/merge_holes_examples.ipynb +388 -0
  7. smoothify-0.3.3/examples/real_world_water_example.ipynb +773 -0
  8. smoothify-0.3.3/examples/smoothify_vs_shapely_comparison.ipynb +683 -0
  9. smoothify-0.3.3/examples/usage_examples.ipynb +839 -0
  10. {smoothify-0.3.2 → smoothify-0.3.3}/images/generate_pipeline_graphic.py +32 -22
  11. smoothify-0.3.3/images/pipeline_steps.png +0 -0
  12. {smoothify-0.3.2 → smoothify-0.3.3}/pyproject.toml +2 -1
  13. {smoothify-0.3.2 → smoothify-0.3.3}/pytest.ini +2 -0
  14. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify/_version.py +3 -3
  15. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify/smoothify_core.py +177 -63
  16. {smoothify-0.3.2 → smoothify-0.3.3/smoothify.egg-info}/PKG-INFO +14 -15
  17. smoothify-0.3.3/smoothify.egg-info/scm_version.json +8 -0
  18. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_smoothify_core.py +48 -0
  19. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_water_quality_sweep.py +11 -5
  20. smoothify-0.3.2/examples/merge_holes_examples.ipynb +0 -388
  21. smoothify-0.3.2/examples/real_world_water_example.ipynb +0 -773
  22. smoothify-0.3.2/examples/smoothify_vs_shapely_comparison.ipynb +0 -683
  23. smoothify-0.3.2/examples/usage_examples.ipynb +0 -837
  24. smoothify-0.3.2/images/pipeline_steps.png +0 -0
  25. smoothify-0.3.2/smoothify.egg-info/scm_version.json +0 -8
  26. {smoothify-0.3.2 → smoothify-0.3.3}/.github/workflows/publish.yml +0 -0
  27. {smoothify-0.3.2 → smoothify-0.3.3}/.gitignore +0 -0
  28. {smoothify-0.3.2 → smoothify-0.3.3}/.pre-commit-config.yaml +0 -0
  29. {smoothify-0.3.2 → smoothify-0.3.3}/.python-version +0 -0
  30. {smoothify-0.3.2 → smoothify-0.3.3}/.vscode/settings.json +0 -0
  31. {smoothify-0.3.2 → smoothify-0.3.3}/LICENSE +0 -0
  32. {smoothify-0.3.2 → smoothify-0.3.3}/RELEASING.md +0 -0
  33. {smoothify-0.3.2 → smoothify-0.3.3}/benchmarks/bench_water.py +0 -0
  34. {smoothify-0.3.2 → smoothify-0.3.3}/examples/Water.gpkg +0 -0
  35. {smoothify-0.3.2 → smoothify-0.3.3}/fuzz/__init__.py +0 -0
  36. {smoothify-0.3.2 → smoothify-0.3.3}/fuzz/generators.py +0 -0
  37. {smoothify-0.3.2 → smoothify-0.3.3}/fuzz/oracle.py +0 -0
  38. {smoothify-0.3.2 → smoothify-0.3.3}/fuzz/runner.py +0 -0
  39. {smoothify-0.3.2 → smoothify-0.3.3}/images/example_1_polygon.png +0 -0
  40. {smoothify-0.3.2 → smoothify-0.3.3}/images/example_2_linestring.png +0 -0
  41. {smoothify-0.3.2 → smoothify-0.3.3}/images/example_3_iterations.png +0 -0
  42. {smoothify-0.3.2 → smoothify-0.3.3}/images/example_4_merging.png +0 -0
  43. {smoothify-0.3.2 → smoothify-0.3.3}/images/generate_example_images.ipynb +0 -0
  44. {smoothify-0.3.2 → smoothify-0.3.3}/images/generate_readme_image.ipynb +0 -0
  45. {smoothify-0.3.2 → smoothify-0.3.3}/images/smoothify_hero.png +0 -0
  46. {smoothify-0.3.2 → smoothify-0.3.3}/images/smoothify_logo.png +0 -0
  47. {smoothify-0.3.2 → smoothify-0.3.3}/scripts/fuzz_run.py +0 -0
  48. {smoothify-0.3.2 → smoothify-0.3.3}/scripts/fuzz_visualize.py +0 -0
  49. {smoothify-0.3.2 → smoothify-0.3.3}/setup.cfg +0 -0
  50. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify/__init__.py +0 -0
  51. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify/coordinator.py +0 -0
  52. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify/geometry_ops.py +0 -0
  53. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify/py.typed +0 -0
  54. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify.egg-info/SOURCES.txt +0 -0
  55. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify.egg-info/dependency_links.txt +0 -0
  56. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify.egg-info/requires.txt +0 -0
  57. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify.egg-info/scm_file_list.json +0 -0
  58. {smoothify-0.3.2 → smoothify-0.3.3}/smoothify.egg-info/top_level.txt +0 -0
  59. {smoothify-0.3.2 → smoothify-0.3.3}/tests/README.md +0 -0
  60. {smoothify-0.3.2 → smoothify-0.3.3}/tests/__init__.py +0 -0
  61. {smoothify-0.3.2 → smoothify-0.3.3}/tests/conftest.py +0 -0
  62. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_all_geometry_types.py +0 -0
  63. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_area_tolerance.py +0 -0
  64. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_auto_segment_length.py +0 -0
  65. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_chaikin.py +0 -0
  66. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_congruence_dedup.py +0 -0
  67. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_convexity_artifacts.py +0 -0
  68. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_corner_rounding.py +0 -0
  69. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_data/convex_pixel_rectangle.gpkg +0 -0
  70. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_data/naip_owm_water_bodies.geojson +0 -0
  71. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_edge_cases_coverage.py +0 -0
  72. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_fuzz.py +0 -0
  73. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_geometry_types.py +0 -0
  74. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_invalid_polygon.py +0 -0
  75. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_merge_holes.py +0 -0
  76. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_real_world_data.py +0 -0
  77. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_self_intersecting_variant.py +0 -0
  78. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_smoothify_api.py +0 -0
  79. {smoothify-0.3.2 → smoothify-0.3.3}/tests/test_synthetic_blob_fuzz.py +0 -0
  80. {smoothify-0.3.2 → smoothify-0.3.3}/tests/visual_tests.ipynb +0 -0
@@ -33,8 +33,11 @@ jobs:
33
33
  run: uv run mypy smoothify/
34
34
 
35
35
  - name: Test
36
- run: uv run pytest tests/ -x -q
36
+ # -n 2 overrides the local `-n auto` default: GitHub runners have 4
37
+ # vCPUs and some tests spawn smoothify's own worker pool, so a smaller
38
+ # fan-out avoids oversubscribing the runner.
39
+ run: uv run pytest tests/ -n 2 -x -q
37
40
 
38
41
  - name: Notebook smoke test
39
42
  if: matrix.python-version == '3.11'
40
- run: uv run pytest --nbmake examples/*.ipynb -q
43
+ run: uv run pytest --nbmake examples/*.ipynb -n 2 -q
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.3] - 2026-07-02
8
+
9
+ ### Changed
10
+ - Start-point variants are now merged by a per-point median consensus instead of a geometric union. Douglas-Peucker is start-vertex dependent, so the four rotated variants disagree slightly about where a feature's vertices land; the union superimposed those disagreements, whereas the median resamples the variants to a common point count, brings them into phase via an FFT cross-correlation, and takes the coordinate-wise median — a start-invariant consensus that outvotes a single bad variant instead of averaging toward it. This is also faster than the union it replaces (~1.3x end-to-end at the default 3 iterations, ~1.4x at 5, on `examples/Water.gpkg`). The median sits at the consensus interior rather than the outer envelope, so the merged ring is marginally smaller before area preservation restores it (with `preserve_area=True`); with `preserve_area=False` the small reduction is left as-is.
11
+ - Faster smoothing with no meaningful change to output. Start-point variants now begin at evenly spaced arc-length positions, computed by interpolating a single new start vertex, rather than rotating to a vertex index on a fully densified ring. The old path segmentized the whole ring only so that index-based rotation would land on evenly spaced points — but every vertex it added was collinear and immediately stripped by the following Douglas-Peucker. Feeding DP just the original vertices plus one cuts its input roughly 3.4x and the simplify step ~3.5x on `examples/Water.gpkg` (total single-core CPU work ~4.4s → ~2.7s, about 1.25x faster end-to-end at the default 3 iterations). The per-point median phase-alignment and Chaikin corner cutting also replace `numpy.roll` with a slice-based cyclic shift (bit-identical, fewer allocations). Output is unchanged within tolerance (per-polygon area drift ≤ 0.01%, worst concave turn unchanged).
12
+
13
+ ### Fixed
14
+ - Eliminated the residual "dimple" fold that appeared where the start-point variants disagreed about a shallow feature near the simplify tolerance. Their union superimposed the disagreement as a double peak with a valley that the area-preservation buffer then sharpened into a fold slipping under the concave-turn seal threshold. The per-point median consensus (see Changed) resolves the variants to one boundary instead of overlaying them, removing the artifact (raster-noise fuzz harness: 4 → 0 residual folds; `examples/Water.gpkg`: 8 → 0).
15
+
7
16
  ## [0.3.2] - 2026-06-30
8
17
 
9
18
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smoothify
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Transform pixelated geometries from raster data into smooth natural looking features
5
5
  Author-email: Nick Wright <nicholas.wright@dpird.wa.gov.au>
6
6
  License-Expression: MIT
@@ -229,22 +229,21 @@ smoothed = smoothify(
229
229
 
230
230
  ## How It Works
231
231
 
232
- Smoothify uses an advanced multi-step smoothing pipeline:
232
+ Smoothify uses an advanced multi-step smoothing pipeline. The numbered steps below correspond to the panels in the figure:
233
233
 
234
234
  <p align="left">
235
235
  <img src="https://raw.githubusercontent.com/DPIRD-DMA/Smoothify/main/images/pipeline_steps.png" alt="Smoothify pipeline steps" width="800">
236
236
  </p>
237
237
 
238
238
 
239
- 1. Joins touching holes (for Polygons, when `merge_holes=True`) so they smooth as one opening
240
- 2. Adds intermediate vertices along line segments (segmentize)
241
- 3. Generates multiple rotated variants (for Polygons) to avoid artifacts
242
- 4. Simplifies each variant to remove noise
243
- 5. Applies Chaikin corner cutting to smooth
244
- 6. Merges all variants via union to eliminate start-point artifacts
245
- 7. Applies final smoothing pass
246
- 8. Optionally restores original area via buffering (for Polygons)
247
- 9. Detects and repairs any sharp folds left by features near the smoothing scale (e.g. one-pixel-wide arms), using a small morphological opening/closing bounded at `segment_length / 4`
239
+ 1. **Pixelated input** a polygon straight from raster-to-vector conversion, with a stair-stepped boundary
240
+ 2. **Multiple variants** (for Polygons) that start at evenly spaced arc-length positions, each simplified to strip staircase noise, so no artifact is tied to a fixed start vertex
241
+ 3. **Chaikin corner cutting** applied to each variant
242
+ 4. **Per-point median merge** a start-invariant consensus that resolves the variants' disagreements
243
+ 5. **Final smoothing pass** on the merged result
244
+ 6. **Restore original area** via buffering (for Polygons, when `preserve_area=True`)
245
+
246
+ Two steps are not shown in the figure: before step 2, touching holes are joined (for Polygons, when `merge_holes=True`) so they smooth as one opening; and after step 6, the result is checked for sharp concave folds left by features near the smoothing scale (e.g. one-pixel-wide arms) and repaired with a small morphological opening/closing bounded at `segment_length / 4`.
248
247
 
249
248
  ## Invalid Geometries
250
249
 
@@ -278,17 +277,17 @@ Smoothify uses [pytest](https://pytest.org/). After cloning the repository, inst
278
277
  # Install dependencies (including the dev group)
279
278
  uv sync
280
279
 
281
- # Run all tests
280
+ # Run all tests (parallelised across CPU cores by default via pytest-xdist)
282
281
  uv run pytest tests/
283
282
 
284
283
  # Run with coverage
285
284
  uv run pytest tests/ --cov=smoothify --cov-report=html
286
285
 
287
- # Run a single test
288
- uv run pytest tests/test_chaikin.py::TestChaikinCornerCutting::test_simple_square_polygon
286
+ # Run a single test (add `-n 0` to disable parallelism for clearer output)
287
+ uv run pytest tests/test_chaikin.py::TestChaikinCornerCutting::test_simple_square_polygon -n 0
289
288
  ```
290
289
 
291
- If you prefer not to use uv, install the dev dependencies into your environment and run `pytest tests/` directly.
290
+ The suite runs in parallel by default (`-n auto` in `pytest.ini`); pass `-n 0` to run serially when debugging. If you prefer not to use uv, install the dev dependencies into your environment and run `pytest tests/` directly.
292
291
 
293
292
  ## Contributing
294
293
 
@@ -196,22 +196,21 @@ smoothed = smoothify(
196
196
 
197
197
  ## How It Works
198
198
 
199
- Smoothify uses an advanced multi-step smoothing pipeline:
199
+ Smoothify uses an advanced multi-step smoothing pipeline. The numbered steps below correspond to the panels in the figure:
200
200
 
201
201
  <p align="left">
202
202
  <img src="https://raw.githubusercontent.com/DPIRD-DMA/Smoothify/main/images/pipeline_steps.png" alt="Smoothify pipeline steps" width="800">
203
203
  </p>
204
204
 
205
205
 
206
- 1. Joins touching holes (for Polygons, when `merge_holes=True`) so they smooth as one opening
207
- 2. Adds intermediate vertices along line segments (segmentize)
208
- 3. Generates multiple rotated variants (for Polygons) to avoid artifacts
209
- 4. Simplifies each variant to remove noise
210
- 5. Applies Chaikin corner cutting to smooth
211
- 6. Merges all variants via union to eliminate start-point artifacts
212
- 7. Applies final smoothing pass
213
- 8. Optionally restores original area via buffering (for Polygons)
214
- 9. Detects and repairs any sharp folds left by features near the smoothing scale (e.g. one-pixel-wide arms), using a small morphological opening/closing bounded at `segment_length / 4`
206
+ 1. **Pixelated input** a polygon straight from raster-to-vector conversion, with a stair-stepped boundary
207
+ 2. **Multiple variants** (for Polygons) that start at evenly spaced arc-length positions, each simplified to strip staircase noise, so no artifact is tied to a fixed start vertex
208
+ 3. **Chaikin corner cutting** applied to each variant
209
+ 4. **Per-point median merge** a start-invariant consensus that resolves the variants' disagreements
210
+ 5. **Final smoothing pass** on the merged result
211
+ 6. **Restore original area** via buffering (for Polygons, when `preserve_area=True`)
212
+
213
+ Two steps are not shown in the figure: before step 2, touching holes are joined (for Polygons, when `merge_holes=True`) so they smooth as one opening; and after step 6, the result is checked for sharp concave folds left by features near the smoothing scale (e.g. one-pixel-wide arms) and repaired with a small morphological opening/closing bounded at `segment_length / 4`.
215
214
 
216
215
  ## Invalid Geometries
217
216
 
@@ -245,17 +244,17 @@ Smoothify uses [pytest](https://pytest.org/). After cloning the repository, inst
245
244
  # Install dependencies (including the dev group)
246
245
  uv sync
247
246
 
248
- # Run all tests
247
+ # Run all tests (parallelised across CPU cores by default via pytest-xdist)
249
248
  uv run pytest tests/
250
249
 
251
250
  # Run with coverage
252
251
  uv run pytest tests/ --cov=smoothify --cov-report=html
253
252
 
254
- # Run a single test
255
- uv run pytest tests/test_chaikin.py::TestChaikinCornerCutting::test_simple_square_polygon
253
+ # Run a single test (add `-n 0` to disable parallelism for clearer output)
254
+ uv run pytest tests/test_chaikin.py::TestChaikinCornerCutting::test_simple_square_polygon -n 0
256
255
  ```
257
256
 
258
- If you prefer not to use uv, install the dev dependencies into your environment and run `pytest tests/` directly.
257
+ The suite runs in parallel by default (`-n auto` in `pytest.ini`); pass `-n 0` to run serially when debugging. If you prefer not to use uv, install the dev dependencies into your environment and run `pytest tests/` directly.
259
258
 
260
259
  ## Contributing
261
260