Topology-Preserving Smoothing - Python Implementation
A Python implementation of the Alternate Sequential Filter controlled by Topology (ASFT) algorithm for smoothing binary images while preserving their topology (number of connected components and holes).
Example: Portrait
| Input | Output |
|---|---|
The algorithm smooths jagged boundaries while preserving topology - all connected components and holes from the original image are maintained.
More Examples
Example 1: Pixelized stains #1
| Input | Output |
|---|---|
Example 2: Pixelized stains #2
| Input | Output |
|---|---|
Example 3: QR-code (Connectivity Comparison)
| Input | Output (C8) | Output (C4) |
|---|---|---|
Example 4: Flying heron
| Input | Output |
|---|---|
Example 5: Trees
| Input | Output |
|---|---|
Example 6: Einstein with a pipe
| Original | Smoothed (ASFT-MED) | Smoothed (ASFT) |
|---|---|---|
Smoothed with: -s 1 -r 3 -c 4 --medial for ASFT-MED and with the same parameters for the pure ASFT.
Reference
Based on the paper:
M. Couprie and G. Bertrand: "Topology preserving alternating sequential filter for smoothing 2D and 3D objects", Journal of Electronic Imaging, Vol. 13, No. 4, pp. 720-730, 2004.
See also the dedicated web-page:
Installation
From PyPI
# Recommended installation (includes numba for 10-100x speedup)
pip install toposmooth[fast]
# If you have issues installing numba (e.g., unsupported platform):
pip install toposmooth
From Source
# Clone the repository
git clone https://github.com/vyastrebov/toposmooth.git
cd toposmooth
# Recommended: install with numba
pip install -e ".[fast]"
# Without numba (if installation fails):
pip install -e .
Required packages
- numpy
- scipy
- Pillow
- numba (included with
[fast], provides 10-100x speedup)
Supported Image Formats
The algorithm supports any bitmap format readable by Pillow:
- PNG (
.png) - recommended for lossless output - JPEG (
.jpg,.jpeg) - input only (lossy compression not ideal for binary output) - PGM (
.pgm) - portable graymap, matches C implementation - BMP (
.bmp) - TIFF (
.tiff,.tif) - And many others...
The output format is automatically determined by the file extension.
Usage
Command Line
After installation, you can use the toposmooth command or run as a Python module:
# Using the installed command
toposmooth input.png output.png
# Or using Python module syntax
python -m toposmooth input.png output.png
# Match C asftmed command: ./asftmed input.pgm 4 3 output.pgm
toposmooth input.pgm output.pgm -s 1 -r 3 -c 4
# With all options
toposmooth input.jpg output.png \
-s 1 # Scale factor (default: 4, use 1 to match input dimensions)
-r 5 # Maximum smoothing radius (default: 5)
-c 4 # Connectivity for white value (1): 4 (neighbor) or 8 (next-neighbour)
-t 128 # Binarization threshold (default: mean intensity, if the input is not binary)
--medial # Use medial axis constraints - strictly preserves the form (default, recommended)
--no-medial # Use plain ASFT without medial axis constraints (removes small features)
--save-binary binary.png # Save the binarized input image
# Show version
toposmooth --version
Python API
from toposmooth import topology_preserving_smooth, load_image, asftmed
from PIL import Image
import numpy as np
# Load and process an image
img = load_image('input.png')
smoothed, binary = topology_preserving_smooth(
img,
scale=1, # Scale factor (1 = no scaling, matches C)
smooth_radius=5, # Maximum ASFT radius
connex=4, # Object connectivity (4 or 8)
threshold=None, # Binarization threshold (None = mean)
use_medial=True # Use medial axis constraints (recommended)
)
# Save result in any format
Image.fromarray(smoothed).save('output.png') # PNG
Image.fromarray(smoothed).save('output.jpg') # JPEG
Image.fromarray(smoothed).save('output.pgm') # PGM
Parameters
-
scale: Integer scaling factor. The image is upscaled before smoothing to allow sub-pixel smoothing with respect to original pixel size. Use
scale=1to match the original image dimensions. -
smooth_radius (rmax): Maximum radius of disk structuring elements. The algorithm applies opening/closing operations with disks of radius 1, 2, ..., rmax. Larger values produce smoother results but take longer (linear complexity with rmax), but the default value rmax=5 produces already very good results.
-
connex: Object connectivity (4 or 8). For 8-connectivity, diagonal neighbors (white pixels, value=1) are considered connected.
-
threshold: Binarization threshold. Pixels above this value become foreground (255), others become background (0). If None, uses the mean intensity.
-
use_medial / --medial: When enabled (default), uses medial axis constraints to preserve thin features, recommended.
Algorithms
ASFT-MED (default, --medial)
Uses medial axes of both foreground and background as constraints. This prevents:
- Thin features from being eroded away
- Small gaps from being filled in
Plain ASFT (--no-medial)
Standard ASFT without constraints. May lose very thin features.
How It Works
-
Binarization: The input image is converted to binary using the threshold.
-
Scaling (optional): The binary image is upscaled by the scale factor.
-
Medial Axis Computation (ASFT-MED only):
- Compute medial axis of foreground (skeleton)
- Compute medial axis of background (inverse skeleton)
-
ASFT: For each radius r from 1 to rmax:
- Apply homotopic pseudo-closing (fills small holes while preserving topology)
- Apply homotopic pseudo-opening (removes small protrusions while preserving topology)
- With medial axis constraints, these operations are bounded to preserve thin structures
-
The result is a smoothed binary image with the same topology as the input.
Performance
- With numba (
pip install toposmooth[fast]): ~1-2 seconds for a 520×372 image with scale=1, radius=3 - Without numba: Much slower (minutes instead of seconds) - only use if numba won't install
- The algorithm scales roughly as O(scale² × rmax × width × height)
- First run with numba may take a few seconds for JIT compilation
Validation
The Python implementation produces pixel-perfect results matching the C implementation for Einstein's binary portrait.
# C version
./asftmed test/einstein.pgm 4 3 c_output.pgm
# Python version
toposmooth test/einstein.pgm py_output.pgm -s 1 -r 3 -c 4
Test Shapes
Run python test_shapes.py to generate comparison panels for various test shapes:
- Rectangle - Grid-aligned rectangle
- Square - Grid-aligned square
- L-Shape - Grid-aligned L shape
- Squares Edge Touch - Two squares sharing a full edge (1 component for both connectivities)
- Squares Single Point Touch - Two squares touching at exactly one 4-neighbor pixel (1 component for both)
- Squares Diagonal Touch - Two squares touching only at diagonal (2 components for 4-conn, 1 for 8-conn)
- Rotated Square 30° - Square rotated by 30 degrees
- Rotated Square 45° - Square rotated by 45 degrees
Each test shows the original shape and results for:
- Connectivity 4 and 8
- ASFT and ASFT-MED algorithms
- Scale factors 1 and 4
Results are saved to test_results/ folder, including all_tests_overview.png with all shapes combined.
Sample Test Result
Information
- Authors: Claude Opus 4.5 in Cursor environment (mainly), Vladislav A. Yastrebov (marginally)
- Validator: Vladislav A. Yastrebov (CNRS, Mines Paris)
- License: GPL-2.0
- Date: Nov-Dec 2025
Metadata
Release files for toposmooth 1.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| toposmooth-1.0.3.tar.gz | 22.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| toposmooth-1.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.0 kB
Release files / toposmooth-1.0.3.tar.gz
| Download URL | toposmooth-1.0.3.tar.gz |
|---|---|
| Size | 22.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
38c1b72c21e76ac19ddd7d31c78178302277f7980dceb8c74180f70b9e39764f
|
|
BLAKE2b-256 checksum How to use checksums |
c754ee8134b495c8cca604ea947601aa38d5ebf093eff77124424d160f3948ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.2
|
Release files / toposmooth-1.0.3-py3-none-any.whl
| Download URL | toposmooth-1.0.3-py3-none-any.whl |
|---|---|
| Size | 19.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c7f1c35416e5cdd52b6be8e512208a37a3723e45d73659c5ba0a583487fab4df
|
|
BLAKE2b-256 checksum How to use checksums |
df8de51bd40ae5b45dfec69e06a56e5c8a60a30dcaca40afc018f71d1b666b05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.2
|