grainmorph 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {grainmorph-0.2.0/src/grainmorph.egg-info → grainmorph-0.2.1}/PKG-INFO +28 -32
  2. {grainmorph-0.2.0 → grainmorph-0.2.1}/README.md +27 -31
  3. {grainmorph-0.2.0 → grainmorph-0.2.1}/pyproject.toml +1 -1
  4. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/__init__.py +1 -1
  5. {grainmorph-0.2.0 → grainmorph-0.2.1/src/grainmorph.egg-info}/PKG-INFO +28 -32
  6. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_analyzer.py +20 -20
  7. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_fitting.py +2 -2
  8. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_generation.py +15 -15
  9. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_metrics.py +3 -3
  10. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_plot.py +3 -3
  11. {grainmorph-0.2.0 → grainmorph-0.2.1}/LICENSE +0 -0
  12. {grainmorph-0.2.0 → grainmorph-0.2.1}/setup.cfg +0 -0
  13. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/analyzer.py +0 -0
  14. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/fitting.py +0 -0
  15. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/mask_ops.py +0 -0
  16. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/metrics.py +0 -0
  17. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/schema.py +0 -0
  18. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/utils/__init__.py +0 -0
  19. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/utils/create_mask.py +0 -0
  20. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/utils/geometry.py +0 -0
  21. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/utils/parametric_mask.py +0 -0
  22. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/utils/plot.py +0 -0
  23. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/validation.py +0 -0
  24. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/wadell/__init__.py +0 -0
  25. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/wadell/boundary.py +0 -0
  26. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/wadell/corner.py +0 -0
  27. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/wadell/discretize.py +0 -0
  28. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/wadell/roundness.py +0 -0
  29. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph/wadell/smoothing.py +0 -0
  30. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph.egg-info/SOURCES.txt +0 -0
  31. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph.egg-info/dependency_links.txt +0 -0
  32. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph.egg-info/requires.txt +0 -0
  33. {grainmorph-0.2.0 → grainmorph-0.2.1}/src/grainmorph.egg-info/top_level.txt +0 -0
  34. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_corner.py +0 -0
  35. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_discretize.py +0 -0
  36. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_mask_ops.py +0 -0
  37. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_smoothing.py +0 -0
  38. {grainmorph-0.2.0 → grainmorph-0.2.1}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grainmorph
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Particle shape analysis and visualization library.
5
5
  Author: GrainMorph Contributors
6
6
  License-Expression: MIT
@@ -53,13 +53,25 @@ python -m ruff check --isolated .
53
53
  python -m ruff format --isolated --check src tests
54
54
  ```
55
55
 
56
+ ## Releasing
57
+
58
+ Pushing a `vX.Y.Z` tag publishes that version to PyPI through
59
+ `.github/workflows/publish.yml`, using PyPI trusted publishing (no API token):
60
+
61
+ 1. Set the same version in `pyproject.toml` and `src/grainmorph/__init__.py`.
62
+ 2. Commit, then tag and push: `git tag -a vX.Y.Z -m "Release X.Y.Z" && git push origin main vX.Y.Z`.
63
+
64
+ The workflow fails if the tag and the `pyproject.toml` version differ, then runs the tests before uploading.
65
+ PyPI must list this repository as a trusted publisher for `grainmorph`
66
+ (owner `phykn`, repository `grainmorph`, workflow `publish.yml`, environment `pypi`).
67
+
56
68
  ## Quick Start
57
69
 
58
70
  ```python
59
- import grainmorph as pm
71
+ import grainmorph as gm
60
72
 
61
- mask = pm.utils.create_circle_mask((256, 256), (128, 128), 60)
62
- results = pm.analyze_mask(mask)
73
+ mask = gm.utils.create_circle_mask((256, 256), (128, 128), 60)
74
+ results = gm.analyze_mask(mask)
63
75
 
64
76
  if results is None:
65
77
  raise ValueError("mask has no foreground pixels")
@@ -73,13 +85,13 @@ print(results["aspect_ratio"]["val"])
73
85
  Visualize the results:
74
86
 
75
87
  ```python
76
- pm.utils.plot_analysis_results(mask, results)
88
+ gm.utils.plot_analysis_results(mask, results)
77
89
  ```
78
90
 
79
91
  ## API
80
92
 
81
93
  ```python
82
- pm.analyze_mask(
94
+ gm.analyze_mask(
83
95
  mask,
84
96
  use_aspect_ratio=True,
85
97
  use_roundness=True,
@@ -162,19 +174,19 @@ Returns `None` for an empty mask. Otherwise, returns an `AnalysisResult` dict. D
162
174
 
163
175
  Mask constructors:
164
176
 
165
- - `pm.utils.create_circle_mask(...)`
166
- - `pm.utils.create_ellipse_mask(...)`
167
- - `pm.utils.create_rectangle_mask(...)`
168
- - `pm.utils.create_square_mask(...)`
169
- - `pm.utils.create_triangle_mask(...)`
170
- - `pm.utils.create_pentagon_mask(...)`
171
- - `pm.utils.create_star_mask(...)`
172
- - `pm.utils.create_particle_mask(...)`
177
+ - `gm.utils.create_circle_mask(...)`
178
+ - `gm.utils.create_ellipse_mask(...)`
179
+ - `gm.utils.create_rectangle_mask(...)`
180
+ - `gm.utils.create_square_mask(...)`
181
+ - `gm.utils.create_triangle_mask(...)`
182
+ - `gm.utils.create_pentagon_mask(...)`
183
+ - `gm.utils.create_star_mask(...)`
184
+ - `gm.utils.create_particle_mask(...)`
173
185
 
174
186
  For example, a triangle in a non-square image uses `(x, y)` vertices:
175
187
 
176
188
  ```python
177
- mask = pm.utils.create_triangle_mask((20, 60), (30, 2), (40, 2), (30, 8))
189
+ mask = gm.utils.create_triangle_mask((20, 60), (30, 2), (40, 2), (30, 8))
178
190
  ```
179
191
 
180
192
  `create_particle_mask` attempts to reach the requested roundness by calibrating
@@ -185,23 +197,7 @@ clipping at the image bounds; `target_met` compares its roundness error with
185
197
 
186
198
  Visualization:
187
199
 
188
- - `pm.utils.plot_analysis_results(mask, results)`
189
-
190
- ## Changes in 0.2.0
191
-
192
- - 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.
193
-
194
- ## Changes in 0.1.9
195
-
196
- - Wadell roundness is several times faster: corner circle fits are vectorized and boundary discretization does less per-step work. Results are unchanged.
197
- - `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.
198
- - 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.
199
-
200
- ## Changes in 0.1.8
201
-
202
- - Corrected the fitted ellipse overlay orientation for rotated particles.
203
- - Unified inscribed-circle fitting used by sphericity and Wadell roundness.
204
- - Simplified ellipse fitting and bounding-box coordinate transforms while preserving the public API and analysis results.
200
+ - `gm.utils.plot_analysis_results(mask, results)`
205
201
 
206
202
  ## License
207
203
 
@@ -33,13 +33,25 @@ python -m ruff check --isolated .
33
33
  python -m ruff format --isolated --check src tests
34
34
  ```
35
35
 
36
+ ## Releasing
37
+
38
+ Pushing a `vX.Y.Z` tag publishes that version to PyPI through
39
+ `.github/workflows/publish.yml`, using PyPI trusted publishing (no API token):
40
+
41
+ 1. Set the same version in `pyproject.toml` and `src/grainmorph/__init__.py`.
42
+ 2. Commit, then tag and push: `git tag -a vX.Y.Z -m "Release X.Y.Z" && git push origin main vX.Y.Z`.
43
+
44
+ The workflow fails if the tag and the `pyproject.toml` version differ, then runs the tests before uploading.
45
+ PyPI must list this repository as a trusted publisher for `grainmorph`
46
+ (owner `phykn`, repository `grainmorph`, workflow `publish.yml`, environment `pypi`).
47
+
36
48
  ## Quick Start
37
49
 
38
50
  ```python
39
- import grainmorph as pm
51
+ import grainmorph as gm
40
52
 
41
- mask = pm.utils.create_circle_mask((256, 256), (128, 128), 60)
42
- results = pm.analyze_mask(mask)
53
+ mask = gm.utils.create_circle_mask((256, 256), (128, 128), 60)
54
+ results = gm.analyze_mask(mask)
43
55
 
44
56
  if results is None:
45
57
  raise ValueError("mask has no foreground pixels")
@@ -53,13 +65,13 @@ print(results["aspect_ratio"]["val"])
53
65
  Visualize the results:
54
66
 
55
67
  ```python
56
- pm.utils.plot_analysis_results(mask, results)
68
+ gm.utils.plot_analysis_results(mask, results)
57
69
  ```
58
70
 
59
71
  ## API
60
72
 
61
73
  ```python
62
- pm.analyze_mask(
74
+ gm.analyze_mask(
63
75
  mask,
64
76
  use_aspect_ratio=True,
65
77
  use_roundness=True,
@@ -142,19 +154,19 @@ Returns `None` for an empty mask. Otherwise, returns an `AnalysisResult` dict. D
142
154
 
143
155
  Mask constructors:
144
156
 
145
- - `pm.utils.create_circle_mask(...)`
146
- - `pm.utils.create_ellipse_mask(...)`
147
- - `pm.utils.create_rectangle_mask(...)`
148
- - `pm.utils.create_square_mask(...)`
149
- - `pm.utils.create_triangle_mask(...)`
150
- - `pm.utils.create_pentagon_mask(...)`
151
- - `pm.utils.create_star_mask(...)`
152
- - `pm.utils.create_particle_mask(...)`
157
+ - `gm.utils.create_circle_mask(...)`
158
+ - `gm.utils.create_ellipse_mask(...)`
159
+ - `gm.utils.create_rectangle_mask(...)`
160
+ - `gm.utils.create_square_mask(...)`
161
+ - `gm.utils.create_triangle_mask(...)`
162
+ - `gm.utils.create_pentagon_mask(...)`
163
+ - `gm.utils.create_star_mask(...)`
164
+ - `gm.utils.create_particle_mask(...)`
153
165
 
154
166
  For example, a triangle in a non-square image uses `(x, y)` vertices:
155
167
 
156
168
  ```python
157
- mask = pm.utils.create_triangle_mask((20, 60), (30, 2), (40, 2), (30, 8))
169
+ mask = gm.utils.create_triangle_mask((20, 60), (30, 2), (40, 2), (30, 8))
158
170
  ```
159
171
 
160
172
  `create_particle_mask` attempts to reach the requested roundness by calibrating
@@ -165,23 +177,7 @@ clipping at the image bounds; `target_met` compares its roundness error with
165
177
 
166
178
  Visualization:
167
179
 
168
- - `pm.utils.plot_analysis_results(mask, results)`
169
-
170
- ## Changes in 0.2.0
171
-
172
- - 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.
173
-
174
- ## Changes in 0.1.9
175
-
176
- - Wadell roundness is several times faster: corner circle fits are vectorized and boundary discretization does less per-step work. Results are unchanged.
177
- - `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.
178
- - 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.
179
-
180
- ## Changes in 0.1.8
181
-
182
- - Corrected the fitted ellipse overlay orientation for rotated particles.
183
- - Unified inscribed-circle fitting used by sphericity and Wadell roundness.
184
- - Simplified ellipse fitting and bounding-box coordinate transforms while preserving the public API and analysis results.
180
+ - `gm.utils.plot_analysis_results(mask, results)`
185
181
 
186
182
  ## License
187
183
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "grainmorph"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  authors = [
9
9
  { name = "GrainMorph Contributors" }
10
10
  ]
@@ -1,5 +1,5 @@
1
1
  from . import utils
2
2
  from .analyzer import analyze_mask
3
3
 
4
- __version__ = "0.2.0"
4
+ __version__ = "0.2.1"
5
5
  __all__ = ["analyze_mask", "utils"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grainmorph
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Particle shape analysis and visualization library.
5
5
  Author: GrainMorph Contributors
6
6
  License-Expression: MIT
@@ -53,13 +53,25 @@ python -m ruff check --isolated .
53
53
  python -m ruff format --isolated --check src tests
54
54
  ```
55
55
 
56
+ ## Releasing
57
+
58
+ Pushing a `vX.Y.Z` tag publishes that version to PyPI through
59
+ `.github/workflows/publish.yml`, using PyPI trusted publishing (no API token):
60
+
61
+ 1. Set the same version in `pyproject.toml` and `src/grainmorph/__init__.py`.
62
+ 2. Commit, then tag and push: `git tag -a vX.Y.Z -m "Release X.Y.Z" && git push origin main vX.Y.Z`.
63
+
64
+ The workflow fails if the tag and the `pyproject.toml` version differ, then runs the tests before uploading.
65
+ PyPI must list this repository as a trusted publisher for `grainmorph`
66
+ (owner `phykn`, repository `grainmorph`, workflow `publish.yml`, environment `pypi`).
67
+
56
68
  ## Quick Start
57
69
 
58
70
  ```python
59
- import grainmorph as pm
71
+ import grainmorph as gm
60
72
 
61
- mask = pm.utils.create_circle_mask((256, 256), (128, 128), 60)
62
- results = pm.analyze_mask(mask)
73
+ mask = gm.utils.create_circle_mask((256, 256), (128, 128), 60)
74
+ results = gm.analyze_mask(mask)
63
75
 
64
76
  if results is None:
65
77
  raise ValueError("mask has no foreground pixels")
@@ -73,13 +85,13 @@ print(results["aspect_ratio"]["val"])
73
85
  Visualize the results:
74
86
 
75
87
  ```python
76
- pm.utils.plot_analysis_results(mask, results)
88
+ gm.utils.plot_analysis_results(mask, results)
77
89
  ```
78
90
 
79
91
  ## API
80
92
 
81
93
  ```python
82
- pm.analyze_mask(
94
+ gm.analyze_mask(
83
95
  mask,
84
96
  use_aspect_ratio=True,
85
97
  use_roundness=True,
@@ -162,19 +174,19 @@ Returns `None` for an empty mask. Otherwise, returns an `AnalysisResult` dict. D
162
174
 
163
175
  Mask constructors:
164
176
 
165
- - `pm.utils.create_circle_mask(...)`
166
- - `pm.utils.create_ellipse_mask(...)`
167
- - `pm.utils.create_rectangle_mask(...)`
168
- - `pm.utils.create_square_mask(...)`
169
- - `pm.utils.create_triangle_mask(...)`
170
- - `pm.utils.create_pentagon_mask(...)`
171
- - `pm.utils.create_star_mask(...)`
172
- - `pm.utils.create_particle_mask(...)`
177
+ - `gm.utils.create_circle_mask(...)`
178
+ - `gm.utils.create_ellipse_mask(...)`
179
+ - `gm.utils.create_rectangle_mask(...)`
180
+ - `gm.utils.create_square_mask(...)`
181
+ - `gm.utils.create_triangle_mask(...)`
182
+ - `gm.utils.create_pentagon_mask(...)`
183
+ - `gm.utils.create_star_mask(...)`
184
+ - `gm.utils.create_particle_mask(...)`
173
185
 
174
186
  For example, a triangle in a non-square image uses `(x, y)` vertices:
175
187
 
176
188
  ```python
177
- mask = pm.utils.create_triangle_mask((20, 60), (30, 2), (40, 2), (30, 8))
189
+ mask = gm.utils.create_triangle_mask((20, 60), (30, 2), (40, 2), (30, 8))
178
190
  ```
179
191
 
180
192
  `create_particle_mask` attempts to reach the requested roundness by calibrating
@@ -185,23 +197,7 @@ clipping at the image bounds; `target_met` compares its roundness error with
185
197
 
186
198
  Visualization:
187
199
 
188
- - `pm.utils.plot_analysis_results(mask, results)`
189
-
190
- ## Changes in 0.2.0
191
-
192
- - 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.
193
-
194
- ## Changes in 0.1.9
195
-
196
- - Wadell roundness is several times faster: corner circle fits are vectorized and boundary discretization does less per-step work. Results are unchanged.
197
- - `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.
198
- - 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.
199
-
200
- ## Changes in 0.1.8
201
-
202
- - Corrected the fitted ellipse overlay orientation for rotated particles.
203
- - Unified inscribed-circle fitting used by sphericity and Wadell roundness.
204
- - Simplified ellipse fitting and bounding-box coordinate transforms while preserving the public API and analysis results.
200
+ - `gm.utils.plot_analysis_results(mask, results)`
205
201
 
206
202
  ## License
207
203
 
@@ -1,14 +1,14 @@
1
1
  import numpy as np
2
2
  import pytest
3
3
 
4
- import grainmorph as pm
4
+ import grainmorph as gm
5
5
 
6
6
 
7
7
  def test_analyze_mask_full():
8
8
  mask = np.zeros((100, 100), dtype=bool)
9
9
  mask[20:80, 20:80] = True
10
10
 
11
- results = pm.analyze_mask(mask)
11
+ results = gm.analyze_mask(mask)
12
12
 
13
13
  assert results is not None
14
14
  assert set(results) == {
@@ -25,7 +25,7 @@ def test_metrics_consistency():
25
25
  y, x = np.ogrid[:100, :100]
26
26
  mask = (x - 50) ** 2 + (y - 50) ** 2 <= 40**2
27
27
 
28
- results = pm.analyze_mask(mask)
28
+ results = gm.analyze_mask(mask)
29
29
 
30
30
  assert results is not None
31
31
  assert results["circularity"]["val"] > 0.9
@@ -38,7 +38,7 @@ def test_analyze_mask_rejects_invalid_eps(eps):
38
38
  mask = np.ones((8, 8), dtype=bool)
39
39
 
40
40
  with pytest.raises(ValueError, match="eps must be > 0"):
41
- pm.analyze_mask(mask, eps=eps)
41
+ gm.analyze_mask(mask, eps=eps)
42
42
 
43
43
 
44
44
  @pytest.mark.parametrize("target_dim", [0, -1])
@@ -46,7 +46,7 @@ def test_analyze_mask_rejects_non_positive_target_dim(target_dim):
46
46
  mask = np.ones((8, 8), dtype=bool)
47
47
 
48
48
  with pytest.raises(ValueError, match="target_dim must be > 0"):
49
- pm.analyze_mask(mask, target_dim=target_dim)
49
+ gm.analyze_mask(mask, target_dim=target_dim)
50
50
 
51
51
 
52
52
  @pytest.mark.parametrize("target_dim", [1.5, True])
@@ -54,7 +54,7 @@ def test_analyze_mask_rejects_non_integer_target_dim(target_dim):
54
54
  mask = np.ones((8, 8), dtype=bool)
55
55
 
56
56
  with pytest.raises(TypeError, match="target_dim must be an integer"):
57
- pm.analyze_mask(mask, target_dim=target_dim)
57
+ gm.analyze_mask(mask, target_dim=target_dim)
58
58
 
59
59
 
60
60
  def test_analyze_mask_uses_largest_component():
@@ -62,7 +62,7 @@ def test_analyze_mask_uses_largest_component():
62
62
  mask[10:30, 10:30] = True
63
63
  mask[50:110, 50:110] = True
64
64
 
65
- results = pm.analyze_mask(mask, use_roundness=False, use_circularity=False)
65
+ results = gm.analyze_mask(mask, use_roundness=False, use_circularity=False)
66
66
 
67
67
  assert results is not None
68
68
  assert results["sphericity"] is not None
@@ -75,7 +75,7 @@ def test_analyze_mask_resize_path_scales_coordinates():
75
75
  mask = np.zeros((1000, 1000), dtype=bool)
76
76
  mask[100:900, 200:800] = True
77
77
 
78
- results = pm.analyze_mask(mask, target_dim=384, use_roundness=False)
78
+ results = gm.analyze_mask(mask, target_dim=384, use_roundness=False)
79
79
 
80
80
  assert results is not None
81
81
  assert results["sphericity"] is not None
@@ -94,7 +94,7 @@ def test_analyze_mask_resize_path_scales_coordinates():
94
94
 
95
95
  @pytest.mark.parametrize("shape", [(1, 1000), (1000, 1)])
96
96
  def test_analyze_mask_resizes_thin_masks_without_crashing(shape):
97
- results = pm.analyze_mask(
97
+ results = gm.analyze_mask(
98
98
  np.ones(shape, dtype=bool),
99
99
  use_roundness=False,
100
100
  )
@@ -108,13 +108,13 @@ def test_analyze_mask_does_not_collapse_non_degenerate_axis():
108
108
  mask = np.zeros((1200, 1200), dtype=bool)
109
109
  mask[100:1100, 550:553] = True
110
110
 
111
- resized = pm.analyze_mask(
111
+ resized = gm.analyze_mask(
112
112
  mask,
113
113
  use_roundness=False,
114
114
  use_circularity=False,
115
115
  use_sphericity=False,
116
116
  )
117
- reference = pm.analyze_mask(
117
+ reference = gm.analyze_mask(
118
118
  mask,
119
119
  target_dim=2000,
120
120
  use_roundness=False,
@@ -133,12 +133,12 @@ def test_analyze_mask_does_not_collapse_non_degenerate_axis():
133
133
 
134
134
 
135
135
  def test_metrics_are_invariant_to_zero_padding():
136
- small = pm.utils.create_circle_mask((100, 100), (50, 50), 2)
137
- large = pm.utils.create_circle_mask((2000, 2000), (1000, 1000), 2)
136
+ small = gm.utils.create_circle_mask((100, 100), (50, 50), 2)
137
+ large = gm.utils.create_circle_mask((2000, 2000), (1000, 1000), 2)
138
138
  options = {"use_roundness": False, "use_aspect_ratio": False}
139
139
 
140
- small_results = pm.analyze_mask(small, **options)
141
- large_results = pm.analyze_mask(large, **options)
140
+ small_results = gm.analyze_mask(small, **options)
141
+ large_results = gm.analyze_mask(large, **options)
142
142
 
143
143
  assert small_results is not None
144
144
  assert large_results is not None
@@ -160,11 +160,11 @@ def test_metrics_are_invariant_to_zero_padding():
160
160
 
161
161
  @pytest.mark.parametrize("target_dim", [32, 384])
162
162
  def test_analysis_geometry_is_invariant_to_translation(target_dim):
163
- mask = pm.utils.create_ellipse_mask((100, 120), (50, 60), 35, 20)
163
+ mask = gm.utils.create_ellipse_mask((100, 120), (50, 60), 35, 20)
164
164
  shifted = np.pad(mask, ((23, 0), (37, 0)))
165
165
 
166
- reference = pm.analyze_mask(mask, target_dim=target_dim)
167
- actual = pm.analyze_mask(shifted, target_dim=target_dim)
166
+ reference = gm.analyze_mask(mask, target_dim=target_dim)
167
+ actual = gm.analyze_mask(shifted, target_dim=target_dim)
168
168
 
169
169
  for key in reference:
170
170
  assert actual[key] is not None
@@ -193,11 +193,11 @@ def test_analysis_geometry_is_invariant_to_translation(target_dim):
193
193
  [(), ("aspect_ratio",), ("roundness",), ("circularity",), ("sphericity",)],
194
194
  )
195
195
  def test_analysis_metric_selection(enabled):
196
- mask = pm.utils.create_circle_mask((80, 80), (40, 40), 20)
196
+ mask = gm.utils.create_circle_mask((80, 80), (40, 40), 20)
197
197
  keys = ("aspect_ratio", "roundness", "circularity", "sphericity")
198
198
  options = {f"use_{key}": key in enabled for key in keys}
199
199
 
200
- results = pm.analyze_mask(mask, **options)
200
+ results = gm.analyze_mask(mask, **options)
201
201
 
202
202
  assert set(results) == set(enabled)
203
203
  assert all(result is not None for result in results.values())
@@ -2,7 +2,7 @@ import cv2
2
2
  import numpy as np
3
3
  import pytest
4
4
 
5
- import grainmorph as pm
5
+ import grainmorph as gm
6
6
  from grainmorph.fitting import find_inscribed_circle, fit_ellipse
7
7
 
8
8
 
@@ -15,7 +15,7 @@ def test_aspect_ratio_payload_uses_fitted_ellipse():
15
15
  points = np.concatenate(contours).astype(np.float32)
16
16
  (center_x, center_y), (width, height), angle = cv2.fitEllipse(points)
17
17
 
18
- results = pm.analyze_mask(
18
+ results = gm.analyze_mask(
19
19
  mask,
20
20
  use_roundness=False,
21
21
  use_circularity=False,
@@ -3,13 +3,13 @@ import json
3
3
  import numpy as np
4
4
  import pytest
5
5
 
6
- import grainmorph as pm
6
+ import grainmorph as gm
7
7
 
8
8
 
9
9
  def test_triangle_vertices_use_cartesian_order():
10
10
  vertices = ((30, 2), (40, 2), (30, 8))
11
11
 
12
- mask = pm.utils.create_triangle_mask((20, 60), *vertices)
12
+ mask = gm.utils.create_triangle_mask((20, 60), *vertices)
13
13
 
14
14
  assert mask.dtype == bool
15
15
  assert all(mask[y, x] for x, y in vertices)
@@ -18,7 +18,7 @@ def test_triangle_vertices_use_cartesian_order():
18
18
 
19
19
 
20
20
  def test_create_particle_mask():
21
- mask = pm.utils.create_particle_mask(
21
+ mask = gm.utils.create_particle_mask(
22
22
  shape=(256, 256),
23
23
  center=(128, 128),
24
24
  radius=50,
@@ -36,7 +36,7 @@ def test_create_particle_mask():
36
36
 
37
37
 
38
38
  def test_create_particle_mask_zero_radius():
39
- mask, info = pm.utils.create_particle_mask(
39
+ mask, info = gm.utils.create_particle_mask(
40
40
  shape=(100, 100),
41
41
  center=(50, 50),
42
42
  radius=0.0,
@@ -50,7 +50,7 @@ def test_create_particle_mask_zero_radius():
50
50
 
51
51
 
52
52
  def test_roundness_rough_particle_golden():
53
- mask = pm.utils.create_particle_mask(
53
+ mask = gm.utils.create_particle_mask(
54
54
  shape=(256, 256),
55
55
  center=(128, 128),
56
56
  radius=60,
@@ -59,7 +59,7 @@ def test_roundness_rough_particle_golden():
59
59
  seed=42,
60
60
  )
61
61
 
62
- results = pm.analyze_mask(mask)
62
+ results = gm.analyze_mask(mask)
63
63
 
64
64
  assert results is not None
65
65
  assert results["roundness"] is not None
@@ -67,7 +67,7 @@ def test_roundness_rough_particle_golden():
67
67
 
68
68
 
69
69
  def test_particle_info_measures_returned_mask():
70
- mask, info = pm.utils.create_particle_mask(
70
+ mask, info = gm.utils.create_particle_mask(
71
71
  shape=(96, 96),
72
72
  center=(0, 0),
73
73
  radius=25,
@@ -76,7 +76,7 @@ def test_particle_info_measures_returned_mask():
76
76
  seed=7,
77
77
  return_info=True,
78
78
  )
79
- results = pm.analyze_mask(
79
+ results = gm.analyze_mask(
80
80
  mask,
81
81
  use_aspect_ratio=False,
82
82
  use_circularity=False,
@@ -92,13 +92,13 @@ def test_particle_info_measures_returned_mask():
92
92
 
93
93
  def test_mask_generators_reject_non_finite_radii():
94
94
  with pytest.raises(ValueError, match="finite"):
95
- pm.utils.create_circle_mask((16, 16), (8, 8), np.nan)
95
+ gm.utils.create_circle_mask((16, 16), (8, 8), np.nan)
96
96
  with pytest.raises(ValueError, match="finite"):
97
- pm.utils.create_ellipse_mask((16, 16), (8, 8), np.inf, 2)
97
+ gm.utils.create_ellipse_mask((16, 16), (8, 8), np.inf, 2)
98
98
  with pytest.raises(ValueError, match="finite"):
99
- pm.utils.create_pentagon_mask((16, 16), (8, 8), np.nan)
99
+ gm.utils.create_pentagon_mask((16, 16), (8, 8), np.nan)
100
100
  with pytest.raises(ValueError, match="finite"):
101
- pm.utils.create_star_mask((16, 16), (8, 8), 5, np.inf)
101
+ gm.utils.create_star_mask((16, 16), (8, 8), 5, np.inf)
102
102
 
103
103
 
104
104
  @pytest.mark.parametrize("field", ["sphericity", "roundness"])
@@ -107,7 +107,7 @@ def test_create_particle_mask_rejects_non_finite_targets(field):
107
107
  options[field] = np.nan
108
108
 
109
109
  with pytest.raises(ValueError, match="finite"):
110
- pm.utils.create_particle_mask(
110
+ gm.utils.create_particle_mask(
111
111
  shape=(32, 32),
112
112
  center=(16, 16),
113
113
  radius=8,
@@ -126,8 +126,8 @@ def test_particle_report_preserves_seeded_mask(radius):
126
126
  "seed": 42,
127
127
  }
128
128
 
129
- mask = pm.utils.create_particle_mask(**options)
130
- reported, info = pm.utils.create_particle_mask(**options, return_info=True)
129
+ mask = gm.utils.create_particle_mask(**options)
130
+ reported, info = gm.utils.create_particle_mask(**options, return_info=True)
131
131
 
132
132
  np.testing.assert_array_equal(mask, reported)
133
133
  assert info["target_met"] == (info["abs_error"] <= info["report_tol"])
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
  import pytest
3
3
 
4
- import grainmorph as pm
4
+ import grainmorph as gm
5
5
  from grainmorph.metrics import _crofton_perimeter
6
6
  from grainmorph.wadell import compute_roundness
7
7
 
@@ -11,10 +11,10 @@ from grainmorph.wadell import compute_roundness
11
11
  [{}, {"max_dev_thresh": 0.5, "alpha_ratio": 0.025, "beta_ratio": 0.0005}],
12
12
  )
13
13
  def test_analysis_roundness_uses_wadell_options(params):
14
- mask = pm.utils.create_circle_mask((100, 100), (50, 50), 30)
14
+ mask = gm.utils.create_circle_mask((100, 100), (50, 50), 30)
15
15
  expected = compute_roundness(mask, **params)
16
16
 
17
- results = pm.analyze_mask(
17
+ results = gm.analyze_mask(
18
18
  mask,
19
19
  use_aspect_ratio=False,
20
20
  use_circularity=False,
@@ -8,18 +8,18 @@ import numpy as np
8
8
  import pytest
9
9
  from matplotlib.patches import Ellipse
10
10
 
11
- import grainmorph as pm
11
+ import grainmorph as gm
12
12
 
13
13
 
14
14
  @pytest.mark.parametrize("angle", [25, 60, -35])
15
15
  def test_ellipse_overlay_aligns_with_rotated_particle(angle):
16
16
  mask = np.zeros((160, 180), dtype=np.uint8)
17
17
  cv2.ellipse(mask, (90, 80), (50, 18), angle, 0, 360, 1, -1)
18
- results = pm.analyze_mask(
18
+ results = gm.analyze_mask(
19
19
  mask, use_roundness=False, use_circularity=False, use_sphericity=False
20
20
  )
21
21
 
22
- pm.utils.plot_analysis_results(mask, results, show=False)
22
+ gm.utils.plot_analysis_results(mask, results, show=False)
23
23
  fig = plt.gcf()
24
24
  try:
25
25
  ax = fig.axes[0]
File without changes
File without changes