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
Quick Start
import grainmorph as pm
mask = pm.utils.create_circle_mask((256, 256), (128, 128), 60)
results = pm.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:
pm.utils.plot_analysis_results(mask, results)
API
pm.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:
pm.utils.create_circle_mask(...)pm.utils.create_ellipse_mask(...)pm.utils.create_rectangle_mask(...)pm.utils.create_square_mask(...)pm.utils.create_triangle_mask(...)pm.utils.create_pentagon_mask(...)pm.utils.create_star_mask(...)pm.utils.create_particle_mask(...)
For example, a triangle in a non-square image uses (x, y) vertices:
mask = pm.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:
pm.utils.plot_analysis_results(mask, results)
Changes in 0.2.0
- Renamed the package from
partimorphtograinmorph: install withpip install grainmorphand import withimport grainmorph. The API is otherwise unchanged from 0.1.9.
Changes in 0.1.9
- Wadell roundness is several times faster: corner circle fits are vectorized and boundary discretization does less per-step work. Results are unchanged.
create_particle_maskcalibrates roughness with 12 bisection steps by default instead of 20, about 1.5x faster. Generated masks can differ slightly; on a 108-case grid the roundness error changed by at most 0.012 and its mean was unchanged.- Component selection, hole filling, and the Crofton perimeter now use OpenCV and NumPy, so
scipyandscikit-imageare no longer dependencies. Results are unchanged.
Changes in 0.1.8
- Corrected the fitted ellipse overlay orientation for rotated particles.
- Unified inscribed-circle fitting used by sphericity and Wadell roundness.
- Simplified ellipse fitting and bounding-box coordinate transforms while preserving the public API and analysis results.
License
MIT
Metadata
Release files for grainmorph 0.2.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 | |
|---|---|---|---|---|
| grainmorph-0.2.0.tar.gz | 25.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| grainmorph-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.2 kB
Release files / grainmorph-0.2.0.tar.gz
| Download URL | grainmorph-0.2.0.tar.gz |
|---|---|
| Size | 25.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aa3e60055be1045d47c115f1a213d926e0122b2dbfadb252fe128790fd1b2454
|
|
BLAKE2b-256 checksum How to use checksums |
f9ef620babec6a12828515c96e53f856cdfd8c1ec9753afd43a083a55d6c9fd6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / grainmorph-0.2.0-py3-none-any.whl
| Download URL | grainmorph-0.2.0-py3-none-any.whl |
|---|---|
| Size | 20.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e65a18dbf86a8316ec83e25c9bb9f9708fecfe7dc51db987935941548c34fbfd
|
|
BLAKE2b-256 checksum How to use checksums |
0ddf02d7e4f804ad503cd5725458d1b31aecb522c2a85a3e58eefa40d5bec9b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for grainmorph 0.2.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.