Skip to main content

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):

  1. Set the same version in pyproject.toml and src/grainmorph/__init__.py.
  2. 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

  • mask must be a 2D numpy.ndarray
  • Allowed values are bool or numeric {0, 1}
  • Floating masks must not contain NaN or inf
  • 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 selection
  • validation.py: public input normalization and analysis-option validation
  • mask_ops.py: component selection, contours, and PreparedMask crop/resize transforms with result-coordinate restoration
  • metrics.py: metric formulas and public result payloads
  • fitting.py: circle and ellipse fitting plus oriented bounding boxes
  • wadell/roundness.py: roundness options and the boundary-to-radius workflow
  • wadell/boundary.py and smoothing.py: closed boundary extraction and periodic smoothing
  • wadell/discretize.py: boundary approximation with (x, y) point arrays
  • wadell/corner.py: convex corner selection, circle fitting, and accepted corner radii
  • utils/: 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)

Source distribution for grainmorph 0.2.1
File Size Uploaded Provenance
grainmorph-0.2.1.tar.gz 24.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for grainmorph 0.2.1
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

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.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page