-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Look at replacing feret and centroid calculations in GrainStats #798
Comments
Done a bit more work investigating this by adding additional test images to If we use a tiny_circle = np.zeros((3, 3), dtype=np.uint8)
rr, cc = draw.circle_perimeter(1, 1, 1)
tiny_circle[rr, cc] = 1
tiny_circle
array([[0, 1, 0],
[1, 0, 1],
[0, 1, 0]], dtype=uint8)
np.argwhere(tiny_circle == 1)
array([[0, 1],
[1, 0],
[1, 2],
[2, 1]])
tiny_triangle = np.asarray([[1, 1], [1, 0]], dtype=np.uint8)
tiny_triangle
np.argwhere(tiny_triangle == 1)
array([[1, 1],
[1, 0]], dtype=uint8)
array([[0, 0],
[0, 1],
[1, 0]]) Just as with the square we can use basic trigonometry to work out what the minimum and maximum ferets of these should be. TriangleMinimum Feret : CircleMinimum Feret : Testing@pytest.mark.parametrize(
("edge_points", "min_expected", "max_expected"),
[
pytest.param([[0, 0], [0, 1], [1, 0], [1, 1]], 1.0, 1.4142135623730951, id="square"),
pytest.param([[0, 0], [0, 1], [1, 0]], 0.7071067811865476, 1.4142135623730951, id="triangle"),
pytest.param([[0, 1], [1, 0], [1, 2], [2, 1]], 1.4142135623730951, 2.0, id="circle"),
],
)
def test_get_min_max_ferets(edge_points, min_expected, max_expected) -> None:
"""Tests the GrainStats.get_min_max_ferets method."""
min_feret, max_feret = GrainStats.get_max_min_ferets(edge_points)
np.testing.assert_almost_equal(min_feret, min_expected)
np.testing.assert_almost_equal(max_feret, max_expected)
Ok this is good, the basic shapes work, however in the case of the I've looked at how small_feret = GrainStats.get_triangle_height(
np.array(lower_hull[lower_index + 1, :]),
np.array(lower_hull[lower_index, :]),
np.array(upper_hull[upper_index, :]),
)
if min_feret is None or small_feret < min_feret:
min_feret = small_feret ...and... small_feret = GrainStats.get_triangle_height(
np.array(upper_hull[upper_index - 1, :]),
np.array(upper_hull[upper_index, :]),
np.array(lower_hull[lower_index, :]),
)
if min_feret is None or small_feret < min_feret:
min_feret = small_feret A useful post Minimum Feret Diameter » Steve on Image Processing with MATLAB - MATLAB & Simulink explains why this is the correct way to calculate the minimum feret (the visual diagrams were really helpful). Thus #755 needs revising before it can be merged to use this method. Apologies for doubting your solution @SylviaWhittle . |
Note: Necessary refactor due to bounding boxes not being returned and needed for traces |
Closes #798 Replaces the functionality for calculating minimum and maximum feret distances within the `GrainStats` class with the new `topostats.measure.feret.min_max_feret()`. + Reorder dictionary returned by `min_max_feret()`. + Dictionary is included in the `stats` dictionary built by `GrainStats` and saved as CSV. + Updates `tests/test_grainstats_minicircle.py::test_grainstats_regression` to include the co-ordinates. + Updates `tests/test_grainstats.py::test_process_scan*` to include co-ordinates. + Removes `GrainStats.get_max_min_feret()` and associated methods that are called as well as their tests. + Co-ordinates for min/max feret are included by default in HDF5 output now that they are part of the returned dataframe. Will have to rethink/change how results are returned by `GraintStats` class if height profiles are to be extracted. Probably most sensible to have profiling as a separate step conducted after GrainStats.
Closes #798 Replaces the functionality for calculating minimum and maximum feret distances within the `GrainStats` class with the new `topostats.measure.feret.min_max_feret()`. + Reorder dictionary returned by `min_max_feret()`. + Dictionary is included in the `stats` dictionary built by `GrainStats` and saved as CSV. + Updates `tests/test_grainstats_minicircle.py::test_grainstats_regression` to include the co-ordinates. + Updates `tests/test_grainstats.py::test_process_scan*` to include co-ordinates. + Removes `GrainStats.get_max_min_feret()` and associated methods that are called as well as their tests. + Co-ordinates for min/max feret are included by default in HDF5 output now that they are part of the returned dataframe. These are rounded to 13 decimal places to address precision errors encountered on Windows machines running the test suite. Will have to rethink/change how results are returned by `GraintStats` class if height profiles are to be extracted. Probably most sensible to have profiling as a separate step conducted after GrainStats.
Closes #798 Replaces the functionality for calculating minimum and maximum feret distances within the `GrainStats` class with the new `topostats.measure.feret.min_max_feret()`. + Reorder dictionary returned by `min_max_feret()`. + Dictionary is included in the `stats` dictionary built by `GrainStats` and saved as CSV. + Updates `tests/test_grainstats_minicircle.py::test_grainstats_regression` to include the co-ordinates. + Updates `tests/test_grainstats.py::test_process_scan*` to include co-ordinates. + Removes `GrainStats.get_max_min_feret()` and associated methods that are called as well as their tests. + Co-ordinates for min/max feret are included by default in HDF5 output now that they are part of the returned dataframe. These are rounded to 13 decimal places to address precision errors encountered on Windows machines running the test suite. Will have to rethink/change how results are returned by `GraintStats` class if height profiles are to be extracted. Probably most sensible to have profiling as a separate step conducted after GrainStats.
Whilst working on #748 I needed to extract the Feret calculations so that I could obtain the co-ordinates for the minimum and maximum feret distances. Work in-progress on
ns-rse/748-grain-profiles
.Initial implementation in #755 includes a bunch of tests as I had some issues getting what I thought were the correct answers out.
As this will be its own, tested, sub-module we can replace the functionality of
GrainStats.get_min_max_feret()
which currently only has a single 2x2 square tested and a regression test in place on the final values of croppedminicircle.spm
. This can also reduce some duplication of code as convex hull is calculated twice, once for all points and then again for the upper and lower hulls.I noticed also that there are calculations to determine the
centroid
of grains/molecules and we could look at replacing these with the Scikit Image'sskimage.measure.regionprops()
which returns thecentroid
(amongst many other things).I started making the change to use
topostats.measure.feret.min_max_feret()
(currently stashed) and found that in the regression test whilst the values calculated for themax_feret
were identical to those returned byGrainStats.get_min_max_feret()
themin_feret
differed.Using some of the shapes in the tests developed for
topostats.measure.feret
to test theGratinstats.get_max_min_ferets()
revealed there may be some issues with themax_feret
. These use incredibly simpletiny_{circle,square,quadrilateral}
shapes as well as simple elipses and the tests fail for bothmin_feret
andmax_feret
(see original test on a simple square attests/test_grainstats.py::test_get_min_max_ferets()
).Code for these tests isn't included in #755 but follows (NB the
axis
,min_feret_coord_target
andmax_feret_coord_target
parameters aren't used in the tests asGrainStats.get_max_min_ferets()
only has one way of sorting convex hull edge points, but it was easier to just copy and paste them from the tests fortopostats.measure.feret.min_max_feret()
).The text was updated successfully, but these errors were encountered: