GrainMorph
GrainMorph analyzes a single 2D binary particle mask and returns geometric shape metrics.
| Metric | Meaning |
|---|---|
| Wadell roundness | Corner curvature-based roundness |
| Circularity | 4 * pi * area / perimeter^2 |
| Sphericity | Inscribed circle radius divided by enclosing circle radius |
| Aspect ratio | Fitted ellipse major axis divided by minor axis |
Roundness, circularity, and sphericity are clipped to 0..1. Aspect ratio is >= 1.
Installation
GrainMorph requires Python 3.12+.
pip install grainmorph
For local development:
pip install -e ".[dev]"
Run the tests and code checks with the same Python environment:
python -m pytest -q
python -m ruff check --isolated .
python -m ruff format --isolated --check src tests
Releasing
Pushing a vX.Y.Z tag publishes that version to PyPI through
.github/workflows/publish.yml, using PyPI trusted publishing (no API token):
- Set the same version in
pyproject.tomlandsrc/grainmorph/__init__.py. - Commit, then tag and push:
git tag -a vX.Y.Z -m "Release X.Y.Z" && git push origin main vX.Y.Z.
The workflow fails if the tag and the pyproject.toml version differ, then runs the tests before uploading.
PyPI must list this repository as a trusted publisher for grainmorph
(owner phykn, repository grainmorph, workflow publish.yml, environment pypi).
Quick Start
import grainmorph as gm
mask = gm.utils.create_circle_mask((256, 256), (128, 128), 60)
results = gm.analyze_mask(mask)
if results is None:
raise ValueError("mask has no foreground pixels")
print(results["roundness"]["val"])
print(results["circularity"]["val"])
print(results["sphericity"]["val"])
print(results["aspect_ratio"]["val"])
Visualize the results:
gm.utils.plot_analysis_results(mask, results)
API
gm.analyze_mask(
mask,
use_aspect_ratio=True,
use_roundness=True,
use_circularity=True,
use_sphericity=True,
roundness_params=None,
eps=0.001,
target_dim=384,
)
| Parameter | Description |
|---|---|
mask |
2D numpy.ndarray with bool or {0, 1} values |
use_* |
Toggle individual metrics on/off |
roundness_params |
Optional dict to override Wadell roundness parameters |
eps |
Tolerance for geometric computations |
target_dim |
Particle bounds larger than this are downscaled for speed, then coordinates are restored |
roundness_params accepts these Wadell options. Defaults are defined in
wadell/roundness.py and also apply to synthetic particle calibration:
| Option | Default | Stage |
|---|---|---|
max_dev_thresh |
0.3 |
Boundary discretization |
circle_fit_thresh |
0.98 |
Corner circle fitting |
alpha_ratio |
0.05 |
Boundary smoothing |
beta_ratio |
0.001 |
Boundary smoothing |
Returns None for an empty mask. Otherwise, returns an AnalysisResult dict. Disabled metrics are omitted; enabled metrics are present and may be None when the geometry cannot be measured.
Result Shape
{
"roundness": {"val": float} | None,
"circularity": {"val": float} | None,
"sphericity": {
"val": float,
"inscribed": {"x": float, "y": float, "r": float},
"enclosing": {"x": float, "y": float, "r": float},
} | None,
"aspect_ratio": {
"val": float,
"ellipse": {
"major": float, "minor": float,
"x": float, "y": float, "angle": float,
"w": float, "h": float, "bbox": list[list[float]],
},
} | None,
}
Input Rules
maskmust be a 2Dnumpy.ndarray- Allowed values are
boolor numeric{0, 1} - Floating masks must not contain
NaNorinf - Empty mask returns
None - The largest 4-connected foreground component is selected and holes are filled
- Surrounding zero padding does not change the measured shape
- Downscaling is skipped if its output would have a one-pixel axis
- Shape, center, and rectangle corner arguments use image order:
(height, width)and(y, x) - Triangle vertices use Cartesian order
(x, y), matching polygon coordinates
Project Layout
analyzer.py: public analysis workflow and metric selectionvalidation.py: public input normalization and analysis-option validationmask_ops.py: component selection, contours, andPreparedMaskcrop/resize transforms with result-coordinate restorationmetrics.py: metric formulas and public result payloadsfitting.py: circle and ellipse fitting plus oriented bounding boxeswadell/roundness.py: roundness options and the boundary-to-radius workflowwadell/boundary.pyandsmoothing.py: closed boundary extraction and periodic smoothingwadell/discretize.py: boundary approximation with(x, y)point arrayswadell/corner.py: convex corner selection, circle fitting, and accepted corner radiiutils/: basic mask construction, parametric particle calibration and reporting, and visualization
Utilities
Mask constructors:
gm.utils.create_circle_mask(...)gm.utils.create_ellipse_mask(...)gm.utils.create_rectangle_mask(...)gm.utils.create_square_mask(...)gm.utils.create_triangle_mask(...)gm.utils.create_pentagon_mask(...)gm.utils.create_star_mask(...)gm.utils.create_particle_mask(...)
For example, a triangle in a non-square image uses (x, y) vertices:
mask = gm.utils.create_triangle_mask((20, 60), (30, 2), (40, 2), (30, 8))
create_particle_mask attempts to reach the requested roundness by calibrating
surface roughness. Set seed for reproducible masks and return_info=True to
receive (mask, info). The report measures the returned mask, including any
clipping at the image bounds; target_met compares its roundness error with
report_tol. A zero-radius particle reports roundness 0.0.
Visualization:
gm.utils.plot_analysis_results(mask, results)
License
MIT
Metadata
Release files for grainmorph 0.2.1
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 | |
|---|---|---|---|---|
| grainmorph-0.2.1.tar.gz | 24.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| grainmorph-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.5 kB
Release files / grainmorph-0.2.1.tar.gz
| Download URL | grainmorph-0.2.1.tar.gz |
|---|---|
| Size | 24.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ce7921dddbf0fd9bd26b88008cecc6844e294150b137b00dc2457be26066e201
|
|
BLAKE2b-256 checksum How to use checksums |
3ef9117d9076fda9a511e2b4b56ffbc983ba8c1760cb0076cdd0bfee8a158c03
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / grainmorph-0.2.1-py3-none-any.whl
| Download URL | grainmorph-0.2.1-py3-none-any.whl |
|---|---|
| Size | 20.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f5ef10b47a1c318c2f754eaa07daf7580fe351f62796de4c3cec382f82828b8d
|
|
BLAKE2b-256 checksum How to use checksums |
6b8d2cf8274e9126a90c7ae8b5a9e1fe09605816100609010aa98f35b130775e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for grainmorph 0.2.1
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for grainmorph-0.2.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA 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.