From ca50227d91448f737a841d2956cf9fb595ad8fe6 Mon Sep 17 00:00:00 2001 From: Caroline Malin-Mayor Date: Tue, 10 Sep 2024 14:36:44 -0400 Subject: [PATCH 1/8] Add example segmentations for testing --- docs/source/index.rst | 1 + docs/source/test_cases/segmentation/.DS_Store | Bin 0 -> 6148 bytes .../segmentation/2d/False Negative.svg | 1119 ++++++++++++++++ .../segmentation/2d/False Positive.svg | 1072 +++++++++++++++ .../segmentation/2d/Good Segmentation.png | Bin 0 -> 15505 bytes .../segmentation/2d/Good Segmentation.svg | 1161 ++++++++++++++++ .../segmentation/2d/Oversegmentation.svg | 1174 +++++++++++++++++ .../segmentation/2d/Undersegmentation.svg | 1161 ++++++++++++++++ .../test_cases/segmentation/2d/good_seg.png | Bin 0 -> 12899 bytes docs/source/test_cases/test_description.rst | 19 + docs/write_test_cases.py | 51 + tests/examples/example_segmentations.py | 264 ++++ 12 files changed, 6022 insertions(+) create mode 100644 docs/source/test_cases/segmentation/.DS_Store create mode 100644 docs/source/test_cases/segmentation/2d/False Negative.svg create mode 100644 docs/source/test_cases/segmentation/2d/False Positive.svg create mode 100644 docs/source/test_cases/segmentation/2d/Good Segmentation.png create mode 100644 docs/source/test_cases/segmentation/2d/Good Segmentation.svg create mode 100644 docs/source/test_cases/segmentation/2d/Oversegmentation.svg create mode 100644 docs/source/test_cases/segmentation/2d/Undersegmentation.svg create mode 100644 docs/source/test_cases/segmentation/2d/good_seg.png create mode 100644 docs/source/test_cases/test_description.rst create mode 100644 docs/write_test_cases.py create mode 100644 tests/examples/example_segmentations.py diff --git a/docs/source/index.rst b/docs/source/index.rst index 7d4aa3bd..d295f08a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -7,6 +7,7 @@ :caption: Examples: examples/ctc + test_cases/test_description .. toctree:: :maxdepth: 2 diff --git a/docs/source/test_cases/segmentation/.DS_Store b/docs/source/test_cases/segmentation/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0c9dc2a3d427801e5cca7c914dbb249bf817bf59 GIT binary patch literal 6148 zcmeH~u?oUK42F~1q2SWd@fIIIpWrC^1ipYn!9^+vI^U!FCzr+QEJgl6@?CNbZU3Ry zh=>l4aVOG?NC!8{*22sbc_VunpQrnDy-gS7Rx@iVJV&jU*?w#jRDcRl0V+TRsKA00 zh;zJKFX);0C{%z7EJFeNJ|ws?2TP@YIuLvW09%yZu=ZI3OjZDMuvDS~)0iGKTGhu8 zt9v_`;<_9xm9 + + + + + + + 2024-09-10T14:28:15.699311 + image/svg+xml + + + Matplotlib v3.9.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/test_cases/segmentation/2d/False Positive.svg b/docs/source/test_cases/segmentation/2d/False Positive.svg new file mode 100644 index 00000000..b105fc78 --- /dev/null +++ b/docs/source/test_cases/segmentation/2d/False Positive.svg @@ -0,0 +1,1072 @@ + + + + + + + + 2024-09-10T14:28:15.619104 + image/svg+xml + + + Matplotlib v3.9.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/test_cases/segmentation/2d/Good Segmentation.png b/docs/source/test_cases/segmentation/2d/Good Segmentation.png new file mode 100644 index 0000000000000000000000000000000000000000..bd384dd9ab9ea5d457412cdbeffdb9f5f85bb42a GIT binary patch literal 15505 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 bdz3Lf=7)k#pEHH7fWb;D-N?Rn`@#PJl5U*w literal 0 HcmV?d00001 diff --git a/docs/source/test_cases/segmentation/2d/Good Segmentation.svg b/docs/source/test_cases/segmentation/2d/Good Segmentation.svg new file mode 100644 index 00000000..59fad995 --- /dev/null +++ b/docs/source/test_cases/segmentation/2d/Good Segmentation.svg @@ -0,0 +1,1161 @@ + + + + + + + + 2024-09-10T14:28:15.530626 + image/svg+xml + + + Matplotlib v3.9.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/test_cases/segmentation/2d/Oversegmentation.svg b/docs/source/test_cases/segmentation/2d/Oversegmentation.svg new file mode 100644 index 00000000..518c2b23 --- /dev/null +++ b/docs/source/test_cases/segmentation/2d/Oversegmentation.svg @@ -0,0 +1,1174 @@ + + + + + + + + 2024-09-10T14:28:15.780199 + image/svg+xml + + + Matplotlib v3.9.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/test_cases/segmentation/2d/Undersegmentation.svg b/docs/source/test_cases/segmentation/2d/Undersegmentation.svg new file mode 100644 index 00000000..596d6939 --- /dev/null +++ b/docs/source/test_cases/segmentation/2d/Undersegmentation.svg @@ -0,0 +1,1161 @@ + + + + + + + + 2024-09-10T14:28:15.862769 + image/svg+xml + + + Matplotlib v3.9.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/test_cases/segmentation/2d/good_seg.png b/docs/source/test_cases/segmentation/2d/good_seg.png new file mode 100644 index 0000000000000000000000000000000000000000..b68c7513b61077221ebb0b7e44741d4c2c397185 GIT binary patch literal 12899 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 SbuCbmh}wD0bGgdqcmE3^Z(CLX literal 0 HcmV?d00001 diff --git a/docs/source/test_cases/test_description.rst b/docs/source/test_cases/test_description.rst new file mode 100644 index 00000000..384a49bc --- /dev/null +++ b/docs/source/test_cases/test_description.rst @@ -0,0 +1,19 @@ +Description of Unit Test Canonical Examples +=========================================== + +To facilitate testing, we have provided a suite of canonical examples +that cover the basic, simple scenarios that can occur in segmentation and +tracking. Here we describe them and show visualizations of each case. + +Matchers should test all the segmentation cases. Metrics should test all the +tracking cases. The examples are generated by functions in the `tests/examples/` +directory. + +Segmentation Canonical Examples +------------------------------- + +.. image:: segmentation/2d/Good\ Segmentation.svg +.. image:: segmentation/2d/False\ Negative.svg +.. image:: segmentation/2d/False\ Positive.svg +.. image:: segmentation/2d/Oversegmentation.svg +.. image:: segmentation/2d/Undersegmentation.svg \ No newline at end of file diff --git a/docs/write_test_cases.py b/docs/write_test_cases.py new file mode 100644 index 00000000..15683716 --- /dev/null +++ b/docs/write_test_cases.py @@ -0,0 +1,51 @@ +import sys + +sys.path.append("../tests/examples") +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +from example_segmentations import ( + false_negative_segmentation_2d, + false_positive_segmentation_2d, + good_segmentation_2d, + oversegmentation_2d, + undersegmentation_2d, +) +from matplotlib.colors import ListedColormap +from matplotlib.patches import Patch + + +def save_pair(gt, pred, outpath, title): + max_label = np.max([gt, pred]) + colors = ["black", "red", "blue", "green"] + colormap = ListedColormap(colors) + fig, ax = plt.subplots(1, 2, figsize=(6, 4)) + ax[0].imshow(gt, cmap=colormap, vmax=4) + ax[0].set_title("Ground Truth") + # ax[0].set_axis_off() + ax[1].imshow(pred, cmap=colormap, vmax=4) + ax[1].set_title("Predicted") + + handles = [Patch(color=colors[i]) for i in range(1, max_label + 1)] + labels = [str(i) for i in range(1, max_label + 1)] + ax[1].legend(handles=handles, labels=labels, title="Label IDs", loc="upper right") + fig.suptitle(title) + fig.tight_layout() + fig.savefig(outpath) + + +if __name__ == "__main__": + two_d_examples = { + "Good Segmentation": good_segmentation_2d(), + "False Positive": false_positive_segmentation_2d(), + "False Negative": false_negative_segmentation_2d(), + "Oversegmentation": oversegmentation_2d(), + "Undersegmentation": undersegmentation_2d(), + } + outdir = Path("source/test_cases/segmentation/2d") + print(outdir.exists()) + for name, arrs in two_d_examples.items(): + outpath = outdir / f"{name}.svg" + gt, pred = arrs + save_pair(gt, pred, outpath, name) diff --git a/tests/examples/example_segmentations.py b/tests/examples/example_segmentations.py new file mode 100644 index 00000000..c9df62c6 --- /dev/null +++ b/tests/examples/example_segmentations.py @@ -0,0 +1,264 @@ +import numpy as np +from skimage.draw import disk + + +def make_one_cell_2d( + label: int = 1, + arr_shape: tuple[int, int] = (32, 32), + center: tuple[int, int] = (16, 16), + radius: int = 7, +) -> np.ndarray: + """Create a 2D numpy array with a single circular cell. + + Args: + label (int, optional): Value of mask in the foreground. Defaults to 1. + arr_shape (tuple[int, int], optional): The size of the numpy array to return. + Defaults to (32, 32). + center (tuple[int, int], optional): The center of the cell, in pixels. + Defaults to (16, 16). + radius (int, optional): The radius of the cell. Defaults to 7. + + Returns: + np.array: A numpy array with a circle at the given center with the + given label. + """ + im = np.zeros(arr_shape, dtype="int32") + rr, cc = disk(center, radius, shape=arr_shape) + im[rr, cc] = label + return im + + +def make_split_cell_2d( + labels=(1, 2), arr_shape=(32, 32), center=(16, 16), radius=9 +) -> np.ndarray: + """Create a 2d numpy array with two cells, each half a circle. + + Args: + labels (tuple, optional): _description_. Defaults to (1, 2). + arr_shape (tuple, optional): _description_. Defaults to (32, 32). + center (tuple, optional): _description_. Defaults to (16, 16). + radius (int, optional): _description_. Defaults to 7. + + Returns: + np.ndarray : A numpy array with two half circles with the given labels. + The pixels with y value greater than center will be the second label + color, and those with y value lass than or equal to the center + will have the first label. + """ + im = np.zeros(arr_shape, dtype="int32") + rr, cc = disk(center, radius, shape=arr_shape) + im[rr, cc] = labels[0] + # get indices where y value greater than center + mask = cc > center[1] + im[rr[mask], cc[mask]] = labels[1] + return im + + +def sphere( + center: tuple[int, int, int], radius: int, shape: tuple[int, int, int] +) -> np.ndarray: + """Get a mask of a sphere of a given radius + + Args: + center (tuple[int, int, int]): The coordinate of the center of the sphere. + radius (int): The radius of the sphere + shape (tuple[int, int, int]): The share of the numpy array mask to return. + + Returns: + np.ndarray: A boolean array with 1s inside the sphere and 0s outside. + """ + assert len(center) == len(shape) + indices = np.moveaxis(np.indices(shape), 0, -1) # last dim is the index + distance = np.linalg.norm(np.subtract(indices, np.asarray(center)), axis=-1) + mask = distance <= radius + return mask + + +def make_one_cell_3d( + label=1, arr_shape=(32, 32, 32), center=(16, 16, 16), radius=7 +) -> np.ndarray: + """Make a numpy array containing a single (spherical) cell in 3d. + + Args: + label (int, optional): _description_. Defaults to 1. + arr_shape (tuple, optional): _description_. Defaults to (32, 32, 32). + center (tuple, optional): _description_. Defaults to (16, 16, 16). + radius (int, optional): _description_. Defaults to 7. + + Returns: + np.ndarray: A numpy array of the given shape containing a sphere + with the given label, radius, and center. + + """ + im = np.zeros(arr_shape, dtype="int32") + mask = sphere(center, radius, shape=arr_shape) + im[mask] = label + return im + + +def make_split_cell_3d( + labels=(1, 2), arr_shape=(32, 32, 32), center=(16, 16, 16), radius=9 +): + """Make a numpy array containing two cells, each half a sphere. + The pixels with y value less than or equal to the center y value will have + the first label, and those with y value greater than the center will + have the second label + + Args: + labels (tuple, optional): _description_. Defaults to (1, 2). + arr_shape (tuple, optional): _description_. Defaults to (32, 32, 32). + center (tuple, optional): _description_. Defaults to (16, 16, 16). + radius (int, optional): _description_. Defaults to 9. + + Returns: + np.ndarray: A numpy array of the given shape containing a sphere + with the given radius, and center. Half the sphere has the first, + label, and the other half has the second label. + """ + im = np.zeros(arr_shape, dtype="int32") + mask = sphere(center, radius, shape=arr_shape) + im[mask] = labels[0] + # get indices where y value greater than center + mask[:, 0 : center[1]] = 0 + im[mask] = labels[1] + return im + + +### CANONICAL 2D SEGMENTATION EXAMPLES ### + + +def good_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: + """A pretty good (but not perfect) pair of segmentations in 2d. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations of + a single cell. The segmentations are circles of the same size with + a slight offset in x and y. + """ + gt = make_one_cell_2d(label=1, center=(15, 15), radius=9) + pred = make_one_cell_2d(label=2, center=(17, 17), radius=9) + return gt, pred + + +def false_positive_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt is empty and the prediction has a + single cell. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations of + a single cell. The gt is empty and the prediction has a single cell. + """ + gt = np.zeros((32, 32), dtype="int32") + pred = make_one_cell_2d(label=1, center=(17, 17), radius=9) + return gt, pred + + +def false_negative_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt has a single cell and the + prediction is empty. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations of + a single cell. The pred is empty and the gt has a single cell. + """ + gt = make_one_cell_2d(label=1, center=(15, 15), radius=9) + pred = np.zeros((32, 32), dtype="int32") + return gt, pred + + +def oversegmentation_2d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt has a single cell and the prediction + splits that into two cells. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations. + The gt has a single circle labeled and the pred splits that circle + into two labels. + """ + gt = make_one_cell_2d(label=1, center=(16, 16), radius=9) + pred = make_split_cell_2d(labels=(2, 3), center=(16, 16), radius=9) + return gt, pred + + +def undersegmentation_2d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt has two cells and the prediction + merges them into one circular cell. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations. + The pred has a single merged circle labeled and the gt has two labels, + each half of the circle. + """ + gt = make_split_cell_2d(labels=(1, 2), center=(16, 16), radius=9) + pred = make_one_cell_2d(label=3, center=(16, 16), radius=9) + return gt, pred + + +### CANONICAL 3D SEGMENTATION EXAMPLES ### + + +def good_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: + """A pretty good (but not perfect) pair of segmentations in 3d. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations of + a single cell. The segmentations are circles of the same size with + a slight offset in x and y. + """ + gt = make_one_cell_3d(label=1, center=(15, 15, 15), radius=9) + pred = make_one_cell_3d(label=2, center=(17, 17, 17), radius=9) + return gt, pred + + +def false_positive_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt is empty and the prediction has a + single cell. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations of + a single cell. The gt is empty and the prediction has a single cell. + """ + gt = np.zeros((32, 32, 32), dtype="int32") + pred = make_one_cell_3d(label=1, center=(17, 17, 17), radius=9) + return gt, pred + + +def false_negative_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt has a single cell and the + prediction is empty. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations of + a single cell. The pred is empty and the gt has a single cell. + """ + gt = make_one_cell_3d(label=1, center=(15, 15, 15), radius=9) + pred = np.zeros((32, 32), dtype="int32") + return gt, pred + + +def oversegmentation_3d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt has a single cell and the prediction + splits that into two cells. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations. + The gt has a single circle labeled and the pred splits that circle + into two labels. + """ + gt = make_one_cell_3d(label=1, center=(16, 16, 16), radius=9) + pred = make_split_cell_3d(labels=(2, 3), center=(16, 16, 16), radius=9) + return gt, pred + + +def undersegmentation_3d() -> tuple[np.ndarray, np.ndarray]: + """A pair of segmentations where the gt has two cells and the prediction + merges them into one circular cell. + + Returns: + tuple[np.ndarray, np.ndarray]: A pair of (gt, pred) segmentations. + The pred has a single merged circle labeled and the gt has two labels, + each half of the circle. + """ + gt = make_split_cell_3d(labels=(1, 2), center=(16, 16, 16), radius=9) + pred = make_one_cell_3d(label=3, center=(16, 16, 16), radius=9) + return gt, pred From da46b7efb12a67ec165f8e0bd52061dc70c33658 Mon Sep 17 00:00:00 2001 From: Caroline Malin-Mayor Date: Tue, 10 Sep 2024 15:18:48 -0400 Subject: [PATCH 2/8] Example testing IOU matcher with segmentation fixtures --- tests/examples/example_segmentations.py | 62 +++++++++++++++++++++++-- tests/matchers/test_iou.py | 35 ++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/tests/examples/example_segmentations.py b/tests/examples/example_segmentations.py index c9df62c6..4b0e61a7 100644 --- a/tests/examples/example_segmentations.py +++ b/tests/examples/example_segmentations.py @@ -1,5 +1,9 @@ +from typing import Any + import numpy as np +import pytest from skimage.draw import disk +from skimage.measure import regionprops def make_one_cell_2d( @@ -125,8 +129,7 @@ def make_split_cell_3d( ### CANONICAL 2D SEGMENTATION EXAMPLES ### - - +@pytest.fixture() def good_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: """A pretty good (but not perfect) pair of segmentations in 2d. @@ -140,6 +143,7 @@ def good_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture() def false_positive_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt is empty and the prediction has a single cell. @@ -153,6 +157,7 @@ def false_positive_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture() def false_negative_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt has a single cell and the prediction is empty. @@ -166,6 +171,7 @@ def false_negative_segmentation_2d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture() def oversegmentation_2d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt has a single cell and the prediction splits that into two cells. @@ -180,6 +186,7 @@ def oversegmentation_2d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture() def undersegmentation_2d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt has two cells and the prediction merges them into one circular cell. @@ -195,8 +202,7 @@ def undersegmentation_2d() -> tuple[np.ndarray, np.ndarray]: ### CANONICAL 3D SEGMENTATION EXAMPLES ### - - +@pytest.fixture() def good_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: """A pretty good (but not perfect) pair of segmentations in 3d. @@ -210,6 +216,7 @@ def good_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture def false_positive_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt is empty and the prediction has a single cell. @@ -223,6 +230,7 @@ def false_positive_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture() def false_negative_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt has a single cell and the prediction is empty. @@ -236,6 +244,7 @@ def false_negative_segmentation_3d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture() def oversegmentation_3d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt has a single cell and the prediction splits that into two cells. @@ -250,6 +259,7 @@ def oversegmentation_3d() -> tuple[np.ndarray, np.ndarray]: return gt, pred +@pytest.fixture() def undersegmentation_3d() -> tuple[np.ndarray, np.ndarray]: """A pair of segmentations where the gt has two cells and the prediction merges them into one circular cell. @@ -262,3 +272,47 @@ def undersegmentation_3d() -> tuple[np.ndarray, np.ndarray]: gt = make_split_cell_3d(labels=(1, 2), center=(16, 16, 16), radius=9) pred = make_one_cell_3d(label=3, center=(16, 16, 16), radius=9) return gt, pred + + +def nodes_from_segmentation( + seg: np.ndarray, + frame: int, + pos_keys=("y", "x"), + frame_key="t", + label_key="label_id", +) -> dict[Any, dict]: + """Extract candidate nodes from a segmentation. Also computes specified attributes. + Returns a networkx graph with only nodes, and also a dictionary from frames to + node_ids for efficient edge adding. + + Args: + segmentation (np.ndarray): A numpy array with integer labels, representing one time + frame. + frame (int): The time frame of this array. Used for making node ids and + for populating the attributes dict. + pos_keys (tuple[str]): The attribute keys to use to store the positions. + frame_key (str, optional): The frame key to use in the attributes dict. + Defaults to "t". + label_key (str, optional): The label key to use in the attributes dict. + Defaults to "label_id" + + Returns: + dict[Any, dict]: A dictionary from node_ids to node attributes, which + can be used to create a networkx graph using add_nodes_from(). + Node Ids are currently "_