imgsym
Detect symmetry axes in images and score mirror symmetry along them. imgsym implements multiple algorithms from symmetry research — 4 detectors and 13 scorers — behind one small API, and benchmarks them head-to-head.
Installation
pip install imgsym
This covers the classical scoring methods and the nxc / xfeatures / r_lip
detectors. Some methods pull optional extras:
pip install "imgsym[deep]" # alexnet, deep_features
pip install "imgsym[wavelets]" # wavelets detector
pip install "imgsym[all]" # everything (+ numba speedups)
Quick start
import cv2
import imgsym
# find symmetry axes: (angles, midpoints, lengths, strengths), strongest first
img = cv2.imread("assets/butterfly.jpg")
bf_axes = imgsym.detect_axes(img, detector="nxc")
vis1 = imgsym.visualize(img, bf_axes)
# score mirror symmetry along an axis; higher = more symmetric
axis = imgsym.get_axis(bf_axes, 0) # strongest detected axis
imgsym.score_along_axis(img, axis, method="hog") # 0.89
# binary shape masks have their own detector
mask = cv2.imread("assets/synthetic_star.png", cv2.IMREAD_GRAYSCALE)
star_axes = imgsym.detect_axes_from_mask(mask, detector="r_lip", mode="multi", threshold=0.9)
vis2 = imgsym.visualize(cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR), star_axes)
natural image — nxc |
binary shape mask — r_lip |
demo.ipynb reproduces every figure on this page and has more to
play with.
Choosing a method
Detectors
Natural images — imgsym.detect_axes(image, detector=...)
Detection accuracy is competition-protocol max-F (a detected axis counts if
its angle is within 10° of ground truth and its center lies on the true
segment), measured on three annotated benchmarks with
scripts/eval_detection_competition.py:
| detector | symComp17 | NYU | symComp13 | speed (s/img) | source |
|---|---|---|---|---|---|
nxc |
0.67 | 0.81 | 0.80 | ~7 | Cicconet et al. (2017) |
xfeatures |
0.59 | 0.74 | 0.57 | 0.05–0.2 | Loy & Eklundh (2006) |
wavelets |
0.46 | 0.53 | 0.49 | ~0.15 | Elawady et al. (2017) |
nxc leads on every benchmark — use it when accuracy matters; xfeatures is
~25× faster and the solid middle ground; wavelets trails clearly.
Binary shapes — imgsym.detect_axes_from_mask(mask, detector=...)
A different input domain, so not comparable above:
| detector | speed (s/img) | source |
|---|---|---|
r_lip |
<0.1 | Nguyen et al. (2022) |
Scorers
Numbers, not guesswork: skill is single-axis discrimination skill (2·AUC−1; 1 = perfect, 0 = chance) averaged over the four datasets of our benchmark (see Benchmark); speed is the median scoring time per crop on CPU. Scores are comparable within a method, not across methods.
Three methods share the top and are statistically close — deep_features
(0.83), alexnet (0.81), hog (0.80). hog runs at 0.45 ms, ~20× faster
than alexnet and ~370× faster than deep_features, which makes it the
default choice; reach for deep_features when accuracy matters more than
speed. Below the top tier there is a clear gap to gabor and the remaining
classical measures.
| scoring method | skill | speed (ms) | source |
|---|---|---|---|
deep_features |
0.83 | 169 | Brachmann & Redies (2016), generalized to modern backbones |
alexnet |
0.81 | 9.9 | Brachmann & Redies (2016) |
hog |
0.80 | 0.45 | Renero-C. et al. (2017) |
gabor |
0.71 | 14.9 | Shaker & Monadjemi (2015) |
gradient |
0.67 | 0.72 | Gnutti, Guerrini & Leonardi (2021) |
multi_scale_gradient |
0.65 | 0.36 | multi-scale extension of the gradient measure |
local_global |
0.65 | 7.8 | Hogeweg et al. (2017), "PatchNN" |
sliding_window |
0.65 | 0.07 | windowed variant of pixel correlation |
pixel_correlation |
0.64 | 0.06 | Mayer & Landwehr (2018) |
phog |
0.63 | 0.85 | Renero-C. et al. (2017) |
dct |
0.61 | 0.15 | Gunlu & Bilge (2009) |
weighted_binary |
0.58 | 0.05 | Bauerly & Liu (2006) |
eros |
0.27 | 1.1 | Smith & Jenkinson (1999) |
Full citations live in each method's docstring (imgsym/scoring/calculators.py).
Where is it symmetric? (PatchNN heatmap)
The local_global scoring method matches every patch to its best mirror-twin
on the other side of the axis. calculate_heatmap returns the per-patch match
quality as a map about the vertical center column (aligned below is the
image rotated so the detected axis is that center column — see the notebook).
calc = imgsym.SymmetryCalculatorFactory.create("local_global")
score, heatmap = calc.calculate_heatmap(aligned) # (H, W) map of mirror-match quality
Blue = high local symmetry, red = low: the mirrored wing bands read blue, while the gravel background is genuinely asymmetric and reads warm. (Butterfly photo from Wikimedia Commons.)
Benchmark
The skill numbers above come from the symmetry-scoring benchmark that ships in
scripts/: every method ranks true symmetry axes against perturbed negatives
on four annotated datasets (~3900 axis units), with derived results
(leaderboards, ablations, timings) tracked under results/. The raw per-image
score JSONs are not tracked — they regenerate deterministically (fixed seeds)
from the datasets, whose sources and expected directory layout are documented
in data/README.md:
python scripts/run_discrimination.py # single-axis protocol
python scripts/run_discrimination.py --protocol multi # multi-axis protocol
The full protocol and analysis are described in the accompanying paper.
License
MIT — see LICENSE.
Metadata
Release files for imgsym 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| imgsym-0.1.0.tar.gz | 70.7 kB | Without provenance | Details for imgsym-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| imgsym-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for imgsym-0.1.0-py3-none-any.whl |
Total release size: 141.8 kB
Release files / imgsym-0.1.0.tar.gz
| Download URL | imgsym-0.1.0.tar.gz |
|---|---|
| Size | 70.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
569c8a8a4dabf474618258dbf8143df30ccc1e964446bb6b0fb50ec3be4d7ff1
|
|
BLAKE2b-256 checksum How to use checksums |
8feda45261c3528f6dd6826cc7c861a84875b4789ee5fb7a22d00a2f55a06125
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / imgsym-0.1.0-py3-none-any.whl
| Download URL | imgsym-0.1.0-py3-none-any.whl |
|---|---|
| Size | 71.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f98194596a6729e2876635d2b1a612054b69c28fb2713375cef85c67e73f27fb
|
|
BLAKE2b-256 checksum How to use checksums |
4908f28f41dec8a9fc70e3af49157196f299ea41d780cc3aee464e6af3a0c8c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Security for imgsym 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.