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

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

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

  • 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 partimorph to grainmorph: install with pip install grainmorph and import with import 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_mask calibrates 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 scipy and scikit-image are 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)

Source distribution for grainmorph 0.2.0
File Size Uploaded Provenance
grainmorph-0.2.0.tar.gz 25.4 kB Without provenance Details

Built distribution (wheel)

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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No 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